I have a data web page with possibly few thousands TDs in it. Some of the TD's will need a bound onclick event that uses the contents, or part of the contents of the TD.
I'm using jQuery to add the onclick closure like this:
$(".date").click(function() {
var d = this.html();
doSomething(this, d, otherparams);
}
Is this efficient? It seems that my page would contain few hundreds, or thousands of almost identical closures. Would it be better to put this doSomething call somewhere else.
Infact, this is very inefficient. Even more because you can so easily workaround it using event delegation. Doing that, will use only one event handler method instead of "thousands".
$('table').delegate('td.date', 'click', function( event ) {
var d = $(this).html();
doSomething(this, d, otherparams);
});
You need to call this construct only once (outside of any loop). It'll bind an click event handler to all tables in the above example (you should be more precise using an id for instance). Since most browser events do "bubble" up the DOM tree, any click which happens in a <td> element will finally reach the <table> and is processed there.
Ref.: .delegate()
Related
I read in one of the articles on the web this sentence:
Adding event handlers to each of the 1000 cells would be a major
performance problem and, potentially, a source of browser-crashing
memory leaks.
But I don't understand it. If for example, I am doing something like this:
const handler = e => // do something
document.querySelectorAll("td").forEach(td => {
td.addEventListener("click", handler);
});
I don't create 1000 functions, they all use the same reference, so what I am missing? What is the problem?
I don't create 1000 functions, they all use the same reference, so what I am missing?
You would still create 1000 function references in the 1000 DOM nodes. This might not seem significant, but the table could grow…
Of course, you are basically right, if you don't create 1000 closures there won't be as many problems. However, do you really have 1000 buttons that all do the same? With closures, it's easy to let them do marginally different things; with only one function you would need to make it dynamically depend on the cell that was clicked (the event target). And when you're doing that already, you could just go ahead and use only a single function reference and event delegation…
The way event delegation works is that you add an event listener in a parent element. When the parent triggers the event, you get a reference of the actual element that triggered the event and then check if that is the element you want to listen to
Maybe a code would help you understand it better
// Listen for the event on the parent...
document.getElementById("event-table").addEventListener("click", function(e) {
// '.target' will now give us our actual clicked element
if(e.target && e.target.nodeName == "td") {
// Wooho, a '<td>' node was clicked
console.log("We have our click event delegated!");
}
});
The way it helps is that now, instead of adding a eventListner on each element I can simply attach it to a parent and then check for it's child.
The real use of this functionality is when you want to attach events on some dynamically created elements. Say a row in your table is added dynamically after a database call. In such case, you would have to reattach a event listener to the new node if you're going the route of attaching the event to each node. But if you're using the delegation approach, as the event is attached to the parent, the newly created child node automatically becomes a part of the event
It can prove to degrade performance if you attach it to a parent that has many child's, which don't actually are the intended nodes for the event. For eg, if you attach the body node itself and then delegate to the 'td' tags inside it ! In theory it would work for sure but would potentially slow things down.
In JavaScript, events will bubble up the DOM chain. When you click on td, any event handler on td will fire, and also on tr and on table, etc., all the way up the chain.
The article is telling you to register the event on table (for example) instead of each td element.
I don't create 1000 functions, they all use the same reference, so
what I am missing? What is the problem?
You are not creating 1000 functions but you are registering 1000 event handlers, which is what the article is advising against. Register one event handler on some parent element, like table, instead.
Here is a jsFiddle example on how to register one event handler for 1000 table cells.
Maybe an analogy will help you better understand it. Say you have a company where customers call you and you send them something.
Attaching a unique event handler to each cell would like having 1000 phone lines, one for each customer and having to hire 1000 employees to man each phone waiting for orders.
Attaching a single event handler to all of the cells is like still having 1000 phone lines, one for each customer but monitored by a single employee who has to figure out which customer's line is ringing and where to send the stuff. You no longer need 1000 employees but you still have 1000 phone lines.
Using event delegation is like having a single phone line that all your customers call and a single employee who know where to send their stuff based on the caller id.
Using a single phone line makes your employee much more efficient.
I always wondered which is the better way of handling events in terms of code manageability, cleanliness and code reuse.
If you use the former then say a list of 10 anchor tags with click handler will have something like:
Click Me
Click Me
Click Me
... 10 times
which looks kind of odd.
With the latter method, using anonymous function, it'd be like:
$('a').on('click', function(e){});
At the end of the day, every event is bound to some element in the DOM. In the case of .bind, you're binding directly to the element (or elements) in your jQuery object. If, for example, your jQuery object contained 100 elements, you'd be binding 100 event listeners.
In the case of .live, .delegate, and .on, a single event listener is bound, generally on one of the topmost nodes in the DOM tree: document, document.documentElement (the element), or document.body.
Because DOM events bubble up through the tree, an event handler attached to the body element can actually receive click events originating from any element on the page. So, rather than binding 100 events you could bind just one.
For a small number of elements (fewer than five, say), binding the event handlers directly is likely to be faster (although performance is unlikely to be an issue). For a larger number of elements, always use .on.
The other advantage of .on is that if you add elements to the DOM you don't need to worry about binding event handlers to these new elements. Take, for example, an HTML list:
<ul id="todo">
<li>buy milk</li>
<li>arrange haircut</li>
<li>pay credit card bill</li>
</ul>
Next, some jQuery:
// Remove the todo item when clicked.
$('#todo').children().click(function () {
$(this).remove()
})
Now, what if we add a todo?
$('#todo').append('<li>answer all the questions on SO</li>')
Clicking this todo item will not remove it from the list, since it doesn't have any event handlers bound. If instead we'd used .on, the new item would work without any extra effort on our part. Here's how the .on version would look:
$('#todo').on('click', 'li', function (event) {
$(event.target).remove()
})
Second method is preferrable, since we should not be mixing our JavaScript with the HTML. (Separation of Concerns) . This way your code is kept clean.
This also works well with dynamically inserted HTML code.
`$('a').on('click', function(e){});` // Using jQuery.
Using Vanilla JS:
document.getElementById("idName").addEventListener("click", function(){}); // Bind events to specific element.
document.addEventListener("click", function(){}); // Bind events to the document. Take care to handle event bubbling all the way upto the document level.
I need to have multiple .click() functions populated on page load, based on how many image records are stored within a mysql database.
so far i have a page that will nicely switch between photos with a <ul> of image buttons
but i have to hand write the jquery that deals with it.
is there a way that i can populate a .js file with the correct amount of .click() functions based on the amount of records on in the data base.
In addition to Alex's answer, if you want to set the click event of elements that don't exist yet or haven't been added to the page, you could do:
$(body).on('click','a.record',function(){
//any existing or future a element with class record will have this click function
});
Instead of adding a separate onclick handler to each element, you should use event delegation and attach a single event handler to some container. Said event handles would catch all the onclick events , as the bubble up through DOM.
You don't need to write a click() for each unique element.
Instead, you could select a bunch of elements with a selector, such as $('a.record') and then chain click() to that...
$('a.record').click(function() {
// Any `a` element with a class of `record` was clicked.
});
The disadvantage of doing it this way is you add a bunch of event listeners and it won't be triggered for future elements.
As others have mentioned, event delegation using on() (if using a newer jQuery) or delegate() (if using an older) is the best, as it only attaches one event listener and will work with future elements added after the event is attached.
$(document).on('click', 'a.record', function() {
// Any `a` element with a class of `record` was clicked, now or in the future.
});
I've used document here, but you should use the nearest ancestor which won't change, which may be the ul element you have described.
I've a web page fulfilled by a JS script that creates a lot of HTML tables.
Number of <table> created can vary between 1 and 1000 with 100 cells in each of it.
My question is : how to bind efficiently the click on theses tables? Should I bind the click on each cell of the <table> or directly to the <table> itself and retrieve the cell clicked in the bind function?
Or have you another idea?
P.S: I'm using IE6+
I suggest you use delegate.
$("table").delegate("td", "click", function(){
alert($(this).text()); // alert td's text.
});
delegate will just bind one event, and that is to the context(in this example, the <table>).
As it seems that you use jQuery, you should use the delegate() method on the table, e.g.:
$('table').delegate('td', 'click', function() {
// $(this) refers the the clicked cell
});
This will bind one event handler to the table and capture the bubbled up click events.
Binding so many event handlers, i.e. an event handler to every cell, is indeed not a good idea, especially not in IE (for performance reasons).
bind event on table for faster execution and get cell details inside that function.
you can find a similar toppic here:
large table with lots of events, is using event bubbling more effecient?
i would use this way:
$("table#yourTable").click(function(evt){
if($(evt.target).is('td')) {
//do whatever you want to do
}
})
I'm trying to build a greasemonkey script which will dynamically create tables of data based on user interaction with... other dynamically created tables of data. My problem is that I'm having to make two passes every time I create a table: one to create the table, and another to go grab all of the objects in the table I want to add event handlers to (by id) and add the various event handlers to them.
If I attempt to, say, add an onClick event to a table td before I've created the table and inserted it into the HTML, I get a "component is not available" exception.
This is incredibly cumbersome, because I either have to maintain, separately, a list of the ids and what I should do to those elements when I make my second pass to add the handlers, or develop a naming convention by which I know, based on the id, what I should do with the element.
There HAS to be a better way to do this. I just haven't figured it out yet. Anyone have any ideas?
Firstly, I'd love to know why you need a different ID for every single TD. Is the ID holding important information, such as an index? In this situation it might be better creating each TD within a loop. Also, obviously you can't attach an event handler to a DOM element which doesn't exist! It doesn't have to be injected into the DOM but it DOES have to exist in some capacity.
jQuery's live() isn't a magical mystery, it just uses event delegation, so it attaches the event to a parent element, such as the table and then decides what happens dependent on the target of the click. Here's a rudimentary example. I register a handler to the 'body' element, and then I test each time to see what the target is, if it's a TD element I doSomething() ->
document.body.onclick = function(e) {
var realTarget = e ? e.target : window.event.srcElement;
if ( realTarget.nodeName.toLowerCase() === 'td' ) {
doSomething();
}
};
Event delegation relies on something called event bubbling (or "propogation") which is the way in which modern browsers implement the event model. Each event, when triggered will travel upwards through the DOM until it can go no further. So if you click on an anchor within a paragraph the anchor's 'click' event will fire and THEN the paragraph's 'click' event will fire etc. etc.
jQuery 1.3+ has a new live() function that can set up event handlers for elements that don't exist yet .. check it out
You have to wait for the element to be added to the page, then add the event handler then.
There is no easy way to say "add this to all elements of this type, now and in the future".
It is possible to have a timer periodically check the page for new elements, applying a queue of events (or other properties) to them as they appear, all behind the scenes. This can be abstracted out and re-used, for example Jquery can do that sort of thing.
As JimmyP pointed out, your problem can easily be solved using event bubbling. You might consider writing a wrapper function to work around browser inconsistencies - my own version can be found here and would be used like this:
capture('click', '#element-id', function(event) {
// `this` will be the originating element
// return `false` to prevent default action
});