AngularJS dropdown directive hide when clicking outside

后端 未结 9 2082
长发绾君心
长发绾君心 2020-11-29 22:06

I\'m trying to create a multiselect dropdown list with checkbox and filter option. I\'m trying to get the list hidden with I click outside but could not figure it out how. A

相关标签:
9条回答
  • 2020-11-29 22:41

    I was not totally satisfied with the answers provided so I made my own. Improvements:

    • More defensive updating of the scope. Will check to see if a apply/digest is already in progress
    • div will also close when the user presses the escape key
    • window events are unbound when the div is closed (prevents leaks)
    • window events are unbound when the scope is destroyed (prevents leaks)

      function link(scope, $element, attributes, $window) {

      var el = $element[0],
          $$window = angular.element($window);
      
      function onClick(event) {
          console.log('window clicked');
      
          // might need to polyfill node.contains
          if (el.contains(event.target)) {
              console.log('click inside element');
              return;
      
          }
      
          scope.isActive = !scope.isActive;
          if (!scope.$$phase) {
              scope.$apply();
          }
      }
      
      function onKeyUp(event) {
      
          if (event.keyCode !== 27) {
              return;
          }
      
          console.log('escape pressed');
      
          scope.isActive = false;
          if (!scope.$$phase) {
              scope.$apply();
          }
      }
      
      function bindCloseHandler() {
          console.log('binding window click event');
          $$window.on('click', onClick);
          $$window.on('keyup', onKeyUp);
      }
      
      function unbindCloseHandler() {
          console.log('unbinding window click event');
          $$window.off('click', onClick);
          $$window.off('keyup', onKeyUp);
      }
      
      scope.$watch('isActive', function(newValue, oldValue) {
          if (newValue) {
              bindCloseHandler();
          } else {
              unbindCloseHandler();
          }
      });
      
      // prevent leaks - destroy handlers when scope is destroyed
      scope.$on('$destroy', function() {
          unbindCloseHandler();
      });
      

      }

    I get $window directly into the link function. However, you do not need to do this exactly to get $window.

    function directive($window) {
        return {
            restrict: 'AE',
            link: function(scope, $element, attributes) {
                link.call(null, scope, $element, attributes, $window);
            }
        };
    }
    
    0 讨论(0)
  • 2020-11-29 22:42

    Watch out, your solution (the Plunker provided in the question) doesn't close the popups of other boxes when opening a second popup (on a page with multiple selects).

    By clicking on a box to open a new popup the click event will always be stopped. The event will never reach any other opened popup (to close them).

    I solved this by removing the event.stopPropagation(); line and matching all child elements of the popup.

    The popup will only be closed, if the events element doesn't match any child elements of the popup.

    I changed the directive code to the following:

    select.html (directive code)

    link: function(scope, element, attr){
    
        scope.isPopupVisible = false;
    
        scope.toggleSelect = function(){
            scope.isPopupVisible = !scope.isPopupVisible;
        }
    
        $(document).bind('click', function(event){
            var isClickedElementChildOfPopup = element
                .find(event.target)
                .length > 0;
    
            if (isClickedElementChildOfPopup)
                return;
    
            scope.$apply(function(){
                scope.isPopupVisible = false;
            });
        });
    }
    

    I forked your plunker and applied the changes:

    Plunker: Hide popup div on click outside

    Screenshot:

    Plunker Screenshot

    0 讨论(0)
  • 2020-11-29 22:52

    OK I had to call $apply() as the event is happening outside angular world (as per doc).

        element.bind('click', function(event) {
        event.stopPropagation();      
        });
    
        $document.bind('click', function(){
        scope.isVisible = false;
        scope.$apply();
        });
    
    0 讨论(0)
  • 2020-11-29 22:58

    Use angular-click-outside

    Installation:

    bower install angular-click-outside --save
    npm install @iamadamjowett/angular-click-outside
    yarn add @iamadamjowett/angular-click-outside
    

    Usage:

    angular.module('myApp', ['angular-click-outside'])
    
    //in your html
    <div class="menu" click-outside="closeThis">
    ...
    </div>
    
    //And then in your controller
    $scope.closeThis = function () {
        console.log('closing');
    }
    
    0 讨论(0)
  • 2020-11-29 22:59

    There is a cool directive called angular-click-outside. You can use it in your project. It is super simple to use:

    https://github.com/IamAdamJowett/angular-click-outside

    0 讨论(0)
  • 2020-11-29 23:03

    The answer Danny F posted is awesome and nearly complete, but Thịnh's comment is correct, so here is my modified directive to remove the listeners on the $destroy event of the directive:

    const ClickModule = angular
    .module('clickOutside', [])
    .directive('clickOutside', ['$document', function ($document) {
        return {
            restrict: 'A',
            scope: {
                clickOutside: '&'
            },
            link: function (scope, el, attr) {
                const handler = function (e) {
                    if (el !== e.target && !el[0].contains(e.target)) {
                        scope.$apply(function () {
                            console.log("hiiii");
                            //  whatever expression you assign to the click-outside attribute gets executed here
                            //  good for closing dropdowns etc
                            scope.$eval(scope.clickOutside);
                        });
                    }
                }
    
                $document.on('click', handler);
    
                scope.$on('$destroy', function() {
                    $document.off('click', handler);
                });
            }
        }
    }]);
    

    If you put a log in the handler method, you will still see it fire when an element has been removed from the DOM. Adding my small change is enough to remove it. Not trying to steal anyone's thunder, but this is a fix to an elegant solution.

    0 讨论(0)
提交回复
热议问题