Search code examples
javascripteventskeypress

How to make keypress event have the target equal to a "selected" element?


TL;DR: how do I implement a "select" helper so that once an element from a set is selected, keypress events are fired with their target equal to the element, not body?


I'm trying to make a number of elements be "selectable": once a user clicks one of them, that one gets selected, and also it should be possible to navigate between them using arrows. Once an element is selected, may be some hotkeys should work as well to interact with it.

I've already implemented the first part: I have a class and a helper

var selectClass = 'selectedView';
var selectView = function(element) {
    // remove other selections
    jQuery('.'+selectClass).removeClass(selectClass);
    // select
    jQuery(element).addClass(selectClass);
    //# to do: scroll into view
};

and also the click handler:

jQuery(document.body).on('click',viewsSelector,function(ev){
    var viewElement = ev.target;
    // don't select views' children
    while(!jQuery(viewElement).is(viewsSelector) && viewElement.parentElement)
        viewElement = viewElement.parentElement;
    selectView(viewElement);
});

Now I'd like to implement the keyboard navigation. The naive implementation

jQuery(document.body).on('keydown','.'+selectClass,function(ev){
    console.log('keydown at selected? key: '+ev.which+', target is ',ev.target);
    //# implement jump
});

doesn't work, because the target is body (which can easily be checked by commenting out the delegation bit ,'.'+selectClass).

But I don't want to handle all keypresses, I need to make those work only when an item is selected. As a workaround I can add a check whether an item is selected, but I wonder if there's a way to make the event's target be the item itself, not document.body. Adding element.focus(); and jQuery(element).focus(); to selectView doesn't help neither.

One particular reason why I'd like to avoid just handling keypresses on body and checking if an item is selected is that some widgets inside items also have to handle those keypresses and "jumps" are not expected in those cases.


Solution

  • Your element is not receiving keyboard events because it is not focused when clicked. All elements are not able to recieve focus when clicked as they don't have tabindex focus flag set except for the following elements. [source:www.w3.org]

    • <a> elements that have an href attribute
    • <link> elements that have an href attribute
    • <button> elements
    • <input> elements whose type attribute are not in the Hidden state
    • <select> elements
    • <textarea> elements
    • Editing hosts
    • Browsing context containers

    If an element is not in focus keyboard events are recieved by the body element.

    When an element is focused, key events received by the document must be targeted at that element. There may be no element focused; when no element is focused, key events received by the document must be targeted at the body element. [source:www.w3.org]

    You can make any HTML element able to receive focus and thereby receive keyboard events by adding a tabindex property.

    $('.box').on('keydown', function(event) {
      console.log(event.target);
    })
    .box {
      height: 120px;
      width: 120px;
      background: #7cf76e;
      float: left;
      margin: 2px;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <div class="box" tabindex="1">A</div>
    <div class="box" tabindex="1">B</div>
    <div class="box" tabindex="1">C</div>