I have defined this event handler:
document.addEventListener("load", function(){
alert("Called on page load");
}, false);
I noticed it does not get called when the boolean flag is set to false(fire at bubble phase). Could someone help me out on why this is the case.
When an event is being sent to an element, it descends the document tree in the capture phase until it reaches the target. Then, if it’s a bubbling event, it bubbles back up.
From 2.1 Introduction to “DOM Events” in the DOM standard:
When an event is dispatched to an object that participates in a tree (e.g. an element), it can reach event listeners on that object’s ancestors too. First all object’s ancestor event listeners whose capture variable is set to true are invoked, in tree order. Second, object’s own event listeners are invoked. And finally, and only if event’s bubbles attribute value is true, object’s ancestor event listeners are invoked again, but now in reverse tree order.
load isn’t a bubbling event, and – here’s the important part – it doesn’t target document. When you add a capture listener, you’re really getting load events from parts of the document’s content that normally receive the event, like scripts or images. On a page with only the script, you won’t see the listener get called at all:
<iframe srcdoc="<script>document.addEventListener('load', () => { alert('loaded'); }, true);</script>"></iframe>
And on a page with load events that fire after the listener is attached, like this Stack Snippet that includes <style>s, you’ll see it more than once:
let i = 0;
document.addEventListener('load', e => {
console.log(`loaded ${++i}: ${e.target.nodeName}`);
}, true);
You probably meant to add a non-capturing listener to window instead of document, because window is something that receives a load event, unlike document. (Or you might have meant something else. There’s a lot of ways to interpret “page load”. See Window: load event on MDN for details on what the load event means on window and alternatives if it wasn’t what you intended.)
window.addEventListener("load", function() {
alert("Called on page load");
}, false);
Related
For example we have a page with a link that has onclick event listener on it. But handler makes stopPropagation. How I can handle that click event was made, if it's not bubble to root anymore?
e.g.
document.addEventListener('click', function(e) {console.log(e);});
a.onclick = function(e) {e.stopPropagation();};
DOM event propagation works in three phases: the capture phase, the target phase and the bubble phase. Roughly, the event first works its way down to the target, reaches the target and then works its way back up.
By default, event handlers are attached in the final bubble phase. If you attach a click event listener in the first capture phase, it will run before the previous handler has had the chance to call stopPropagation.
See this question for a deeper explanation.
The simple answer is, add a third argument, true, when adding your event listener.
document.addEventListener('click', someFunction, true)
This flag (called useCapture) will call someFunction on all clicks in a document, even when the user clicked inside an element with a click handler that called event.stopPropagation.
With options
If you're already passing an object of options as the third argument, simply include capture: true in them:
document.addEventListener('click', someFunction, { capture: true, ...someMoreOptions })
Why?
Enabling the handler's useCapture mode like this means the listener listens during the earlier "capture" phase of the event (which starts at the outmost element then trickles down through children), instead of the later "bubble" phase (which starts at the innermost element and bubbles back up through ancestors, and is the one stopPropagation blocks).
Side effects
That also means that applying this setting changes the timing: your capture phase click event will occur before any click events of either type inside child or descendant elements.
For example, in the above function, if a user clicks on a button on the page, the someFunction attached to the document's capture phase will be called before any handlers attached to the button; whereas without setting use capture to true, you'd expect it to be called after.
I want to add a load event on an image that would affect that image even when that image is added to the document after the page's initial load. For a click event I would do someting like this:
$(document).on('click', '.elem', function(e) {
// do stuff
});
When I try something similar with the load event, however, it does not to work. This is what I have tried:
$(document).on('load', '.image', function() {
// do stuff
});
This event is simply never triggered. Does anyone know what I may be doing wrong, or how to achieve this?
This answer is incorrect. It's possible to do this using the capture phase, see Dhia Louhichi's answer. I'll delete this answer when I can (i.e.., once it's no longer the accepted answer).
By their nature, delegated handlers only work for events that bubble. Not all do, and load is one of the ones that doesn't. The jQuery documentation even highlights this:
In all browsers, the load, scroll, and error events (e.g., on an element) do not bubble.
You'll have to add the load handlers to the images when you add them.
What I mean by "delegated handlers only work for events that bubble":
Events that bubble work like this (in the "bubbling" phase, which is the phase you normally work with): The event is fired on the element where it originates, and then on that element's parent, then that element's parent, etc. until it gets to the document element (html). This diagram from the DOM3 events spec may help make this clearer:
Using a delegated handler (the kind you're using in your question) relies on bubbling. Consider this HTML:
<div id="container">
<div class="content">xxxx</div>
<div class="content">xxxx</div>
<div class="content">xxxx</div>
</div>
If you do $("#container").on("click", ".content", ...) you're not hooking the event on the "content" divs, even though jQuery will make it seem a bit like you are. You're hooking the event on the "container" div. When the event bubbles down to the container div, jQuery looks at where it started and sees whether it passed through any "content" divs during its bubbling. If it did, jQuery calls your handler as though (mostly) you'd hooked the event on the "content" div. That's why delegated handlers work when you add elements later; the event isn't hooked on the element, but on the container.
So that's why it won't work for load: load doesn't bubble, so even though it fires on the img elements you add, it doesn't bubble to the parent and so on, and so you never see it. To see it, you have to hook it on the specific element, not an ancestor of it.
This code shows handling the load event for img elements created in the future, without explicitly adding a listener/handler to them, by using the capture phase of the event process on document.body (also works when attached to document, but not window because of backward compatibility issues):
document.body.addEventListener(
"load",
function (event) {
var elm = event.target;
if (elm.nodeName.toLowerCase() === 'img') {
console.log("Loaded: " + event.target.src);
}
},
true // Capture phase
);
Live Example:
document.body.addEventListener(
"load",
function (event) {
var elm = event.target;
if (elm.nodeName.toLowerCase() === 'img') {
console.log("Loaded: " + event.target.src);
}
},
true // Capture phase
);
// Brief wait, then add an image
setTimeout(function() {
document.body.insertAdjacentHTML(
"beforeend",
"<img src='https://via.placeholder.com/150/202080?text=Some+Image'>"
);
}, 400);
This is tested and works in at least the following:
IE9+
Chrome and other Chromium-based browsers (Opera, Edge, Vivaldi, ...)
Firefox
iOS Safari
The behavior is also documented. In fact, coincidentally the specification gives this example mentioning load by name (scroll down slightly from that link):
EXAMPLE 5
The following is one way to interpret the above tables: the load event will trigger event listeners attached on Element nodes for that event and on the capture and target phases. This event is not cancelable. If an event listener for the load event is attached to a node other than Window, Document, or Element nodes, or if it is attached to the bubbling phase only, this event listener would not be triggered.
That's saying load will be fired in the capture and target phases, but not the bubbling phase (since the event doesn't bubble).
By default, when you use addEventListener, the handler is attached for the target phase of the element you call addEventListener on and the bubbling phase for any element within that element. If you add the third argument with the value true, though, it attaches the handler for the target phase of the element you call addEventListener on (as before) and the capture phase for any element within that element. So the code above will handle load for document.body during the target phase (except document.body doesn't fire load) and also handle load for the capture phase of any element within document.body.
More about event flows in the specification, including this handy diagram:
There are two scripts in a document:
// my_script.js goes first
document.onclick = function() {
alert("document clicked");
};
// other_script.js comes after
// this overrides the onclick of my script,
// and alert will NOT be fired
document.onclick = function() {
return false;
};
To make sure my click event does not get overridden by other script, I switched to addEventListener.
// my_script.js goes first
document.addEventListener("click", function() {
alert("document clicked");
}, false);
// other_script.js comes after
document.addEventListener("click", function() {
return false;
}, false);
Now I got another question. Since return false in the second code is defined after alert, how come it does not prevent alert from being called?
What if I want my script to get total control of click event (like return false all the time disregarding events defined in other scripts)?
What if I want my script to get total control of click event (like return false all the time disregarding events defined in other scripts)?
If you can register your handler first, before they do, you can do that, provided the browser you're using correctly implements DOM3 events (which it probably does unless it's IE8 or earlier).
There are (at least) four things involved here:
Preventing the default.
Stopping propagation to ancestor elements.
Stopping other handlers on the same element from being called.
The order in which handlers are called.
In order:
1. Preventing the default
This is what return false from a DOM0 handler does. (Details: The Story on Return False.) The equivalent in DOM2 and DOM3 is preventDefault:
document.addEventListener("click", function(e) {
e.preventDefault();
}, false);
Preventing the default may not be all that relevant to what you're doing, but since you were using return false in your DOM0 handler, and that prevents the default, I'm including it here for completeness.
2. Stopping propagation to ancestor elements
DOM0 handlers have no way to do this. DOM2 ones do, via stopPropagation:
document.addEventListener("click", function(e) {
e.stopPropagation();
}, false);
But stopPropagation doesn't stop other handlers on that same element getting called. From the spec:
The stopPropagation method is used prevent further propagation of an event during event flow. If this method is called by any EventListener the event will cease propagating through the tree. The event will complete dispatch to all listeners on the current EventTarget before event flow stops.
(My emphasis.)
3. Stopping other handlers on the same element from being called
Naturally, this didn't come up for DOM0, because there couldn't be other handlers for the same event on the same element. :-)
As far as I'm aware, there's no way to do this in DOM2, but DOM3 gives us stopImmediatePropagation:
document.addEventListener("click", function(e) {
e.stopImmediatePropagation();
}, false);
Some libraries offer this feature (even on non-DOM3 systems like IE8) for handlers hooked up via the library, see below.
4. The order in which handlers are called
Again, not something that related to DOM0, because there couldn't be other handlers.
In DOM2, the specification explicitly says that the order in which the handlers attached to an element are called is not guaranteed; but DOM3 changes that, saying that handlers are called in the order in which they're registered.
First, from DOM2 Section 1.2.1:
Although all EventListeners on the EventTarget are guaranteed to be triggered by any event which is received by that EventTarget, no specification is made as to the order in which they will receive the event with regards to the other EventListeners on the EventTarget.
But this is superceded by DOM3 Section 3.1:
Next, the implementation must determine the current target's candidate event listeners. This must be the list of all event listeners that have been registered on the current target in their order of registration.
(My emphasis.)
Some libraries guarantee the order, provided you hook up the events with the library.
It's also worth noting that in Microsoft's predecessor to DOM2 (e.g., attachEvent), it was the opposite of DOM3's order: The handlers were called in reverse order of registration.
So taking #3 and #4 together, if you can register your handler first, it will get called first, and you can use stopImmediatePropagation to prevent other handlers getting called. Provided the browser implements DOM3 correctly.
All of this (including the fact that IE8 and earlier don't even implement DOM2 events, much less DOM3) is one reason people use libraries like jQuery, some of which do guarantee the order (as long as everything is hooking up their handlers via the library in question) and offer ways to stop even other handlers on the same element getting called. (With jQuery, for instance, the order is the order in which they were attached, and you can use stopImmediatePropagation to stop calls to other handlers. But I'm not trying to sell jQuery here, just explaining that some libs offer more functionality than the basic DOM stuff.)
If I register two event listeners to the same event target. Which event handler will be called first?
Example:
document.addEventListener("click", function() {
// do something 1
}, true); // using the capturing phase
document.addEventListener("click", function() {
// do something 2
}, true); // using the capturing phase
I do not found my answer in the w3c specification.
DOM-Level-3-Events
Bold emphasis added to answer the question (via the w3c specification: http://www.w3.org/TR/DOM-Level-3-Events/#event-phase)
First, the implementation must determine the current target. This must be the next pending event target in the partial propagation path, starting with the first. From the perspective of an event listener this must be the event target the listener has been registered on.
Next, the implementation must determine the current target's candidate event listeners. This must be the list of all event listeners that have been registered on the current target in their order of registration. [HTML5] defines the ordering of listeners registered through event handler attributes. Once determined, the candidate event listeners must not be changed; adding or removing listeners does not affect the current target's candidate event listeners.
Finally, the implementation must process all candidate event handlers in order and trigger each handler if all the following conditions are met...
I have personally always relied on this behavior, and I have not seen a modern browser behave differently than the spec describes.
I understand that an event has two modes -- bubbling and capturing.
When an event is set to bubble, does Javascript checks up to "document"?
When an event is set to capture, does Javascript always starts from "document"?
How does Javascript know where to stop/start?
Let's say I have the following code in my body tag.
<div id='outer'>
<div id='inner'></div>
</div>
When I set an event to #inner to bubble, does Javascript check up to document or does it stop at #outer?
From W3C Document Object Model Events
I know I'm nitpicking but it isn't javascript that handles the events you are describing, it is the DOM-engine (Document Object Model). In the browser there are bindings between the javascript and DOM engines so that events can be propagated to javascript, but it is not limited to javascript. For example MSIE has support for BASIC.
When an event is set to bubble, does Javascript checks up to "document" ?
1.2.3 "This upward propagation will continue up to and including the Document"
"Any event handler may choose to prevent further event propagation by calling the stopPropagation method of the Event interface. If any EventListener calls this method, all additional EventListeners on the current EventTarget will be triggered but bubbling will cease at that level"
When an event is set to capture, does Javascript always starts from "document"?
1.2.2 "Capture operates from the top of the tree, generally the Document,"
Event bubbling
JavaScript checks all the way up to document. If you add a listener on document and a listener on inner, both listeners fire.
Event capturing
JavaScript starts from document and goes all the way down to inner. If you add a listener on document and a listener on inner, both listeners fire.
My Findings
Turns out that the browser does some sort of smart processing so that it
a) doesn't have to loop through the entire parent hierachy
and
b) doesn't have to loop through all events.
Proof
a) It takes the browser no time to trigger both click events when the inner div is clicked:
Fiddle
b) It takes the browser no time to trigger both click events when the inner div is clicked when lots of other events exist that are attached to other DOM elements not in the parent hierachy:
Fiddle
Partial answer..
1 - When an event is set to bubble, does Javascript check up to "document" ?
Not if one of the elements in the hierarchy decides to stop the bubbling by calling stopPropagation()