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');
}
});
Related
I am working with this code on this page: http://experiments.wemakesites.net/css3-treeview-with-multiple-node-selection.html
My problem, is that it want the selection to go in reverse. That is, to check all the parents of the box checked instead of the children.
For example, in the link above, if you were to click My Documents, I would want Documents and Libraries checked as well.
I have tried using parents(), and multiple versions of parent().parent().prev().prev(), and the only thing I came up with that was somewhat functional was this:
$(".acidjs-css3-treeview").delegate("label input:checkbox", "change", function() {
var
checkbox = $(this),
nestedList = checkbox.parent().parent().parent().prev().prev(),
selectNestedListCheckbox = nestedList.find("input:checkbox");
if(checkbox.is(":checked")) {
return selectNestedListCheckbox.prop("checked", true);
}
selectNestedListCheckbox.prop("checked", false);
});
This would pick only the first parent, and I would need all the parents. I suppose I could construct a loop to keep using this same script, but it seems very ugly.
What would be a cleaner way of doing what I am looking for?
You can do this using jQuery's parents() method, then checking the immediate children for inputs. You might need to modify this depending on the layout of your html. I've demonstrated this with a jsfiddle.
The key part is:
var checkboxes = $this.parents().children('input');
In full:
$('input').on('change', function() {
var $this = $(this);
var checkboxes = $this.parents().children('input');
if($this.is(":checked")) {
return checkboxes.prop("checked", true);
}
checkboxes.prop("checked", false);
});
I am trying to remove a class when clicking on an element with a specific class. I made this javascript and it does work. But is this correct syntax to do it this way Can this be more efficient?
// Clear notifications alerts
$(document).on('click', '.js-clear-notifications', function() {
$('.AlertNotifications').remove();
});
// clear inbox alerts
$(document).on('click', '.js-clear-inbox', function() {
$('.AlertInbox').remove();
});
Your javascript code is correct, provided that you load jQuery as well.
Furthermore you have the most efficient solution, where you use a single event handler to handle events that originate on multiple elements.
The alternative would be:
$('.js-clear-notifications').on('click', function() {
$('.AlertNotifications').remove();
});
Which attaches as many event handlers as there are elements in the jQuery object. Slightly less efficient, though probably you would never notice except in extreme cases.
To me a more proper way to do it is something like this:
...
$('.js-clear-inbox').on('click', function() {
$('.AlertInbox').remove();
});
...
I will also suggest to have more specific selectors i.e.
$('div .js-clear-inbox')
I hope that this helps.
I am editing this in response to the feedback in the comments.
If what you want is to remove all elements with AlertNotifications class, which is what your code does, then what you have is correct.
If what you want is to remove only the class, which is what the text of the post said, you want removeClass, not remove:
$('.js-clear-notifications').on('click',function() {
$(this).removeClass('AlertNotificitions');
}
The new way, if you have already defined the variable, the proper way to delete it from the DOM would be:
var elem = document.getElementById("myDiv");
elem.remove();
But if you are just beginning out, .remove would be your best opinion.
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).
I am making a Sentence Generator. So far, it can take a list of words. Then it gets data from sentence.yourdictionary.com to take a sentence. I display the first sentence from sentence.yourdictionary.com using $("ul.example>li").first().Then it is put into a paragraph <p id="sents">.
So if you entered in the words yo and nose your output would be
<p id="sents"><li id="yo"><strong>Yo</strong> ' money
back a hund'd times, de preacher says!</li><li id="nose">I will no longer be caught with a
bleeding <strong>nose</strong>, heart palpatations, week long benders or hurting myself in any major way.</li></p>
I want a function to be called when you hover over the new list items.
$(document).ready( function() {
$("li").hover( function () {
$(this).append("<span>Testing</span>");
var id = $(this).attr("id");
console.log(id);
}, function () {
$(this).find("span:last").remove();
});
});
This doesnt work after the new list items are injected into the DOM. I tried adding an event listener for mousemove, but then when you hover over it the word "test" shows up a bunch of times! How can I make it happen after the new list items are injected?
Here is a jfiddle if you want some clarification: http://jsfiddle.net/varhawk5/cNKyx/1/
Thank you so much. Sorry I'm just learning javascript!
EDIT
To fix this issue, I used the .on() function as the comments suggested. There is no "hover" event though, so I think this is the only way.
$("body").on("mouseenter", "li#topWord", function() {
var word = $(this).data("word");
var sents = sentences[word]
$(this).html("<div class='restOfSents' data-word='" + word +"'></div>");
for(var i=1; i<sentences[word].length; i++) {
$(".restOfSents").append($(sentences[word][i]));
}
console.log(sents);
});
$("body").on("mouseleave", "li", function() {
// Remove the new div
});
$(document).ready( function() {
$(document).on('hover','li', function () {
$(this).append("<span>Testing</span>");
var id = $(this).attr("id");
console.log(id);
}, function () {
$(this).find("span:last").remove();
});
});
You're right! The reason for this, is that $(document).ready() only gets called on page load. You can either manually add a new event hook to each new element as you add it, or take advantage of jQuery's "on" functionality which will automatically detect new dom elements which match your criteria.
You should make use of .on() rather than .hover():
$('li').on('mouseenter', function() { ... })
Also you shouldn't use IDs for this. Make use of data-* attributes instead. Otherwise your code will break when a user enters the same word twice (as IDs are unique).
var id = $(this).attr('data-example');
I just want to know if there is any difference (especially in terms of performance) between these two approaches:
1)
$('.container').append('<div class="divChild">test div</div>');
$('.container .divChild').click(function() { alert('test'); });
2)
var $childDiv = $('<div class="divChild">test div</div>');
$childDiv.appendTo($('.container')).click(function() { alert('test'); });
So basically the second approach seems to be much faster since I don't really have to search for the divChild div twice, but I need to add click event to the div.
Is that so?
The first point to make here is: Unless you're doing this a lot (thousands of times in a very short period of time), it's unlikely to matter.
But I would suspect the most efficient thing to do would be to remember the result of $('.container') and then use children on it:
var c = $('.container');
c.append('<div class="divChild">test div</div>');
c.children('.divChild').click(function() { alert('test'); });
Or actually, technically, you don't need the variable:
$('.container')
.append('<div class="divChild">test div</div>')
.children('.divChild')
.click(function() { alert('test'); });
But I find long chains like that hard to read, hard to maintain, and hard to debug.
Another option is:
$('<div/>', {
'class': 'divChild',
'text': 'test div',
'click': function(){
alert('test');
}
}).appendTo('.container');
http://jsfiddle.net/SANMW/