I'm just started learning angularJS and I wish to build some dropdowns and tabs with bootstrap and angular. I know there is a full angular bootstrap library out there, but I don't want to use it, since I want to know whats going on behind the scenes. So I'm trying to build the needed dropdown functionality, but haven't been able to do so. The problem is:
I have to click the dropdown if its opened before it can
disappear again. I wish to be able to click / touch anywhere but the
dropdown to close i again.
I've made a pen of the current markup/functionality: http://codepen.io/anon/pen/EBwlA
This is my first angular project so if anything is not done "the correct way" please feel free to tell me :)
Thanks
you can just attach an ng-click event to every li or a tag like this
ng-click="toggleOpen($event)"
if there is a more specific method you want to investigate let me know
this is a method using a service that will allow you to have collapsable elements working together
app.service('collapsables',['$rootScope',function($rootScope){
return{
notify:function(event,payload){
$rootScope.$broadcast(event,payload);
}
}
}]);
app.directive('collapsable',['collapsables',function(collapsables){
return{
restrict:'A'
controller:function($scope){
$scope.Toggle=function(){
$scope.expanded=!$scope.expanded;
collapsables.notify('toggled',{scid:$scope.$id,group:group});
}
$scope.$on('toggled',function($event,details){
var canClose=(!$scope.group || $scope.group==details.group || !details.group) && $scope.$id!=details.scid && $scope.expanded
$scope.expanded=canClose.
})
}
}
}])
now this this you can attach the collapsable directive to any element and it should allow you to comunicate the intents among all collapsables, you shuld be also be able to group them in case you want to isolate behaviors, notice that this directive doesn't cfreta a scope merely extends the xisting one to add a behavior, which is bad if you have several of this in the same controller, in that case a variant of this needs to be implemented using isolated scopes, but the idea remains the same
Related
I have a directive that controls what buttons someone can see based on their user role:
import { store } from '../store/';
import * as types from '../store/types';
const hide = vnode => {
if (vnode.elm.parentElement) {
vnode.elm.parentElement.removeChild(vnode.elm);
}
};
export const userRole = {
update(el, binding, vnode) {
const userId = store.getters[types.GET_USER_ID];
const { value, modifiers } = binding;
if (value.role) {
if (Reflect.has(modifiers, 'manager')) {
if (value.role[0] !== userId) hide(vnode);
}
};
Then I'll have a button like this:
<vue-button
v-userRole.manager="{role: job.role}"
#click.prevent.stop="e => payoutJob(job.id)"
>
Button Text
</vue-button>
All the buttons will show on the page before the user directive loads. So tons of buttons flash on the page. And then 2 seconds later only two buttons show, as that is what they have permission to see. How do I prevent this?
I would want at the very least, no buttons to appear on the page until the logged in user is matched against the user role directive file.
User information is stored in location storage, in vuex and every page that loads checks for a signed in user.
The way you have created this I think means that this will always happen - your directive is removing the HTML only after it has been created. For the element not to have been created in the first place you need instead to use a v-if or similar. I can see two ways to fix this, one a work-around as a minimal change to what you have, the other I would consider a better solution (but it is of course up to you).
To work around your problem have all of your buttons with a style of display:none and then in your directive either delete the elemet or clear the style (or change the class - however you choose to implement it). That way at least the button won't appear.
However, a better fix, I think, is to create your own component with a userRole property. That component will then do the check you have above (for example through a computed property) and then show or hide the component as required.
EDIT
I ran a quick fiddle to test the principals behind what you were doing - just changing the hook from update to inserted may fix it. Obviously your code will be more complex so your milage may vary ;).
I would focus on Vue instance - https://v2.vuejs.org/v2/guide/instance.html - I think you might use beforeCreate.
Also another idea as Euan Smith wrote, use v-if and put in your data true/false information of loading rights (set it false, and when the role is known set it to true).
In my angular 1.5 html5 application, I have an accordion group and inside it's body I have Couple of check-boxes. Since direct scope binding will not work inside accordion, I'm using ng-click event as attached.
This works as expected, I'm getting click events with correct value.
I have another reset button on screen, when user clicks this button I have to reset all filters including the checkbox inside the accordion. Even after I reset the model value to false, checkbox still shows as checked. I know this is because the binding is not there.
How can I update the checkbox value from javascript. Is there any angular way. I'm not a big fan of JQuery.
Regards,
Nixon
We faced a similar issue with the data bindings while using accordian.
Instead of using directly model variable, we created an object of it.
For eg, instead of using $scope.includeLocalParties, try using $scope.checkbox.includeLocalParties.
Also initialize it in your controller. Something like this:
$scope.checkbox = { includeLocalParties : false};
Hope it helps!
I have a SPA in Angular Material which is displaying an mdToolbar element with a hamburger menu + left sidenav. That menu at the moment resides in my index.html where I have also set-up a <ui-view> element to render the view.
Now I have a view with a mdList in it. When the user selects some items, I want a delete icon to appear in the toolbar. That delete icon should be linked to the delete action of my controller which is of course specific to the view loaded, not to index.html.
I want to know what a recommended pattern for this would be. I can think of some ways to do it, but those are ugly. I was thinking in the direction of being able to have some placeholder area in the toolbar which I can replace with contents from my view, where the element actions (ngClick) are linked to the actions of the view controller. Does anyone know a good tutorial or codepen-like example of how to do this?
Update
I've now got something implemented that I'm happy with, but it's not quite there yet. What I did is create a menuService which is injected in the controller behind my menu (it's not a separate view, could be but doesn't make a difference in this scenario). The menu controller binds to this service and other services can inject stuff in it. In my test scenario, I inject a string which is then displayed in the toolbar, all ok.
The only thing I now need to do is instead of a string, inject a button with an event handler which goes back to the controller of the view. I'm not quite certain how to do that yet.
Another update
So I created this class:
export class CommandButton {
svgSrc: string;
click: () => void;
}
which I can inject into my menuService and then binds to the menu controller. Works fine for the icon (the button appears) but as one might expect (I did), the click function doesn't work. I set this in the view controller as follows:
var deleteButton = new Services.CommandButton();
deleteButton.svgSrc = 'icons/ic_delete_24px.svg';
deleteButton.click = this.deleteLogs;
this.menuService.setButtons([deleteButton]);
And the code for this.deleteLogs is simply:
deleteLogs() {
console.log('deleting logs');
}
Code for the buttons:
<div ng-repeat="button in ctrl.buttons">
<md-button ng-click="button.click">
<md-icon md-svg-src="{{button.svgSrc}}" class="md-icon md-24"></md-icon>
</md-button>
</div>
What I hoped for is that this would trigger the deleteLogs method in the view controller, but that's not the case. I need data from the view as that is where the items are selected. As far as I can see there's just nothing happening so the binding fails somewhere. What would be a good way to make sure the click event makes to to the view controller function? I could do a $rootscope.$broadcast but that feels hacky.
Last update
Never mind, I found my own bug. The binding of the event was incorrect, should have been (note the parenthesis):
<md-button ng-click="button.click()">
In the meantime I've figured out a nice way to do this. The post itself now also contains the answer.
I have a single page app using Backbone, and whenever I over over something and then click the "back" button, the popover forever stays.
I want to destroy all instances of popover when a new instance is loaded.
Finding the popovers that are created through the data API is not difficult and has been covered in other answers like those of David Mulder and Amir Popovich. You just do:
$("[data-toggle='popover']").popover('hide');
Or you can use destroy if you need to or prefer to.
The challenge is to handle those popovers that are created dynamically.
Marking the Elements with Popovers
I would implement something like this. I'd override the default popover method and I'd try to perform this override as early as possible so that everything that needs a popover uses my override. What it does is just mark elements that use a popover with a class. Bootstrap does not mark them itself:
// Override popover so as to mark everything that uses a popover.
var old_popover = $.fn.popover;
function my_popover() {
this.addClass('marked-as-having-a-popover');
return old_popover.apply(this, arguments);
}
$.fn.popover = my_popover;
Then to clear everything before the unloading, I'd put in the code that detects the unloading the following:
$(".marked-as-having-a-popover").popover('hide');
Or it could use destroy rather than hide if testing shows that it works better for your use-case.
Now, the method above will work if the override happens early enough and you do not have a page where multiple jQueries are loaded. (Yep, this is possible.) I use something similar to deal with tooltips in one of my applications so I know the principle is sound. It so happens that in my app, all tooltips are created by my code so there is no risk of missing something.
Finding All Elements with Popovers, Even Unmarked
If you are in a situation where a popover can be created without being marked (I call this an "escapee"), then you need to query the whole DOM and find which elements have popovers. There is no shortcut here. You cannot rely on attributes like data-content because popovers can be created wholly dynamically (i.e. without any of the data- attributes). Also, all kinds of elements can get popovers, so you cannot reliably assume that only button elements will have a popover. The only surefire way to find everything that needs handling is to look at each element in the DOM and check whether it has a popover:
// Obviously this is quite expensive but in a situation where there *can* be escapees
// then you have to check all elements to see if they have a popover.
$("*").each(function () {
// Bootstrap sets a data field with key `bs.popover` on elements that have a popover.
// Note that there is no corresponding **HTML attribute** on the elements so we cannot
// perform a search by attribute.
var popover = $.data(this, "bs.popover");
if (popover)
$(this).popover('hide');
});
Again, destroy could be used rather than hide.
Proof of Concept
Here is a fiddle that illustrates the entire thing:
"Add a Dynamic Popover" simulates code that would add a popover when the override is in effect.
"Add an Escapee" simulates code that would add a popover and somehow manage to use the original Bootstrap code.
"Clear Marked" clears only the marked popovers.
"Clear All" clears every single popover marked or not.
try with this:
$('YOUR_ELEMENT_SELECTOR').popover('dispose');
reference url: https://getbootstrap.com/docs/4.1/components/popovers/
Its very simple, just you have to call one function popover() with argument "destroy" to destroy the popover. It will destroy all popovers which is created by $("[data-toggle=popover]").popover();
you can check documentation for more options and arguments of popover().
I suggest you to destroy popovers with having specific class name instead of using following code.
$("[data-toggle='popover']").popover('destroy');
The above code will destroy all popovers in the page. So instead of this, use class selector.
$(".YourClassName").popover('destroy');
If you have problems and need to remove all for sure:
$('.popover').remove();
will help (Popover automatic add this class, even for dynamicly created objects). It destroys all the popover DOM-Object incl. callbacks, etc.
But thats the rough way. Typically I displose all by popover class (clean way) and to be sure I do a hard clean up after. Works for me fine!
$('.popover').popover('dispose');
$('.popover').remove();
If you like to remove all execpt one, use a filter() with :not-Selector
$('.popover').filter(':not(#yourID)').popover('dispose');
$('.popover').filter(':not(#yourID)').remove();
popover adds also a id with a random number
#popoverxxxxx where xxxxx is a five digit number.
this helps sometimes to compare popovers. Of cause this could also be used to identify the popovers.
Something generic like this (assuming you're using data-bindings) should do the trick:
$('[data-toggle="popover"]').popover('hide')
or the more extreme call
$('[data-toggle="popover"]').popover('destroy')
though I doubt that would make sense often. Still to address the specific bug you're encountering you should create a minimal test case so that that bug itself can be addressed.
Oh and if you specifically want to check for open popovers you can use .data("bs.popover").$tip.parent().length (which is a bit of an hack), for example:
$('[data-toggle="popover"]:eq(0)').data("bs.popover").$tip.parent().length == 1
You can hide all popovers by using this:
$("[data-toggle='popover']").popover('hide');
You can destroy all popovers by using this:
$("[data-toggle='popover']").popover('destroy');
The difference between hide and destory is that when you hide a popover you do not need to reactive it, but when you destroy it you do.
Check out my JSFIDDLE and then:
Click on all popovers and then click hide. After clicking hide you can click on the popovers again.
Click on all popovers and then click destroy. After clicking destroy try clicking on the popovers again and see that nothing will happen since they are destroyed. In order to make them functional again, you will need to click on reactive and then try.
Popovers must be initialized manually, so you know exactly what you have to destroy because you did initialize it.
You should just call the destroy function with the same selector.
Or maybe I am missing something ?
I'm working in AngularJS / Jade, and I need to change an icon when you click on something. Currently I have the following code:
a(onclick='return false', href='#general', data-toggle='tab') General
span.glyphicon(ng-class='isGeneralChanged(selected) ? "glyphicon-ok-circle" : "glyphicon-adjust"')
Then below that:
.form-group
label Has the design entered?
toggle-switch(model='selected.general_engineer_made_inspection', on-label='Yes', off-label='No')
So basically the problem I have is that here it's easy, it's either "yes" or "no" and that changes the icon, but the next tab is just a list of names that once you just click on one the icon in the tab should change. I'm not sure how I could solve this. ng-click event?
Use a boolean to hide,show,toggle elements. Angular makes this easy. (look up ng-show, ng-hide, ng-switch)
Also, I prefer to use ng-class this way:
i.fa.fw(ng-class="{true:'fa-lock', false:'fa-unlock'}[locked]")
The initial value is set in the controller:
$scope.locked = true;
Then, a button that toggles the boolean (and thus, the class) is added to the view. Note the ng-click="locked=!locked"
button(ng-click="locked=!locked" ng-switch="locked")
span(ng-switch-when="true") Lock
span(ng-switch-when="false") Unlock
We can also employ the boolean to hide or show whatever we'd like:
pre(ng-show="locked") Locked!
pre(ng-show="!locked) Unlocked!
Here's a Plunk http://plnkr.co/edit/0PDT2cpQGwFKsu8h1hdg?p=preview that covers all of the above (albeit, in HTML, not Jade). The demo uses FontAwesome.