I have a big table list of products, which is dynamic and changes with ajax. There is a for about 100 rows in that table.
I saw already that a contextual method $('.className', '#parentElement'); of selecting elements performs much better than usual $('.className'); or $('#parentElement .className');. (correct me if wrong)
In some circumstances i need to select a specific element with jQuery, there are dynamically added unique id="product-name-123456" and class="mainproductOffer" to each new row.
So, what performs faster from these two methods ?
$('tr[id^=product-name-]').click(function(){...});
$('tr.mainproductOffer').click(function(){...});
or
Is there any other method doing the same thing faster ?
In this case use the class selector, it is faster than the attribut selector.
You should use event delegation - for example:
$('table').on('click','.mainproductOffer',function() {});
to reduce the number of event handlers. If the class (mainproductOffer) is only used for the TR tag, remove the Tag from the selector too.
So, what performs faster from these two methods ?
$('tr.mainproductOffer').click(function(){...});
Is there any other method doing the same thing faster ?
Unsurpassed article 'jQuery Anti-Patterns for Performance' would help you
Use on() (event delegation), since its dynamically added.
Class selector is faster than attribute selector
$('tr').on( 'click', '.mainproductOffer', function(){...});
$('tr.mainproductOffer').click(function(){.....; OR
$('table').on( 'click', 'tr.mainproductOffer', function(){...;
are faster since
according to the jquery documentation
jQuery can process simple selectors of the form tag#id.class very quickly when they are used to filter delegated events. So, "#myForm", "a.external", and "button" are all fast selectors. Delegated events that use more complex selectors, particularly hierarchical ones, can be several times slower--although they are still fast enough for most applications. Hierarchical selectors can often be avoided simply by attaching the handler to a more appropriate point in the document. For example, instead of $("body").on("click", "#commentForm .addNew", addComment) use $("#commentForm").on("click", ".addNew", addComment).
Related
I've got a page with some Javascript / jQuery stuff, for example:
(function()
{
$('.tip').tooltip();
$('.test').click(function()
{
alert('Clicked!')
});
}();
On the page I insert some HTML with jQuery so the DOM changes. For example, I insert a extra element with the class "tip" or "test". The just new inserted elements doesn't work because jQuery is working with the non-manipulated DOM and the just inserted elements aren't there. So I've searched around and came to this solution for the "click":
$('body').on('click','.click',function()
{
alert('Clicked!')
});
I don't understand why, but this way it's working with the manipulated DOM and the jQuery stuff works on the new inserted elements. So my first question is, why does this work and just the click() function not? And the second question, why do I have to point to the "body"?
Finally, my third question is, how get this done with the tooltip?
I know that there is so many information about this subject (previous the delegate() and live() function I've read) but I can't found a explanation about it. And I can't get my third question solved with the information I found.
I'm looking forward to your responses!
Extra question:
4) Is it recommended to point always to the "body" for this kind of situations? It's always there but for possible performance issues?
So my first question is, why does this work and just the click()
function not?
Because the event handler is now delegated to a parent element, so it remains even after replacing/manipulating child elements.
Ancient article on event delegation for your perusal - but the concepts remain the same:
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
And the second question, why do I have to point to the "body"
You don't, any suitable parent element will do. For example, any direct parent (a div wrapper, for instance) which does not get replaced.
Finally, my third question is, how get this done with the tooltip?
You need to re-initialize your tooltip plugin on the newly inserted elements. For example:
$.get("foo.html", function (html) {
$("#someDiv").html(html);
$("#someDiv").find(".tip").tooltip();
});
The click() event doesn't work when you manipulate the DOM because JQuery is not watching for DOM changes. When you bind the click() event it is selecting the elements that are on the page at that time. New ones are not in the list unless you explicitly bind the event.
Because you have pointed the click() event on the body. JQuery then checks to see if the target of the click matches any of the event handlers (like what you have created) match the element clicked. This way any new elements will get the event 'associated' with them.
Because the tooltip isn't an event that you can place on the body, you will need to re-initialize it when the element is created.
EDIT:
For your fourth question, is it depends. The advantage of binding to the body is that you don't accidentally bind an event to an element more than once. The disadvantage is that you are adding event handlers that need to be checked on each event and this can lead to performance issues.
As for your concerns about DRY, put the initialization of the tooltips into a function and call that when you add them. Trying to avoid having the same function call is a little overkill in this regard, IMO.
Events are bound to the specific object you are binding it to.
So something like $('.tip').tooltip() will perform the tooltip() functionality on $('.tip') which is actually just a collection of objects that satisfies the css selector .tip. The thing you should take note of is, that collection is not dynamic, it is basically a "database" query of the current page, and returns a resultset of HTML DOM objects wrapped by jQuery.
Therefore calling tooptip() on that collection will only perform the tooltip functionality on the objects within that collection, anything that was not in that collection when tooltip is called will not have the tooltip functionality. So adding an element that satisfies the .tip selector, after the tooltip() call, will not give it the tooltip functionality.
Now, $('body').on('click','.click', func) is actually binding the click event to the body tag (which should always exist :P), but what happens is it captures whether the click event has passed through an element your target css selector (.click in this case), so since the check is done dynamically, new elements will be captured.
This is a relatively short summary of what's going on... I hope it helped
UPDATE:
Best way for your tooltip thing is to bind tooltip after you have added elements, e.g.
$('#container').load('www.example.com/stuff', function() {
$('.tip', $(this)).tooltip();
});
As a example of jQuery code (https://coderwall.com/p/7uchvg), I read that the expression $('#foo a'); behaves like this:
Find every a in the page and then filter a inside #foo.
And it does not look efficient.
Is that correct? And if yes, how should we do that in a better way?
That is correct - Sizzle (jQuery's selector engine) behaves the same way as CSS selectors. CSS and Sizzle selectors are evaluated right-to-left, and so #foo a will find all a nodes, then filter those by nodes that descend from #foo.
You improve this by ensuring that your leaf selectors have a high specificity, usually by giving them a class or ID.
how should we do that in a better way?
Use the context parameter from jQuery.
$('a', '#foo');
Now jQuery will search all anchors within the context of the element with id: foo.
In your query the context is defaulted to document when omitted:
$('#foo a'); == $('#foo a', document);
In this case, your query is indeed not efficient.
You might take a look at this article.
While it is true that Sizzle is a right-to-left engine (which is the same way css is interpreted), it is not true that the specific selector in your example would select all anchor elements on the page and then filter their parents to match the id of "foo". Sizzle actually optimizes any selector that starts with an ID and uses that as the context for the entire selection, rather than using the document. In other words, the selector you've chosen basically translates to:
document.getElementById("foo").getElementsByTagName("a")
Really, that's not a bad selector at all.
However, given the other things jQuery needs to do (which includes looping over the elements to merge them onto the jQuery instance), jQuery("#foo").find("a") will always be the fastest because jQuery implements a jQuery object creation shortcut for id-only selectors, and then it does the find rooted from #foo.
In other words, Sizzle itself is not much different when doing Sizzle("#foo a") and Sizzle("a", document.getElementById("foo")), but jQuery("#foo").find... will be faster because of jQuery's own ID shortcut.
By the way, my remarks on Sizzle is assuming querySelectorAll is not being used. If it is, Sizzle just passes it on to qsa, which still isn't as fast as using jQuery's ID shortcut.
You can use find() for more granular control on your selector order:
$('#foo').find('a');
This will of course be more impressive with more complex selectors, where you can chain find() and filter().
For the record $('#foo').find('a') === $('a','#foo')
[Update] ok, I realized later that it's exactly what your link says...
The jQuery selector engine (Sizzle) has been refactored last year, you'll find detailed explanations here:
http://www.wordsbyf.at/2011/11/23/selectors-selectoring/
Instead of filtering with a inside #foo elements, simply attach a class to a elements and get a elements with class like $("a.class");. This would be more efficient.
Yet another "try it for yourself":
jsperf for various selectors on 10000 elements
jsperf for various selectors on 300 elements
jsperf for various selectors on a "more representative DOM"
Doesn't seem to be much difference with a "flat" DOM (1 & 2), but the performance varies much more with a nested DOM.
Also note that some of the test cases aren't selecting the correct elements (i.e. $('.a') vs $('.a', context)), but I left them from the original tests just for comparison.
This example will retrieve the all anchors elements a in an element called foo, to Find every a in the page and then filter a inside #foo as you want u should select a #foo
$("a #foo");
this will retrieve all the foo elements inside a elements.
$(".hovertip").parent().live('hover', function() {
...
The above code doesn't seem to register.
This doesn't seem to work either:
$(".hovertip").parent().live({
mouseenter:
function() {
...
Any examples of .live, .delegate, .bind, or .on working with a jQuery selector and a .parent() selector with .hover() or mouseenter: and mouseleave:?
Update: I've created a separate question to address the dynamic DOM issue this Question has raised: jQuery .on() with a .parent() and dynamic selector
Try:
$(".hovertip").parent().on('hover', function() {
alert('yay');
});
Note: .on was introduced in jQuery 1.8.
Working demo http://jsfiddle.net/pRB8n/ Hover over test test - in the demo
If you really want to use .delegate try this please: http://jsfiddle.net/TURBX/2/ - http://api.jquery.com/delegate/
Delegate
Attach a handler to one or more events for all elements that match the
selector, now or in the future, based on a specific set of root
elements.
Hope rest fits the needs :)
P.S. - .live is deprecated: for further if you keen - my old post here: :) What's wrong with the jQuery live method?
under category you will see: http://api.jquery.com/live/ "deprecated"
I would add a comment to Tats_innit's post, but I can't.
As per the documentation on live,
Chaining methods is not supported. For example, $("a").find(".offsite, .external").live( ... ); is not valid and does not work as expected.
That's why .parent() does not work.
Binding to parent
Event delegation (handled by the deprecated live and .delegate, and now by .on/.one) only moves downwards. You can't have an upward event delegation like you seem to want to do here.
That is to say if the parent of ".hovertip" does not exist then clearly ".hovertip" does not exist so you are actually binding to nothing.
If your goal is to bind the event to the parent of ".hovertip" when it appears, then you're SOL since delegation only moves downwards (to descendants).
Your options to handle that would be:
* Bind to the parent of .hovertip when it is appended to the DOM.
* Know a selector for the parent of .hovertip ahead of time and bind to it immediately, perhaps through delegation.
Delegating to child
If your goal is to have the event fire when .hovertip is hovered, but .hovertip may not be in the DOM and its parent is not known, you must use a method like this:
$("known parent selector of .hovertip").on('hover', '.hovertip', function () {
"known parent selector of .hovertip" has to be an element that you know ahead of time. If you can't know, you have to use document, but I'd suggest to try to get as close as possible. You can only use this on elements that exist in the DOM at the time of binding.
I think what you are looking for, actually, is something along these lines:
$(document).on('mouseover', '.hovertip', function() {
// handle your mouseover changes, here
});
$(document).on('mouseout', '.hovertip', function() {
// handle your mouseout changes, here
});
.live, .bind, are all deprecated, AFAIK, which means they'll go away in the future, and you might not want to rely on their continued support.
It would also be far better to replace $(document) with a selector that's closer to your .hovertip elements, but above them in the DOM nesting, so they can respond to your event, but without forcing jQuery to watch for every event on every element in the whole document. I simply put document in there as an example, as I don't know what the rest of your structure looks like.
http://jsfiddle.net/mori57/qa7py/
As I think about it, I think it's worth pointing out that throwing things to .parent() may not always work out the way you expect, especially if you're modifying the DOM. I think it's far safer to set a higher-level event handler.
If you must use something like the .parent(), I always found more accurate results with .closest(), and giving it a selector also helps the parsing engine narrow its search. You don't want one parent triggering the hover state for /all/ the .hovertips at one time, which could happen in some cases.
i have a event i trigger on every link in my site.
but then i want it to NOT trigger on links i've got class='nofocus' on.
for example
<a>link</a>
<a class='nofocus'>register</a>
$('a').live('click', function() {
$('#searchbox').focus();
}
how do i rewrite the $('a) so the second link wont trigger the event?
Theoretically, there are three options.
1. Using the “attribute not equal to” selector
The “attribute not equal to” selector matches elements that either don’t have the specified attribute or do have the specified attribute but not with a certain value.
$('a[class!=nofocus]')
This will only work as long as you don’t use multiple classes on your A elements in your markup, e.g. foo.
See Selectors/attributeNotEqual in the jQuery docs for more information.
2. Using .not()
Another option is to select all the A elements first, then filter the results, removing elements with class="nofocus" from the result set.
$('a').not('.nofocus')
This is more flexible, because it allows the use of multiple classes on A elements.
Also, it’s slightly faster than using The Attribute Not Equal To Selector™ in Firefox, but slightly slower in Safari.
See Traversing/not in the jQuery docs for more information.
3. Using the :not() selector
The fastest (and shortest) option is to use the :not selector:
$('a:not(.nofocus)')
Also, my tests point out that this is by far the fastest method of the three — more than twice as fast as using the attribute not equal to selector!
Performance comparison of the three options
I created a jsPerf test case so you can test this yourself: http://jsperf.com/get-elements-without-specific-class.
TL;DR: Use $('a:not(.nofocus)').
Try the :not() selector (docs):
$('a:not(.nofocus)').live('click', function() {
$('#searchbox').focus();
}
Selector: $("a:not(.nofocus)") will select all links without the nofocus class.
Use $("a:first"). http://docs.jquery.com/Selectors/first to just get the first one.
$('a:not(.nofocus)')
should do the job
http://docs.jquery.com/Selectors/not
I just wrote a $().bind('event') function and then got concerned that this kind of a call might be very expensive if jQuery has to run through each element in the DOM to bind this event.
Or maybe, it's just about as efficient as an event could be. The jQuery docs I've read aren't making this clear. Any opinions?
There are two things that can make your event binding code slow: the selector and the # of bindings. The most critical of the two is the # of bindings, but the selector could impact your initial performance.
As far as selectors go, just make sure you don't use pure class name selectors like .myclass. If you know that the class of myclass will always be in a <div> element, make your selector be div.myclass as it will help jQuery find the matching elements faster. Also, don't take advantange of jQuery letting you give it huge selector strings. Everything it can do with string selectors it can also do through functions, and this is intentional, as it is (marginally, admittedly) faster to do it this way as jQuery doesn't have to sit around to parse your string to figure out what you want. So instead of doing $('#myform input:eq(2)'); you might do $('input','#myform').eq(2);. By specifying a context, we are also not making jQuery look anywhere it doesn't have to, which is much faster. More on this here.
As far as the amount of bindings: if you have a relatively medium-sized amount of elements then you should be fine - anything up to 200, 300 potential element matches will perform fine in modern browsers. If you have more than this you might want to instead look into Event Delegation.
What is Event Delegation? Essentially, when you run code like this:
$('div.test').click(function() {
doSomething($(this));
});
jQuery is doing something like this behind the scenes (binding an event for each matched element):
$('div.test').each(function() {
this.addEventListener('click', function() {
doSomething(this);
}, false);
});
This can get inefficient if you have a large amount of elements. With event delegation, you can cut down the amount of bindings done down to one. But how? The event object has a target property that lets you know what element the event acted on. So you could then do something like this:
$(document).click(function(e) {
var $target = $(e.target);
if($target.is('div.test')) { // the element clicked on is a DIV
// with a class of test
doSomething($target);
}
});
Thankfully you don't actually have to code the above with jQuery. The live function, which is advertised as an easy way to bind events to elements that do not yet exist, is actually able to do this by using event delegation and checking at the time an action occurs if the target matches the selector you specify to it. This has the side effect, of course, of being very handy when speed is important.
The moral of the story? If you are concerned about the amount of bindings your script has just replace .bind with .live and make sure you have smart selectors.
Do note, however, that not all events are supported by .live. If you need something not supported by it, you can check out the livequery plugin, which is live on steroids.
Basically, you're not going to do any better.
All it is doing is calling attachEventListener() on each of your selected elements.
On parse time alone, this method is probably quicker than setting inlined event handlers on each element.
Generally, I would consider this to be a very inexpensive operation.