Handling JavaScript events not handled by other elements - javascript

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);
})

Related

JS Foreach Alternative (timeout) for Queue

I'm working on the "Approve All" button. The process here is when I click "Approve All," each individual "Approve" button will be triggered as "click" all at once, and then it will send POST requests to the controller. However, when I clicked Approve All button, there was a race condition causing the controller returns Error 500: Internal server error. I have tried using JS setTimeout() with value 1500*iter, but when the iterator gets higher, for example at i = 100, then it would take 1500*100 => 150000ms (150s). I hope that explains the problem clearly. Is there a way to prevent such a case?
Here is my code, I'm using JQuery:
let inspection = $this.parents("li").find("ul button.approve"); // this will get all 'approve' button to be clicked at once
inspection.each((i,e)=>{
(function () {
setTimeout(function () {
$(e).data("note",r);
$(e).click();
}, 1500 * i); // this acts like a queue, but when i > 100, it takes even longer to send POST requests.
})(this,i,e,r);
});
// then, each iteration will send a POST request to the controller.
$("#data-inspection ul button.approve").on("click", function() {
// send POST requests
});
Any help would be much appreciated. Thank you.
That 500 error may also be the server crashing from being unable to process all the requests simultaneously.
What I'd recommend is using an event-driven approach instead of setTimeout. Your 1500ms is basically a guess - you don't know whether clicks will happen too quickly, or if you'll leave users waiting unnecessarily.
I'll demonstrate without jQuery how to do it, and leave the jQuery implementation up to you:
// use a .js- class to target buttons your buttons directly,
// simplifying your selectors, and making them DOM agnostic
const buttonEls = document.querySelectorAll('.js-my-button');
const buttonsContainer = document.querySelector('.js-buttons-container');
const startRequestsEvent = new CustomEvent('customrequestsuccess');
// convert the DOMCollection to an array when passing it in
const handleRequestSuccess = dispatchNextClickFactory([...buttonEls]);
buttonsContainer.addEventListener('click', handleButtonClick);
buttonsContainer.addEventListener(
'customrequestsuccess',
handleRequestSuccess
);
// start the requests by dispatching the event buttonsContainer
// is listening for
buttonsContainer.dispatchEvent(startRequestsEvent);
// This function is a closure:
// - it accepts an argument
// - it returns a new function (the actual event listener)
// - the returned function has access to the variables defined
// in its outer scope
// Note that we don't care what elements are passed in - all we
// know is that we have a list of elements
function dispatchNextClickFactory(elements) {
let pendingElements = [...elements];
function dispatchNextClick() {
// get the first element that hasn't been clicked
const element = pendingElements.find(Boolean);
if (element) {
const clickEvent = new MouseEvent('click', {bubbles: true});
// dispatch a click on the element
element.dispatchEvent(clickEvent);
// remove the element from the pending elements
pendingElements = pendingElements.filter((_, i) => i > 0);
}
}
return dispatchNextClick;
}
// use event delegation to mitigate adding n number of listeners to
// n number of buttons - attach to a common parent
function handleButtonClick(event => {
const {target} = event
if (target.classList.contains('js-my-button')) {
fetch(myUrl)
.then(() => {
// dispatch event to DOM indicating request is complete when the
// request succeeds
const completeEvent = new CustomEvent('customrequestsuccess');
target.dispatchEvent(completeEvent);
})
}
})
There are a number of improvements that can be made here, but the main ideas here are that:
one should avoid magic numbers - we don't know how slowly or quickly requests are going to be processed
requests are asynchronous - we can determine explicitly when they succeed or fail
DOM events are powerful
when a DOM event is handled, we do something with the event
when some event happens that we want other things to know about, we can dispatch custom events. We can attach as many handlers to as many elements as we want for each event we dispatch - it's just an event, and any element may do anything with that event. e.g. we could make every element in the DOM flash if we wanted to by attaching a listener to every element for a specific event
Note: this code is untested

Is there a global browser object I can add a listener to catch all thrown errors?

I'm using Selenium to crawl an AngularJS site and would like to create a list of all thrown errors (the specific errors I'm actually interested in are these lex errors). AngularJS exposes $exceptionHandler to overwrite exception handling behavior. But would rather stay away from modifying app code as much as possible as these are remote tests.
I'm using Firefox so browser.manage().logs() fails. I suspect this question is closely related to Log console errors using protractor - this SO answer.
I have tried window.onerror and window.addEventListener('error', () => { ... }); but neither of them catch the thrown error. I wonder if there is something that AngularJS is doing that blocks propagation to the window maybe?
My goal:
window.thrownErrors = [];
[Some global object].throw = (error) => {
console.error(error);
window.thrownErrors.push(error);
}
throw new Error('throw test');
console.log(window.thrownErrors); // => ['throw test']
Use the useCapture option when adding the event handler:
window.addEventListener('error', () => { ... }, true);
The third argument can be a Boolean indicating whether events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. Events that are bubbling upward through the tree will not trigger a listener designated to use capture. Event bubbling and capturing are two ways of propagating events that occur in an element that is nested within another element, when both elements have registered a handle for that event. The event propagation mode determines the order in which elements receive the event. See DOM Level 3 Events and JavaScript Event order for a detailed explanation. If not specified, useCapture defaults to false.
For more information, see
MDN Web API Reference - addEventListener
Unable to understand useCapture parameter in addEventListener
What is event bubbling and capturing?

I do not understand Using addEventListener javascript

Hello am really stuck on getting more information on addEventListener can someone please point me to the right direction, I will like to get some more information on what type of argument can be passed into a function parameter is their a website or a link that i can view for all available javascript function and tell me what a parameter takes. in the below eg eventOne.addEventListener() is called however this code taken else where and am unsure why the argument are passed into the parameter and why a function with no name given
var eventOne = document.querySelector("h1")
eventOne.addEventListener("mouseover", function () {
eventOne.textContent = 'mouse over'
})
Anytime you interact with a browser window an event fires. The addEventListener method listens for any event you tell it to. The idea behind this method is basically you telling your browser: Hey, when this thing happens to this element, please execute this code.
Here is a link to all the events you can listen for.
The addEventListener method takes two arguments:
The first argument is the event you want to listen for. The code example listening for a mouseover event. According to the events reference this event fires off when a pointing device is moved onto the element that has the listener attached or onto one of its children.
The second argument is a callback function. This is the function that will execute when that event fires off on that DOM element.
Here is a more common example:
const heading = document.querySelector('h1');
heading.addEventListener('click', function () {
console.log("I have clicked the h1 tag")
})
You can use this tactic to implement logic into your code when certain events happen. It is very powerful.
Let me briefly try to explain ... before I ask you to simply "google it" and start reading.
"When things happen," such as when the mouse-pointer moves over something, an "event" is sent to the thing that it happened to ... and it "bubbles up" from there. Of course you cannot predict when these events will happen, but you can "listen" for them.
When an event is "listened to," what happens is that the specified function() will be called at that time, with various optional parameters. (Furthermore, there's some additional "JavaScript voodoo magic" that can happen -- Google the term, "closure.")
So, when the mouse passes over this object, the specified function will be called at that time, and it will change the textContent as shown.
Now ... "off to Google-land!" There are literally thousands of articles on the Internet which explain this much better than I did. (May I recommend: "w3schools.com." There, you can actually "try things out!")

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.

What the difference between `onmessage` and `.addEventListener`?

I'm trying to get data with server-sent event, what the different using
source.onmessage vs source.addEventListener?
source.onmessage is the built in function wrapper for EventSource that is triggered when new data is sent to the client. It fires when no event attribute is returned (default) and doesn't fire when it is set.
addEventListener is similar, but differs in that it listens for a specific event name, and triggers on its presence, allowing you to separate your functionality for multiple events. You can then parse the JSON data returned. It can be used on any event type. Have a look at this example:
source.addEventListener("login", function(e) {
// do your login specific logic
var returnedData = JSON.parse(e);
console.log(returnedData);
}, false);
This snippet will listen for a server message with event specified as login, then it triggers the callback function.
More info:
https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events
http://html5doctor.com/server-sent-events/
I assume you're talking about addEventListener('message') vs onmessage. They do the same thing, but I'd recommend using onmessage because with addEventListener, there's always a possibility of unexpectedly adding the same listener twice, e.g. due to a laggy page reload, or some hot-reload during development. In those cases the handler function could fire twice on every event, which leads to weird behaviors.

Categories