单击元素外部时触发事件的指令 [英] Directive that fires an event when clicking outside of the element

查看:26
本文介绍了单击元素外部时触发事件的指令的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道有很多问题问类似的事情.但没有人真正解决我的问题.

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.

为什么我需要这个功能?我正在构建一个应用程序,在这个应用程序中,有 3 个下拉菜单,5 个下拉列表(如已选择).所有这些都是角度指令.让我们假设所有这些指令都是不同的.所以我们有 8 个指令.它们都需要一个相同的功能:当点击元素的外侧时,需要隐藏下拉菜单.

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.

我有 2 个解决方案,但都有问题:

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

解决方案 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);
      })
    }
  }
})

这是解决方案 A 的示例:单击此处

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.

解决方案 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
}]);

这是解决方案 B 的示例:单击此处

Here is example for solution B: click here

如果页面中只有一个指令但我的应用程序中没有,则解决方案 A 有效.因为是防止冒泡,所以先点击dropdown1,显示dropdown1,再点击dropdown2,防止点击事件,所以dropdown1在dropdown1外点击还是显示.

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.

解决方案 B 在我现在使用的应用程序中有效.但问题是它会导致性能问题.每次单击应用程序中的任何位置都会处理太多的单击事件.在我目前的情况下,有 8 个单击事件与文档绑定,因此每次单击执行 8 个功能.这导致我的应用程序很慢,尤其是在 IE8 中.

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

推荐答案

我不会使用 event.stopPropagation() 因为它会导致您在解决方案 A 中看到的那种问题.如果可能,我也会使用模糊和焦点事件.当您的下拉列表附加到输入时,您可以在输入失去焦点时关闭它.

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>

我尚未测试您的 onClick 功能.我认为它按预期工作.希望这会有所帮助.

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

这篇关于单击元素外部时触发事件的指令的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆