Context
I have a backbone app with an event listener for focus events on a textarea. Backbone uses jQuery events, so core of my question centers around jQuery focus events.
Question
Is there a way to tell how an element came into focus, be it by click or tab?
The behavior of how the cursor gets positioned needs to be handled differently between these two cases, however there doesn't seem to be a way to distinguish between the two offhand.
I could listen to click events, however will still need to listen to focus to capture tabbing - this will overlap with click event as it will also focus the textarea resulting in double events.
I may to rethink this entirely.
JSBin Example
$('textarea')
.focus(function(event){
console.log('You focused me by' + event.type);
// Here I wish I know if the focus came from a 'click' or 'tab' events
});
<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-2.1.1.min.js"></script>
</head>
<body>
<form>
<input placeholder="focus me, then tab" type="text"><br>
<textarea>Focus me via click. Now try via tabbing.</textarea>
</form>
</body>
</html>
.onfocus() listener can get called in a number of ways which makes it a tricky even to bind to.
Click on element
Tab or Shift-Tab to element
jQuery programatic focus $( "#target" ).focus();
Switching between programs, and refocusing the internet browser
There is no unique identifier in the onfocus event to determine how it came into focus.
From what I found it is best to be more explicit and listen to click() and onkeyup() events to handle unique behaviors between them and avoid unexpected function calls (like the browser is refocused).
onkeyup() is great for capturing tab events as the tab key will be released 'up' when tabbing in, but not when tabbing out.
JSBin
$('textarea')
.click(focusedBy)
.keyup(checkTab);
function checkTab(event){
if (event.keyCode === 9) {
focusedBy(event);
}
}
function focusedBy (event){
console.log('You focused me by ' + event.type);
}
you will need a combo of focus, click and blur events to determine the origin of "getting focus". click->set value, focus -> check if that clickvalue was set -> do what you must -> reset on blur. you might also want to be looking out for ontouchdown
You could set a clicked variable on mousedown.
You'll need to blur the textarea on mousedown so that focus will will be triggered on mouseup:
var clicked= false;
$('textarea')
.focus(function(event) {
if(clicked) {
$('#status').html('clicked');
clicked= false;
}
else {
$('#status').html('tabbed');
}
})
.mousedown(function(event) {
clicked= true;
$(this).blur();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
<input placeholder="focus me, then tab" type="text"><br>
<textarea>Focus me via click. Now try via tabbing.</textarea>
</form>
<div id="status"></div>
here's a scaling way to do it without rerouting events or simulating extra actions:
var targ=$('textarea');
targ.focus(function(event){
console.log('You focused me by ' + targ.eventType);
// Here I wish I know if the focus came from a 'click' or 'tab' events
});
$("body").mousedown(function(e){
targ.eventType="mouse";
}).keydown(function(e){
targ.eventType="keyboard";
});
this uses the jQuery collection to store the last event type, which is set by document-wide handlers.
if you need to re-use this functionality on other input types, just add more selectors to targ and differentiate in the handler using event.target.
http://jsbin.com/ruqekequva/2/edit
Related
Note this question. I see that there are other approaches besides just triggering the tab keypress event, but I'd still like to know why triggering the tab key press event doesn't move focus to the next input field.
Code Pen
HTML
<textarea></textarea>
<textarea></textarea>
<textarea></textarea>
JS
$('textarea').on('keydown', function(e) {
if (e.metaKey && e.which === 40) {
console.log('test');
$(this).trigger({
type: 'keypress',
which: 9
});
}
});
Because the tab event is a native browser event/action for changing focus. The .trigger() function only triggers the event handlers that are assigned to it. Note there is more information given from jQuery's site:
The .trigger() function cannot be used to mimic native browser events, such as clicking on a file input box or an anchor tag. This is because, there is no event handler attached using jQuery's event system that corresponds to these events.
There is a plug-in for this though called jquery-simulate to handle this. That being said the tab key changing focus is actually a default action in the web browser. Firing a browsers native event does not mean it will do it's default action, as the documentation for KeyboardEvents mentions:
Note that manually firing an event does not generate the default action associated with that event. For example, manually firing a key event does not cause that letter to appear in a focused text input. In the case of UI events, this is important for security reasons, as it prevents scripts from simulating user actions that interact with the browser itself.
Due to the order of events on different elements of a form (see http://jsfiddle.net/fs7norbL/, obligatory code at bottom), I need to handle both the onchange and onclick event of a checkbox.
I want to know if a checkbox onchange and onclick event both happened due to the same human interaction or not. I may only handle one of them (whichever comes first) for each human interaction but need to be able to handle both event types.
event.timestamp won't do due to this long standing open mozilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=238041 .
There doesn't seem to be anything else in the event that I can use.
How to determine whether two events were caused by the same human interaction.
<form>
<input type="checkbox"/>
$(':checkbox').click(function() {
console.log('checkbox click');
});
$(':checkbox').change(function() {
console.log('checkbox change');
});
$('form').change(function() {
console.log('form change');
});
You may trigger the change event in the click handler -> delegate the callback to one function with some message ( or add local variable with state reachable from click and change handers).
$(':checkbox').click(function() {
console.log('checkbox clicked');
this.change();
});
Thus you'll know when what happened.
My code:
<input class="quantity" type="text" value="33000">
<script>
$(document).ready(
$('.quantity').keyup(function (event) {
alert('up');
MyVeryImportantValidate($(this), event.key)
});
..
</script>
My problem, if introduced into the Input still holding the keys and mouse make click on any other element in page... event keyup does not work.
I think the documentation in jquery states it clearly, it is going to the item that has focus.
http://api.jquery.com/keyup/
The keyup event is sent to an element when the user releases a key on
the keyboard. It can be attached to any element, but the event is only
sent to the element that has the focus. Focusable elements can vary
between browsers, but form elements can always get focus so are
reasonable candidates for this event type.
In fact the focus is lost even on holding a key down in this input text area. If I hold down the a letter key it repeats until I click outside it changing the focus.
How to keep the Focus on one textbox ? even if you click anywhere in a browser.
$("#txtSearch").focus();
You need to subscribe to the blur event of the textbox and reinstate focus with a small timeout:
$('#txtSearch').blur(function (event) {
setTimeout(function () { $("#txtSearch").focus(); }, 20);
});
This way you don't rely on subscribing to the events of any other element on the page. If you subscribe to body click or html click, it won't run if any other element prevents propagation of its click event, also it won't work when tabbing out of the textbox.
Example:
<!-- I will not propagate my click to top level DOM elements -->
<button id="button">Click me</button>
<script>
$('#button').click(function (event) {
event.stopPropagation();
});
</script>
Konstantin Dinev answer works ok in most cases, but if you dont want to lose the focus only when clicking on certain parts of the html just do this:
$(".nofocus").on( "mousedown", function (e) {
return false;
});
In my case i'm doing a small html text editor and i dont want to lose the control when pressing an action button, but yes in any other case.
I just need to add the nofocus class to the button and it will not take the control
$("html").click(function(){
$("#txtSearch").focus();
});
Live Demo: http://jsfiddle.net/QhaLY/
It's also possible without jQuery and without re-focusing, if you just need a click on specific elements to not grab the focus
just listen for the mousedown event and cancel it
element.addEventListener("mousedown", event => {event.preventDefault(); event.stopPropagation()});
$('body').click(function(){$("#txtSearch").focus();});
There are other ways of loosing focus than clicking area away from the input i.e. tabbing. If you want to prevent loosing focus use blur event i.e.
document.getElementById('txtSearch').addEventListener('blur', e => {
e.target.focus();
});
I use onMouseDown={e => e.preventDefault()} and that works just fine.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
jQuery - How can I bind all events on a DOM element?
Imagine, if we want to make some element completely initeractable.
We could, of course bind a prevent default for a click event as follows:
$('form *').bind('click', function(event) {
event.preventDefault();
});
But that's just one event, and there are many more, like hover, focus, selectstart, and many more.
We could specify them all in one line like 'click focus hover dblclick blur selectstart' but that doesn't make much sense and is not easy to maintain.
So, is it possible to bind an event listener without discriminating for the type of the event? Maybe some native JavaScript listeners allow it?
No such possibility because not all elements support same events and not all events behave in the same way. You always have to explicitly provide a list of events whether defined statically or dynamically by a script that spits out event names.
Even though I linked to a script that creates an array of event names, these are made on one element only. You should of course be generating this with a more complex and slower script that enumerates over all elements in question and adds missing events. Using Javascript objects as associative array for faster searching whether a particular event has been added or not.
A better suggestion
What you're trying to do is likely a highly over-engineered solution. When I'm creating a demo clickable interface that should disable some elements (be it links, buttons or anything else) I rather do it by defining a CSS class that disables an element in question and have a simple script that does disabling afterwards.
You could leverage this even further by also providing which events you'd like to disable on particular element (with default being a click event).
<!-- no events; use defaults -->
No-follow link
<button class="disable">Nothing happens</button>
<!-- provide events -->
No-follow link
<form class="disable" data-events="submit">...</form>
Script
$(function() {
var disable = function(evt) {
evt.preventDefault();
console.log("Prevented on " + evt.target.tagName);
};
$(".disable").each(function() {
var ctx = $(this);
ctx.bind(ctx.data("events") || "click", disable);
});
});
Using smart defaults
Upper example defines one single event default. click event. This is fine and works in majority of cases, but not in all. form elements for instance would always have to define submit event that should be disabled. So. Smart defaults then. We should also consider the fact that list events that need supression is usually short. And if we cover majority of cases using defaults we only have a small overhead on those elements that actually do deviate from defaults.
$(function() {
// click is still default event
// this object defines per element events that aren't just click
var extraDefaults = {
form: "submit"
};
var disable = function(evt) {
evt.preventDefault();
console.log("Prevented on " + evt.target.tagName);
};
$(".disable").each(function() {
var ctx = $(this);
ctx.bind(
// use inline-defined events
ctx.data("events") ||
// use extra defaults if present
extraDefaults[this.tagName.toLower()] ||
// just use default click event
"click",
disable);
});
});
You can bind most jQuery events like this :
$("#elementID").on(Object.keys(jQuery.event.fixHooks).join(" "), function(e) {
e.preventDefault();
});
This will preventDefault on the following events :
click dblclick mousedown mouseup mousemove mouseover mouseout
mouseenter mouseleave keydown keypress keyup contextmenu
FIDDLE
Well after considering all the options, it still does not look convenient for all this event hustling. As it also has to bind the handlers for each event individually the script will hit the performance as well.
I am going to stick with a much simpler solution, just putting a div with transparent bg on top to cover our element.
$('form').css('position','relative').prepend($('<div class="mask" style="position:absolute;z-index:9000;height:100%;width:100%;background-image:url(1px_transparent.png);"></div>'));
Which is going to automatically fill the whole area of the element, alternatively, we can use a half-transparent picture so it will be also understood by a user that this is locked element, and would not cause confusion.
And to unlock we simply remove the .mask div from our element.
EDIT
New Fiddle: http://jsfiddle.net/YAdXk/8/
Actually we can disable tabbing by setting tabindex attribute to -1
.find('input,textarea,select').attr('tabindex','-1');
The updated fiddle prevents from tabbing as well.
EDIT2
OR, we can extend jQuery to use our custom lock() and unlock() functions on any element.
See the last fiddle: http://jsfiddle.net/YAdXk/13/
(function($) {
$.fn.lock= function() {
return this.each(function() {
$(this).css('position','relative').prepend($('<div class="mask" style="position:absolute;z-index:9000;height:100%;width:100%;background-image:url('+transparent_picture+');"></div>')).find('input,textarea,select').attr('tabindex','-1');
});
};
$.fn.unlock= function() {
return this.each(function() {
$(this).find('*').removeAttr('tabindex').filter('.mask').remove();
});
};
})( jQuery )
var all_events = "click blur focus mouse"; //etc...
$('form *').bind(all_events, function(event) {
event.preventDefault();
});
Now is easier to maintain ;)
jQuery defines all shortcut event types here, so you can use that string to store all events for re-use:
var events = "blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu";
$('button').bind(events, function() {
// hey
});
Yes, it is possible, to catch all events of one type at once! But you'll need to specify all the event types explicitly.
Your code example of "form *" is inefficient, and would not catch events on elements that are added after your code executes.
Because of the bubbling effect of javascript events, you can assign a catch all event handler on the most parent element, eigther $("form") or $("body"), and add preventDefault() to that.
Example code:
$("a").on("click", function() {
$("body").append("<p>Clicked...</p>");
});
$("body").on("click", function(e) {
e.preventDefault();
});
with:
<div>
<p>Click on me</p>
</div>
On JSfiddle: http://jsfiddle.net/erlang/EHeBK/
The concept of catching all events on a parent element, is often referred to as event delegation.