Optimize live elements' selectors in jQuery - javascript

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).

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/

jQuery - performance of element detection

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');
});

Enable/Disable events of DOM elements with JS / jQuery

I stuck here with a little problem I have put pretty much time in which is pretty bad compared to its functionality.
I have tags in my DOM, and I have been binding several events to them with jQuery..
var a = $('<a>').click(data, function() { ... })
Sometimes I would like to disable some of these elements, which means I add a CSS-Class 'disabled' to it and I'd like to remove all events, so no events are triggered at all anymore. I have created a class here called "Button" to solve that
var button = new Button(a)
button.disable()
I can remove all events from a jQuery object with $.unbind. But I would also like to have the opposite feature
button.enable()
which binds all events with all handlers back to the element
OR
maybe there is a feature in jQuery that actually nows how to do that?!
My Button Class looks something similar to this:
Button = function(obj) {
this.element = obj
this.events = null
this.enable = function() {
this.element.removeClass('disabled')
obj.data('events', this.events)
return this
}
this.disable = function() {
this.element.addClass('disabled')
this.events = obj.data('events')
return this
}
}
Any ideas? Especially this rebind functionality must be available after disable -> enable
var a = $('<a>').click(data, function() { ... })
I found these sources that did not work for me:
http://jquery-howto.blogspot.com/2008/12/how-to-disableenable-element-with.html
http://forum.jquery.com/topic/jquery-temporarily-disabling-events
-> I am not setting the events within the button class
Appreciate your help.
$("a").click(function(event) {
event.preventDefault();
event.stopPropagation();
return false;
});
Returning false is very important.
Or you could write your own enable and disable functions that do something like:
function enable(element, event, eventHandler) {
if(element.data()[event].eventHandler && !eventHandler) { //this is pseudo code to check for null and undefined, you should also perform type checking
element.bind(event, element.data()[event]);
}
else (!element.data()[event] && eventHandler) {
element.bind(event, element.data()[event]);
element.data({event: eventHandler}); //We save the event handler for future enable() calls
}
}
function disable(element, event) {
element.unbind().die();
}
This isn't perfect code, but I'm sure you get the basic idea. Restore the old event handler from the element DOM data when calling enable. The downside is that you will have to use enable() to add any event listener that may need to be disable() d. Otherwise the event handler won't get saved in the DOM data and can't be restored with enable() again. Currently, there's no foolproof way to get a list of all event listeners on an element; this would make the job much easier.
I would go on this with different approach:
<a id="link1">Test function</a>
<a id="link2">Disable/enable function</a>
<script type="text/javascript">
$(function() {
// this needs to be placed before function you want to control with disabled flag
$("#link1").click(function(event) {
console.log("Fired event 1");
if ($(this).hasClass('disabled')) {
event.stopImmediatePropagation();
}
});
$("#link1").click(function() {
console.log("Fired event 2");
});
$("#link2").click(function() {
$("#link1").toggleClass("disabled");
});
});
</script>
This may not be what you require, since it may effect also other functions binded into this event later. The alternative may be to modify the functions itself to be more like:
$("#link1").click(function(event) {
console.log("Fired event 1");
if ($(this).hasClass('disabled')) {
return;
}
// do something here
});
if that is an option.
Instead of adding event handler to each element separately, you should use event delegation. It would make much more manageable structure.
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
http://cherny.com/webdev/70/javascript-event-delegation-and-event-hanlders
http://brandonaaron.net/blog/2010/03/4/event-delegation-with-jquery
This why you can just check for class(es) on clicked element , and act accordingly. And you will be able even to re-eanble them , jsut by changing the classes of a tag.
P.S. read the links carefully, so that you can explain it to others later. Event delegation is a very important technique.
You could use an <input type="button"> and then use $("#buttonID").addAttr('disabled', 'disabled'); and $("#buttonID").removeAttr('disabled');. Disabling and enabling will be handled by the browser. You can still restyle it to look like an anchor, if you need that, by removing backgrounds and borders for the button. Be aware though, that some margins and padding might still bugger u in some browsers.

Do looping through items and adding handlers to it hurt performance

Does it hurt in performance when I loop through list-items and add a click-handler to all separate items?
The reason I do this is because I would only like to make the list item clickable if it contains an hyperlink.
The code I'm currently using is:
$('ul.paginator li').each(function() {
if ($('a', this).length > 0) {
$(this).css('cursor', 'pointer');
$(this).click(function() {
location.href = $('a', this).attr('href');
});
}
});
I'm not sure how much it might hurt performance, but have you considered using a somewhat simplified jQuery selector:
$('ul.paginator li:has(a)').each(
function(){
$(this).css('cursor','pointer').click(
function(){
location.href = $(this).find('a').attr('href');
});
});
Incidentally, the performance would depend on the number of elements you're searching through more than anything else. Just a few and it's likely to be imperceptible, a few thousand and it will (probably) be noticeable.
Edited to reduce the expense of has():
$('ul.paginator li a').each(
function(){
var address = this.href;
$(this).closest('li').css('cursor','pointer').click(
function(){
location.href = address;
});
});
This should be less expensive, as it will select only those a elements within an li, and then move up to affect that li element.
depends how many rows there are. If there are thousands of them, then yes. If there are a modest amount then not really enough to be noticeable.
An alternative approach would be to put the click handler on the element that contains the items, and then when a click event comes in, to use the data in the event passed to the handler to determine what to do. One handler.
Yes, it is better to use delegate with a proper selector that selects only the items you want.
There will be only one handler created and attached.
If you don't want to use has() than this will be enough (no need for multiple handlers):
$('ul.paginator').delegate('click', 'li', function() {
var link = $('a', this);
if (link.length > 0) {
location.href = link.attr('href');
}
});

Simple click event delegation not working

I have a div
<div class="myDiv">
somelink
<div class="anotherDiv">somediv</div>
</div>
Now, using event delegation and the concept of bubbling I would like to intercept clicks from any of myDiv, myLink and anotherDiv.
According to best practices this could be done by listening for clicks globally (hence the term 'delegation') on the document itself
$(document).click(function(e) {
var $eventElem = $(e.target);
var bStopDefaultClickAction = false;
if ($eventElem.is('.myDiv'))
{
alert('Never alerts when clicking on myLink or anotherDiv, why????');
bStopDefaultClickAction = true;
}
return bStopDefaultClickAction;
});
See my alert question above. I was under the impression that clicks bubble. And it somewhat does because the document actually receives my click and starts delegating. But the bubbling mechanism for clicks on myLink and anotherDiv doesn't seem to work as the if-statement doesn't kick in.
Or is it like this: clicks only bubble one step, from the clicked src element to the assigned delegation object (in this case the document)? If that's the case, then I need to handle the delegation like this:
$('.myDiv').click(function(e) {
//...as before
});
But this kind of defeates the purpose of delegation as I now must have lots of 'myDiv' handlers and possibly others... it's dead easy to just have one 'document' event delegation object.
Anyone knows how this works?
You should use live event from JQuery (since 1.3), it use event delegation :
http://docs.jquery.com/Events/live
So you code will be :
$(".myDiv").live("click", function(){
alert('Alert when clicking on myLink elements. Event delegation powaa !');
});
With that, you have all the benefices of event delegation (faster, one event listener etc..), without the pain ;-)
The event target will not change. You need to mirror what jquery live does and actually check if $eventElem.closest('. myDiv') provides a match.
Try:
$(document).click(function(e) {
var $eventElem = $(e.target);
var bStopDefaultClickAction = false;
if ( $eventElem.closest('.myDiv').length )
{
alert('Never alerts when clicking on myLink or anotherDiv, why????');
bStopDefaultClickAction = true;
}
return bStopDefaultClickAction;
});
Event.target is always the element that triggered the event, so when you click on 'myLink' or 'anotherDiv' you store a reference to these objects using $(e.target); So what you do in effect is: $('.myLink').is('.myDiv') which returns false, and that's why the alert() is not executed.
If you want to use event delegation this way, you should check wheter event.target is the element or any of its children, using jQuery it could be done like this:
$(e.target).is('.myDiv, .myDiv *')
Seems to work fine to me. Try it here: http://jsbin.com/uwari
Check this out: One click handler in one page
var page = document.getElementById("contentWrapper");
page.addEventListener("click", function (e) {
var target, clickTarget, propagationFlag;
target = e.target || e.srcElement;
while (target !== page) {
clickTarget = target.getAttribute("data-clickTarget");
if (clickTarget) {
clickHandler[clickTarget](e);
propagationFlag = target.getAttribute("data-propagationFlag");
}
if (propagationFlag === "true") {
break;
}
target = target.parentNode;
}
});

Categories

Resources