0

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.

gsklee
  • 4,774
  • 4
  • 39
  • 55

2 Answers2

2

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();
        }
      });
    }
  };
Jason Goemaat
  • 28,692
  • 15
  • 86
  • 113
  • Is it possible to attach only one event listener for the keydown event without using a wrapper "keyMapped" directive? – gsklee Jul 03 '13 at 06:07
0

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.

Community
  • 1
  • 1
John Woodruff
  • 1,608
  • 16
  • 29