2013-07-17 8 views
26

Это дополнительный вопрос к этому вопросу: AngularJS input with focus kills ng-repeat filter of listНе знаю, как скрыть DIV при щелчке за пределы DIV

В основном мой код использует AngularJS поп-выход в DIV (выдвижной ящик) на для фильтрации списка вещей. В большинстве случаев этот пользовательский интерфейс блокируется, поэтому нажатие на блокирующий div закрывает ящик. Но в некоторых случаях мы не блокируем пользовательский интерфейс и должны позволить пользователю щелкнуть за пределами ящика, чтобы отменить поиск или выбрать что-то еще на странице.

Моя первоначальная мысль заключалась в том, чтобы прикрепить оконный обработчик window.onclick, когда выдвижной ящик открывается, и если что-либо щелкнуто, кроме ящика, оно должно закрыть ящик. Вот как я сделал бы это в чистом приложении для JavaScript. Но в Angular это немного сложнее.

Вот jsfiddle с образцом, который я на основе @ примере 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(); 
    } 

} 

ответ

21

Выдвижной ящик не закрывается, так как событие щелчка происходит вне цикла дайджеста и Угловая не знает, что $ 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.

+1

Большое спасибо @Bertrand! То, что $ apply было именно тем, что мне не хватало! Я нахожусь на 5-й день использования Angular (профессионально) - все-таки некоторые ключевые понятия, на которые мне понадобится время, чтобы ознакомиться! Надеюсь, я смогу вернуть благосклонность в ближайшем будущем! Я проверю, что JSBin для директивы, и опубликуйте результаты, когда это будет сделано и работает. –

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> 

пролет с нг-мыши используется для открытия меню, то 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 для проверки детей, но вы, возможно, можете сделать это без необходимости.

я получаю некоторые неприятные ошибки с версией других народов, как пытаюсь вызвать $ применить(), когда его уже в цикле, моя версия предотвращает распространение и безопасные проверки против $ применять()

12

Предлагает add $ 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; 
    } 
};