Javascript keydown function conflict on input focus - javascript

How to prevent keydown event when input is focused?
I have a function that assigns some action to keyboard keys (mainly player control), but when using <input> all assigned keys trigger that action as well. Therefore it is impossible to use <input> properly when typing.
function keyboardShortcuts(e){
//Do some stuff when keyboard keys are pressed
switch(e.code){
case 'KeyJ':
console.log('KeyJ');
break;
//...
}
}
document.addEventListener('keydown',keyboardShortcuts);
<input id="search" placeholder="search...">
How to fix it? Should I check if input is not focused?
function keyboardShortcuts(e){
if(document.getElementById('search') !== document.activeElement){
switch(e.code){
//...
}
}
}
Or maybe there is some method similar to e.preventDefault()?
document.getElementById('search').addEventListener('focus',function(){
//... prevent keyboardShortcuts frum running ?????
});

Answer:
Since you're applying the keydown event handler to the entire document, the best way to handle this is to diffuse the function based upon what element initiated the event. You can find this out by using event.target
Combining this with HTMLElement.prototype.matches can allow you to avoid elements that match a selector, for instance any input elements.
if(e.target.matches("input")) return;
Example:
function keyboardShortcuts(e){
//Do some stuff when keyboard keys are pressed
if(e.target.matches("input")) return;
switch(e.code){
case 'KeyJ':
console.log('KeyJ');
break;
//...
}
}
document.addEventListener('keydown',keyboardShortcuts);
<h3> Will Escape Custom KeyDown </h3>
<input id="search" placeholder="search...">
<hr />
<h3> Will Trigger Custom KeyDown </h3>
<textarea></textarea>
<hr/>
<small> Pressing <kbd>J</kbd> will fire <highlight>console.log</highlight> from Custom KeyDown Event</small>
You'll notice, in the above, input doesn't fire a console log when pressing j, but textarea still does.
Adding a Class:
I would suggest adding a specific class to your markup that is used for avoiding the global keydown handler. For instance something like defaultKeys
Example:
function keyboardShortcuts(e){
//Do some stuff when keyboard keys are pressed
if(e.target.matches(".defaultKeys")) return;
switch(e.code){
case 'KeyJ':
console.log('KeyJ');
break;
//...
}
}
document.addEventListener('keydown',keyboardShortcuts);
<h3> Defaulted KeyDown Events </h3>
<hr/>
<input id="search" class="defaultKeys" placeholder="search...">
<br>
<textarea class="defaultKeys"></textarea>
<hr/>
<h3> Custom KeyDown Events </h3>
<input />
<br>
<textarea></textarea>
<hr/>
<small> Pressing <kbd>J</kbd> will fire <highlight>console.log</highlight> from Custom KeyDown Event</small>
Avoiding Groups of Elements:
It may be beneficial to avoid entire containers of elements - a.e. if you have a submission form or a player chat, etc.
You can utilize the same basic principles, but because currently selectors cannot look up the tree( they can't say am I a great-grandchild of an element that does match .defaultKeys ? ) we have to recursively check up the tree.
This is done with a simple do...while loop that continues until it finds a match for the specified selector OR it hits the body tag.
The second out( the body tag check ) is normally not necessary as parentElement should only be able to go up to the HTML element, however, when dealing with IFrames, DOMParser, and, in the future, Realms - you may have multiple DOMs injected into one another, and therefore, multiple body tags.
Basically, we simply want to give a for-sure recursive out when it reaches the top of the current DOM.
function keyboardShortcuts(e) {
// get init element and set default flag
let element = e.target,
useDefault = false;
// if this element has defaultKeys class
// OR if any parent element has defaultKeys class
// set default flag to true
do {
if (element.matches(".defaultKeys")) {
useDefault = true;
break;
}
if ( element.tagName === "BODY" ) break;
} while ((element = element.parentElement));
// if flag is true, escape function
if (useDefault) return;
// check for custom key events
switch (e.code) {
case "KeyJ":
console.log("KeyJ");
}
}
document.addEventListener("keydown", keyboardShortcuts);
<div id="group" class="defaultKeys">
<h3> These elements are in the same parent div </h3>
<input />
<br/>
<textarea></textarea>
</div>
<h3> Outside of Group </h3>
<textarea></textarea>
Conclusion
Hope this helps! Happy coding!

Related

Change focus to next input field on "Enter" key with VueJS & Quasar

I have a form that works strictly with a Barcode code that simulates an Enter event at the end of the read. (No keyboard and mouse). I'm having a hard time sending the focus to the next element (input sometimes a button). I prepared a playground for you so you can checkout my code. At some point this worked before a quasar dress-up and now it isn't. I refuse to think this is a quasar issue and more like a "I suck" problem lol.
The process is simple in theory. Wait for the input field to read the entire barcode before it fires the focus event. My best guess is to use the change event. When I tried the input or keydown event, it registered other stuff and fired other functions on every digit.. Big no-no, especially when making api calls.
Here is my sendFocus method.
sendFocus: function(e) {
document.addEventListener("keydown", function(e) {
var input = e.target.nodeName.toLowerCase() === "input";
var form = e.target.form;
if (e.key === "Enter" && input) {
var index = Array.prototype.indexOf.call(form, e.target);
form.elements[index + 1].focus();
}
});
}
And the link to the codepen. Thanks in advance
With #change (or native DOM onchange) event, any associated handler will get invoked as many times as you hit a key on the keyboard, and in your case, you are literally attaching a new handler every time a key is pressed (you seem to be good with Javascript and jQuery, so I hope the codepen was just for mere illustration... Unless I'm missing something?).
But anyway, in Vue (for this particular purpose), we usually want #keydown.enter. Have a read on Event Handling with Key Codes.
That being said, there are several approaches to achieving this "jump-to-next-field" behavior, but please consider the following example (which should also be applicable to Quasar's q-input).
new Vue({
el: '#app',
methods: {
focusNext(e) {
const inputs = Array.from(e.target.form.querySelectorAll('input[type="text"]'));
const index = inputs.indexOf(e.target);
if (index < inputs.length) {
inputs[index + 1].focus();
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.11"></script>
<div id="app">
<form>
<div>
<label>Field #1</label>
<input type="text" #keydown.enter="focusNext" />
</div>
<div>
<label>Field #2</label>
<input type="text" #keydown.enter="focusNext" />
</div>
<div>
<label>Field #3</label>
<input type="text" />
</div>
</form>
</div>

Find Cursor in which input

I have two inputs
<input id="foo" name="foo"/>
<input id="bar" name="bar"/>
How to find cursor present in which input?
I am able to find position of cursor in it, but I need to know in which input cursor exists.
While other answers give you the current active element. I prefer to provide another answer which give you the exact 'which input' you asked.
<input id="foo" name="foo" onfocus="onfocus(this)" onblur="onblur(this)"/>
<input id="bar" name="bar" onfocus="onfocus(this)" onblur="onblur(this)"/>
<script>
var idOfInputFocused = ""; // what you need
function onfocus(input) {
idOfInputFocused = input.id;
}
function onblur(input) {
idOfInputFocused = "";
}
</script>
If you need to find the element that is currently active/on focus, use
document.activeElement
https://www.w3schools.com/jsref/prop_document_activeelement.asp
If the element has the cursor, it has the focus, you can find out using activeElement][1].
var activeElement = document.activeElement;
If you can't modify html, and need to know over which input cursor is positioned you can try such approach
document.addEventListener("mouseover", function(e) {
if(e.target.tagName === "INPUT") {
console.log(e.target.id);
document.getElementById("result").innerHTML = e.target.id;
}
});
Here you can find more information about events delegation - https://javascript.info/event-delegation, so you can wrap your inputs into some block and set event listener to that wrapper, listen any events on it and figure out what happened inside.
And be sure to remove event listener after necessary actions were performed)
Information about removing event listeners you can find here https://www.w3schools.com/jsref/met_element_removeeventlistener.asp.
link to test this solution https://www.w3schools.com/code/tryit.asp?filename=GB7P6V4ACH1O

How To Make A Tab Loop On HTML Form

When a user tabs through form input boxes it goes in order. I figured out that you could order them though when using tabindex=x. I have 5 inputs on my form, so I have tabindex 5 different times. After that it goes to different items on the page, so is there a way to make it just loop through those 5 items? By the way I can not use tabindex=0 because then I would have to add that to 100s of items. So basically my question is how can I make a so called "tab loop".
You can't declaratively set up a tab loop. That feature is designed to work within the normal tabbing behavior of the browser, which is to visit all tab-able elements in both the page and the browser chrome.
If you want to prevent tabbing away from a chosen subset of elements, you'll need a little JavaScript and you better know very well why you're doing it and what it'll break, give some visual cues that the form is focused to the detriment of the rest of the UI, and think of some non-visual clients for whom keyboard navigation is even more important.
Assuming that you have indeed knowingly judged that it's OK to hijack tabbing, one relatively safe way to do this is to create a dummy tabbable element (has to be displayed, but could be practically invisible) with a tabIndex that makes it the next after the last item in your form, and another right before the first in your form.
dummyItemBeforeForm.addEventListener('focus', function(e){
lastItemOfMySuperImportantForm.focus() }, true )
dummyItemAfterForm.addEventListener('focus', function(e){
firstItemOfMySuperImportantForm.focus() }, true )
That will make the focus loop back to the beginning of the form when tabbing away from the last item, and loop to the end when shift-tabbing from the first.
Make sure that the dummy items are disabled by default and only become focusable once the user focuses your form and gets the visual cues that the form is now kind of modal, and disable the dummies again once the user is done with the form.
And please, please, test this with real users, and see if they like it or if they panic because you broke their expected tab behavior.
I found an easy way to achieve this using jQueryUI. I created a .tabloop class and I'm using the following snippet.
The :tabbable selector is not natively included in jQuery, it's part of jQueryUI but you can easily make your own.
// Focus loop inside element with class tabloop
$(function() {
$(document).on('keydown', '.tabloop :tabbable:not([readonly])', function(e) {
// Tab key only (code 9)
if (e.keyCode != 9)
return;
// Get the loop element
var loop = $(this).closest('.tabloop');
// Get the first and last tabbable element
var firstTabbable = loop.find(':tabbable:not([readonly])').first();
var lastTabbable = loop.find(':tabbable:not([readonly])').last();
// Leaving the first element with Tab : focus the last one
if (firstTabbable.is(e.target) && e.shiftKey == true) {
e.preventDefault();
lastTabbable.focus();
}
// Leaving the last element with Tab : focus the first one
if (lastTabbable.is(e.target) && e.shiftKey == false) {
e.preventDefault();
firstTabbable.focus();
}
});
});
.tabloop {
border: 1px red solid;
padding: 1ch;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<p>
<label for="text1">Field 1:</label>
<input type="text" id="text1">
</p>
<p>
<label for="text2">Field 2:</label>
<input type="text" id="text2">
</p>
<p>
Link
</p>
<p>
<button type="button">Button</button>
</p>
<div class="tabloop">
<p>
<label for="text3">Field 3:</label>
<input type="text" id="text3">
</p>
<p>
<label for="text4">Field 4:</label>
<input type="text" id="text4">
</p>
<p>
Link
</p>
<p>
<button type="button">Button</button>
</p>
</div>
As a follow up to #NicolasBernier JqueryUI answer, this is the code that worked w/o the ':tabbable' keyword.
// first block deals with first tabbable element
$('uniqueContainerOfElementInQuestion').find('firstTabbableElement(ex."a")').first().on('keydown', function (e) {
// first ele w/ shift and tab
if (e.shiftKey == true && e.keyCode == 9) {
console.log('Shift tab on first')
e.preventDefault();
// focus on element
// if it isn't working uncomment out timeout as possible test/quickfix
// setTimeout(()=>{
$('uniqueContainerOfElementInQuestion').find('lastTabbableElement(ex."button")').last().focus();
// })
}
});
$('uniqueContainerOfElementInQuestion').find('lastTabbableElement(ex."button")').last().on('keydown', function (e) {
// Leaving the last element with Tab : focus the first one
if (e.shiftKey == false && e.keyCode == 9) {
console.log('tab on last')
e.preventDefault();
// setTimeout(()=>{
$('uniqueContainerOfElementInQuestion').find('firstTabbableElement(ex."a")').first().focus();
// })
}
});

Multiple "if" Statements?

If I wanted to hold SHIFT and right-click to perform a function, what would I need to change here?
JAVASCRIPT
function myfunction(e) {
if (Input.GetMouseButtonDown(1))
if (e.shiftKey==1) {
//dostuff
}
}
HTML
<input onContextMenu="myfunction(e)" />
The onContextMenu event is only triggered when you right-click on something, so lose the check for which mouse button was pressed. Just check e.shiftKey.
function myfunction(e){
if(e.shiftKey){
// Do Stuff
}
}
Also, make sure you pass the event to the function:
<input onContextMenu="myfunction(event)" />
Or better yet, don't use inline event handlers, and bind the event in JavaScript with addEventListener.
EDIT: You can also use an onclick event, and check for right-click (should be 2):
function myfunction(e){
if(e.button === 2 && e.shiftKey){
// Do Stuff
}
}
Then do:
<input onClick="myfunction(event)" />
e.which corresponds to the mouse button pressed. 3 is a right-click.
Here's a fiddle which shows how you might test for a shift-right-click on an input.

Support for cancelling DHTML dialog box with escape

Imagine a DHTML dialog box with the following markup:
<div id="someDialog" class="dialog">
<h2>Title of dialog</h2>
Lots: <input ...>
of: <select ...>
controls: <textarea ...>
<input type="submit" value="OK">
<input type="reset" value="Cancel">
</div>
The user will expect hitting escape to cancel the dialog. This in itself isn't hard -- just add a keydown event handler to document.documentElement to check for ev.keyCode == 27, and use that to close the topmost dialog on the page.
The problem is this – there are certain circumstances where it's important that the browser sees the escape key first. For example, if the browser prompts with an autocomplete menu for <input type="text">, pressing escape should cancel that, not cancel the dialog. If you bring up the dropdown/popup menu for a <select>, pressing escape should close that, not the dialog.
How do you arrange to handle the escape key for a window, if and only if the browser doesn't need escape keypresses for something?
Edit: Stack Exchange itself has this very fault. If I click the "Would you like to have responses to your questions sent to you via email?" link, which opens a DHTML dialog box, then tab to the frequency dropdown, press alt-down to open the dropdown menu, then escape to close the dropdown menu, the whole dialog closes. This should not happen. The browser's control implementation should have first pickings on the escape key under these circumstances.
After some decent researching and trial/error, the best/only solution here seems to be creating your own custom form controls.
The following is a failed attempt to solve the problem.
http://jsfiddle.net/CoryDanielson/4jBgs/10/
Here's basically how it works.
First, there's an activeInput variable which stores a DOMElement of the input that the user is focused on. (only if the input is escapable)
var activeInput = false;
In order to populate this variable, I created an array of the DOMElements that you mentioned can be escaped (textboxes with autocomplete, select elements)
var escapableElements = [];
escapableElements = escapableElements.concat(
Array.prototype.slice.call(document.getElementsByTagName('select')),
Array.prototype.slice.call(document.getElementsByTagName('input'))
//add more elements here
);
and then looped through the array and attached eventListeners for the focus and blur (lose focus) events. (i included the for each function at the bottom of this post)
forEach(escapableElements, function() {
this.addEventListener('focus', registerActiveElement);
this.addEventListener('blur', deregisterActiveElement);
});
function registerActiveElement() {
if (!activeInput)
activeInput = this;
//console.log('registered'); //testing only
}
function deregisterActiveElement() {
if (activeInput)
activeInput = false;
//console.log('deregistered'); //testing only
}
After that, I wired up an eventListener for the keydown event. Inside of it, I checked to see if there is an activeInput if there is, I just return true; which will let the browser do what it wants (escape from autocomplete, etc) if there IS NOT an activeInput, I checked if ESC was pressed and call hide_dialog_box(event.keyCode);
The only difference from the paragraph in your question about handling the ESC keypress is that I checked to see if there was an activeInput beforehand. If there is an activeInput, I did nothing (let the browser handle ESC natively) if there's no activeInput I called event.preventDefault() which will cancel the browser's native handling of ESC and then called the function hide_dialog_box(keyCode) and then did return false; which also helps to prevent the browser from handling the ESC keypress.
document.addEventListener('keydown', function(event) {
if (!activeInput) {
if (event.keyCode == 27) { //esc
event.preventDefault();
hide_dialog_box(event.keyCode);
return false;
}
} else {
return true; //if active input, let browser function
}
/*
if the browser prompts with an autocomplete menu for
<input type="text">, or options on a <select> drop down
pressing escape will cancel that, not cancel the dialog.
*/
});
The last 2 snippits of code are the function hide_dialog_box(keyCode) and the function I wrote to wrote to loop through the NodeList called escapableElements
function hide_dialog_box(keyCode) {
var dialog_box = document.getElementById('dialog_box');
dialog_box.style.display = 'none';
}
function forEach(list, callback) {
for (var i = 0; i < list.length; i++)
{
//calls the callback function, but places list[i] as the 'this'
callback.call(list[i]);
}
}

Categories