Angular在元素事件之外单击

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

我试图build立一个指令,当鼠标在当前元素之外单击时将执行一个expression式。

为什么我需要这个function? 我正在build立一个应用程序,在这个应用程序,有3下拉菜单,5下拉列表(如select)。 所有这些都是angular度指令。 我们假设所有这些指令是不同的。 所以我们有8个指令。 他们都需要一个相同的function:当点击元素旁边时,需要隐藏下拉菜单。

我有2个解决scheme,但都得到了问题:

答案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); }) } } }) 

以下是解决schemeA的示例: 单击此处

当你点击第一个下拉菜单,然后工作,然后点击第二个input,第一个应该隐藏,但不是。

解决schemeB:

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

这里是解决schemeB的例子: 点击这里

解决scheme如果在页面中只有一个指令,但不在我的应用程序中,则工作。 因为这是防止冒泡,所以首先当我点击dropdown1,显示dropdown1,然后点击dropdown2,单击事件被阻止,所以dropdown1仍然显示在那里,即使我点击下拉以外1。

解决schemeB在我现在使用的应用程序中工作。 但问题是这是一个性能问题。 在应用程序中的任何位置点击一次,就可以处理太多的点击事件。 在我目前的情况下,有8个点击事件与文档绑定,所以每点击一下就执行8个function。 这导致我的应用程序非常慢,特别是在IE8中。

那么有没有更好的解决scheme呢? 谢谢

我不会使用event.stopPropagation(),因为它会导致您在解决schemeA中看到的那种问题。如果可能的话,我也会使用模糊和焦点事件。 当您的下拉菜单附加到input时,可以在input失去焦点时closures它。

但是,处理文档上的点击事件也不是那么糟糕,所以如果您想避免多次处理相同的点击事件,只需在文档不再需要时从文档中解除绑定。 除了在下拉菜单之外单击时评估的expression式之外,该指令还需要知道它是否处于活动状态:

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

使用该指令时,只需提供另一个expression式:

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

我还没有testing过你的onClick函数。 我假设它按预期工作。 希望这可以帮助。

你应该使用ngBlur和ngFocus来显示或隐藏你的下拉菜单。 当有人点击它,然后它变得焦点,否则它变得模糊。

另外,请参考这个问题如何设置焦点在input字段? 在AngularJS中设置焦点。

编辑:对于每个指令(下拉菜单或列表,我们称之为Y),当你点击一个元素(让它称为X)时,你将不得不显示它,当你点击Y以外的任何地方时,你需要隐藏它(不包括X明显)。 Y有财产isyvisisble。 所以当有人点击X(ng-click)时,将“isYvisible”设置为true,将Focus设置为Y.当有人点击Y(ng-blur)时,则将“isYvisible”设置为false,将其隐藏。 您需要在两个不同的元素/指令之间共享一个variables(“isYvisible”),您可以使用控制器或服务的范围来执行此操作。 还有其他的select,但这不在质疑范围之内。

您的解决schemeA是最正确的,但是您应该在指令中添加另一个参数,以便跟踪它是否已打开:

 link: function(scope, elem, attr, ctrl) { elem.bind('click', function(e) { // this part keeps it from firing the click on the document. if (isOpen) { e.stopPropagation(); } }); $document.bind('click', function() { // magic here. isOpen = false; scope.$apply(attr.clickAnywhereButHere); }) } 
 post: function ($scope, element, attrs, controller) { element.on("click", function(){ console.log("in element Click event"); $scope.onElementClick = true; $document.on("click", $scope.onClick); }); $scope.onClick = function (event) { if($scope.onElementClick && $scope.open) { $scope.onElementClick = false; return; } $scope.open = false; $scope.$apply(attrs.clickAnywhereButHere) $document.off("click", $scope.onClick); }; } 

这是我正在使用的一个解决scheme(可能有点晚的答案,但希望有助于其他人来通过这个)

  link: function (scope, element, attr) { var clickedOutsite = false; var clickedElement = false; $(document).mouseup(function (e) { clickedElement = false; clickedOutsite = false; }); element.on("mousedown", function (e) { clickedElement = true; if (!clickedOutsite && clickedElement) { scope.$apply(function () { //user clicked the element scope.codeCtrl.elementClicked = true; }); } }); $(document).mousedown(function (e) { clickedOutsite = true; if (clickedOutsite && !clickedElement) { scope.$apply(function () { //user clicked outsite the element scope.codeCtrl.elementClicked = false; }); } }); } 

这里是我使用的只需要点击事件的解决scheme(在ngClick指令中作为$ event使用)。 我想要一个菜单​​项,点击时会:

  • 切换子菜单的显示
  • 隐藏任何其他子菜单,如果显示
  • 如果在外面发生点击,则隐藏子菜单。

这段代码在菜单项上设置了“active”类,这样就可以显示或者隐藏它的子菜单

 // this could also be inside a directive's link function. // each menu element will contain data-ng-click="onMenuItemClick($event)". // $event is the javascript event object made available by ng-click. $scope.onMenuItemClick = function(menuElementEvent) { var menuElement = menuElementEvent.currentTarget, clickedElement = menuElementEvent.target, offRootElementClick; // where we will save angular's event unbinding function if (menuElement !== clickedElement) { return; } if (menuElement.classList.contains('active')) { menuElement.classList.remove('active'); // if we were listening for outside clicks, stop offRootElementClick && offRootElementClick(); offRootElementClick = undefined; } else { menuElement.classList.add('active'); // listen for any click inside rootElement. // angular's bind returns a function that can be used to stop listening // I used $rootElement, but use $document if your angular app is nested in the document offRootElementClick = $rootElement.bind('click', function(rootElementEvent) { var anyClickedElement = rootElementEvent.target; // if it's not a child of the menuElement, close the submenu if(!menuElement.contains(anyClickedElement)) { menuElement.classList.remove('active'); // and stop outside listenting offRootElementClick && offRootElementClick(); offOutsideClick = undefined; } }); } } 

@ lex82答案是好的,并构成这个答案的基础,但我的几个方面有所不同:

  1. 它在TypeScript中
  2. 当范围被销毁时,它将删除点击绑定,这意味着您不必使用属性单独pipe理点击绑定
  3. 该超时确保了如果通过鼠标事件创build了具有click-out的对象,那么完全相同的鼠标事件实际上不会无意中触发closures机制

     export interface IClickOutDirectiveScope extends angular.IScope { clickOut: Function; } export class ClickOutDirective implements angular.IDirective { public restrict = "A"; public scope = { clickOut: "&" } public link: ($scope: IClickOutDirectiveScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes) => void; constructor($timeout: angular.ITimeoutService, $document: angular.IDocumentService) { ClickOutDirective.prototype.link = ($scope: IClickOutDirectiveScope, $element: angular.IAugmentedJQuery, attrs: ng.IAttributes) => { var onClick = (event: JQueryEventObject) => { var isChild = $element[0].contains(event.target); var isSelf = $element[0] === event.target; var isInside = isChild || isSelf; if (!isInside) { if ($scope.clickOut) { $scope.$apply(() => { $scope.clickOut(); }); } } } $timeout(() => { $document.bind("click", onClick); }, 500); $scope.$on("$destroy", () => { $document.unbind("click", onClick); }); } } static factory(): ng.IDirectiveFactory { const directive = ($timeout: angular.ITimeoutService, $document: angular.IDocumentService) => new ClickOutDirective($timeout, $document); directive.$inject = ["$timeout", "$document"]; return directive; } } angular.module("app.directives") .directive("clickOut", ClickOutDirective.factory());