AngularJS Directive - How to let an event only be triggered once if otherwise it'll be triggered on multiple elements?

允我心安 提交于 2019-12-24 19:09:54

问题


This is what I currently have:

http://plnkr.co/edit/L9XqEyOtRSGHc9rqFPJX

I am trying to make it so that when I press the down key for the first time, the focus will move onto #div2; repeating the action should have the focus moving onto li > div:first-child.

In the demo however, upon the first time the down key is pressed, the focus will jump to li > div:first-child directly, because both the key-map on #div1 and #div2 captured the same keydown event, so the focus jumps from #div1 to #div2 then #div2 to li > div:first-child in one go.

How should I solve this issue or, better yet, is there anyway to improve how the codes are structured? I am not sure whether those event listeners are attaching to the DOM in the optimal fashion right now.


回答1:


You are attaching a keydown handler to the document for each element. I don't think stopPropagation() will do any good because the two handlers are on the same element and it won't propagate up from document but both will still fire.

I suggest re-evaluating how you're approaching it. You only wish the element with the focus class to have it's options evaluated, so why not wrap all those elements in an element with your directive and have it listen only once and choose the element to act on.

(plunker)

  <div key-mapped="">
    <!-- children will apply key map of element with focus class -->
    <div id="div1" class="focus" key-map="{
      40: '#div2'
    }">Hello</div>

directive:

  }).directive('keyMapped', function($rootScope) {
    return {
      restrict: 'A',
      link: function(scope, element, attr) {
        angular.element(document).bind('keydown', function(event) {
          var target = $(element).find('.focus');
          console.log('target: ' + target);

          var options = scope.$eval(target.attr('key-map'));

----EDIT----

Someone let me know if it's not a good practice, but you could always put the event handler on your directive object and ensure it is only set once and send a custom event to the element with the 'focus' class that you bind to in your link function.

(plunker)

}).directive('keyMap', function($rootScope) {
  var dir = {
    onKeydown: function(event) {
      var element = document.querySelector('.focus');
      var newEvent = new CustomEvent("keyMapKeydown", {
        detail: { keyCode: event.keyCode },
        bubbles: false,
        cancelable: true
      });
      element.dispatchEvent(newEvent);
    },
    registered: false, // ensure we only listen to keydown once
    restrict: 'A',
    link: function(scope, element, attr) {
      // register listener only if not already registered
      if (!dir.registered) {
        dir.registered = true;
        angular.element(document).bind('keydown', dir.onKeydown);
      }

      var options = scope.$eval(attr.keyMap);

      // listen for custom event which will be dispatched only to the
      // element that has the 'focus' class
      element.bind('keyMapKeydown', function(event) {
        var keyCode = event.detail.keyCode;
        if (options && (keyCode in options)) {
          element.removeClass('focus');
          angular.element(document.querySelector(options[keyCode])).addClass('focus');                
          event.stopPropagation();
        }
      });
    }
  };



回答2:


You need to stop the propagation of events. Events propagate upwards. In your directive put something like the following:

$(elm).click(function (event) {                
    event.stopPropagation();
});

Or, you could create a stop-propagation directive itself. This SO question has a great way of doing it.



来源:https://stackoverflow.com/questions/17439530/angularjs-directive-how-to-let-an-event-only-be-triggered-once-if-otherwise-it

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!