Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
I am developing/have developed a few systems now, I have come across a querk in my programming, I seem to delegation all my JQuery functions to the document like so:
$(document).on('click', '.modal-editor', function () {
//code
});
$(document).on('click', '.another-class', function () {
//code
});
$(document).on('click', '#another-id', function () {
//code
});
Is there anything wrong with this?
UPDATE:
Ok so in essence there is nothing wrong with this until:
The applications reaches a scale where delegation needs to be more localised to prevent slowing down UI. "Every event that takes place needs to run every selector against every element between the e.target and the document"
Delegation like this will increase the chances of unexpected behaviour like propagation if nested functions are used.
Referring back to my question (best practice), best practice would be to delegate the event to the closest static element as possible to attempt to avoid the above.
"Is there anything wrong with this?"
Yes. Every event that takes place needs to run every selector against every element between the e.target and the document. That's unnecessarily expensive.
Basically like this simplified pseudo code:
// start on the e.target
var node = e.target;
// For the target and all its ancestors until the bound element...
while (node && node !== this) {
// ...test the current node against every click selector it was given
for (var i = 0; i < click_selectors.length; i++) {
// If a selector matches...
if ($(node).is(selector)) {
// ...run the handler for that selector
}
}
node = node.parentNode;
}
Also, if your code or some other code you loaded binds a handler between the e.target and the document, and calls e.stopPropagation(), the event will never reach the document, and so yours will not fire.
It's much better to keep your delegation as close to the intended element(s) as possible. And use it primarily for dynamically loaded elements because of the extra overhead it adds, especially in the case of events that fire rapidly.
Yes. In itself there isn't, nto really, but delegating all events from the document (root of the DOM) does meant that all click events, including those you're not interested in will be handled at least partially:
$(document).on('click', '$another-id', function(){});
is a particularly bad idea, in that respect: you're after a single element, but if I click anywhere in the body, jQ will do something like:
if (event.target.id == 'another-id')
{
return callback.call(event.target, $(event));//where callback is the anonymous function you passed to on
}
return event;
So all click events result in a function call. This can slow your UI down.
By no means should you stop delegating events, but bind the listeners wisely. If all the DOM elements you want to use are contained within the #container div, for example, then bind your listeners there. If you want to handle navigation events, bind the listener to the node that contains all the navigation elements your after
Add to that that, if you cancel an event, but failed to stopPropagation, the event will still end up invoking all your other listeners that might be queued. Returnign false can also cause trouble, seeing as, in jQ, return false is translated to e.preventDefault(); e.stopPropagation(). So be careful when dealing with nested elements, if you want to handle a click on links in your page, but also on elements like <a class='navigation'>, both handlers might be called, depending on the selectors used:
$(document).on('click', 'a', function(){});//is called for all links
$(document).on('click', 'a.navigation', function(){});//is called for navigation
Which will be invoked first? Which would you want to use in a given situation? There's room for error here, that shouldn't be there:
$('#navContainer').on('click', 'a.navigation', function(){});//is called for all links
At least makes things a tad safer, clearer and lighter, too.
If you want to delegate an event, using an ID selector, and the element already exists in the DOM, don't delegate:
$('#another-id').on('click', function(){});
is shorter, and will likely even be faster anyway
Delegation is great when you want a particular event to apply to multiple elements, without each element having its own event handler. In the instance above this is likely for the first two methods.
Where there will only ever be one element with that matches the criteria in theory it might have a performance implication, depending on the size of your document, due to every event having to be tested against the handler filter to see if it matches.
Also, if every method is added as a delegate, and you are frequently loading and uploading sections of the page those events are going to hang around longer than the elements on the page that they belong to.
You can delegate your handlers to something other than the document element, such as a surrounding div or something instead perhaps in this kind of scenario. Or use event namespacing to make sure events aren't being added multiple times.
There is nothing wrong with your code,
$(document) is not necessary to be place always in event delegation, that selector refers to the closest parent element to which your elements are added dynamically:
Syntax:
$(closest parent selector).on('event','target selector', function(){
});
You can also use document.body
$(document.body).on('event','target selecor',function(){
});
Side Note: For existing DOM elements you can use normal click event.
$('selector').click(function(){
});
OR
$('selector').on('click',function(){
});
For more information refer to .on() API documentation.
Related
I'm working on a grid with 20 columns and 100+ rows. Every column has an input field. Now I want to put eventHandlers on the input fields like change and keyup, focus, and much more. So there can be 20*100 events = 2000!
What is the best way to do this? I've read something about eventHandlers and memory problems.
This is how I think I should do it:
$("#grid input").each(function() {
$(this).keyup(function() {
//
});
$(this).change(function() {
//
});
});
Or is this the best way to do it?
$("#grid").keyup(function() {
//
});
You're looking for event delegation.
$("#grid").on("change", "input", function() {
// Handle the event here, `this` refers to the input where it occurred
});
That attaches one handler (on #grid), which jQuery then fires only if the event passed through an element matching the second selector. It calls the handler as though the handler were attached to the actual input. Even events like focus that don't natively bubble are supported through jQuery's mechanations.
More in the on documentation.
I'd suggest using Event Delegation, like so:
$("#grid").on("keyup", "input", function() {
...
});
Rather than adding a listener to each and every input, you're only adding one to #grid.
As per this great answer: What is DOM event delegation?
Another benefit to event delegation is that the total memory footprint used by event listeners goes down (since the number of event bindings go down). It may not make much of a difference to small pages that unload often (i.e. user's navigate to different pages often). But for long-lived applications it can be significant.
There are some really difficult-to-track-down situations when elements removed from the DOM still claim memory (i.e. they leak), and often this leaked memory is tied to an event binding. With event delegation you're free to destroy child elements without risk of forgetting to "unbind" their event listeners (since the listener is on the ancestor). These types of memory leaks can then be contained (if not eliminated, which is freaking hard to do sometimes. IE I'm looking at you).
Is there a way to get all elements that have a certain event listener attached to them?
I know I can get them if the event listener is defined as an attribute:
var allElemsInBodyWithOnclickAttr = $("body").find("*[onclick]");
But I have elements that have event listeners that are attached by code and thus have no onclick attribute.
So when I do (for example) this:
$("a").on("click", function(evt) {
alert("Hello");
});
..then using the code below doesn't fire the click on those anchor elements:
$("a[onclick]").trigger("click");
I guess I could loop through all the elements and check if they have the listener I'm looking for (using this SO question) but I can't imagine that's going to perform very well..
Can't add comments, but still - consider using https://github.com/ftlabs/fastclick for removing the delay.
That helped me, when i was developing app using cordova.
Also, didn't notice you have already mentioned this post, so i have written implementation for 'loop through all elements'-type-of-solution
Array.prototype.reduce.call(
$('body').children(),
(answer, node) => {
if (typeof $._data($(node)[0], 'events') != 'undefined') {
answer.push(node);
}
return answer;
},
[]
);
I think this is not possible, since it is not possible to test if a single element has an event listener attached to it.
Look here
So the only way to do that is to manage a map which contains a reference to each event handler for each event for each element.
Edit
With respect to the answer of #Ivan Shmidt, I must correct my answer: Obviously It seams to be possible with jQuery. That is because jQuery is holding a reference of attached event handlers, BUT events attached using good old .addEventListener() would bypass this and also not be found this way.
I am using jQuery each function and on click on current i.e $(this) I am executing JS code, It will be more clear if you look on following code
$('.switch-cal').each(function(){
$(this).on( 'click', function( e ){
.... CODE HERE ....
Please tell me correct way of use "this" with ".on" inside ".each" function.
Thank you.
1. Short answer: There is no need for the each:
Do not use click inside of each... you do not need to with jQuery. It handlers collections automatically for most operations (including event handlers):
e.g.
$('.switch-cal').click(function(){
//$(this) is the calendar clicked
.... CODE HERE ....
2. Try a delegated handler:
It is often more convenient to use a single delegated event handler (attached to the nearest non-changing ancestor element, or document if none is handy). The best feature of delegated events is that they can work on elements that do not even exist yet! :)
The run-time overhead is quite low as they only need to apply the selector to the elements in the bubble-chain. Unless you are processing 50,000 mouse operations per second the speed difference is negligible, so for mouse events, don't be put of by ridiculous claims of inefficiency (like the down-voter's ranting below). The benefits usually out-way any extra overhead. Nobody clicks 50,000 times per second!:
e.g.
$(document).on('click', '.switch-cal', function(){
//$(this) is the calendar clicked
.... CODE HERE ....
Delegated events work by listening for events (in this case click) bubbling up the DOM to a non-changing ancestor element. It then applies the selector. It then calls the function on each matching element that caused the event.
The closer the non-changing element is to the elements in question the better, to reduce the number of tests required, but document is your fall-back if nothing else is convenient. Do not use body as it has problems relating to styling that means events may not fire.
#TrueBlueAussie had the correct answer. You should use a single delegated event.
$(document).on('click', '.switch-cal', function(){
//$(this) is the calendar clicked
.... CODE HERE ....
Still if you want to keep your "this" scope, you may
$('.switch-cal').each(function(){
$(this).on( 'click', function( e ){
.... CODE HERE ....
}.bind(this))
});
On my page, the user clicks on an element in order to edit it. To facilitate this, I assign the class editable to all such elements.
How should I listen for clicks on all these elements? Currently, I'm doing this:
document.body.addEventListener("click", (event) => {
if (event.target.classList.contains("editable")) {
// do stuff
}
});
The alternative would be to set a listener on every element, like this:
const editables = document.getElementsByClassName("editable");
for (const editable of editables) {
editable.addEventListener("click", editElement);
}
It seems to me that the first way must be better for performance, since it's only one element being listened on, but is it possible to degrade performance by attaching all such events to the body element? Are there any other considerations (e.g. browser implementations of event handling) that I'm neglecting which would suggest doing it the second way?
Short answer: definitely do it the first way. Event delegation is way more performant, but requires extra conditionals in your code, so it's basically a complexity versus performance tradeoff.
Longer Answer: For a small number of elements, adding individual event handlers works fine. However, as you add more and more event handlers, the browser's performance begins to degrade. The reason is that listening for events is memory intensive.
However, in the DOM, events "bubble up" from the most specific target to the most general triggering any event handlers along the way. Here's an example:
<html>
<body>
<div>
<a>
<img>
</a>
</div>
</body>
</html>
If you clicked on the <img> tag, that click event would fire any event handlers in this order:
img
a
div
body
html
document object
Event delegation is the technique of listening to a parent (say <div>) for a bunch of event handlers instead of the specific element you care about (say <img>). The event object will have a target property which points to the specific dom element from which the event originated (in this case <img>).
Your code for event delegation might look something like this:
$(document).ready(function(){
$('<div>').on('click', function(e) {
// check if e.target is an img tag
// do whatever in response to the image being clicked
});
});
For more information checkout Dave Walsh's blog post on Event Delegation or duckduckgo "event delegation".
NOTE ON CODE SAMPLE IN OP: In the first example, target.hasClass('editable') means that the specific thing clicked on must have the class editable for the if block to execute. As one of the commenters pointed out, that's probably not what you want. You might want to try something along these lines instead:
$(document).on('click', function(e) {
if ($(e.target).parents(".editable").length) {
// Do whatever
}
});
Let's break that down a bit:
$(e.target) - anything that on the page that was clicked converted to jQuery
.parents(".editable") - find all the ancestors of the element clicked, then filter to only include ones with the class "editable"
.length - this should be an integer. If 0, this means there are no parents with "editable" class
Another plus point for the first method
I was using the second (alternative) method that you have mentioned I noticed that when the ajax loaded... the newly created elements were not listening the event. I had to redo the for loop after ajax every time.
With the first method which looks like following in my code also works with ajax.
document.addEventListener('click', function (e) {
if (hasClass(e.target, 'classname')) {
// do stuff
}
}, false);
So first one is better
I know I may be asking for the moon here but I'm looking for some experienced opinons on the best way to add event listeners or rather 'When' or 'Where' to add them in the js file.
Take my take as an example. I have a page which has a bunch of onclick events that now have to be handled by properties in the JS file
eg
var test = document.getElementById("i-am-element");
test.onclick = testEventListener;
My question is where exactly I should add this in the js file.
How I was planning to go about it was to have something like the following
$(document).ready(function() {
var test = document.getElementById("test-me-shane");
test.onclick = testEventListener;
//add all the functions to different elements
});
myfunction1(){}
myfunction2(){}
myfunction3(){}
So that once the document is ready, only then are all the event listeners added. Is this acceptable or is there are more universally accepted way of doing it.
NOTE: I know this question may appear subjective so I'm going with the correct answer will be the most popular way you've seen seen event listeners added. I'm sure there must be a majority acceptance on this and I apologize in advance if its similiar to something like where you should declare variables, at the start or when you need them.
In Java, should variables be declared at the top of a function, or as they're needed?
You really want to be able to add all your event listeners in the same place; why? Simply for ease-of-maintenance.
Because of this, the best place to put all your event listeners is in a place where you can guarantee all elements you'll possibly want to bind event handlers to are available.
This is why the most common place to bind your event handlers is after the DOMReady event has fired $(document).ready().
As always, there are some exceptions to the rule. Very occasionally, you might want to bind an event handler to an element as soon as it is available; which is after the closing tag of the element has been defined. In this instance, the following snippet should be used:
<div id="arbitrary-parent">
<h1 id="arbitrary-element">I need an event handler bound to me <strong>immediately!</strong></h1>
<script>document.getElementById("arbitrary-element").onclick = function () { alert("clicked"); }</script>
</div>
The other thing you should consider is how you are going to bind your handlers. If you stick to: DOMElement.onclick = function () { };, you're limiting yourself to binding on handler per event.
Instead, the following approach allows you to bind multiple handlers per event:
function bind(el, evt, func) {
if (el.addEventListener){
el.addEventListener(evt, func, false);
} else if (el.attachEvent) {
el.attachEvent('on' + evt, func);
}
}
Is there a specific reason why you don't simply specify the association when you declare the element in the html <someTag id="i-am-an-element" onclick="functionToTheEventToExecute()"> </someTag> I guess so.