Javascript Event Queueing: XHR response in blur queued before parent onclick - javascript

For the following test case (I replaced the XHR call with setTimeout to represent an XHR call with a very fast response):
<html>
<body>
<div onclick="console.log('onclick parent')">
Event Queue Test:
<input type=text onblur="console.log('onblur'); setTimeout(function() {console.log('onblur timeout')})"/>
</div>
</body>
</html>
If I click in the text field and then click on the text preceding the text field, I get the following output in Chrome (v70):
onblur
onblur timeout
onclick parent
I would have expected the parent onclick handler to have been queued before the setTimeout event. In Chrome, at least, it does not. Is this behavior specified by the standard or implementation-dependent? I'm also interested in workarounds to always have the parent onclick handler run before the timeout (or XHR) handler.
In my actual use case, this issue is problematic because the events are not guaranteed to be queued in the same order, depending on how long the XHR request takes to run.

This won't necessarily help with your actual XHR problem, but I think I understand what's going on. I extended your sample code, using jQuery for convenience:
<div class=parent>Label: <input class=field></div>
and the JavaScript:
var $d = $(document);
var t = {
mousedown: -1,
mouseup: -1,
focus: -1,
click: -1,
blur: -1,
timer: -1
};
["mousedown", "mouseup", "focus", "click"].forEach(function(event) {
$d.on(event, ".parent", function() {
t[event] = Date.now();
})
});
$d.on("blur", ".field", function() {
t.blur = Date.now();
setTimeout(function() {
t.timer = Date.now();
}, 150);
});
$d.on("click", ".parent", function() {
setTimeout(function() {
var race = Object.keys(t).sort(function(k1, k2) { return t[k1] - t[k2];});
console.log("Result: ", race);
race.forEach(function(key) { t[key] = -1; });
}, 1000);
});
What that code does is order the events by the times they actually happen. In the situation of clicking on the text after clicking and focusing in the input field, the order with a short timeout (less than 100ms or so) is
focus
mousedown
blur
timer
mouseup
click
The "blur" and "timer" events are for the input field and its setTimeout() callback. If I make that timeout interval longer, like 150ms or so, then I get
focus
mousedown
blur
mouseup
click
timer
That, I think, makes some sense: the "mousedown" and "mouseup" events are driven by your actual physical interaction with the mouse button. The "click" event does not make sense until "mouseup" is received. With a short timer in the "blur" handler, the timer can fire before the mouse has physically communicated the "up" event through your OS etc.

Related

Why does jquery.blockUI swallow onClick event?

Summary Using jquery.blockUI seems to hide / swallow / mask button click event.
Tech Stach
backbone and marionette
backbone.radio
underscore
jquery
jquery.blockUI
(all latest version)
The App
The app consists of a text input and a button.
In terms of backbone/marionette terminology, there is
a top view which has 2 regions
container view which as the text input
footer view which has the button
The container view is backed by a model.
The footer has a button, Clicking the button sends a backbone.radio event.
This event is picked up in the top view.
When the user leaves the text input, an API (server / backend) is called. In the example a Promise / setTimeout is used to simulate the call.
In the example the button calls console.log.
Code
Here is the JSFiddle Example on JSFiddle and below the Javascript code
// ------------------------------------------------------------------
var Model = Backbone.Model.extend({
defaults: {
"SearchCriteria": {
"Min": { "value": "abc123", "ReadOnly": true }
}
},
async callBackend() {
//$.blockUI(); //<----- uncomment this and the button click is swallowed
await new Promise(resolve => setTimeout(resolve, 3000));
$.unblockUI();
}
});
// ------------------------------------------------------------------
// ------------------------------------------------------------------
var ContainerView = Marionette.View.extend({
template: _.template('<div><label>Container</label></div><div><input id = "min" name = "min" type = "text"/></div>'),
events: {
'change': 'onChangeData',
},
async onChangeData(data) {
console.log('start onChangeData');
await this.model.callBackend();
this.render();
console.log('end onChangeData');
}
});
// ------------------------------------------------------------------
// ------------------------------------------------------------------
var FooterView = Marionette.View.extend({
template: _.template('<div><button class="btn-footer-test">Footer</button></div>'),
events: {
"click .btn-footer-test": () => {
console.log('click test ...');
Backbone.Radio.channel("maske").trigger("select:test");
}
},
});
// ------------------------------------------------------------------
// ------------------------------------------------------------------
var TopView = Marionette.View.extend({
template: _.template("<div id='container'></div><div id='footer'></div>"),
regions: {
container: '#container',
footer: '#footer'
},
events: {
'change': 'onChangeData',
},
initialize() {
this.listenTo(Backbone.Radio.channel("maske"), "select:test", this.onTest, this);
},
onRender() {
this.showChildView('container', new ContainerView({
model: new Model()
}));
this.showChildView('footer', new FooterView());
},
onChangeData(data) {
},
onTest() {
//NOT called if jquery.blockUI present ******
console.log('onTest');
}
});
// ------------------------------------------------------------------
$(document).ready(function () {
console.log('Start');
const topView = new TopView();
topView.render();
$('body').append(topView.$el);
});
Use
The user uses the app like so. The user
changes the text input
and directly clicks the button (without tabbing out of the field first!)
Expected Behavior
the change to the text input triggers a change event.
jquery.blockUI
async call
jquery unblockUI
the click event to the button is executed
Actual Behavior
When the jquery.blockUI function is present the click event to the button is not executed. Commenting jquery.blockUI the button click event occurs, however before the await returns.
Questions
What am I doing wrong?
Why is the click event swallowed?
What am I doing wrong?
Your expectations are wrong. There is no implicit mechanism in JavaScript that serializes asynchronous events one after the other has completed. You (developer) are responsible for synchronization of asynchronous events.
Why is the click event swallowed?
Click event fires when a mousedown and mouseup event occur on the same element. And this is not your case. The order of events is as follows:
mousedown on <button>
change on <input>; causes displaying overlay <div> via blockUI
mouseup on overlay <div>
click on closest common parent element of elements that triggered mousedown and mouseup, which is <body>
Technically it seems to be impossible to click the button after the input changed, because overlay is displayed before mouseup, however there is one way. If you click and hold the mouse button while the overlay is being displayed and release afterwards, the click event will be triggered on the button, but this isn't something that you want anyway.
Try to play with this snippet. It logs every mousedown, mouseup, click and change event. It registers asynchronous change event handler on <input> thet does nothing for the first second, then displays an overlay, then sleeps for 3 seconds and finally hides the overlay. You can observe various behaviours based on how long you kept the button mouse depressed.
Change input text and quickly click the button
button.mousedown
input.change
button.mouseup
button.click
Change input text, click the button and hold for 1 sec, then release
button.mousedown
input.change
div.mouseup
body.click
Change input text, click the button and hold for 4 sec (until overlay disappears), then release
button.mousedown
input.change
button.mouseup
button.click
$(document).on('mousedown mouseup click change', e => {
console.log(e.target.tagName.toLowerCase() + '.' + e.type);
});
$('input').change(async _ => {
await new Promise(resolve => setTimeout(resolve, 1000));
const $div = $('<div style="position:fixed; left:0; top:0; right:0; bottom:0; background-color:rgba(0,0,0,0.5); z-index:999">').appendTo('body');
await new Promise(resolve => setTimeout(resolve, 3000));
$div.remove();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" placeholder="type something, then click the button" style="width:250px">
<button>Button</button>
You will have pretty hard time getting around this, because in normal situations the click event won't be fired on the button and therefore I'd suggest you to rethink your user interface. Why do you even block user interface when you use async/promises? It was invented to avoid blocking.

How to call a function in jquery ui spinner stop event

I am using jquery ui spinner, I want to call a function on spinner stop event.
I do not want to call that function every stop event, only when the user stop clicking up or down arrow.
Change event works only the textbox loss the focus, I want when text box is on focus.
Thanks in Advance..
If you want your callback to be triggered after a sequence of clicks is over, you can use _.debounce for that. Here is the code and the plunker:
$(function(){
var expectedDelayBetweenClicks = 500;
var element = $("input");
element.spinner({
start: function(e, ui) {
$('.selected-value').text('Waiting...');
},
stop: _.debounce(function(e, ui) {
$('.selected-value').text(e.target.value);
}, expectedDelayBetweenClicks)
})
});
This guarantees that you callback will be triggered only after several clicks, if the delay between those clicks is 500 ms. You can decrease this delay or increase it based on how fast you expect users to click the spinner.

Clearinterval on mouse click or hover

I have a fullscreen image slider which doesn’t have an autoplay functionality, so I had to write custom script to click on the next button.
Here it is
var interval = setInterval(function() {
document.querySelector('.fp-controlArrow.fp-next').click();
}, 7000);
setTimeout(function( ) { clearInterval( interval ); }, 44000);
But now I’d love to clearInterval whenever user clicks on the button of the same class (.fp-controlArrow.fp-next) . Can the JS distinguish the difference between simulated click and real mouse click somehow? If so, what would be the code for that?
And if not, maybe it is possible to clear interval on hovering the button with the .fp-controlArrow.fp-next class ?
Thanks!
Yes, you can distinguish between a user generated event vs a code generated event by using isTrusted property of the event object in the event listener.
var elem = document.querySelector('.fp-controlArrow.fp-next');
elem.addEventListener("click", function( event ) {
if(event.isTrusted)
clearInterval(interval);
}, false);
https://developer.mozilla.org/en/docs/Web/API/Event/isTrusted
You can use mousedown event for Real click.
var el = document.querySelector('.fp-controlArrow.fp-next')
el.addEventListener('mousedown', function(){
clearInterval( interval );
});
Working example: https://jsfiddle.net/fov47eny/
Also you can use isTrusted but it has limited support on browsers.
if (e.isTrusted) {
/* The event is trusted. */
} else {
/* The event is not trusted. */
}

When I dblclick, it runs .click twice

I have 2 functions when you click on an item, .click and .dblclick
The problem is, that when you dblclick, it runs the .click function twice.
Is there a way to fix this?
$(".item").click(function() {
if (price[this.id] === 1) {
consoleUpdate("This item is worth " +price[this.id]+ " coin");
}
}
$("#fishItem").unbind("dblclick").bind("dblclick").dblclick(function() {
coins = coins+1;
coinUpdate();
invFish1--;
if (invFish1 === 0) {
$("#fishItem").appendTo("#itemStage");
}
});
Per the jQuery documentation for the dblclick event:
It is inadvisable to bind handlers to both the click and dblclick
events for the same element. The sequence of events triggered varies
from browser to browser, with some receiving two click events before
the dblclick and others only one. Double-click sensitivity (maximum
time between clicks that is detected as a double click) can vary by
operating system and browser, and is often user-configurable.
You want to change your events so that you don't have a .click and .dblclick on the same element.
http://api.jquery.com/dblclick/
var cnt=1;
$( "#target" ).click(function(e) {
if(cnt==1)
{
// action on single click if you dont want anything in in single click then use here
e.preventDefault();
}else{
// work on double click
cnt=1;// again set counter
}
cnt++;
});
reference e.preventDefault();
As others mentioned, you can't really bind both click and double click to the same item. There are, however, workarounds.
One way to handle this would be to handle the click code and then check for double-click:
var lastClickTime = 0;
$(".item").click(function() {
var thisClickTime = (new Date).getTime();
if (thisClickTime - lastClickTime < 500) {
//Double-click
}
else {
//Click
}
lastClickTime = thisClickTime;
});
This code essentially captures a "last click time" and if this click time is less than 500 milliseconds from the last click time, then it fires the double-click code. Otherwise it will fire the click code.
The downside with this is that the click-code will be called first and then the double-click code. Also, you're forcing your users into a 500ms double-click. While this is pretty standard, it's not guaranteed. (Slower clickers may have trouble with this.)
A JSFiddle that demonstrates this technique.
You could improve this code by setting a 500ms timeout for the click code and then cancelling the click event if a double-click is fired. The downside with this is that it will force a 500 ms delay between the click and the "click" code.
A JSFiddle that demonstrates the timeout.
If you don't mind handling one single-click, look at the event object details attribute. It is the number of clicks.
$(".item").click(function(event) {
if (event.details === 1) {
//handle single click
} else if (event.details === 2) {
// handle double click
}
}

Display DIV only if user has been Idle for X amount of time

I would like to display a helpful DIV that basically shows the user how to accomplish something on a particular page, but only if the user has been idle for a period of time, say, 30seconds.
What I mean by "Idle" is:
Not clicking any links
Not right clicking anywhere
Exceptions:
I would like to exclude the following conditions from the Is User Idle rule:
User has scrolled up or down/left or right
User has pressed mouse button on an empty area on the site/ or on an element which has no source/link for example, an image with no hyperlink.
and, Pressing keyboard buttons
Can this be done? Or can we only detect when a particullar event occurs?
Any thoughts/suggestions/resources will be greatly appreciated.
Thank you
fairly basic...
var trigger = 30000
$.(function(){
setInterval('displayInf()',trigger );
$('body').bind('click dblclick keypress mousemove scroll', function(){
clearDisplayInf();
});
});
function displayInf()
{
$('body').append('<div>Your notification div</div>');
}
function clearDisplayInf()
{
trigger = clearInterval(trigger);
trigger = setInterval('displayInf()', 30000 );
}
that should do the trick - you could add some script to make the div removable and start the timer again once its removed but that just polishing up really..
Event in DOM would bubble from leaf to root, thus add a event listener on document would make sense.
But since we are possibiliy stop bubbling for click event in certain element, register click event on document may not work perfectly, in that case, register mousedown and mouseup event would help:
var timer; // create a timer at first
// restart timer on click
function startIdle() {
timer = setTimeout(function() { /* show div */ }, time);
}
if (document.addEventListener) {
document.addEventListener('mouseup', startIdle, false);
}
else {
document.attachEvent('onmouseup', startIdle);
}
// start the first timer
startIdle();

Categories

Resources