17

Twitter Bootstrapdropdown嵌套在tr. 可以通过tr点击ng-click。单击页面上的任意位置将折叠下拉菜单。该行为是在指令中定义的$document.bind('click', closeMenu)

因此,当打开菜单并且用户单击一行时,我希望菜单关闭(因为它确实如此)并且我想阻止该行上的单击事件。

JSFiddle: http: //jsfiddle.net/LMc2f/1/
JSFiddle + 指令内联:http : //jsfiddle.net/9DM8U/1/

来自 ui-bootstrap-tpls-0.10.0.js 的相关代码:

angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) {
  var openElement = null,
      closeMenu   = angular.noop;
  return {
    restrict: 'CA',
    link: function(scope, element, attrs) {
      scope.$watch('$location.path', function() { closeMenu(); });
      element.parent().bind('click', function() { closeMenu(); });
      element.bind('click', function (event) {

        var elementWasOpen = (element === openElement);

        event.preventDefault();
        event.stopPropagation();

        if (!!openElement) {
          closeMenu();
        }

        if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
          element.parent().addClass('open');
          openElement = element;
          closeMenu = function (event) {
            if (event) {
              event.preventDefault();
              event.stopPropagation();
            }
            $document.unbind('click', closeMenu);
            element.parent().removeClass('open');
            closeMenu = angular.noop;
            openElement = null;
          };
          $document.bind('click', closeMenu);
        }
      });
    }
  };
}]);

我不知道如何停止ng-click里面的底层事件closeMenu

注意:我找不到访问方法,$event因此无法尝试$event.stopPropagation()

4

3 回答 3

20

我倾向于只$event.stopPropagation()从模板本身内部调用。与事件相关的逻辑很可能属于那里。也应该使单元测试更容易。查看模板的人也会知道,如果不查看底层控制器,事件不会冒泡。

<div ng-click="parentHandler()">
    <div ng-click="childHandler(); $event.stopPropagation()"></div>
</div>
于 2014-10-03T20:01:12.623 回答
16

下拉指令绑定文档上的单击事件,但是当您单击行时,事件开始从目标元素向下传播到根文档节点(td-> tr-> table-> document)。

所以这就是为什么你的ng-click处理程序,你在你的行上,总是被调用,即使指令在文档点击时“停止”气泡。

解决方案是在为文档添加点击处理程序时使用useCapture标志。

启动捕获后,指定类型的所有事件将被分派到注册的侦听器,然后再分派到 DOM 树中它下面的任何 EventTarget。mdn

现在,要指示下拉指令使用您自己的处理程序,您需要更改指令的来源。但这是第三方指令,出于可维护性的原因,您可能不想这样做。

这就是强大的角度 $decorator 发挥作用的地方。您可以使用 $decorator 即时更改第三方模块的源代码,而无需实际接触实际的源文件。

因此,使用装饰器并在文档节点上使用自定义事件处理程序,这就是您可以使该下拉列表行为的方式:

FIDDLE

var myApp = angular.module('myApp', []);

/**
 * Original dropdownToggle directive from ui-bootstrap.
 * Nothing changed here.
 */
myApp.directive('dropdownToggle', ['$document', '$location', function ($document, $location) {
  var openElement = null,
      closeMenu   = angular.noop;
  return {
    restrict: 'CA',
    link: function(scope, element, attrs) {
      scope.$watch('$location.path', function() { closeMenu(); });
      element.parent().bind('click', function() { closeMenu(); });
      element.bind('click', function (event) {

        var elementWasOpen = (element === openElement);

        event.preventDefault();
        event.stopPropagation();

        if (!!openElement) {
          closeMenu();
        }

        if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
          element.parent().addClass('open');
          openElement = element;
          closeMenu = function (event) {
            if (event) {
              event.preventDefault();
              event.stopPropagation();
            }
            $document.unbind('click', closeMenu);
            element.parent().removeClass('open');
            closeMenu = angular.noop;
            openElement = null;
          };
          $document.bind('click', closeMenu); /* <--- CAUSE OF ALL PROBLEMS ----- */
        }
      });
    }
  };
}]);


/**
 * This is were we decorate the dropdownToggle directive
 * in order to change the way the document click handler works
 */
myApp.config(function($provide){
  'use strict';

  $provide.decorator('dropdownToggleDirective', [
      '$delegate',
      '$document',
      function ($delegate, $document) {

        var directive = $delegate[0];
        var openElement = null;
        var closeMenu = angular.noop;

        function handler(e){
            var elm = angular.element(e.target);
          if(!elm.parents('.dropdown-menu').length){
            e.stopPropagation();
            e.preventDefault();
          }
          closeMenu();
          // After closing the menu, we remove the all-seeing handler
          // to allow the application click events to work nnormally
          $document[0].removeEventListener('click', handler, true);
        }

        directive.compile = function(){
          return function(scope, element) {
            scope.$watch('$location.path', closeMenu);
            element.parent().bind('click', closeMenu);
            element.bind('click', function (event) {

              var elementWasOpen = (element === openElement);

              event.preventDefault();
              event.stopPropagation();

              if (!!openElement) {
                closeMenu();
              }

              if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
                element.parent().addClass('open');
                openElement = element;
                closeMenu = function (event) {
                  if (event) {
                    event.preventDefault();
                    event.stopPropagation();
                  }
                  $document.unbind('click', closeMenu);
                  element.parent().removeClass('open');
                  closeMenu = angular.noop;
                  openElement = null;
                };


                // We attach the click handler by specifying the third "useCapture" parameter as true
                $document[0].addEventListener('click', handler, true);
              }
            });
          };
        };

        return $delegate;
      }
  ]);

});

更新:

请注意,除非目标元素是实际的下拉选项,否则更新的自定义处理程序将防止冒泡。这将解决即使单击下拉选项也阻止单击事件的问题。

这仍然不会阻止事件冒泡到行(从下拉选项),但这与下拉指令没有任何关系。无论如何,为了防止这种冒泡,您可以将$event对象传递给ng-click表达式函数并使用该对象来阻止偶数冒泡到表格行:

<div ng-controller="DropdownCtrl">
  <table>
    <tr ng-click="clicked('row')">
      <td>

        <div class="btn-group">
          <button type="button" class="btn btn-default dropdown-toggle">
            Action <span class="caret"></span>
          </button>
          <ul class="dropdown-menu" role="menu">
            <li ng-repeat="choice in items">
              <a ng-click="clicked('link element', $event)">{{choice}}</a>
            </li>
          </ul>
        </div>

      </td>
    </tr>
  </table>
</div>
function DropdownCtrl($scope) {
  $scope.items = [
    "Action",
    "Another action",
    "Something else here"
  ];

  $scope.clicked = function(what, event) {
    alert(what + ' clicked');
    if(event){
      event.stopPropagation();
      event.preventDefault();
    }
  }

}
于 2014-01-31T22:44:21.453 回答
12

您需要在小提琴的第 2 行上传递 ng-click 事件,然后在您的方法中对该对象执行 preventDefault 和 stopPropigation

<tr ng-click="do($event)">
于 2014-01-31T18:50:18.873 回答