In my use case I have many events occurring at the same time, and each event has different ways of being handled.
Due to event bubbling sometimes it is necessary to ignore specific events (not executing the callback handler) because that event was already handled.
Sometimes you can not simply stop propagation of the event without breaking something else.
For the above reasons I have tried to set a custom flag or data on the event object. For example after it had been handled setting event.handlerExecuted = true. However I found this way of doing it to work inconsistently, and it is basically a hack.
I also want to avoid using global flags or storing references to event objects somewhere..
Some events behave like mousedown -> mouseup -> click each of these "steps" represents a different event, so altering the mousedown event does not guarantee you still have that data available on the click event object.
Is there a proper way of dealing with this problem?
Related
Let's say I have bunch of click events. Also one/few of them is for document object.
Which one is better for performance? Click event for each element or :
document.addEventListener('click', (e)=>{
if(e.target == firstObject){ firstFunction(e) }
if(e.target == secondObject){ secondFunction(e) }
if(e.target == ThirdObject){ thirdFunction(e) }
})
Neither is "better." They each have their place in your toolkit.
A single delegated handler is more complex in that you have to do the kind of dispatch you're doing in your example (often using closest or matches), but has the advantage that if you're adding/removing elements you want to act on, you don't have to juggle event handlers.
Directly-assigned handlers are simpler (at least on elements that aren't added/removed), can prevent propagation, and let you keep your code more modular, more in keeping with the single responsibility principle.
Use the one that makes the most sense in a given context.
I think event listener for each element is better if possible, and makes sense in terms of code quality. There are some cases though where a document event listener will be needed ( for example to emulate a click outside behaviour)
That being said here are some of reasons that makes event listener for each element a better solution
event propagation is handled for you by the browser, if you decide to have only one event handler for the whole document, and u want to have event listeners for elements that are contained in each other, then you will need to handle propagation your self. That is to say you need to handle the order in which functions run yourself, and then you will have some either complex generic solution, or a specific imperative verbose code with a lot of if else statements.
Easier to read code, this is even more true for recent frameworks for web like react, angular, etc..., so for example assume you want to have a listener for clicks on the document, where that code should reside, in which file, and which component should own the code.
Removal of event listeners is handled for you by the browser apis, the browser gives you a way to remove event listeners. If you decide to go with a global event listener then you should handle removing event listeners yourself.
Your code will be hard to refactor and easier to break later, because you are coupling your document (or container ) event listener to your components internals. That is if you decide to change the structure of these components later, your document based event listener will probably break. This will depend a lot on how you identify the target of clicks, for example if you were identifying them by class names or other attributes, then these attributes might change later for reasons like styling.
and if you depend on ids for example you might eventually have unexpected results. because what happens for example if you added a listener for an element that has id, removed that element, and then later added another element with same id.
You miss on the development tooling provided for you by browsers, browsers can show you attached listeners for elements, with a document based event listener you wont be able to do that
It's better if you add one by one, because then you can remove event whenever it finish. Moreover you have more control about this event.
I have a web-application that needs to capture any keyboard event on the page, and target them to the appropriate editable-div.
If the editable-div has focus, then the event flows to my event handler and to the DOM to push the character corresponding to the key into the DIV.
However, if the editable-div is not the current focus target, I am able to capture the event with my event handler, but the character corresponding to the key pressed is not pushed into the DIV.
My previous implementation had a dependency on jQuery, and $.trigger() was doing the right thing: moving the keyboard event from a non-matching target to the default editable-div I choose.
I am trying to achieve the same without jQuery, and with Google Closure. I tried various incantations of dispatchEvent without success in triggering the new virtual keypress.
In ClojureScript, trying to do something naive such as
(.dispatchEvent new-target (.getBrowserEvent event))
will cause the browser to complain that The event is already being dispatched.
Is there any simple solution to this problem?
You can use goog.testing.events/fireKeySequence to create events similar like jQuery's trigger.
More discussions.
Problem: I need to bind any number of event handlers to any number of elements (DOM nodes, window, document) at dynamically runtime and I need to be able to update event binding for dynamically created (or destroyed) nodes during the lifetime of my page. There are three options that I can see for tackling this problem:
I) Event delegation on window
II) Direct event binding on each node
III) Event delegation on common ancestors (which would be unknown until runtime and would potentially need to be recalculated when the DOM is altered)
What is the most efficient way of doing this?
A little context
I am working on a set of pages that need analytics tracking for user events (clicks, scrolling, etc.) and I want to be able to easily configure these event handlers across a bunch of pages without needing to write a script to handle the event binding for each instance. Moreover, because I may have the need to track new events in the future, or to track events on elements that are dynamically added to/removed from the page, I need to be able to account for changes in the DOM that occur during the lifetime of the page.
As an example of what I'm currently considering, I would like to create a function that accepts a config object that allows the programmer to specify default handlers for each event, and allow them to override them for specific elements:
Analytics.init({
// default handlers for each event type
defaultHandlers: {
"click": function(e) { ... },
"focus": function(e) { ... }
},
// elements to listen to
targetElements: {
// it should work with non-DOM nodes like 'window' and 'document'
window: {
// events for which the default handlers should be called
useDefaultHandlers: ['click'],
// custom handler
"scroll": function(e) { ... }
},
// it should work with CSS selectors
"#someId": {
useDefaultHandlers: ['click', 'focus'],
"blur": function(e) { ... }
}
}
});
Sources
SO: Should all jQuery events be bound to document?
SO: How to find the nearest common ancestors of two or more nodes
jQuery docs: $.fn.on()
I usually delegate events on the document.documentElement object because:
It represents the <html> element on the page, which holds everything which holds all the HTML tags the user can interact with.
It is available for use the moment JavaScript starts executing, negating the need for a window load or DOM ready event handler
You can still capture "scroll" events
As for the efficiency of event delegation, the more nodes the event must bubble up the longer it takes, however we're talking ~1 to 2 ms of time difference -- maybe. It's imperceptible to the user. It's usually the processing of a DOM event that introduces a performance penalty, not the bubbling of the event from one node to another.
I've found the following things negatively affect JavaScript performance in general:
The more nodes you have in the document tree, the more time consuming it is for the browser to manipulate it.
The greater the number of event handlers on the page the more JavaScript slows down, though you would need 100s of handlers to really see a difference.
Mainly, #1 has the biggest impact. I think trying to eek out a performance boost in event handling is a premature optimization in most cases. The only case I see for optimizing event handling code is when you have an event that fires multiple times per second (e.g. "scroll" and "mousemove" events). The added benefit of event delegation is that you don't have to clean up event handlers on DOM nodes that will become detached from the document tree, allowing the browser to garbage collect that memory.
(From the comments below) wvandell said:
The performance costs of event delegation have little to do with the actual 'bubbling' of events ... there is a performance hit incurred when delegating many selectors to a single parent.
This is true, however let's think about the perceived performance. Delegating many click events won't be noticeable to the user. If you delegate an event like scroll or mousemove, which can fire upwards of 50 times per second (leaving 20 ms to process the event) then the user can perceive a performance issue. This comes back to my argument against premature optimizations of event handler code.
Many click events can be delegated with no problem on a common ancestor, such as document.documentElement. Would I delegate a "mousemove" event there? Maybe. It depends on what else is going on and if that delegated "mousemove" event feels responsive enough.
While using PhoneGap, it has some default JavaScript code that uses document.addEventListener, but I have my own code which uses window.addEventListener:
function onBodyLoad(){
document.addEventListener("deviceready", onDeviceReady, false);
document.addEventListener("touchmove", preventBehavior, false);
window.addEventListener('shake', shakeEventDidOccur, false);
}
What is the difference and which is better to use?
The document and window are different objects and they have some different events. Using addEventListener() on them listens to events destined for a different object. You should use the one that actually has the event you are interested in.
For example, there is a "resize" event on the window object that is not on the document object.
For example, the "readystatechange" event is only on the document object.
So basically, you need to know which object receives the event you are interested in and use .addEventListener() on that particular object.
Here's an interesting chart that shows which types of objects create which types of events: https://developer.mozilla.org/en-US/docs/DOM/DOM_event_reference
If you are listening to a propagated event (such as the click event), then you can listen for that event on either the document object or the window object. The only main difference for propagated events is in timing. The event will hit the document object before the window object since it occurs first in the hierarchy, but that difference is usually immaterial so you can pick either. I find it generally better to pick the closest object to the source of the event that meets your needs when handling propagated events. That would suggest that you pick document over window when either will work. But, I'd often move even closer to the source and use document.body or even some closer common parent in the document (if possible).
The window binding refers to a built-in object provided by the browser. It represents the browser window that contains the document. Calling its addEventListener method registers the second argument (callback function) to be called whenever the event described by its first argument occurs.
<p>Some paragraph.</p>
<script>
window.addEventListener("click", () => {
console.log("Test");
});
</script>
Following points should be noted before select window or document to addEventListners
Most of the events are same for window or document but
some events like resize, and other events related to loading,
unloading, and opening/closing should all be set on the window.
Since window has the document it is good practice to use document to
handle (if it can handle) since event will hit document first.
Internet Explorer doesn't respond to many events registered on the
window,so you will need to use document for registering event.
You'll find that in javascript, there are usually many different ways to do the same thing or find the same information. In your example, you are looking for some element that is guaranteed to always exist. window and document both fit the bill (with just a few differences).
From mozilla dev network:
addEventListener() registers a single event listener on a single
target. The event target may be a single element in a document, the
document itself, a window, or an XMLHttpRequest.
So as long as you can count on your "target" always being there, the only difference is what events you're listening for, so just use your favorite.
In my opinion, it is generally better to pick the closest object to the source of the event that meets your needs when handling propagated events.
So, if you want the event to happen to the element, it's better to use window.addEventListener() (assume the window variable is an element) because the most important thing here when listening to an event is that the code and event execution work faster: the only thing that matters in this case.
If I have an element (html) nested in another element and both of them have a click handler attached, clicking the inner element executes its click handler and then bubbles up to the parent and executes its click handler. That's how I understand it.
Do events bubble up the DOM tree if there are no events attached that are the same and if so, is it worth putting a event.stopPropagation() at the end of every handler to stop this and speed things up?
events almost always bubble up unless event.cancelBubble=true is set or event.stopPropagation() is used. You are only aware of it, though, when one of your event
handlers gets tripped.
See http://en.wikipedia.org/wiki/DOM_events for a list of events which bubble. (Note: in the table of HTML events, cancelable refers to the effectiveness of event.preventDefault() or return false to cancel the default action, not bubbling)
Also see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow, in particular 1.2.1 Basic Flow to understand the capture phase and bubbling phase of event propagation.
EDIT
http://mark-story.com/posts/view/speed-up-javascript-event-handling-with-event-delegation-and-bubbling suggests there is a performance gain by stopping propagation but provides no data.
http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/a9af0aa4216a8046 suggests that browsers should be optimized for bubbling behaviour and says there should be no significant performance difference. Again no data.
http://developer.yahoo.com/performance/rules.html#events provides a good technique for improving event-handling performance, but doesn't directly talk about stopPropagation performance.
Ultimately, you'd have to profile the difference to get a good idea of the benefits on your site.
I suppose this behavior is already well optimized by browsers, so you won't be able to catch significant performance boost when stopping propagations (except, perhaps, for really-really complex nested DOM structures). If you are worried by performance and deal with lots of events, you may be interested in event delegation instead.
Also, you should remember your code should stay readable and self-explainable. stopPropagation() is a method used for certain purpose, so using it in every method could be really confusing.