Clearing the JavaScript/jQuery Event Queue - javascript

We found, during a recent upgrade to Google Chrome (version 55+) where pointer events were introduced "with a newer standard that unifies both models: pointer events.", we need to modify how we handle events to be backwards compatible. Our application is written specifically for Chrome.
We have been experimenting this morning, trying figure out which event has been fired (based on Chrome version) so we can handle the event appropriately for all of our application's users. As a test I wrote the following:
$(document).on('mousedown touchstart pointerdown', '.foo', function(event){
console.log( event.type + ": " + event.which );
switch(event.type) {
case 'mousedown':
console.log('moused!');
break;
case 'touchstart':
console.log('touched!');
break;
case 'pointerdown':
console.log('pointed');
break;
};
})
In non-mobile devices both mousedown and pointerdown are registered. In mobile devices all three events are spit out to the console.
CLARIFICATION: What is needed is for none of the other events to fire once the first one is encountered and handled. In other words, if pointerdown fires, for example, then I need to prevent mousedown and touchstart from firing. Subsequent clicks should work as well, so if I get a pointerdown and click again I should get another pointerdown (or whatever event is appropriate for the browser version or device).
I expected break; would cause jQuery to leave the switch(), but that doesn't happen. So I added the .clearQueue() to each case:
$(this).clearQueue();
Once again, I expected that the event would be encountered and handled, the queue would be cleared and I wouldn't have any other events fired.
I was wrong.
I am beginning to feel like I am missing something obvious but I cannot put my finger on it. I have tried .stop(), .finish() as well as combinations of these functions in order to intercept the one function needed (based on browser version) and clear the queue so the rest will not fire.
I thought I understood the JavaScript event queue pretty well, but apparently I am wrong about that too.
Am I missing something obvious? If so, what it is?

I believe that .stop() and .finish() are related to working with an animation queue, not an event queue and .clearQueue() is not related to the event queue either:
The JQuery documentation says:
When used without an argument, .clearQueue() removes the remaining
functions from fx, the standard effects queue. In this way it is
similar to .stop(true). However, while the .stop() method is meant to
be used only with animations, .clearQueue() can also be used to
remove any function that has been added to a generic jQuery queue with
the .queue() method.
But, if you have handled an event and wish to stop others from being raised, you could implement your own handled flag in a closure as a work-around:
Here's an example (executable version here):
// These free variables will be shared in the various callback functions below
var handled = false;
var eventType = null;
$(document).on('mousedown touchstart pointerdown', '.foo', function(event){
// If the current event hasn't been handled or if the current
// has been handled but the event is the same as the previous fired
// event, proceed:
if(!handled || (handled && event.type === eventType)){
// None of the events have been handled yet.
// Determine which has fired and act appropriately:
// Register the current event for comparison later
eventType = event.type;
console.log( eventType + ": " + event.which );
switch(eventType) {
case 'mousedown':
console.log('moused!');
break;
case 'touchstart':
console.log('touched!');
break;
case 'pointerdown':
console.log('pointed');
break;
};
// Flag that one of the events registered earlier has been handled
handled = true;
} else {
// One of the earlier registered events has already been handled
// Prevent the event from performing its native function
// (i.e. cancel the event)
event.preventDefault();
// Don't allow the event to propagate
event.stopPropagation();
}
});

You could just return false at the end, and only the first type supported (and first fired) will be caught by any system.
The break works just fine but it does not affect this issue as these are different events and so fire the handler as many times as the events triggered.
$(document).on('mousedown touchstart pointerdown', '.foo', function(event){
console.log( event.type + ": " + event.which );
switch(event.type) {
case 'mousedown':
console.log('moused!');
break;
case 'touchstart':
console.log('touched!');
break;
case 'pointerdown':
console.log('pointed');
break;
};
// added the following line which forces any subsequent events for the same action and also stop the event from bubbling up the dom
return false;
})
.foo{
padding:50px;
margin:10px;
border:5px double #ccc;
text-align:center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="foo">.foo box for clicking</div>

Related

Given an array of EventEmitters, listen for only the first event

In my code, I have an array of EventEmitter objects. I know that at some point, one of them will fire an event called launched.
I only want to listen for the first event fired by an emitter in this array, and ignore any subsequent events.
My current code is:
emitterArray.forEach(emitter => {
emitter.once('launched', () => { console.log('Event fired!') });
});
However, this will wait for every emitter in the array to fire the event once.
I want the program to end after the exactly one of the emitters has fired this event exactly once.
Note: Terminating the process with process.exit in the event listener is not an option.
In your module, you can initialise a global (top module level) flag to false and make every event handler to check for that flag.
if (flag) {
return
}
flag = true
console.log('Event fired')
Since node.js is single-thread, you shouldn't get any race conditions there and even when all the event handlers will get triggered, you can control which of them will actually run its code.
Hope this makes sense.

preventDefault versus if check skip handling mouse and touch?

Suppose I want to use both touch, and mouse (mouse, and touch screen might be available).
There is at least one way to deal.
When the touch fires use preventDefault (first way):
let onMove = event => {
//either touch, or mouse
};
document.body.addEventListener('touchmove', event=>{
//prevent mouse if this event fires
event.preventDefault();
onMove(event);
}, false);
document.body.addEventListener('mousemove', event=>{
onMove(event);
}, false);
What if instead we do this (second way):
let didTouch = false;
let onMove = event => {
//either touch, or mouse
};
document.body.addEventListener('touchmove', event=>{
didTouch = true;
onMove(event);
}, false);
document.body.addEventListener('mousemove', event=>{
if(didTouch){
didTouch = false;
}else{
onMove(event);
}
}, false);
Is the second way viable for handling both touch, and mouse? The first way is recommended, but I'm interested in the possibility of using either way as long as there isn't unforeseen problems.
In my opinion, the second approach is not a good solution because:
Performance: You are handling 2 events (touch and mouse). The first solution prevents mouse event from firing, so your application is doing less event handling.
Complexity: didTouch is adding unnecessary complexity to your code... as a general rule of thumb, if you can achieve the same result by writing less code, the the shorter solution is preferred, unless there is a valid benefit in using the longer solution.
Another potential problem to keep in mind is Mutual Exclusion. JavaScript is Thread Safe and you don't really need to worry about a second event firing and changing your didTouch flag, while you are still processing the first event. But keep in mind that it is not impossible to run into mutex problems with async methods.
I had this problem one time, but with other events, check my solution:
let handler = function (e) {
// do stuff
};
if (isTouchDevice()) {
key.addEventListener('touchstart', handler);
} else {
key.addEventListener('mousedown', handler);
}
function isTouchDevice() {
var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
var mq = function (query) {
return window.matchMedia(query).matches;
};
if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
return true;
}
// include the 'heartz' as a way to have a non matching MQ to help terminate the join
// https://git.io/vznFH
var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');
return mq(query);
}

How does stream.Readable.prototype.on handle events in node.js source code?

I was confused when reading code of stream.Readable in Node.js.
here is source code:
https://github.com/nodejs/node/blob/master/lib/_stream_readable.js#L778-L799
Readable.prototype.on = function(ev, fn) {
const res = Stream.prototype.on.call(this, ev, fn);
if (ev === 'data') {
// Start flowing on next tick if stream isn't explicitly paused
if (this._readableState.flowing !== false)
this.resume();
} else if (ev === 'readable') {
const state = this._readableState;
if (!state.endEmitted && !state.readableListening) {
state.readableListening = state.needReadable = true;
state.emittedReadable = false;
if (!state.reading) {
process.nextTick(nReadingNextTick, this);
} else if (state.length) {
emitReadable(this);
}
}
}
return res;
};
Obviously, the if statements only handle data and Readable event, but according to the API document, the on method of stream.readable also accept other events such as close , end , error.
So my question is :
According to the source code, how did stream.Readable handle other events except data and readable?
What you are seeing here is an override for the .on() method so that the Readable class can watch what event listeners are being attached and can do something special when someone installs a listener for the data event or the readable event.
The first line of this function:
const res = Stream.prototype.on.call(this, ev, fn);
is where the Readable passes the callback and event name arguments to its parent so that the normal implementation will be run. A Stream implements the EventEmitter interface so calling the super method with Stream.prototype.on.call(this, ev, fn) will give .on() it's expected default behavior.
Then after calling the parent, it checks to see if the event that someone is listening to is the data event or readable event and then implements a little extra functionality when one of those event listeners is attached.
For the data event, it resumes the stream so that it will start flowing if it was paused and if it was set to flowing mode. This is probably because when a Readable is being initially created and configured, if it starts flowing the stream before the data event listener is attached, then data on the stream could be missed. So, it doesn't start flowing until someone is around to listen to data events.
Note, there are potentially lots of over events that occur on the stream and those are all handled by the call to the base class in the first line. What you are seeing here is just some special behavior that the Readable class wants to implement when two specific event listeners are first added. This code does not affect when those events are sent or how they are listened to. It just triggers a little behavior in the Readable state when a listener for one of these events is first attached.

How to keep the proper `this` context in JavaScript when using recursion, inheritance, and encapsulation

I am using ES6 in FF (native, I'm not using a translator). I have the following class setup:
Note that I'm only showing relations in that diagram, not proper types and what-not.
The Canvas class represents an HTML5 canvas, and the Ellipse class represents an Ellipse drawn on the canvas. The goal I have here is that I can move my mouse on the canvas, and get events on the shapes I draw, such as the ellipse. Since the question is not in regards to the event translation, please assume that that works just as well as you would expect with HTML elements. However, I will note that the _propagate call is what handles this, and it works with recursion (explained later).
The full definition of subscribe looks just like addEventListener: (eventName, callback, [isCapture]).
Inside of canvas, I have the following code:
addObject(object){
this.__children.unshift(object);
console.log("Susbscribing to " + object.toString());
object.subscribe(MouseEventType.MouseDown, this.objectMouseDown);
}
// ...
canvas.addObject(new Ellipse());
So, I add an ellipse to Canvas, and when I do that, I subscribe to the ellipse's MouseDown event. Note that at this point, the console.log will output "Subscribing to Ellipse"
Now, for the subscribe code is this:
subscribe(eventName, func, useCapture = false){
// If the event exists, add the method, otherwise, throw an exception
if(this._subscribers[eventName]){
this._subscribers[eventName][useCapture].push(func);
console.log(this.toString() + " has had somebody subscribe to " + eventName);
}
else{
throw eventName + " is not a valid event";
}
}
And again, at this point, the console.log correctly identifies the object as Ellipse.
Finally, we get to the __dispatchEvent code, which is the following:
__dispatchEvent(eventName, eventData, isCapture = false){
if(this._subscribers[eventName] && this._subscribers[eventName][isCapture]){
for(var func of this._subscribers[eventName][isCapture]){
// Set the sender to `this`
console.log("Sending " + eventName + " event from " + this.toString());
eventData.sender = this;
// setTimeout = run in new thread
// Really weird syntax so things are kept in scope correctly
// -- func is passed into an anonymous method, which then calls that function with the event data
((f) => setTimeout(() => f(eventData), 0))(func);
}
}
}
At this point, I want to click on the Ellipse, and I should get the following flow:
Mouse Click Detected on HTML5 Canvas Element, and it sent to Canvas class
Canvas class dispatches MouseDown capture event to itself
Canvas calls _propagate on the Ellipse
Ellipse dispatches both the capture and bubble event to itself << Problem lies here
Canvas dispatches the bubble event to itself
Simple enough, and pretty much works just like the mouse events in HTML.
So, the problem is that in my callback, I have this code:
objectMouseDown(e){
console.log(e.sender.toString());
}
I'm expecting that I should be logged "Ellipse" again, however, I'm getting "Canvas" instead.
A couple things to note:
- The dispatcher says that it is sending the Ellipse mouse-down event
- The call to the callback doesn't seem to happen until after the Canvas get's a callback for its MouseDown (while bubbling)
- If I remove the subscription the the Canvas MouseDown, then the Ellipse MouseDown does get called correctly.
Here is a log dump with the standard setup:
> Sending MouseDown event from Ellipse << From the dispatch
> Sending MouseDown event from Canvas << From the dispatch
> Canvas << From the event handler
And, if I remove the MouseDown on Canvas
> Sending MouseDown event from Ellipse << From the dispatch
> Ellipse << From the event handler
Please let me know if I didn't explain something well enough. I'm guessing that this has something to do with JavaScript's this being really weird. However, I've been unable to figure out how to store it properly. I even tried setting a member variable to be this when the subscription happens, since things are correct at that level, but that didn't work. And at this point, I'm at a loss.
The issue doesn't seem to have anything to do with this specifically, but with the fact that you are deferring the execution of the event handler and that you are sharing eventData between multiple __dispatchEvent calls.
Since you defer the execution of the handler until the next tick, every handler gets the same eventData.sender value.
That's exactly what you see in the output:
> Sending MouseDown event from Ellipse << From the dispatch
> Sending MouseDown event from Canvas << From the dispatch
> Canvas << From the event handler
The second call will set eventData.sender to the canvas object, hence when f(eventData) is called afterwards in the next tick, eventData.sender will refer to the canvas object.
In a comment you are saying
setTimeout = run in new thread
but that's not correct. setTimeout adds the function to a queue. The queue is processed whenever the thread is idle, i.e. in your case after all __dispatch methods have been called. You can learn more about the processing model on MDN.
Without knowing more about your code, a simple solution would be to defer setting the sender until the handler is called:
__dispatchEvent(eventName, eventData, isCapture = false){
if(this._subscribers[eventName] && this._subscribers[eventName][isCapture]){
// Use `let` for block scope
for(let func of this._subscribers[eventName][isCapture]){
setTimeout(() => {
// Set the sender to `this`
console.log("Sending " + eventName + " event from " + this.toString());
eventData.sender = this;
func(eventData);
}, 0);
}
}
}
The alternative would be to pass a new instance of eventData to every handler.

Handling JavaScript events not handled by other elements

I am writing a script where I would like to handle mouse events only if they have not been handled by any other element before.
I can attach an event listener to the document object, but it will receive all events regardless of whether they have been already handled.
I have no control over the elements in the HTML page so I cannot manually stopPropagation() when the event is handled.
Any ideas?
From this article here.
It seems its not yet possible to do this.
Which event handlers are registered?
One problem of the current implementation of W3C’s event registration
model is that you can’t find out if any event handlers are already
registered to an element. In the traditional model you could do:
alert(element.onclick)
and you see the function that’s registered to
it, or undefined if nothing is registered. Only in its very recent DOM
Level 3 Events W3C adds an
eventListenerList
to store a list of event
handlers that are currently registered on an element. This
functionality is not yet supported by any browser, it’s too new.
However, the problem has been addressed.
Fortunately
removeEventListener()
doesn’t give any errors if the event
listener you want to remove has not been added to the element, so when
in doubt you can always use removeEventListener().
So, you can accomplish something like you want under a certain set of circumstances. Specifically, if
You can load your own custom JavaScript before the original
You know which elements you are listening for others to throw events on
Effectively, what you do is replace the original addEventListener method on the target element with a custom one that intercepts the call, does some special processing, and then lets it continue per normal. This 'special processing' is a new function that wraps the original callback, and marks the event arguments with some state to let you know someone else handeled the event already. Here is a proof of concept (with a jsFiddle)
Target HTML:
<div id='asdf'>asdf</div>​
JavaScript:
var target = document.getElementById('asdf');
var original = target.addEventListener;
var updated = function(){
// Grab the original callback, so we can use it in our wrapper
var originalFunc = arguments[1];
// Create new function for the callback, that lets us set a state letting us know it has been handled by someone
// Finish the callback by executing the original callback
var newFunc = function(e){
console.log('haha, intercepted you');
e.intercepted = true;
originalFunc.call(this, e);
};
// Set the new function in place in the original arguments 'array'
arguments[1] = newFunc;
// Perform the standard addEventListener logic with our new wrapper function
original.apply(this, arguments);
};
// Set the addEventListener on our target to our modified version
target.addEventListener = updated;
// Standard event handling
target.addEventListener('click', function(e){
console.log('original click');
console.log('intercepted?', e.intercepted);
})

Categories