我正在尝试使用复选框和过滤器选项创建多选下拉列表。我试图隐藏列表,我点击外面,但无法弄清楚如何。感谢您的帮助。
答案 0 :(得分:67)
注意,您的解决方案(问题中提供的Plunker)在打开第二个弹出窗口时(在具有多个选择的页面上)不会关闭其他框的弹出窗口。
通过单击一个框打开一个新弹出窗口,将始终停止click事件。该事件永远不会到达任何其他打开的弹出窗口(关闭它们)。
我通过删除event.stopPropagation();
行并匹配弹出窗口的所有子元素来解决这个问题。
如果events元素与弹出窗口中的任何子元素都不匹配,则只会关闭弹出窗口。
我将指令代码更改为以下内容:
select.html(指令代码)
link: function(scope, element, attr){
scope.isPopupVisible = false;
scope.toggleSelect = function(){
scope.isPopupVisible = !scope.isPopupVisible;
}
$(document).bind('click', function(event){
var isClickedElementChildOfPopup = element
.find(event.target)
.length > 0;
if (isClickedElementChildOfPopup)
return;
scope.$apply(function(){
scope.isPopupVisible = false;
});
});
}
我分叉你的plunker并应用了更改:
Plunker: Hide popup div on click outside
<强>截图:强>
答案 1 :(得分:50)
这是一篇旧帖子,但如果这有助于此处的任何人都是一个单击外部的工作示例,它不依赖于任何角度。
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);
});
}
});
}
}
});
答案 2 :(得分:8)
好的,我必须调用$ apply(),因为事件发生在角度世界之外(按照doc)。
element.bind('click', function(event) {
event.stopPropagation();
});
$document.bind('click', function(){
scope.isVisible = false;
scope.$apply();
});
答案 3 :(得分:7)
我通过监听全局点击事件来实现它:
.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);
});
}
}])
然后通过新闻服务
广播事件本身angular.factory('News', ['$rootScope', function($rootScope) {
var news = {};
news.setClick = function( target ) {
this.clickTarget = target;
$rootScope.$broadcast('click');
};
}]);
然后,您可以随时随地收听广播。这是一个示例指令:
.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();
}
}
}])
我希望它有所帮助!
答案 4 :(得分:4)
有一个名为angular-click-outside
的酷指令。您可以在项目中使用它。它使用起来非常简单:
答案 5 :(得分:3)
我对提供的答案并不完全满意,所以我自己做了。改进:
窗口事件未绑定(防止泄漏)
函数链接(范围,$元素,属性,$ window){
var el = $element[0],
$$window = angular.element($window);
function onClick(event) {
console.log('window clicked');
// might need to polyfill node.contains
if (el.contains(event.target)) {
console.log('click inside element');
return;
}
scope.isActive = !scope.isActive;
if (!scope.$$phase) {
scope.$apply();
}
}
function onKeyUp(event) {
if (event.keyCode !== 27) {
return;
}
console.log('escape pressed');
scope.isActive = false;
if (!scope.$$phase) {
scope.$apply();
}
}
function bindCloseHandler() {
console.log('binding window click event');
$$window.on('click', onClick);
$$window.on('keyup', onKeyUp);
}
function unbindCloseHandler() {
console.log('unbinding window click event');
$$window.off('click', onClick);
$$window.off('keyup', onKeyUp);
}
scope.$watch('isActive', function(newValue, oldValue) {
if (newValue) {
bindCloseHandler();
} else {
unbindCloseHandler();
}
});
// prevent leaks - destroy handlers when scope is destroyed
scope.$on('$destroy', function() {
unbindCloseHandler();
});
}
我直接将$window
放入链接功能。但是,您无需执行此操作即可获得$window
。
function directive($window) {
return {
restrict: 'AE',
link: function(scope, $element, attributes) {
link.call(null, scope, $element, attributes, $window);
}
};
}
答案 6 :(得分:1)
Danny F发布的答案很棒而且几乎完整,但是Thịnh的评论是正确的,所以这是我修改的指令,用于删除该指令的$ destroy事件中的侦听器:
const ClickModule = angular
.module('clickOutside', [])
.directive('clickOutside', ['$document', function ($document) {
return {
restrict: 'A',
scope: {
clickOutside: '&'
},
link: function (scope, el, attr) {
const handler = function (e) {
if (el !== e.target && !el[0].contains(e.target)) {
scope.$apply(function () {
console.log("hiiii");
// whatever expression you assign to the click-outside attribute gets executed here
// good for closing dropdowns etc
scope.$eval(scope.clickOutside);
});
}
}
$document.on('click', handler);
scope.$on('$destroy', function() {
$document.off('click', handler);
});
}
}
}]);
如果将日志放入处理程序方法中,则当从DOM中删除元素时,仍会看到它触发。添加我的小更改就足以将其删除。不试图窃取任何人的雷声,但这是对一种优雅解决方案的修正。
答案 7 :(得分:0)
<强>安装:强>
bower install angular-click-outside --save
npm install @iamadamjowett/angular-click-outside
yarn add @iamadamjowett/angular-click-outside
<强>用法:强>
angular.module('myApp', ['angular-click-outside'])
//in your html
<div class="menu" click-outside="closeThis">
...
</div>
//And then in your controller
$scope.closeThis = function () {
console.log('closing');
}
答案 8 :(得分:0)
我发现https://github.com/IamAdamJowett/angular-click-outside
中的实现存在一些问题例如,如果从DOM中删除了单击的元素,则上述指令将触发逻辑。 这对我不起作用,因为我有一个模态逻辑,单击后用ng-if删除了该元素。
我重写了他的执行方式。尚未经过战斗测试,但似乎效果更好(至少在我的情况下如此)
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;
}