Angular directive two way databinding fails - angularjs

I have a dropdown directive with a function which sets the active item in the dropdown:
angular.module('clientApp')
.directive('customDropDown', function ($filter, calculationsFactory) {
return {
templateUrl: 'template/custom-drop-down/custom-drop-down.html',
restrict: 'E',
scope: {
resources: '=',
activeResource: '=',
},
link: function postLink(scope) {
scope.setActiveResource = function(resource){
scope.activeResource = resource;
};
}
};
});
Markup:
<custom-drop-down resources="medias" active-resource="activeMedia"></custom-drop-down>
DirectiveTemplate:
<div class="btn-group" dropdown>
<button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle style="width:100%;" ng-click="$event.stopPropagation()">
{{activeResource.title}} <span class="caret pull-right" style="margin-top:8px;"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="resource in filtredResources"
ng-click="setActiveResource(resource)"
ng-class="{active: resource === activeResource}">
<a href>{{resource.title}}</a>
</li>
</ul>
</div>
Problem is that the function don't two way bind "activeMedia" in this case. It does not get updated.
I tried to replicate the case in jsfiddle but there it works. Anything I could have missed?
Update
I have identified the issue.
My directive is placed within the accordion directive from ui-bootstrap:
<accordion>
<accordion-group heading="Resources" is-open="true">
<custom-drop-down resources="medias" active-resource="activeMedia"></custom-drop-down>
</accordion-group>
</accordion>
If I move my directive outside the accordion directive, it works. So somehow the accordion directive is interfering with my directive. Possible to solve without modifying the code for ui-bootstrap?

In your case it is a scoping issue, as discussed here.
For elements inside angular-bootstrap controls you need to use $parent. as a prefix on scope variables.
<accordion>
<accordion-group heading="Resources" is-open="true">
<custom-drop-down resources="$parent.medias" active-resource="$parent.activeMedia"></custom-drop-down>
</accordion-group>
</accordion>

Related

$event.stopPropagation doesn't play nice with Bootstrap data-toggle

I have a list of elements, each containing a ng-click. Nested inside each element is a div that should toggle a Bootstrap modal.
I have added the $event.stopPropagation to the nested div because i don't want the ng-click event from the parent element to fire, but this causes the modal to not begin displayed.
<div id="segment{{segment.Id}}" class="message" ng-class="{activeSegment: segment.Selected}" ng-click="!segmentIsLoaded || selectSegment(segment)">
<div>
<div class="pull-left">
<span>ID: {{segment.Id}}</span>
<span>{{segment.Name}} </span>
<i class="fa fa-info-circle fa-lg" data-toggle="tooltip" data-original-title="{{segment.Description}}" tooltip></i><br />
<small>{{formatJsonDate(segment.Updated)}}</small>
</div>
<div class="btn btn-danger pull-right" data-toggle="modal" data-target="#deleteSegmentModal" ng-click="$event.stopPropagation();">
<span><i class="fa fa-trash-o" aria-hidden="true"></i></span>
</div>
</div>
</div>
Any known work-arounds for this?
I should perhaps mention that the stopPropagation() works as far as preventing the click event from firing. My issue is with the bootstrap modal not being activated through data-toggle attribute
Have you tried using a directive
<a ng-click='expression' stop-event='click'>
.directive('stopEvent', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
if(attr && attr.stopEvent)
element.bind(attr.stopEvent, function (e) {
e.stopPropagation();
});
}
};
});
check SO link How can I make an AngularJS directive to stopPropagation?

AngularJS + Bootstrap: dropdown list not populating correctly

I could sure use some guidance on this... I've got a simple Bootstrap dropdown list that doesn't seem to be populating properly, whether I use ng-repeat to retrieve the list from the model OR list the items manually.
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="input-group-btn">
<button type="button" class="btn btn-info" ng-click="submit()"> {{selectedAction.name}}</button>
<button type="button" class="btn btn-default btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <span class="caret"></span></button>
<ul class="dropdown-menu dropdown-menu-right">
<li ng-repeat="action in actions">
{{action.name}}
</li>
</ul>
</div><!-- /btn-group -->
</div><!-- /.col-lg-4 -->
</div><!-- /.row -->
Also, more importantly, the ng-click directive should change the DOM via normal binding, but this doesn't seem to work either.
var DropDownApp = angular.module('DropDownApp', []);
DropDownApp.controller('DropDownCtrl', ['$scope', function($scope) {
$scope.actions = [
{id: 'action1', name: 'Action 1'},
{id: 'action2', name: 'Action 2'},
{id: 'action3', name: 'Action 3'}
];
$scope.selectedAction = $scope.actions[0];
$scope.setAction = function(action) {
$scope.selectedAction = action;
console.log("selected==>", $scope.selectedAction);
$scope.submit();
};
$scope.submit = function() {
console.log($scope.selectedAction.id);
};
}]);
You can see the code behavior here: Plunker
Please note the points below:
Include ui.bootstrap dependancy in app.js to work with Angular ui-bootstrap.
This directive is composed by three parts:
uib-dropdown which transforms a node into a dropdown.
uib-dropdown-toggle which allows the dropdown to be toggled via click. This directive is optional.
uib-dropdown-menu which transforms a node into the popup menu.
Each of these parts need to be used as attribute directives.
Please find the working plunker here: http://plnkr.co/edit/v2uonllub3GsTFUxdFys?p=preview
Cheers!

angular bootstrap accordion is-open scope

I am trying to figure out how to close an accordion group from a button within the group.. seems like it should be easy.. but looks to be something with the scope being defined only within the group and not available in the controller? in the code snippet below the first button is how I would like to close the accordion group. The second button works.
Here is a simple plunkr on what I'm working on https://plnkr.co/edit/bghRaioszH3SZmiWxcoH?p=preview
<uib-accordion close-others="true" ng-controller="testCtrl">
<uib-accordion-group panel-class="panel-primary" is-open="status.isOpen">
<uib-accordion-heading>
Open: {{ status.isOpen }}
<i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.isOpen, 'glyphicon-chevron-right': !status.isOpen}"></i>
</uib-accordion-heading>
<button class="btn btn-warning" ng-click="close()">Cancel</button>
<button class="btn btn-warning" ng-click="status.isOpen=!status.isOpen">Cancel</button>
</uib-accordion-group>
</uib-accordion>
To access the status of an accordion group through your controller's scope, you need to do something like this:
Move ng-controller="testCtrl" to the <body> element
Define status explicitly in your controller's scope:
.controller('testCtrl', function($scope) {
$scope.status = {
isOpen: true
}
$scope.close = function(){
$scope.status.isOpen = false;
};
});

Two-way binding on the contents of a recursive directive in Angular?

I'd like to produce a recursively structured document corresponding to a tree structure in my model.
The entire document is contentEditable and the models should react to changes made to an <li> within the view.
I'm using the RecursiveHelper module to avoid endless loops. I'm still trying to figure out post-link, compile, etc.
I'm a little confused which elements are associated with which controllers and scopes.
I know that an iscolate scope is being created at each level of recursion, but I'm not sure how that affects my ability to reference variables within that iscolate scope as models to bind to.
In my main.js:
.directive('bullet',function(RecursionHelper){
return {
restrict: "E",
scope:
{
node: '=node',
},
controller: function(),
template:
`
<button class="btn btn-default" ng-click="node.toggleExpanded()" ng-show="node.children.length != 0">
<span ng-show="!node.expanded" class="glyphicon glyphicon-plus" aria-hidden="true"></span>
<span ng-show="node.expanded" class="glyphicon glyphicon-minus" aria-hidden="true"></span>
</button>
{{node.content}}
<ul class="list-group-sm" ng-show="node.expanded">
<li class="list-group-item" ng-repeat="child in node.children">
<bullet node="child" ng-model="child"></bullet>
</li>
</ul>
`,
compile: function(element) {
return RecursionHelper.compile(element, function(scope, elm, attrs, ctrl, transcludeFn){
});
}
}
})
Then within my index.html:
<ul class="list-group-sm" contentEditable="true">
<li class="list-group-item" ng-repeat="child in currentBullet.children">
<bullet node="child"> </bullet>
</li>
</ul>

How to prevent an angular-bootstrap dropdown from closing (Unbind Event which was bound by a directive)

I am using the Angular-Bootstrap Dropdown. I want to prevent it from closing on click until the user closes it intentionally.
Default state is: The Dropdown closes when clicking somewhere in the Document.
I identified the relevant lines of code: (Line 12, dropdown.js)
this.open = function( dropdownScope ) {
if ( !openScope ) {
$document.bind('click', closeDropdown); // line to unbind
$document.bind('keydown', escapeKeyBind);
}
}
You can find the full code here: Link to Github
I don't want to change the original sources of angular-bootstrap to keep my project open for updates.
My question:
How can i unbind an event bound by a Directive to the document in an Angular Controller?
I solved this by adding the following to my drop down-menu. This prevents the drop down from closing unless you click on the tag that opens it
<ul class="dropdown-menu" ng-click="$event.stopPropagation()">
For those who are using Angular UI-Bootstrap 0.13.0 or later version, here is the cleaner way that states on the UI-Bootstrap documentation.
By default the dropdown will automatically close if any of its elements is clicked, you can change this behavior by setting the auto-close option as follows:
always - (Default) automatically closes the dropdown when any of its
elements is clicked.
outsideClick - closes the dropdown automatically only when the user
clicks any element outside the dropdown.
disabled - disables the auto close. You can then control the
open/close status of the dropdown manually, by using is-open. Please
notice that the dropdown will still close if the toggle is clicked,
the esc key is pressed or another dropdown is open. The dropdown will
no longer close on $locationChangeSuccess events.
Here is the link to the documentation:
https://angular-ui.github.io/bootstrap/#/dropdown
You can stop event from bubbling up in DOM Tree in angular 2 and above by adding event propagation.
Ex: (click)="$event.stopPropagation()"
This is another hack, but you could add a directive to stop the toggle event from propagating. For example something like this worked for my specific use case:
<div>
<div class="btn-group" dropdown is-open="status.isopen" ng-controller="DropDownCtrl">
<button type="button" class="btn btn-primary dropdown-toggle" ng-disabled="disabled">
Button dropdown <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-click="goToPage('Action')">Action</li>
<li disable-auto-close>Don't Dismiss</li>
<li ng-click="goToPage('SomethingElse')">Something else here</li>
</ul>
</div>
Adding this directive to an element should disable the auto close behavior:
angular.module('plunker', ['ui.bootstrap'])
.controller('DropDownCtrl', ['$scope', '$location',
function($scope, $location) {
// Controller logic here
$scope.goToPage = function(page) {
console.log("Going to " + page + ". Dropdown should close");
$location.path(page);
};
}])
.directive('disableAutoClose', function() {
// directive for disabling the default
// close on 'click' behavior
return {
link: function($scope, $element) {
$element.on('click', function($event) {
console.log("Dropdown should not close");
$event.stopPropagation();
});
}
};
});
Plunker Example Here
This is a crude way of overriding it. You need to control the is-open attribute manually and hijack the on-toggle event, example:
<div class="btn-group" dropdown is-open="ctrl.isOpen" on-toggle="toggled(open)">
<button type="button" class="btn btn-primary dropdown-toggle">
Button dropdown <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li>Action</li>
<li>Another action</li>
<li>Something else here</li>
<li class="divider"></li>
<li>Separated link</li>
</ul>
</div>
Controller:
$scope.toggled = function (open) {
$timeout(function () {
$scope.ctrl.isOpen = true;
});
};
I would ask for a property on the dropdownConfig constant (something like autoClose) for a permanent solution.
This is an even more crude way of overriding it based on Rob Jacobs answer except that it prevents the ugly flickering ulilcht commented on:
$scope.toggled = function (open) {
$scope.open = true;
var child = $scope.$$childHead;
while (child) {
if (child.focusToggleElement) {
child.isOpen = true;
break;
}
child = child.$$nextSibling;
}
};
You can also use this solution : https://gist.github.com/Xspirits/684beb66e2499c3ff0e5
Gives you a bit more of control over the dropdown, if that's ever needed.
You can decorate directives.
With this way you don't have to touch the original code and you can keep the original behaviour.
You can put a close button inside the dropdown
HTML
<div class="dropdown-menu keep-dropdown-open-on-click" role="menu">
<i class="icon-close close-dropdown-on-click"></i>
</div>
JS
angular.module('app').config(uiDropdownMenuDecorate);
uiDropdownMenuDecorate.$inject = ['$provide'];
function uiDropdownMenuDecorate($provide) {
$provide.decorator('dropdownMenuDirective', uiDropdownMenuDecorator);
uiDropdownMenuDecorator.$inject = ['$delegate'];
function uiDropdownMenuDecorator($delegate) {
var directive = $delegate[0];
var link = directive.link;
directive.compile = function () {
return function (scope, elem, attrs, ctrl) {
link.apply(this, [scope, elem, attrs, ctrl]);
elem.click(function (e) {
if (elem.hasClass('keep-dropdown-open-on-click') && !angular.element(e.target).hasClass('close-dropdown-on-click')) {
e.stopPropagation();
}
});
};
};
return $delegate;
}
}
Here is what the code looks like using the approved angular-bootstrap auto-close method. Notice the auto-close attribute goes on the top <div>.
<div class="btn-group" uib-dropdown auto-close="disabled">
<button id="single-button" type="button" class="btn btn-primary" uib-dropdown-toggle>
Button dropdown <span class="caret"></span>
</button>
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button">
<textarea class="form-control" ng-model="description" rows="4" placeholder="Description"></textarea>
</ul>
</div>

Resources