Why is tab keypress causing focus change also triggering keyup event? - javascript

Pressing the tab key which triggers a focus change is also received by the input receiving the focus as a keyup.
a: <input type='text'/><br/>
b: <input type='text' onkeyup='alert("wtf?")'/><br/>
http://jsfiddle.net/59SnP/
As my control also uses tab (not in the example), I would want the focus related keyup event being consumed (but I want to receive other non-focus-change related tab events). I tried to research the rationale behind the current behavior but found nothing. The question: Where is this current behavior specified (event not consumed by focus change), and what would be a cross-browser workaround to force consuming it. Thx.

You can try this. I changed your keyup event in your input :
<input type='text' onkeyup="if(!tabPressed){ alert('This is it !'); }"/>
And I added a little event handler which will raise a flag when the tab button is pressed :
var tabPressed = false;
document.addEventListener('keydown', function (e) {
if(e.keyCode == 9) {
tabPressed = true;
} else {
tabPressed = false;
}
}, false);

Based on Nathan's insight, here is a fully working example:
// First part of Nathan's HACK (set a sentinel when a focus changing tab has happened)
var tabPressed = false;
// remove this listener to break the functionality
$(document).on("keydown", function (e) {
if(e.keyCode == 9) {
tabPressed = true;
} else {
tabPressed = false;
}
});
// The listener on the client input that would kill the keyup tab event upon focus change
$("#magic").on("keyup", function(e) {
if (tabPressed && e.keyCode==9) {
tabPressed = false; // reset the sentinel
e.stopImmediatePropagation()
e.preventDefault()
}
})
And here is the second part, which is a simple skeleton of something meaningful. We disable TAB inside the input, and log it as we do with other keyups:
$("#magic").on("keydown", function(e) {
if (e.keyCode==9) {
e.preventDefault()
e.stopPropagation()
}
})
$("#magic").on("keyup", function(e) {
$(this).val($(this).val() + " " + e.keyCode)
e.stopPropagation()
e.preventDefault()
})
The HTML backing the story is as simple as:
a: <input type='text'/><br/>
b: <input type='text'/><br/>
c: <input type='text' id='magic'/><br/>
If you want to play with it, here it is on jsfiddle
NOTE: This still is not the perfect solution, the sentinel is just reset inside the control, so if a tabpress moving the focus does not activate our input, the sentinel stucks, and the first event will be swallowed.. So here is an example of wrong behaviour:
Click on input A
Press TAB (focus moves to input B, tabPressed becomes true)
Click on input C
Press TAB (it is eaten up as sentinel is true)
Press TAB (now it goes through)
Still it is slightly better to have to press TAB twice as to have something happening automatically, wo user control...

Related

JavaScript for disabling Tab Key to go to browser tab and address bar

Once I have an JS alert active, I am trying to stop tab key from going to browser bar and address in Firefox. I have the form focus working just cannot work out how to stop tabbing to the browser level once a JS alert is fired. Any idea how I can fix that?
My code:
$('a').on( 'keyup', function( e ) {
if( e.which == 9 ) {
console.log( 'pressed');
}
} );
Just use the keydown event, and prevent default actions then return false:
$(document).on("keydown", function(e) {
if (e.keyCode == 9) {
e.preventDefault();
return false;
}
})
I am looking to create a scenario that you are explaining. I created an alert box and then print to the console the name of the active element. The problem is that when the alert box is active the browser halts JavaScript. It is not possible with an alert box.
function javaScriptIsRunning(){
console.log("running");
setTimeout(javaScriptIsRunning, 1000);
};
javaScriptIsRunning();
alert('a');
var onKeyUpcode = document.addEventListener("keyup", function(e){
console.log(document.activeElement.localName);
});

on down arrow KeyPress, click event is getting fired

on down arrow keypress , click event is getting fired, event.keycode is undefined
$(".dropdown:not(.li-search) a.dropdown-toggle", ".navbar-collapse").on("click", function(event) {
var target = $(this).attr("target");
if (event.keyCode !== '40'){
if (!$(".li-menu").is(":visible") && target === undefined) {
location.href=this.href;
} else {
window.open(this.href, '_blank');
}
}
});
in this code i am trying to open main menu in new tab , but external link is getting open on down arrow keypress
call preventDefault() function.
$(".dropdown:not(.li-search) a.dropdown-toggle", ".navbar-collapse").on("click", function(event) {
event.preventDefault();
var target = $(this).attr("target");
if(event.keyCode!=='40'){
if (!$(".li-menu").is(":visible") && target===undefined) {
location.href=this.href;
}
else {
window.open(this.href,'_blank');
}
}
});
See the keycode for the reference https://css-tricks.com/snippets/javascript/javascript-keycodes/
in order to configure your app for particular key event
Looking at the classes dropdown-toggle, navbar-collapse, I'm guessing that you are using Bootstrap library.
If that is the case, the behaviour you are seeing is reasonable. Let's break down the issues:
on down arrow keypress , click event is getting fired
Q: You have only bind the handler on click event so why are it is being triggered on keypress?
A: Because this is a feature of bootstrap dropdown. To have better accessibilty, bootstrap triggers click event on the keydown of up, down, esc and space keys.
event.keycode is undefined
Since it is a click event handler and not some keyboard event handler like keydown or keypress, event.keyCode should be undefined
Note: You are using a strict equality in the following condition
if (event.keyCode !== '40')
This will check both the type and value of the operands. Now, event.keyCode always return a Number while '40' is a string, hence the above condtion will yield false even if keyCode is 40. You should correct it to:
if (event.keyCode !== 40)
Now, if you want to stop the redirect on down key, you should check whether the event triggered is an original event or was triggered by some js logic. For this, you may choose jQuery's event.isTrigger or event.originalEvent
Here's a code snippet:
$(".dropdown:not(.li-search) a.dropdown-toggle", ".navbar-collapse").on("click", function(event) {
var target = $(this).attr("target");
// Check if NOT an triggered event
if (!event.isTrigger) {
if (!$(".li-menu").is(":visible") && target === undefined) {
location.href = this.href;
} else {
window.open(this.href, '_blank');
}
}
});
<a> tags will fire the click event when you press enter on them. However you will not have a keyCode on the event because it is not a Key* event. If you want to know the keyCode add a keyDown or keyUp handler as well. You could also handle both by doing something like the following:
$(".dropdown:not(.li-search) a.dropdown-toggle", ".navbar-collapse").on("click keydown", function(event) {
var target = $(this).attr("target");
if(event.type === 'keydown' && event.keyCode!=='40'){
if (!$(".li-menu").is(":visible") && target===undefined) {
location.href=this.href;
}
else {
window.open(this.href,'_blank');
}
}
});
You'll probably also want to add an event.preventDefault(); in there if you wish to prevent default browser behaviour from taking place.

Use a preventdefault to get out of the loop after pressing Enter key

This is a complete revision of my initial question, all unnecessary resources and references were deleted
I am tying the same event listener to 2 different elements: a button and Enter key, and it looks like the following:
var funcelement = function(){
//function code
};
$('#buttonID').click(funcelement);
$('#inputID').keyup(function () {
if (event.which == 13) {
$('#buttonID').trigger('click');
}
})
What I am trying to do is to prevent propagation of the enter key press if focus is on the submit button(#buttonID) by using preventDefault().
So I tried various combinations to make it work. The following is the latest result on my attempts
$('#inputID').keyup(function () {
var hasfocus = $('#buttonID').is(':focus') || false;
if (event.which == 13) {
if (!hasfocus) {
event.preventDefault();
$('#buttonID').trigger('click');
//hasfocus = true;
}
else {
//event.preventDefault();
//$('#buttonID').trigger('click');
}
}
})
After I enter a text into an input box and press Enter key, a confirmation window with yes/cancel buttons pops up with focus on yes button. Once I press Enter again, another window confirming that changes were made pops up with Ok button focused on it. Once I press Enter again, everything I need is being made.
However, there is one problem: after the last step is done, I am going back to the if (!hasfocus) line.
How do I prevent that from happening? Once the stuff I need is done - I don't want to go into that line again.
You can pass a parameter to into the function and stop the propagation there like so:
var funcelement = function(event, wasTriggeredByEnterKey){
if (wasTriggeredByEnterKey && $('#buttonID').is(':focus')) {
event.stopPropagation;
}
//function code
};
$('#buttonID').click(funcelement);
$('#inputID').keyup(function () {
if (event.which == 13) {
$('#buttonID').trigger('click', [true]);
}
}
)
UPDATE
In order to answer your revised issue, you should use the "keydown" event rather than "keyup" when working with alerts. This is because alerts close with the "keydown" event but then you are still triggering the "keyup" event when you release the enter key. Simply change the one word like this:
$('#inputID').keydown(function () {
var hasfocus = $('#buttonID').is(':focus') || false;
if (event.which == 13) {
if (!hasfocus) {
event.preventDefault();
$('#buttonID').trigger('click');
//hasfocus = true;
}
else {
//event.preventDefault();
//$('#buttonID').trigger('click');
}
}
})

Prevent form submission with enter key

I just wrote this nifty little function which works on the form itself...
$("#form").keypress(function(e) {
if (e.which == 13) {
var tagName = e.target.tagName.toLowerCase();
if (tagName !== "textarea") {
return false;
}
}
});
In my logic I want to accept carriage returns during the input of a textarea. Also, it would be an added bonus to replace the enter key behavior of input fields with behavior to tab to the next input field (as if the tab key was pressed). Does anyone know of a way to use the event propagation model to correctly fire the enter key on the appropriate element, but prevent form submitting on its press?
You can mimic the tab key press instead of enter on the inputs like this:
//Press Enter in INPUT moves cursor to next INPUT
$('#form').find('.input').keypress(function(e){
if ( e.which == 13 ) // Enter key = keycode 13
{
$(this).next().focus(); //Use whatever selector necessary to focus the 'next' input
return false;
}
});
You will obviously need to figure out what selector(s) are necessary to focus on the next input when Enter is pressed.
Note that single input forms always get submitted when the enter key is pressed. The only way to prevent this from happening is this:
<form action="/search.php" method="get">
<input type="text" name="keyword" />
<input type="text" style="display: none;" />
</form>
Here is a modified version of my function. It does the following:
Prevents the enter key from working
on any element of the form other
than the textarea, button, submit.
The enter key now acts like a tab.
preventDefault(), stopPropagation() being invoked on the element is fine, but invoked on the form seems to stop the event from ever getting to the element.
So my workaround is to check the element type, if the type is not a textarea (enters permitted), or button/submit (enter = click) then we just tab to the next thing.
Invoking .next() on the element is not useful because the other elements might not be simple siblings, however since DOM pretty much garantees order when selecting so all is well.
function preventEnterSubmit(e) {
if (e.which == 13) {
var $targ = $(e.target);
if (!$targ.is("textarea") && !$targ.is(":button,:submit")) {
var focusNext = false;
$(this).find(":input:visible:not([disabled],[readonly]), a").each(function(){
if (this === e.target) {
focusNext = true;
}
else if (focusNext){
$(this).focus();
return false;
}
});
return false;
}
}
}
From a usability point of view, changing the enter behaviour to mimic a tab is a very bad idea. Users are used to using the enter key to submit a form. That's how the internet works. You should not break this.
The post Enter Key as the Default Button describes how to set the default behaviour for enter key press. However, sometimes, you need to disable form submission on Enter Key press. If you want to prevent it completely, you need to use OnKeyPress handler on tag of your page.
<body OnKeyPress="return disableKeyPress(event)">
The javascript code should be:
<script language="JavaScript">
function disableEnterKey(e)
{
var key;
if(window.event)
key = window.event.keyCode; //IE
else
key = e.which; //firefox
return (key != 13);
}
</script>
If you want to disable form submission when enter key is pressed in an input field, you must use the function above on the OnKeyPress handler of the input field as follows:
<input type="text" name="txtInput" onKeyPress="return disableEnterKey(event)">
Source: http://www.bloggingdeveloper.com/post/Disable-Form-Submit-on-Enter-Key-Press.aspx
Set trigger for both the form and the inputs, but when the input events are triggered, stop the propagation to the form by calling the stopPropagation method.
By the way, IMHO, it's not a great thing to change default behaviors to anything any average user is used to - that's what make them angry when using your system. But if you insist, then the stopPropagation method is the way to go.
In my case i wanted to prevent it only in a dinamically created field, and activate some other button, so it was a little bit diferent.
$(document).on( 'keypress', '.input_class', function (e) {
if (e.charCode==13) {
$(this).parent('.container').children('.button_class').trigger('click');
return false;
}
});
In this case it will catch the enter key on all input's with that class, and will trigger the button next to them, and also prevent the primary form to be submited.
Note that the input and the button have to be in the same container.
The previous solutions weren't working for me, but I did find a solution.
This waits for any keypress, test which match 13, and returns false if so.
in the <HEAD>
function stopRKey(evt) {
var evt = (evt) ? evt : ((event) ? event : null);
var node = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null);
if ((evt.which == 13) && (node.type == "text")) {
return false;
}
}
document.onkeypress = stopRKey;
I prefer the solution of #Dmitriy Likhten, yet:
it only worked when I changed the code a bit:
[...] else
{
if (focusNext){
$(this).focus();
return false; } //
}
Otherwise the script didn't work.
Using Firefox 48.0.2
I modified Dmitriy Likhten's answer a bit, works good. Included how to reference the function to the event. note that you don't include () or it will execute. We're just passing a reference.
$(document).ready(function () {
$("#item-form").keypress(preventEnterSubmit);
});
function preventEnterSubmit(e) {
if (e.which == 13) {
var $targ = $(e.target);
if (!$targ.is("textarea") && !$targ.is(":button,:submit")) {
var focusNext = false;
$(this).find(":input:visible:not([disabled],[readonly]), a").each(function () {
if (this === e.target) {
focusNext = true;
} else {
if (focusNext) {
$(this).focus();
return false;
}
}
});
return false;
}
}
}

JavaScript: Check if mouse button down?

Is there a way to detect if a mouse button is currently down in JavaScript?
I know about the "mousedown" event, but that's not what I need. Some time AFTER the mouse button is pressed, I want to be able to detect if it is still pressed down.
Is this possible?
Regarding Pax' solution: it doesn't work if user clicks more than one button intentionally or accidentally. Don't ask me how I know :-(.
The correct code should be like that:
var mouseDown = 0;
document.body.onmousedown = function() {
++mouseDown;
}
document.body.onmouseup = function() {
--mouseDown;
}
With the test like this:
if(mouseDown){
// crikey! isn't she a beauty?
}
If you want to know what button is pressed, be prepared to make mouseDown an array of counters and count them separately for separate buttons:
// let's pretend that a mouse doesn't have more than 9 buttons
var mouseDown = [0, 0, 0, 0, 0, 0, 0, 0, 0],
mouseDownCount = 0;
document.body.onmousedown = function(evt) {
++mouseDown[evt.button];
++mouseDownCount;
}
document.body.onmouseup = function(evt) {
--mouseDown[evt.button];
--mouseDownCount;
}
Now you can check what buttons were pressed exactly:
if(mouseDownCount){
// alright, let's lift the little bugger up!
for(var i = 0; i < mouseDown.length; ++i){
if(mouseDown[i]){
// we found it right there!
}
}
}
Now be warned that the code above would work only for standard-compliant browsers that pass you a button number starting from 0 and up. IE uses a bit mask of currently pressed buttons:
0 for "nothing is pressed"
1 for left
2 for right
4 for middle
and any combination of above, e.g., 5 for left + middle
So adjust your code accordingly! I leave it as an exercise.
And remember: IE uses a global event object called … "event".
Incidentally IE has a feature useful in your case: when other browsers send "button" only for mouse button events (onclick, onmousedown, and onmouseup), IE sends it with onmousemove too. So you can start listening for onmousemove when you need to know the button state, and check for evt.button as soon as you got it — now you know what mouse buttons were pressed:
// for IE only!
document.body.onmousemove = function(){
if(event.button){
// aha! we caught a feisty little sheila!
}
};
Of course you get nothing if she plays dead and not moving.
Relevant links:
MouseEvent's button (DOM 2)
MSDN's button
Update #1: I don't know why I carried over the document.body-style of code. It will be better to attach event handlers directly to the document.
This is an old question, and the answers here seem to mostly advocate for using mousedown and mouseup to keep track of whether a button is pressed. But as others have pointed out, mouseup will only fire when performed within the browser, which can lead to losing track of the button state.
However, MouseEvent (now) indicates which buttons are currently pushed:
For all modern browsers (including Safari v11.1+ [v11.3+ on iOS]), use MouseEvent.buttons
For Safari < 11.1 (11.3 on iOS), use MouseEvent.which (buttons will be undefined for Safari) Note: which uses different numbers from buttons for Right and Middle clicks.
When registered on document, mousemove will fire immediately as soon as the cursor reenters the browser, so if the user releases outside then the state will be updated as soon as they mouse back inside.
A simple implementation might look like:
var primaryMouseButtonDown = false;
function setPrimaryButtonState(e) {
var flags = e.buttons !== undefined ? e.buttons : e.which;
primaryMouseButtonDown = (flags & 1) === 1;
}
document.addEventListener("mousedown", setPrimaryButtonState);
document.addEventListener("mousemove", setPrimaryButtonState);
document.addEventListener("mouseup", setPrimaryButtonState);
That code tracks the state of the primary mouse button (typically the left), ignoring the state of other mouse buttons.
If more complicated scenarios are required (different buttons/multiple buttons/control keys), check out the MouseEvent docs.
I think the best approach to this is to keep your own record of the mouse button state, as follows:
var mouseDown = 0;
document.body.onmousedown = function() {
mouseDown = 1;
}
document.body.onmouseup = function() {
mouseDown = 0;
}
and then, later in your code:
if (mouseDown == 1) {
// the mouse is down, do what you have to do.
}
the solution isn't good.
one could "mousedown" on the document, then "mouseup" outside the browser, and on this case the browser would still be thinking the mouse is down.
the only good solution is using IE.event object.
I know this is an old post, but I thought the tracking of mouse button using mouse up/down felt a bit clunky, so I found an alternative that may appeal to some.
<style>
div.myDiv:active {
cursor: default;
}
</style>
<script>
function handleMove( div ) {
var style = getComputedStyle( div );
if (style.getPropertyValue('cursor') == 'default')
{
// You're down and moving here!
}
}
</script>
<div class='myDiv' onmousemove='handleMove(this);'>Click and drag me!</div>
The :active selector handles the mouse click much better than mouse up/down, you just need a way of reading that state in the onmousemove event. For that I needed to cheat and relied on the fact that the default cursor is "auto" and I just change it to "default", which is what auto selects by default.
You can use anything in the object that is returned by getComputedStyle that you can use as a flag without upsetting the look of your page e.g. border-color.
I would have liked to set my own user defined style in the :active section, but I couldn't get that to work. It would be better if it's possible.
If you're working within a complex page with existing mouse event handlers, I'd recommend handling the event on capture (instead of bubble). To do this, just set the 3rd parameter of addEventListener to true.
Additionally, you may want to check for event.which to ensure you're handling actual user interaction and not mouse events, e.g. elem.dispatchEvent(new Event('mousedown')).
var isMouseDown = false;
document.addEventListener('mousedown', function(event) {
if ( event.which ) isMouseDown = true;
}, true);
document.addEventListener('mouseup', function(event) {
if ( event.which ) isMouseDown = false;
}, true);
Add the handler to document (or window) instead of document.body is important b/c it ensures that mouseup events outside of the window are still recorded.
The following snippet will attempt to execute the "doStuff" function 2 seconds after the mouseDown event occurs in document.body. If the user lifts up the button, the mouseUp event will occur and cancel the delayed execution.
I'd advise using some method for cross-browser event attachment - setting the mousedown and mouseup properties explicitly was done to simplify the example.
function doStuff() {
// does something when mouse is down in body for longer than 2 seconds
}
var mousedownTimeout;
document.body.onmousedown = function() {
mousedownTimeout = window.setTimeout(doStuff, 2000);
}
document.body.onmouseup = function() {
window.clearTimeout(mousedownTimeout);
}
In case someone else runs into this, you can use .matches with the :active selector:
function mouseDown() {
return document.body.matches(":active");
}
Using the MouseEvent api, to check the pressed button, if any:
// Mouse buttons
document.addEventListener('mousedown', e => console.log(e.buttons))
// Keyboard keys
document.addEventListener('keydown', e => console.log(e.key))
Return:
A number representing one or more buttons. For more than one button
pressed simultaneously, the values are combined (e.g., 3 is primary +
secondary).
0 : No button or un-initialized
1 : Primary button (usually the left button)
2 : Secondary button (usually the right button)
4 : Auxilary button (usually the mouse wheel button or middle button)
8 : 4th button (typically the "Browser Back" button)
16 : 5th button (typically the "Browser Forward" button)
You can combine #Pax and my answers to also get the duration that the mouse has been down for:
var mousedownTimeout,
mousedown = 0;
document.body.onmousedown = function() {
mousedown = 0;
window.clearInterval(mousedownTimeout);
mousedownTimeout = window.setInterval(function() { mousedown += 200 }, 200);
}
document.body.onmouseup = function() {
mousedown = 0;
window.clearInterval(mousedownTimeout);
}
Then later:
if (mousedown >= 2000) {
// do something if the mousebutton has been down for at least 2 seconds
}
You need to handle the MouseDown and MouseUp and set some flag or something to track it "later down the road"... :(
Short and sweet
I'm not sure why none of the previous answers worked for me, but I came up with this solution during a eureka moment. It not only works, but it is also most elegant:
Add to body tag:
onmouseup="down=0;" onmousedown="down=1;"
Then test and execute myfunction() if down equals 1:
onmousemove="if (down==1) myfunction();"
Using jQuery, the following solution handles even the "drag off the page then release case".
$(document).mousedown(function(e) {
mouseDown = true;
}).mouseup(function(e) {
mouseDown = false;
}).mouseleave(function(e) {
mouseDown = false;
});
I don't know how it handles multiple mouse buttons.
If there were a way to start the click outside the window, then bring the mouse into the window, then this would probably not work properly there either.
As said #Jack, when mouseup happens outside of browser window, we are not aware of it...
This code (almost) worked for me:
window.addEventListener('mouseup', mouseUpHandler, false);
window.addEventListener('mousedown', mouseDownHandler, false);
Unfortunately, I won't get the mouseup event in one of those cases:
user simultaneously presses a keyboard key and a mouse button, releases mouse button outside of browser window then releases key.
user presses two mouse buttons simultaneously, releases one mouse button then the other one, both outside of browser window.
var mousedown = 0;
$(function(){
document.onmousedown = function(e){
mousedown = mousedown | getWindowStyleButton(e);
e = e || window.event;
console.log("Button: " + e.button + " Which: " + e.which + " MouseDown: " + mousedown);
}
document.onmouseup = function(e){
mousedown = mousedown ^ getWindowStyleButton(e);
e = e || window.event;
console.log("Button: " + e.button + " Which: " + e.which + " MouseDown: " + mousedown);
}
document.oncontextmenu = function(e){
// to suppress oncontextmenu because it blocks
// a mouseup when two buttons are pressed and
// the right-mouse button is released before
// the other button.
return false;
}
});
function getWindowStyleButton(e){
var button = 0;
if (e) {
if (e.button === 0) button = 1;
else if (e.button === 1) button = 4;
else if (e.button === 2) button = 2;
}else if (window.event){
button = window.event.button;
}
return button;
}
this cross-browser version works fine for me.
Below jQuery example, when mouse is over $('.element'), color is changing depending on which mouse button is pressed.
var clicableArea = {
init: function () {
var self = this;
('.element').mouseover(function (e) {
self.handlemouseClick(e, $(this));
}).mousedown(function (e) {
self.handlemouseClick(e, $(this));
});
},
handlemouseClick: function (e, element) {
if (e.buttons === 1) {//left button
element.css('background', '#f00');
}
if (e.buttons === 2) { //right buttom
element.css('background', 'none');
}
}
};
$(document).ready(function () {
clicableArea.init();
});
Well, you can't check if it's down after the event, but you can check if it's Up... If it's up.. it means that no longer is down :P lol
So the user presses the button down (onMouseDown event) ... and after that, you check if is up (onMouseUp). While it's not up, you can do what you need.

Categories

Resources