AngularJS dropdown directive hide when clicking outside

后端 未结 9 2083
长发绾君心
长发绾君心 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 23:03

    I found some issues with the implementation in https://github.com/IamAdamJowett/angular-click-outside

    If for example the element clicked on is removed from the DOM, the directive above will trigger the logic. That didn't work for me, since I had some logic in a modal that, after click, removed the element with a ng-if.

    I rewrote his implementation. Not battle tested, but seems to be working better (at least in my scenario)

    angular
      .module('sbs.directives')
      .directive('clickOutside', ['$document', '$parse', '$timeout', clickOutside]);
    
    const MAX_RECURSIONS = 400;
    
    function clickOutside($document, $parse, $timeout) {
      return {
        restrict: 'A',
        link: function ($scope, elem, attr) {
          // postpone linking to next digest to allow for unique id generation
          $timeout(() => {
            function runLogicIfClickedElementIsOutside(e) {
              // check if our element already hidden and abort if so
              if (angular.element(elem).hasClass('ng-hide')) {
                return;
              }
    
              // if there is no click target, no point going on
              if (!e || !e.target) {
                return;
              }
    
              let clickedElementIsOutsideDirectiveRoot = false;
              let hasParent = true;
              let recursions = 0;
    
              let compareNode = elem[0].parentNode;
              while (
                !clickedElementIsOutsideDirectiveRoot &&
                hasParent &&
                recursions < MAX_RECURSIONS
              ) {
                if (e.target === compareNode) {
                  clickedElementIsOutsideDirectiveRoot = true;
                }
    
                compareNode = compareNode.parentNode;
                hasParent = Boolean(compareNode);
                recursions++; // just in case to avoid eternal loop
              }
    
              if (clickedElementIsOutsideDirectiveRoot) {
                $timeout(function () {
                  const fn = $parse(attr['clickOutside']);
                  fn($scope, { event: e });
                });
              }
            }
    
            // if the devices has a touchscreen, listen for this event
            if (_hasTouch()) {
              $document.on('touchstart', function () {
                setTimeout(runLogicIfClickedElementIsOutside);
              });
            }
    
            // still listen for the click event even if there is touch to cater for touchscreen laptops
            $document.on('click', runLogicIfClickedElementIsOutside);
    
            // when the scope is destroyed, clean up the documents event handlers as we don't want it hanging around
            $scope.$on('$destroy', function () {
              if (_hasTouch()) {
                $document.off('touchstart', runLogicIfClickedElementIsOutside);
              }
    
              $document.off('click', runLogicIfClickedElementIsOutside);
            });
          });
        },
      };
    }
    
    function _hasTouch() {
      // works on most browsers, IE10/11 and Surface
      return 'ontouchstart' in window || navigator.maxTouchPoints;
    }
    
    0 讨论(0)
  • 2020-11-29 23:04

    This is an old post but in case this helps anyone here is a working example of click outside that doesn't rely on anything but angular.

    module('clickOutside', []).directive('clickOutside', function ($document) {
    
            return {
               restrict: 'A',
               scope: {
                   clickOutside: '&'
               },
               link: function (scope, el, attr) {
    
                   $document.on('click', function (e) {
                       if (el !== e.target && !el[0].contains(e.target)) {
                            scope.$apply(function () {
                                scope.$eval(scope.clickOutside);
                            });
                        }
                   });
               }
            }
    
        });
    
    0 讨论(0)
  • 2020-11-29 23:05

    I realized it by listening for a global click event like so:

    .directive('globalEvents', ['News', function(News) {
        // Used for global events
        return function(scope, element) {
            // Listens for a mouse click
            // Need to close drop down menus
            element.bind('click', function(e) {
                News.setClick(e.target);
            });
        }
    }])
    

    The event itself is then broadcasted via a News service

    angular.factory('News', ['$rootScope', function($rootScope) {
        var news = {};
        news.setClick = function( target ) {
            this.clickTarget = target;
            $rootScope.$broadcast('click');
        };
    }]);
    

    You can then listen for the broadcast anywhere you need to. Here is an example directive:

    .directive('dropdown', ['News', function(News) {
      // Drop down menu für the logo button
      return {
        restrict: 'E',
        scope: {},
        link: function(scope, element) {
          var opened = true;
          // Toggles the visibility of the drop down menu
          scope.toggle = function() {
            element.removeClass(opened ? 'closed' : 'opened');
            element.addClass(opened ? 'opened' : 'closed');
          };
          // Listens for the global click event broad-casted by the News service
          scope.$on('click', function() {
            if (element.find(News.clickTarget.tagName)[0] !== News.clickTarget) {
              scope.toggle(false);
            }
          });
          // Init
          scope.toggle();
        }
      }
    }])
    

    I hope it helps!

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