Не уверен, как скрыть div при нажатии OUTSIDE из div
Это следующий вопрос по этому вопросу: Вход AngularJS с фокусом убивает фильтр ng-repeat списка
В основном мой код использует AngularJS для выпадания div (ящика) справа для фильтрации списка вещей. В большинстве случаев этот пользовательский интерфейс блокируется, поэтому нажатие на блокирующий div закрывает ящик. Но в некоторых случаях мы не блокируем пользовательский интерфейс и должны позволить пользователю щелкнуть за пределами ящика, чтобы отменить поиск или выбрать что-то еще на странице.
Моя первоначальная мысль заключалась в том, чтобы прикрепить обработчик window.onclick, когда ящик открывается, и если что-либо щелкнуто, кроме ящика, оно должно закрыть ящик. Это как я сделал бы это в чистом приложении для JavaScript. Но в Angular это немного сложнее.
Вот пример jsfiddle с образцом, который я основал на примере @Yoshi jsBin: http://jsfiddle.net/tpeiffer/kDmn8/
Ниже приведен соответствующий фрагмент кода из этого примера. В основном, если пользователь щелкает за пределами ящика, я вызываю $scope.toggleSearch, так что $scope.open возвращается к false.
Код работает, и даже если $scope.open переходит от истинного к 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();
}
}
Ответы
Ответ 1
Ящик не закрывается, потому что событие click происходит за пределами цикла дайджеста, а Angular не знает, что переменная $scope.open изменилась. Чтобы исправить это, вы можете вызывать $scope. $Apply() после $scope.open установлено значение false, это вызовет цикл дайджеста.
$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.
Ответ 2
Я предлагаю добавить $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();
};
}
};
Ответ 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;
}
};
Ответ 4
Я не мог найти решение, на котором я был на 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()