Case
Case

Reputation: 1847

Dropdown Toggle with AngularJS That Closes on Click Elsewhere

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

Answers (2)

cstuncsik
cstuncsik

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

charlietfl
charlietfl

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

DEMO

Upvotes: 2

Related Questions