Reputation: 1847
Implementing a simple dropdown directive that uses ng-show to show or hide a menu is trivial in AngularJS. I want to also close the menu if the user clicks elsewhere in the screen after having opened the menu. What is the simplest possible correct way to do this? I have tried using $document.bind and $document.unbind to unbind from the document afterwards, but it does not seem to work as expected, and I can't work ought how it ought to work due to questions of scoping.
I'm aware the ui-bootstrap has a similar directive (dropdownToggle), and I've dug through the source of it, but it looks much more complicated and introduces a dependency on an older version of bootstrap.
Here is an example plunker (that does not close the menu on click elsewhere).
Code:
<!DOCTYPE html>
<html ng-app="plunker" >
<head>
<link data-require="[email protected]" data-semver="3.0.0" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.js"></script>
<script src="example.js"></script>
<link href="///netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div ng-app="dropdown">
<div menu-status-widget></div>
</div>
</body>
</html>
Upvotes: 0
Views: 7425
Reputation: 2786
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;
}
}
Upvotes: 1
Reputation: 171679
Here's how to handle situation using ng-click
. You need to pass $event
argument into your ng-click
function so can call $event.stopPropagation()
ng-click
doesn't seem to react the same way a jQuery handler would. It seems to actually trigger the event after all the code is run, so this causes the $document
handler you just added to actually fire.
<div ng-click="buttonAction($event)" >
scope.buttonAction = function($event) {
$event.stopPropagation()
if (!scope.showMenu) {
var closeMe = function(scope) {
scope.showMenu = false;
$document.unbind('click', this);
};
$document.bind('click', function(event) {
scope.$apply(function(){
closeMe(scope)
})
});
scope.showMenu = true;
} else {
scope.showMenu = false;
}
};
In my mind it is simpler to forget about ng-click
and just use element.bind
. Either way you still have to use scope.$apply()
to change scope so angular runs a digest
Upvotes: 2