This is a follow-up question to this question: AngularJS input with focus kills ng-repeat filter of list
这是这个问题的后续问题:带焦点的AngularJS输入会杀死列表的ng-repeat过滤器
Basically my code is using AngularJS to pop-out a div (a drawer) on the right for filtering a list of things. Most times this is used the UI is blocked so clicking on that blocking div closes the drawer. But in some cases we don't block the UI and need to allow the user to click outside of the drawer to cancel the search or select something else on the page.
基本上我的代码使用AngularJS弹出右边的div(抽屉)来过滤事物列表。大多数情况下,这是使用UI被阻止所以点击阻止div关闭抽屉。但在某些情况下,我们不会阻止UI,并且需要允许用户在抽屉外单击以取消搜索或在页面上选择其他内容。
My initial thought was to attach a window.onclick handler when the drawer opens and if anything is clicked other than the drawer it should close the drawer. That's how I would do it in a pure JavaScript app. But in Angular it is being a bit more difficult.
我最初的想法是在抽屉打开时附加一个window.onclick处理程序,如果点击抽屉以外的任何东西,它应该关闭抽屉。这就是我在纯JavaScript应用程序中的表现。但在Angular中,它有点困难。
Here is a jsfiddle with a sample that I based on @Yoshi's jsBin example: http://jsfiddle.net/tpeiffer/kDmn8/
这是一个基于@Yoshi的jsBin示例的示例jsfiddle:http://jsfiddle.net/tpeiffer/kDmn8/
The relevant piece of code from this sample is below. Basically if the user clicks outside of the drawer I invoke $scope.toggleSearch so that $scope.open is set back to false.
此示例中的相关代码段如下。基本上,如果用户点击抽屉外部,我会调用$ scope.toggleSearch,以便将$ scope.open设置回false。
The code works, and even though the $scope.open goes from true to false it doesn't modify the DOM. I'm sure this has something to do with the lifecycle of events or perhaps when I modify $scope (since it is from a separate event) that it is a copy and not the original...
代码有效,即使$ scope.open从true变为false,它也不会修改DOM。我确定这与事件的生命周期有关,或者当我修改$ scope(因为它来自一个单独的事件)它是一个副本而不是原始...
The ultimate goal on this will be for it to be a directive for ultimate reusability. If anyone can point me in the right direction to do that I would be grateful.
最终目标是使其成为最终可重用性的指令。如果有人能指出我正确的方向去做那件事,我将不胜感激。
$scope.toggleSearch = function () {
$scope.open = !$scope.open;
if ($scope.open) {
$scope.$window.onclick = function (event) {
closeSearchWhenClickingElsewhere(event, $scope.toggleSearch);
};
} else {
$scope.$window.onclick = null;
}
};
and
和
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();
}
}
4 个解决方案
#1
21
The drawer is not closing because the click event occurs outside the digest cycle and Angular doesn't know that $scope.open has changed. To fix it you can call $scope.$apply() after $scope.open is set to false, this will trigger the digest cycle.
抽屉未关闭,因为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.
}
};
Here is your Fiddle.
这是你的小提琴。
I was also trying to create a directive for the search drawer, if it helps (it needs some fixes :)). Here is a JSBin.
我也试图为搜索抽屉创建一个指令,如果它有帮助(它需要一些修复:))。这是一个JSBin。
#2
12
I suggest to add $event.stopPropagation(); on the view right after on the ng-click. You don't need to use jQuery.
我建议添加$ event.stopPropagation();在ng-click之后的视图上。您不需要使用jQuery。
<button ng-click="toggleSearch();$event.stopPropagation();">toggle</button>
Then, you can use this simplified js.
然后,您可以使用此简化的js。
$scope.toggleSearch = function () {
$window.onclick = null;
$scope.menuOpen = !$scope.menuOpen;
if ($scope.model.menuOpen) {
$window.onclick = function (event) {
$scope.menuOpen = false;
$scope.$apply();
};
}
};
#3
3
The accepted answer will throw an error if you click on the button to close the drawer/popup, and the button is located outside of it, because $apply()
will be executed twice.
如果单击按钮关闭抽屉/弹出窗口,并且按钮位于其外部,则接受的答案将引发错误,因为$ apply()将执行两次。
This is a simplified version, that doesn't need call the whole toggleSearch()
function again and prevents the double $apply()
.
这是一个简化版本,不需要再次调用整个toggleSearch()函数并阻止double $ 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;
}
};
#4
2
I couldn't find a solution i was 100% happy with, this is what i used:
我找不到一个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>
the span with ng-click is used to open the menu, the div.accountMenu is toggled open or closed
使用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();
}
}
This uses jQuery for child checking but you can probably do it without if needed.
这使用jQuery进行子检查,但您可以在没有需要的情况下执行此操作。
I was getting some nasty errors with other peoples version, like trying to call $apply() when its already in a cycle, my version prevents propagation and safe-checks against $apply()
我在其他人的版本中遇到了一些令人讨厌的错误,比如当它已经在一个循环中时试图调用$ apply(),我的版本阻止传播并对$ apply()进行安全检查
#1
21
The drawer is not closing because the click event occurs outside the digest cycle and Angular doesn't know that $scope.open has changed. To fix it you can call $scope.$apply() after $scope.open is set to false, this will trigger the digest cycle.
抽屉未关闭,因为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.
}
};
Here is your Fiddle.
这是你的小提琴。
I was also trying to create a directive for the search drawer, if it helps (it needs some fixes :)). Here is a JSBin.
我也试图为搜索抽屉创建一个指令,如果它有帮助(它需要一些修复:))。这是一个JSBin。
#2
12
I suggest to add $event.stopPropagation(); on the view right after on the ng-click. You don't need to use jQuery.
我建议添加$ event.stopPropagation();在ng-click之后的视图上。您不需要使用jQuery。
<button ng-click="toggleSearch();$event.stopPropagation();">toggle</button>
Then, you can use this simplified js.
然后,您可以使用此简化的js。
$scope.toggleSearch = function () {
$window.onclick = null;
$scope.menuOpen = !$scope.menuOpen;
if ($scope.model.menuOpen) {
$window.onclick = function (event) {
$scope.menuOpen = false;
$scope.$apply();
};
}
};
#3
3
The accepted answer will throw an error if you click on the button to close the drawer/popup, and the button is located outside of it, because $apply()
will be executed twice.
如果单击按钮关闭抽屉/弹出窗口,并且按钮位于其外部,则接受的答案将引发错误,因为$ apply()将执行两次。
This is a simplified version, that doesn't need call the whole toggleSearch()
function again and prevents the double $apply()
.
这是一个简化版本,不需要再次调用整个toggleSearch()函数并阻止double $ 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;
}
};
#4
2
I couldn't find a solution i was 100% happy with, this is what i used:
我找不到一个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>
the span with ng-click is used to open the menu, the div.accountMenu is toggled open or closed
使用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();
}
}
This uses jQuery for child checking but you can probably do it without if needed.
这使用jQuery进行子检查,但您可以在没有需要的情况下执行此操作。
I was getting some nasty errors with other peoples version, like trying to call $apply() when its already in a cycle, my version prevents propagation and safe-checks against $apply()
我在其他人的版本中遇到了一些令人讨厌的错误,比如当它已经在一个循环中时试图调用$ apply(),我的版本阻止传播并对$ apply()进行安全检查