我知道有很多问题要问类似的事情。但没有人真正解决我的问题。
我正在尝试构建一个指令,当鼠标点击当前元素之外时将执行一个表达式。
为什么我需要这个功能?我正在构建一个应用程序,在这个应用程序中,有3个下拉菜单,5个下拉列表(如选中)。所有这些都是角度指令。让我们假设所有这些指令都不同。所以我们有8个指令。并且所有这些都需要相同的功能:当点击元素时,需要隐藏下拉列表。
我有2个解决方案,但都有问题:
解决方案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:click here
的示例单击第一个下拉列表,然后单击第二个输入,第一个应该隐藏但不是。
解决方案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的示例:click here
解决方案如果页面中只有一个指令但我的应用程序中没有指令,则可以正常工作。因为它阻止了冒泡,所以首先当我点击dropdown1,显示dropdown1,然后点击dropdown2,点击事件被阻止,所以dropdown1仍然显示在那里,即使我点击dropdown1。
解决方案B在我的应用程序中工作,我现在正在使用它。但问题是它会导致性能问题。在应用中的任何位置,每次单击都会处理太多的点击事件。在我目前的情况下,有8个click事件与文档绑定,所以每次单击都执行8个函数。这导致我的应用程序非常慢,尤其是在IE8中。
那么有更好的解决方案吗?感谢
答案 0 :(得分:32)
我不会使用event.stopPropagation(),因为它会导致您在解决方案A中看到的问题。如果可能,我还会使用模糊和聚焦事件。当您的下拉列表附加到输入时,您可以在输入失去焦点时将其关闭。
但是,处理文档上的单击事件也不是那么糟糕,因此如果您想避免多次处理相同的单击事件,只需在文档不再需要时将其解除绑定。除了在下拉列表外单击时评估的表达式,该指令还需要知道它是否处于活动状态:
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);
}
});
}
};
}]);
使用该指令时,只需提供另一个表达式:
<your-dropdown click-anywhere-but-here="close()" is-active="isDropdownOpen()"></your-dropdown>
我还没有测试你的onClick功能。我认为它按预期工作。希望这会有所帮助。
答案 1 :(得分:9)
您应该使用ngBlur和ngFocus来显示或隐藏您的下拉菜单。当有人点击它然后它会聚焦,否则它会变得模糊。
另外,请参阅此问题How to set focus on input field?以在AngularJS中设置焦点。
编辑: 对于每个指令(下拉菜单或列表,我们称之为Y),当你点击一个元素(让我们称之为X)时你必须显示它,当你点击Y之外的任何地方时你需要隐藏它(显然不包括X) 。 Y的财产是Yvisisble。 所以当有人点击X(ng-click)然后将“isYvisible”设置为true并将Focus设置为Y. 当有人点击Y(ng-blur)之外然后你将“isYvisible”设置为false时,它会被隐藏。 您需要在两个不同的元素/指令之间共享变量(“isYvisible”),并且您可以使用控制器或服务的范围来执行此操作。还有其他替代方案,但这不在问题范围之内。
答案 2 :(得分:3)
您的解决方案A是最正确的,但您应该在指令中添加另一个参数,以便在打开时进行跟踪:
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);
})
}
答案 3 :(得分:3)
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);
};
}
答案 4 :(得分:2)
这是我正在使用的解决方案(可能有点迟到,但希望对其他人有所帮助)
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;
});
}
});
}
答案 5 :(得分:2)
比最热门的答案更简单的版本,对我来说它更清晰,工作得很好!
app.directive('clickAnywhereButHere', function() {
return {
restrict : 'A',
link: {
post: function(scope, element, attrs) {
element.on("click", function(event) {
scope.elementClicked = event.target;
$(document).on("click", onDocumentClick);
});
var onDocumentClick = function (event) {
if(scope.elementClicked === event.target) {
return;
}
scope.$apply(attrs.clickAnywhereButHere);
$(document).off("click", onDocumentClick);
};
}
}
};
});
答案 6 :(得分:1)
这是我使用的解决方案,只需要点击事件(在ngClick指令中作为$ event提供)。我想要一个包含项目的菜单,点击后会:
此代码设置类&#39;活跃&#39;在菜单项上,以便可用于显示或隐藏它的子菜单
// 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;
}
});
}
}
答案 7 :(得分:1)
@ lex82答案很好,并形成了这个答案的基础,但我的方法有所不同:
超时确保如果通过鼠标事件创建了click-out
on的对象,那么相同的鼠标事件实际上不会无意中触发关闭机制
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());