jQuery - performance of element detection - javascript

I'm reading code of my collegues and I'm wondering if the performance could be improved.
For any button-event there is code like that:
a)
$("body").on("click", ".aButtonName", function() { ....});
$("body").on("click", ".aButtonName", function() { ....});
$("body").on("click", ".aButtonName", function() { ....});
....
$("body").on("click", ".aButtonName", function() { ....});
b) Would it be faster to analyse each target-event, after the body is clicked:
$(document.body).on('click', function( e ){
var trg = $(e.target).closest('button');
if(!trg || !trg.attr('class'))
return;
if ( trg.attr('class').indexOf('my_button') > -1) {
....

It would most likely reduce performance. In the second scenario, you end up executing the callback function, passing parameters, and performing several DOM interactions just to exit. In the first, jQuery performs a selector match and only executes the function if there is a match.
In both styles, the event is handled once it "bubbles up" (propagates) to the body. This "delegate-style" checks to see if the selector matches the target (or a parent).
As others suggest, caching the $("body") would save a number of DOM queries and you could get better performance by using a closer/smaller delegate than body.

Question is not clear. If you want to raise an event whenever we click on body Simply u can follow the below code.
<body>
<ul>
......
</ul>
</body>
In js file
$('body').click(function(event){
$(this).find('ul').addClass('selected');
});

Related

Equivalent of jQuery code in Javascript

This is the jQuery code I have
$('p').click(function(){
alert("click successful!");
});
This is the JS code I could come up with
window.onload = function() {
var para = document.getElementsByTagName('p');
for(var i = 0; i < para.length; i++) {
para[i].addEventListener('click',function(){
alert("click successful!");
});
}
}
The Javascript code is too bulky, is there a way where I can select a tag by its name and write the code as -
"If any 'p' tag is clicked, alert('click successful')"
instead of looping through all the <p></p> tags?
Any alternative way using tag name?
You can use event delegation - add a click handler to a higher level element and check event.target
document.body.addEventListener("click", function(e) {
if (e.target.tagName.toLowerCase() == "p") alert("click succeeded");
});
Demo: http://jsfiddle.net/jdkr3sch/
jQuery is "less code" because you're calling a pre-written function. Don't want to use jQuery? Then write your own functions.
function addEventToElements(tagname,handler) {
var elems = document.getElementsByTagName(tagname);
for(var i = 0, l = elems.length; i<l; i++) {
elems[i].addEventListener('click',handler);
}
}
Now in your actual code, you can just write:
addEventToElements('p',function() {alert("click succeeded");});
Congratulations, you have re-invented jQuery.
... Or not, because the reason jQuery is so popular is that it does a lot more. Things like normalising browser support (for those that use attachEvent or the old onEventName handlers) are half the reason jQuery exists, and you'd have to account for all of them in your own re-invention ;)
Here's a shorter way.
document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('click', function(evt) {
if (evt.target.matches('p, p *')) alert('Paragraph clicked!');
}, false);
}, false);
Notes:
1) This has the advantage of event delegation, which is something I'd suggest looking into. In a nutshell, means you bind the event once, not N times, and then interrogate which element fired it when it fires, i.e. in the callback, not at the point of declaring the event as you are currently.
2) For waiting to use elements, use the DOMContentLoaded event rather than window.onload - the former is (loosely) analogous to jQuery's DOM ready handler.
3) matches() is a relatively modern method and won't work in ancient browsers, or may need a vendor-prefixed version - http://caniuse.com/#feat=matchesselector
for selecting:
document.querySelectorAll('p')
(also for more than one element p ).
AFAIK this is the closest thing to $('p')
addEventListener('click',function(){alert("click successful!")}
to add click handler to single element.
to simulate an array you can use the [].slice.call on the dom element collection (in this way you use .forEach() method) .
all together:
[].slice.call(document.querySelectorAll('p')).
forEach(function(x){x.addEventListener('click',
function(){alert("click successful!")
})})
https://jsfiddle.net/maio/m861hbmh/

Optimize live elements' selectors in jQuery

I read a lot about optimization in jQuery in some links below:
jQuery Website , Performance
jQuery Best Practices - Greg Franko
jQuery Coding Standards and Best Practices
14 Helpful jQuery Tricks, Notes, and Best Practices
and more ...
But none of them mentioned for .on() caching selectors. I don't know if there is any way to use cached elements in these kind of selectors.
for example I have a lot of these selectors in my script.js file.
$(document).on('click', '.menu li.remove', function(e){ ... });
$(document).on('click', '.menu li.edit', function(e){ ... });
$(document).on('click', '.menu li.action', function(e){ ... });
$(document).on('click', '.menu li.anotherAction', function(e){ ... });
and much more. .menu is a menu and can be anywhere in document, so I can't use specific id container to select it. like this:
$('#sidebar').on('click', '.menu li.action', function(e){ ... });
is there any way to optimize these selectors. Checking for existence maybe, caching .menu if it is possible.
When you need to eek out every last bit of performance, you probably need to ditch abstractions.
If you do your own delegation, you'll certainly see a performance improvement.
Because in the example you gave, all the delegation is identical except for the class name, I'd bind a single handler, put the code in separate functions, and then examine the e.target and its ancestors manually looking for the .menu li. If that's found, then check the class of the li, and invoke the correct handler.
var handlers = {
remove: function() {/*your code*/},
edit: function() {/*your code*/},
action: function() {/*your code*/},
anotherAction: function() {/*your code*/}
};
var targets = Object.keys(handlers);
document.onclick = function(e) {
e = e || window.event;
var li;
var node = e.target || e.srcElement;
var targetClass;
do {
if (!li) {
if (node.nodeName === "LI") {
li = node;
}
} else if (node.className.indexOf("menu") > -1) {
targetClass = li.className
break;
}
} while(node = node.parentNode);
if (!targetClass)
return;
for (var i = 0; i < targets.length; i++) {
if (targetClass.indexOf(targets[i]) > -1) {
handlers[targets[i]].call(li, e);
}
}
}
In the code above, as we traverse up from the e.target, we first check to see if we're on an li. If so, grab it and continue one.
As we continue, we no longer need to check for li elements, but we now need to check for an element with the menu class. If we find one, we grab the class name of the li we previously found and then halt the loop.
We now know we have our menu li.someClass element. So then we can use the class that we found on the li to look up the proper function to invoke from our list of functions we made above.
You should note that my .indexOf() class testing is ad hoc, and could result in false positives. You should improve it. Also, the code needs more tweaking since we're caching the li without knowing if it actually has a class that we're interested in. That should be fixed as well.
I'll leave it to you to add the necessary tweaks if you desire. :-)
I personally think you are worrying about speed where speed is not an issue.
If the menus are not loaded dynamically, there is nothing stopping you from combining delegated event handlers with normal jQuery selectors to target more of the closer elements (e.g. your .menu class):
e.g.
$('.menu').on('click', 'li.remove', function(e){ ... })
.on('click', 'li.edit', function(e){ ... })
.on('click', 'li.action', function(e){ ... })
.on('click', 'li.anotherAction', function(e){ ... });
This will create a handler on each menu (so closer to the elements).
If your menus are dynamically loaded, then your existing code is perfectly fine, as my understanding is that delegated event handlers only apply the selector argument to the elements in the bubble chain. If that is the case, delegated events will be pretty darn fast anyway. Certainly faster than you can click your mouse! I have never had speed issue with delegated event handlers and I probably overuse them in my plugins (I always assume dynamic content in those).

Jquery window.load function and Ajax call

I'm using the following jquery code in my page:
jQuery(window).load(function(){
jQuery('#narrow-by-list dd > ol.filter_list').each(function(){
var FormHeight = jQuery(this).outerHeight();
if(FormHeight > 70){
jQuery(this).next('.layer_nav_more').css("display", "inline-block");
jQuery(this).height(70).css("display", "block");
}else{
jQuery(this).height(70).css("display", "block");
}
});
jQuery(".layer_nav_more").click(function(){
jQuery(this).prev('.filter_list').animate({ height:205 }, 500, function() {
jQuery(this).addClass("scrollable");
});
});
});
The page also uses ajax calls to update it's content, so after content is refreshed the jquery code is ignored. I don;t think that posting the full js file which handles ajax will help you. I guess that the following lines should be quite ok for you to understand what's going on:
requestUrl = document.location.href
if (requestUrl.indexOf('#') >= 0) {
var requestUrl = requestUrl.substring(0,requestUrl.indexOf('#'));
}
if (requestUrl.indexOf('?') >= 0) {
requestUrl = requestUrl.replace('?', '?no_cache=true&');
} else {
requestUrl = requestUrl + '?no_cache=true';
}
requestUrl = this.replaceToolbarParams(requestUrl);
this.showLoading();
new Ajax.Request(requestUrl, {
method : 'post',
parameters : parameters,
onSuccess: this.onSuccessSend.bindAsEventListener(this),
onFailure: this.onFailureSend.bindAsEventListener(this)
});
What can I do to fix this?
EDIT:
I changed the code based on David's recommendations
jQuery(window).load(function(){
function adjust_list_height(){
jQuery('#narrow-by-list dd > ol.filter_list').each(function(){
var FormHeight = jQuery(this).outerHeight();
if(FormHeight > 70){
jQuery(this).next('.layer_nav_more').css("display", "inline-block");
jQuery(this).height(70).css("display", "block");
}else{
jQuery(this).height(70).css("display", "block");
}
});
}
adjust_list_height();
jQuery(document).on('click', '.layer_nav_more', function(){
jQuery(this).prev('.filter_list').animate({ height:205 }, 500, function() {
jQuery(this).addClass("scrollable");
});
});
});
so after content is refreshed the jquery code is ignored
No it isn't. It's not going to be automatically re-invoked, clearly, but why should it be? The handler you posted is for the window's load event. Unless you're loading the window again, I wouldn't expect the code to execute again.
It sounds like the problem is that you're adding new elements to the page after you've added click handlers to existing elements. Keep in mind that handlers are attached to elements, not to selectors. So if a particular element doesn't exist when you execute this code, it's not going to get a click handler.
The standard approach to this is to defer handling click events to parent elements. Any common parent element will do, as long as it's not removed/replaced during the life of the page. document is often used for this, but any parent div or anything like that would work just as well. Something like this:
jQuery(document).on('click', '.layer_nav_more', function(){
//...
});
What this does is attach the actual click handler to document instead of to the matching .layer_nav_more elements. When any element invokes a click, that event will propagate upwards through the parent elements and invoke any click handlers on them. When it gets to this handler on the document, jQuery will filter for the originating element using that second selector. So this will effectively handle any clicks from .layer_nav_more elements.
Any other functionality that you need to invoke when the page content changes (functionality besides delegate-able event handlers) would need to be re-invoked when you logically need to do so. For example, executing .each() over a series of elements like you're doing. There's no way to "defer" that, so you'd want to encapsulate it within a function of its own and simply execute that function whenever you need to re-invoke that logic.

Observing click of ajax code checks elements in order

I have a problem where I need the document observe click function (or something similar) to get elements in order from child out. The reason being is I want to have e.stopPropagation() on the child element, but the parent calls anyways because it is first in line.
For example:
<script type="text/javascript">
document.observe('click', function(e, el) {
if (el = e.findElement('.parent a')) {
e.stopPropagation();
}
if (el = e.findElement('.parent')){
alert('parent');
}
});
</script>
<div class="parent">
Child Click Function
Parent Click Function
</div>
In this example, parent gets found first, so the alert gets called. the reason I have to to do it this way rather than the element.observer is that its Ajax generated content and Prototype doesn't appear to have a live() function like jQuery.
Does anyone have a work around for this?
Thanks.
Prototype definitely does have a .live() equivalent and it is called Event.on(). It also works as an instance method, Element#on().
document.on('click', '.parent', function(event, element) {
if (!event.findElement('.parent a')) {
alert('parent');
}
});
You're observing on "document", the currentTarget of your event e is document.
'.parent a' and '.parent' exists in your document, so el.findElement('.parent a') and e.findElement('.parent') will always return an element.
.parent is not found first in your script, but you dont stop the process ( or use an if/else statement ) when you enter in your first if. So the process continue and the script find the .parent element in document.
Can you explain what you're trying to do ? Why do you want the event to stop bubbling ?
btw :
1) el is undefined in your script.
2) you use "=" and not "==" in your script when you're using findElement();

run a function when a user clicks on any list element

This is probably a very common question, but I was unable to find an answer myself;
All my list elements call the function setQuery like this
onClick="admin_stats.setQuery(this);"
rather than [hardcode] add this to every list element, is there a way to simply have it run when a list element is clicked?
I'm not very familiar with jQuery Live or binding/unbinding, but I think they would play a role here?
Before I reinvent a rather square-looking wheel I thought I might ask =)
edit: my list elements look like
<ul>
<li id="usersPerMonth" onClick="admin_stats.setQuery(this);">Users per Month</li>
<li id="statsByUser" onClick="admin_stats.setQuery(this);">Stats by User</li>
</ul>
the this.attr("id") is then used to look up what the actually SQL text looks like, from a json-style variable:
queries : {
usersPerMonth : " ... some sql ...",
statsByUser : " ... some sql ...",
...
}
so that's why I have the divs named that way (and I'm open to design suggestions)
$(function() {
$('#myList').delegate('li', 'click', function() {
admin_stats.setQuery( this );
});
});
This assumes your <ul> element has the ID myList. It will handle clicks inside of it on any <li> elements, calling your function, and passing this as the argument.
The .delegate() code is wrapped in $(function() {}); so that it doesn't run until the DOM is ready. This is a shortcut for jQuery's .ready() function.
Yes - use jQuery like this:
$('li').live("click", function(event){
admin_stats.setQuery(event.target);
});
This is assuming you want to set it to every li element. You can find the documentation for live here: http://api.jquery.com/live/
What live does makes sure that all elements passed to it will always have the click handler in the function specified.
jQuery is probably your best bet. Are you adding new elements on the fly, or will the elements be there when the attach is ready to go? If they're "satic" in the sense that once the page is loaded that's it, you could use:
$(document).ready(function(){
$('li').bind('click',function(e){ // may need to change the selector to be more specific
admin_stats.setQuery(this);
});
});
that needs to fire onClick... so:
$('li').click(function(){
admin_stats.setQuery($(this));
});
In jQuery you select elements using CSS-style selectors (can use this if more elements will not be added; this uses .click()):
$(function() {
$('#liContainer li').click(function() {
admin_stats.setQuery(this);
});
});
We put the whole thing inside $(function() { ... }); because the page needs to be ready before binding the event handler. Alternatively, you could do it like this if more elements are added. This uses .delegate():
$(function() {
$('#liContainer').delegate('li', 'click', function() {
admin_stats.setQuery(this);
});
});

Categories

Resources