0

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>
4
  • seems to be a problem adding a document clcik handler within ng-click. Use own click handler. can use angular-ui dropdown code as reference Commented Nov 9, 2013 at 1:17
  • @charlietfl the document click handler does work; If I drop an alert in it, I get an alert when I click elsewhere. Commented Nov 9, 2013 at 22:44
  • what i experienced was the document clcik handler seems to fire as soon as you bind it within ng-click. WHen using external events that angular isn't aware of to change scope, you have to change the scope within $.apply which triggers digest . Once you do this using the 'ng-clcik approach, the menu never open...the 2 cancel each other Commented Nov 9, 2013 at 22:49
  • that's why I suggest following pattern the angular-ui folks use and avoid ng-click, Commented Nov 9, 2013 at 22:51

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

2 Comments

Perfect! Thanks for the extra explanation.
your previous comment made a light go off...I actually played with this for about 1/2 hour the other day and got frustrated with ng-clcik ...forgot you could pass $event to it
1

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;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.