Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
182 views
in Technique[技术] by (71.8m points)

javascript - Directive that fires an event when clicking outside of the element

I know there are many questions ask similar thing. But no one really solve my issue.

I'm trying to build an directive which will execute an expression when mouse click outside the current element.

Why I need this function? I'm building an app, in this app, there are 3 dropdown menu, 5 dropdown list(like chosen). All these are angular directives. Let's assume all these directives are different. So we have 8 directives. And all of them need a same function: when click out side the element, need hide the dropdown.

I have 2 solutions for this, but both got issue:

Solution A:

app.directive('clickAnywhereButHere', function($document){
  return {
    restrict: 'A',
    link: function(scope, elem, attr, ctrl) {
      elem.bind('click', function(e) {
        // this part keeps it from firing the click on the document.
        e.stopPropagation();
      });
      $document.bind('click', function() {
        // magic here.
        scope.$apply(attr.clickAnywhereButHere);
      })
    }
  }
})

Here is an example for solution A: click here

When you click the first dropdown, then working, then click second input, the first should hide but not.

Solution B:

app.directive('clickAnywhereButHere', ['$document', function ($document) {
    directiveDefinitionObject = {
        link: {
            pre: function (scope, element, attrs, controller) { },
            post: function (scope, element, attrs, controller) {
                onClick = function (event) {
                    var isChild = element.has(event.target).length > 0;
                    var isSelf = element[0] == event.target;
                    var isInside = isChild || isSelf;
                    if (!isInside) {
                        scope.$apply(attrs.clickAnywhereButHere)
                    }
                }
                $document.click(onClick)
            }
        }
    }
    return directiveDefinitionObject
}]);

Here is example for solution B: click here

Solution A working if there is just one directive in the page but not in my app. Because it's prevent bubbling, so first when I click on dropdown1, show dropdown1, then click on dropdown2, click event be prevent, so dropdown1 still showing there even I click outside the dropdown1.

Solution B working in my app which I'm using now. But the issue is it's cause a performance issue. Too many click event be handled on every single click on anywhere in the app. In my current case, there are 8 click event bind with document, so every click execute 8 functions. Which cause my app very slow, especially in IE8.

So is there any better solution for this? Thanks

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I would not use event.stopPropagation() since it causes exactly the kind of problems you see in solution A. If possible, I would also resort to blur and focus events. When your dropdown is attached to an input, you can close it when the input loses the focus.

However, handling click events on the document is not so bad either, so if you want to avoid handling the same click event several times, just unbind it from the document when it is not needed anymore. In addition to the expression being evaluated when clicking outside the dropdown, the directive needs to know whether it is active or not:

app.directive('clickAnywhereButHere', ['$document', function ($document) {
    return {
        link: function postLink(scope, element, attrs) {
            var onClick = function (event) {
                var isChild = $(element).has(event.target).length > 0;
                var isSelf = element[0] == event.target;
                var isInside = isChild || isSelf;
                if (!isInside) {
                    scope.$apply(attrs.clickAnywhereButHere)
                }
            }
            scope.$watch(attrs.isActive, function(newValue, oldValue) {
                if (newValue !== oldValue && newValue == true) {
                    $document.bind('click', onClick);
                }
                else if (newValue !== oldValue && newValue == false) {
                    $document.unbind('click', onClick);
                }
            });
        }
    };
}]);

When using the directive, just provide another expression like this:

<your-dropdown click-anywhere-but-here="close()" is-active="isDropdownOpen()"></your-dropdown>

I have not tested your onClick function. I assume it works as expected. Hope this helps.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...