在 jQuery click 事件中停止底层 ng-click 的传播 [英] Stop propagation of underlying ng-click inside a jQuery click event

查看:30
本文介绍了在 jQuery click 事件中停止底层 ng-click 的传播的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Twitter Bootstrap dropdown 嵌套在 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;返回 {限制:'CA',链接:函数(范围,元素,属性){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();如果(!!openElement){关闭菜单();}if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {element.parent().addClass('open');openElement = 元素;closeMenu = 函数(事件){如果(事件){event.preventDefault();event.stopPropagation();}$document.unbind('click', closeMenu);element.parent().removeClass('open');closeMenu = angular.noop;openElement = null;};$document.bind('click', closeMenu);}});}};}]);

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

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

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

这就是为什么您的行中的 ng-click 处理程序总是被调用,即使指令停止"了文档点击时的气泡.

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

<块引用>

启动捕获后,所有指定类型的事件将被在被分派给任何人之前分派给注册的监听者DOM 树中它下面的 EventTarget.mdn

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

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

因此,使用装饰器并在文档节点上使用自定义事件处理程序,您可以通过以下方式使下拉菜单生效:

FIDDLE

var myApp = angular.module('myApp', []);/*** 来自 ui-bootstrap 的原始 dropdownToggle 指令.* 这里没有任何变化.*/myApp.directive('dropdownToggle', ['$document', '$location', function ($document, $location) {var openElement = null,closeMenu = angular.noop;返回 {限制:'CA',链接:函数(范围,元素,属性){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();如果(!!openElement){关闭菜单();}if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {element.parent().addClass('open');openElement = 元素;closeMenu = 函数(事件){如果(事件){event.preventDefault();event.stopPropagation();}$document.unbind('click', closeMenu);element.parent().removeClass('open');closeMenu = angular.noop;openElement = null;};$document.bind('click', closeMenu);/* <---所有问题的原因----- */}});}};}]);/*** 这是我们装饰 dropdownToggle 指令* 为了改变文档点击处理程序的工作方式*/myApp.config(function($provide){'使用严格';$provide.decorator('dropdownToggleDirective', ['$委托','$文档',函数($delegate,$document){var 指令 = $delegate[0];var openElement = null;var closeMenu = angular.noop;函数处理程序(e){var elm = angular.element(e.target);if(!elm.parents('.dropdown-menu').length){e.stopPropagation();e.preventDefault();}关闭菜单();//关闭菜单后,我们移除全视处理程序//允许应用程序点击事件正常工作$document[0].removeEventListener('click', handler, true);}指令.编译=函数(){返回函数(范围,元素){scope.$watch('$location.path', closeMenu);element.parent().bind('click', closeMenu);element.bind('click', function (event) {var elementWasOpen = (element === openElement);event.preventDefault();event.stopPropagation();如果(!!openElement){关闭菜单();}if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {element.parent().addClass('open');openElement = 元素;closeMenu = 函数(事件){如果(事件){event.preventDefault();event.stopPropagation();}$document.unbind('click', closeMenu);element.parent().removeClass('open');closeMenu = angular.noop;openElement = null;};//我们通过将第三个useCapture"参数指定为 true 来附加点击处理程序$document[0].addEventListener('click', handler, true);}});};};返回 $delegate;}]);});

更新:

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

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

<表格><tr ng-click="clicked('row')"><td><div class="btn-group"><button type="button" class="btn btn-default dropdown-toggle">操作 </span><ul class="dropdown-menu" role="menu"><li ng-repeat="选择项目"><a ng-click="clicked('link element', $event)">{{choice}}</a>

</td></tr>

function DropdownCtrl($scope) {$scope.items = [行动","另一个动作",这里还有别的东西"];$scope.clicked = 函数(什么,事件){警报(什么 + '点击');如果(事件){event.stopPropagation();event.preventDefault();}}}

A Twitter Bootstrap dropdown is nested inside a tr. The tr is clickable through ng-click. Clicking anywhere on the page will collapse the dropdown menu. That behavior is defined in a directive through $document.bind('click', closeMenu).

So, when menu is opened, and the user click on a row, I want the menu to close (as it does) AND I want to prevent the click event on the row.

JSFiddle : http://jsfiddle.net/LMc2f/1/
JSFiddle + directive inline : http://jsfiddle.net/9DM8U/1/

Relevant code from 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);
        }
      });
    }
  };
}]);

I can't figure out how to stop the underlying ng-click event inside closeMenu.

NOTE : I can't find a way to access $event so I haven't been able to try $event.stopPropagation().

解决方案

The dropdown directive binds the click event on the document, but when you click on the row, the event starts propagating from the target element down to the root document node (td -> tr -> table -> document).

So that's why your ng-click handler, that you have on your row, always gets called, even though the directive is "stopping" the bubble on document click.

Solution is to use the useCapture flag when adding the click handler for the document.

After initiating capture, all events of the specified type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. mdn

Now, to instruct the dropdown directive to use your own handler, you need to change the source of the directive. But it's a third party directive, and you probably don't want to do that, for maintability reasons.

This is where the powerful angular $decorator kicks in. You may use the $decorator to change the source of the third-party module on-the-fly, without actually touching the actual source files.

So, with decorator in place, and with custom event handler on the document node, this is how you can make that dropdown behave:

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;
      }
  ]);

});

UPDATE:

Note that the updated custom handler will prevent bubbling unless the target element is the actual drop-down option. This will solve the problem where click event was being prevented even when clicking on the drop-down options.

This still won't prevent the event to bubble down to row (from drop-down option), but this is something that's not in any way related to the drop-down directive. Anyway, to prevent such bubbling you can pass the $event object to ng-click expression function and use that object to stop the even to bubble down to the table row:

<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();
    }
  }

}

这篇关于在 jQuery click 事件中停止底层 ng-click 的传播的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆