Подтвердить что ты не робот

Не уверен, как скрыть 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();
    }

}
4b9b3361

Ответ 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()