For your first point, you can use event delegation to handle all elements in a single event handler. Add the event listener to a container that is common to all elements and then identify the element that the event originated on with event.target
.
One caveat with this is that it relies on event bubbling, but focus
events don't bubble, so instead you have to use the focusin
event which does.
Here's a simplified example:
function handleFocus(event) {
const el = event.target;
console.log('Focus', el);
}
function handleClick(event) {
const el = event.target;
console.log('Click', el);
}
const container = document.getElementById('container');
container.addEventListener('focusin', handleFocus);
container.addEventListener('click', handleClick);
<div id="container">
<button class="btn-class1" id="btn-id1" aria-label="btn-label1">Button 1</button>
<input class="input-class1" id="input1" aria-label="input-label1">
<div class="div-class1" id="div1" aria-label="div-label1">I'm Div 1</div>
<button class="btn-class2" id="btn-id2" aria-label="btn-label2">Button 1</button>
<input class="input-class2" id="input2" aria-label="input-label2">
<div class="div-class2" id="div2" aria-label="div-label2">I'm Div 1</div>
</div>
For your second point you're going to have to use a bit of trickery as the FocusEvent
and MouseEvent
does not distinguish between different types of input devices.
Click event
For the click event, my go-to solution is to use the mouse coordinates to determine if it was triggered by a keyboard or mouse. If it was triggered by a keyboard, event.pageX
and event.pageY
will always be 0
but if it was triggered by a mouse they will both be 0
only if you clicked in the absolute top left corner of the window, and it's pretty unlikely that any reasonably well designed web page would have something clickable in that position.
Ex:
function handleClick(event) {
const msg = document.getElementById('msg');
if (event.pageX === 0 && event.pageY === 0) {
msg.value = 'Clicked by keyboard';
} else {
msg.value = 'Clicked by mouse';
}
}
document.getElementById('click-btn').addEventListener('click', handleClick);
<button id="click-btn">Click me!</button>
<input id="msg" type="text" readonly/>
Focus event
For the focus event we can exploit the :focus-visible
CSS pseudo-class. This doesn't work in Internet Explorer but hopefully that's not a problem now. This pseudo-class is intended to differentiate between scenarios where focus needs to made apparent through styling because it was caused by a generic event (such as keyboard tabbing) and when it doesn't because focus was caused by a deliberate, directed action (such as a mouse click on the element itself). I bet this reasoning is similar to the motivation you have for wanting to differentiate between them.
We can make use of this in our JS too, heres an example:
function handleFocus(event) {
const msg = document.getElementById('msg');
if (event.target.matches(':focus-visible')) {
msg.value = 'Focused by keyboard';
} else {
msg.value = 'Focused by mouse';
}
}
document.getElementById('focus-ipt').addEventListener('focusin', handleFocus);
<label><input id="focus-ipt" type="checkbox"/> Focus me!<br/></label>
<input id="msg" type="text" readonly/>
Disclaimer: Since this is trickery there's no guarantee that it works across all browsers, in all situations or that they will keep working in the future. Browsers might change these behaviors. Especially the :focus-visible
trick is problematic as it relies on the browser's interpretation of when it should apply. One current issue is that it applies to text fields (<input type="text"/>
and <textarea/>
) even when those are focused by a mouse click.
So keep this in mind when using these tricks