这是此问题的后续问题:AngularJS input with focus kills ng-repeat filter of list
基本上我的代码使用AngularJS弹出右侧的div(抽屉)来过滤事物列表。大多数情况下,这是使用UI被阻止所以点击阻止div关闭抽屉。但在某些情况下,我们不会阻止UI,并且需要允许用户在抽屉外单击以取消搜索或在页面上选择其他内容。
我最初的想法是在抽屉打开时附加一个window.onclick处理程序,如果点击抽屉以外的任何东西它应该关闭抽屉。这就是我在纯JavaScript应用程序中的表现。但在Angular中,它有点困难。
这是一个基于@Yoshi的jsBin示例的示例jsfiddle:http://jsfiddle.net/tpeiffer/kDmn8/
此示例中的相关代码如下。基本上,如果用户点击抽屉外部,我会调用$ scope.toggleSearch,以便将$ scope.open设置回false。
代码有效,即使$ scope.open从true变为false,它也不会修改DOM。我确定这与事件的生命周期有关,或者当我修改$ scope(因为它来自一个单独的事件)时,它是一个副本,而不是原始...
最终目标是使其成为终极可重用性的指令。如果有人能指出我正确的方向去做那件事,我将不胜感激。
$scope.toggleSearch = function () {
$scope.open = !$scope.open;
if ($scope.open) {
$scope.$window.onclick = function (event) {
closeSearchWhenClickingElsewhere(event, $scope.toggleSearch);
};
} else {
$scope.$window.onclick = null;
}
};
和
function closeSearchWhenClickingElsewhere(event, callbackOnClose) {
var clickedElement = event.target;
if (!clickedElement) return;
var elementClasses = clickedElement.classList;
var clickedOnSearchDrawer = elementClasses.contains('handle-right')
|| elementClasses.contains('drawer-right')
|| (clickedElement.parentElement !== null
&& clickedElement.parentElement.classList.contains('drawer-right'));
if (!clickedOnSearchDrawer) {
callbackOnClose();
}
}
答案 0 :(得分:23)
抽屉未关闭,因为click事件发生在摘要周期之外,而Angular不知道$ scope.open已更改。要修复它,你可以在$ scope.open设置为false之后调用$ scope。$ apply(),这将触发摘要周期。
$scope.toggleSearch = function () {
$scope.open = !$scope.open;
if ($scope.open) {
$scope.$window.onclick = function (event) {
closeSearchWhenClickingElsewhere(event, $scope.toggleSearch);
};
} else {
$scope.open = false;
$scope.$window.onclick = null;
$scope.$apply(); //--> trigger digest cycle and make angular aware.
}
};
这是您的Fiddle。
我还试图为搜索抽屉创建一个指令,如果它有帮助(它需要一些修复:))。这是一个JSBin。
答案 1 :(得分:12)
我建议添加$ event.stopPropagation();在ng-click之后的视图上。您不需要使用jQuery。
<button ng-click="toggleSearch();$event.stopPropagation();">toggle</button>
然后,你可以使用这个简化的js。
$scope.toggleSearch = function () {
$window.onclick = null;
$scope.menuOpen = !$scope.menuOpen;
if ($scope.model.menuOpen) {
$window.onclick = function (event) {
$scope.menuOpen = false;
$scope.$apply();
};
}
};
答案 2 :(得分:3)
如果单击按钮关闭抽屉/弹出窗口,并且该按钮位于其外部,则接受的答案将引发错误,因为$apply()
将执行两次。
这是一个简化版本,不需要再次调用整个toggleSearch()
函数并阻止双$apply()
。
$scope.toggleSearch = function() {
$scope.open = !$scope.open;
if ($scope.open) {
$window.onclick = function(event) {
var clickedElement = event.target;
if (!clickedElement) return;
var clickedOnSearchDrawer = elementClasses.contains('handle-right') || elementClasses.contains('drawer-right') || (clickedElement.parentElement !== null && clickedElement.parentElement.classList.contains('drawer-right'));
if (!clickedOnSearchDrawer) {
$scope.open = !$scope.open;
$window.onclick = null;
$scope.$apply();
}
}
} else {
$window.onclick = null;
}
};
答案 3 :(得分:2)
我无法找到一个100%满意的解决方案,这就是我使用的:
<div class="options">
<span ng-click="toggleAccountMenu($event)">{{ email }}</span>
<div ng-show="accountMenu" class="accountMenu">
<a ng-click="go('account')">Account</a>
<a ng-click="go('logout')">Log Out</a>
</div>
</div>
使用ng-click的跨度用于打开菜单,div.accountMenu被切换为打开或关闭
$scope.accountMenu = false;
$scope.toggleAccountMenu = function(e){
if(e) e.stopPropagation();
$scope.accountMenu = !$scope.accountMenu;
if ($scope.accountMenu) {
$window.onclick = function(e) {
var target = $(e.target);
if(!target) return;
if(!target.hasClass('accountMenu') && !target.is($('.accountMenu').children())){
$scope.toggleAccountMenu();
}
};
} else if (!e) {
$window.onclick = null;
$scope.$apply();
}
}
这使用jQuery进行子检查,但是如果需要的话,你可以这样做。
我在其他人的版本中遇到了一些令人讨厌的错误,比如当它已经在一个循环中时试图调用$ apply(),我的版本阻止传播并对$ apply()进行安全检查