29

这是这个问题的后续问题:AngularJS input with focus kills ng-repeat filter of list

基本上我的代码是使用 AngularJS 在右侧弹出一个 div(抽屉)来过滤列表。大多数情况下,UI 会被阻止,因此单击该阻止 div 会关闭抽屉。但在某些情况下,我们不会阻止 UI,需要允许用户在抽屉外单击以取消搜索或选择页面上的其他内容。

我最初的想法是在抽屉打开时附加一个 window.onclick 处理程序,如果单击抽屉以外的任何内容,它应该关闭抽屉。这就是我在纯 JavaScript 应用程序中的做法。但在 Angular 中,这有点困难。

这是一个 jsfiddle,其中包含我基于 @Yoshi 的 jsBin 示例的示例: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();
    }

}
4

4 回答 4

24

抽屉没有关闭,因为单击事件发生在摘要循环之外,并且 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. 
    }
};

这是你的小提琴

如果有帮助,我还尝试为搜索抽屉创建一个指令(它需要一些修复:))。这是一个JSBin

于 2013-07-17T19:30:37.407 回答
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();
        };
    }
};
于 2014-01-16T01:11:57.187 回答
3

如果您单击按钮关闭抽屉/弹出窗口,则接受的答案将引发错误,并且该按钮位于其外部,因为$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;
  }
};
于 2014-09-08T15:23:00.210 回答
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 的 span 用于打开菜单,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() 的安全检查

于 2013-11-23T11:25:39.820 回答