1

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.

YakovL
  • 7,557
  • 12
  • 62
  • 102

1 Answers1

4

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>
YakovL
  • 7,557
  • 12
  • 62
  • 102
Munim Munna
  • 17,178
  • 6
  • 29
  • 58
  • Nice :) Could you please clarify if elements recieve keypress events *only* if they are in focus (meaning, for instance, that clicking an input field inside an element will remove focus from the element)? Would be nice to be sure (any docs confirming that?). The other question would be if this is the only way to make an element (say, a `div`) focusable, but I've found an SO thread regarding this https://stackoverflow.com/q/1599660/3995261 so I'd say it's not a question for now (although they don't cite any docs). – YakovL Feb 20 '18 at 16:50
  • [Here](https://www.w3.org/TR/html50/editing.html#focus) you can find all your answers pretty neat. Just read section 7.4 and 7.4.1. – Munim Munna Feb 20 '18 at 17:41
  • Added references to the answer, solves your first and third query, for your second query, Yes, clicking on a child element will remove parents focus, like when you click on any input and the document-body loses focus. – Munim Munna Feb 20 '18 at 18:30
  • Thanks for the answers, the explanations and careful reading definitely deserve the bounty (I've read 7.4 but missed the explicit "when no element is focused, key events received by the document must be targeted at the body element" bit somehow). I'll accept the answer once I test and use in production but it looks like this is all I need. – YakovL Feb 20 '18 at 18:56
  • Glad to help :) Going through docs I also came to know that it is called __tabindex focus flag__ and a lot insights i never knew :D – Munim Munna Feb 21 '18 at 04:35