Fastest way to hide thousands of <li> elements? - javascript

I have an autocomplete form where the user can type in a term and it hides all <li> elements that do not contain that term.
I originally looped through all <li> with jQuery's each and applied .hide() to the ones that did not contain the term. This was WAY too slow.
I found that a faster way is to loop through all <li> and apply class .hidden to all that need to be hidden, and then at the end of the loop do $('.hidden').hide(). This feels kind of hackish though.
A potentially faster way might be to rewrite the CSS rule for the .hidden class using document.styleSheets. Can anyone think of an even better way?
EDIT: Let me clarify something that I'm not sure too many people know about. If you alter the DOM in each iteration of a loop, and that alteration causes the page to be redrawn, that is going to be MUCH slower than "preparing" all your alterations and applying them all at once when the loop is finished.

Whenever you're dealing with thousands of items, DOM manipulation will be slow. It's usually not a good idea to loop through many DOM elements and manipulate each element based on that element's characteristics, since that involves numerous calls to DOM methods in each iteration. As you've seen, it's really slow.
A much better approach is to keep your data separate from the DOM. Searching through an array of JS strings is several orders of magnitude faster.
This might mean loading your dataset as a JSON object. If that's not an option, you could loop through the <li>s once (on page load), and copy the data into an array.
Now that your dataset isn't dependent on DOM elements being present, you can simply replace the entire contents of the <ul> using .html() each time the user types. (This is much faster than JS DOM manipulation because the browser can optimize the DOM changes when you simply change the innerHTML.)
var dataset = ['term 1', 'term 2', 'something else', ... ];
$('input').keyup(function() {
var i, o = '', q = $(this).val();
for (i = 0; i < dataset.length; i++) {
if (dataset[i].indexOf(q) >= 0) o+='<li>' + dataset[i] + '</li>';
}
$('ul').html(o);
});
As you can see, this is extremely fast.
Note, however, that if you up it to 10,000 items, performance begins to suffer on the first few keystrokes. This is more related to the number of results being inserted into the DOM than the raw number of items being searched. (As you type more, and there are fewer results to display, performance is fine – even though it's still searching through all 10,000 items.)
To avoid this, I'd consider capping the number of results displayed to a reasonable number. (1,000 seems as good as any.) This is autocomplete; no one is really looking through all the results – they'll continue typing until the resultset is manageable for a human.

I know this is question is old BUT i'm not satisfied with any of the answers. Currently i'm working on a Youtube project that uses jQuery Selectable list which has around 120.000 items. These lists can be filtered by text and than show the corresponding items. The only acceptable way to hide all not matching elements was to hide the ul element first than hide the li elements and show the list(ul) element again.

You can select all <li>s directly, then filter them: $("li").filter(function(){...}).hide() (see here)
(sorry, I previously posted wrong)

You can use the jQuery contains() selector to find all items in a list with particular text, and then just hide those, like this:
HTML:
<ul id="myList">
<li>this</li>
<li>that</li>
<ul>​
jQuery
var term = 'this';
$('li:contains("' + term + '")').hide();​

You could use a more unique technique that uses technically no JavaScript to do the actual hiding, by putting a copy of the data in an attribute, and using a CSS attribute selector.
For example, if the term is secret, and you put a copy of the data in a data-term attribute, you can use the following CSS:
li[data-term*="secret"] {
display: none;
}
To do this dynamically you would have to add a style to the head in javascript:
function hideTerm(term) {
css = 'li[data-term*="'+term+'"]{display:none;}'
style = $('<style type="text/css">').text(css)
$('head').append(style);
}
If you were to do this you would want to be sure to clean up the style tags as you stop using them.
This would probably be the fastest, as CSS selection is very quick in modern browsers. It would be hard to benchmark so I can't say for sure though.

How about:
<style>
.hidden{ display: none; }
</style>
That way you don't have to do the extra query using $('.hidden').hide() ?

Instead of redefining the Stylesheets rules, you can directly define 'hide'
class property to "display:none;" before hand and in your page, you can just
apply the class you defined after verifying the condition through javascript,
like below.
$("li").each(function(){if(condition){$(this).addClass('hide');}});
and later, if you want to show those li's again, you can just remove the class like below
$("li").each(function(){if(condition){$(this).removeClass('hide');}});

Related

Best way to optimize jQuery selector and why?

I am using this jQuery selector multiple times in my JSP:
$("#customers tbody tr td input[type='checkbox'][name='selectedCustomers']")
The solution I found on some blogs is that I should do first:
var customer=$('#customers')
And then use the above customer object for further calls.
customer.find("tbody tr td input[type='checkbox'][name='selectedCustomers']")
My question is, does this solution will make any difference and why?
My understanding
When I do
$("#customers tbody tr td input[type='checkbox'][name='selectedCustomers']")
jQuery internally will first get the object associated with div id="customers"
(by document.getElementById("customers")) and then will traverse to the specified
checkbox. And if I go by the suggested solution then document.getElementById("customers") will be fired only once and the rest will be the same. So I am saving myself from unnecessary multiple document.getElementById but the rest will be the same. Is my understanding correct? If yes is, just for the sake of my knowledge, is document.getElementById a more costly operation?
EDIT:-
i am not using only above said selector multiple times but also other possible selector under div id="customer". So question again is whats is difference in terms of performance if I cache the customer object first and if i don't do it?
There is no way you need to be that specific. I'm guessing, at the very most, this:
$('#customers td [name="selectedCustomers"]')
... which should improve performance. Next, if you're actually querying for selectedCustomers each time, you should cache the whole thing:
var selectedCustomers = $('#customers td [name="selectedCustomers"]');
Otherwise, if the name is dynamic, and you only have one item with the same name per page...
var item = $(document.getElementsByName(someName)[0]);
Caching just $('#customers'), on the other hand, is pretty much pointless. .find on customers will do just as much work as the whole selector in the first place, especially with querySelector support.
You seem to be missing the fundamental point of caching the object. Once the object is cached, any further traversal or manipulation within that selector will be performed on the stored object and doesn't require a search of the DOM to first locate the selector and create the collection every time you need to use it
Every time you call $("#customers tbody tr td input[type='checkbox'][name='selectedCustomers']") a search of the document has to be performed to create the collection of elements before any changes can be made to the collection.
Caching the collection means no further searches need to be made therefore improving performance
/* locate and store the collection once*/
var $checkboxes=$("#customers tbody input[name='selectedCustomers']");
/* look within previously stored collection*/
$checkboxes.filter(/* expression*/ ).doSomething();
Using document.getElementById will be faster than a jQuery search, simply because it doesn't require addiitonal function calls made by jQuery library. However if you wish to use result as a jQuery object like:
$( document.getElementById('foo'))
the gains are likely not worth worrying about for a single use to cache an object
So I am saving myself from unnecessary multiple document.getElementById but the rest will be the same.
Yes. But maybe also no, as selectors are evaluated from right to left (see this article or this SO question). And assuming an efficient engine, it had less work to do if it does that evaluation only on a part of the document tree if you first select #customers and then .find() in it. But I'm not 100% sure about that.
Is document.getElementById a more costly operation?
No, it is very cheap. Ids are the standard attribute to identify single elements, and browsers will build very performant lookup tables for it - you can assume it to be nearly O(1).
customer.find("tbody tr td input[type='checkbox'][name='selectedCustomers']")
On the other hand, DOM selector queries which need to evaluate the DOM tree are very costly, especially if done manually in JS code (jQuery sizzle) and not native - though this rather simple query will be delegated to the native querySelectorAll.
I am guessing that #customers is your table element. So for performance, omit the tbody tr td tags, they are obligatory (assuming you have not used them to explicitly exclude checkboxes from <thead>/<tfoot> or <th> elements). You will not find an <input> as a direct child of a table element anyway - and the selector engine has much less to do.
Further, if you know your markup well and can make the assumption that only checkboxes have that name attribute, you might omit the tagname and type attribute selectors as well. And that means you can delegate to the native getElementsByName, which should boost performance a little bit again:
$(document.getElementById("customers").getElementsByName("selectedCustomers"))
If you need to check for the elements to be checkboxes, you still could filter them. With that, you might end up with
$(customer.get(0).getElementsByName("selectedCustomers")).filter(":checkbox")
However, to proof the performance gains you only can test, test, test; and you'll need to do that on your actual full page.
http://jsperf.com/different-jquery-selector-tests
Check out this little test. Basically $('#div').find('#p'); is the fastest and $('div').find('#p'); is the slowest.

Repeated IDs in an html document .. how bad an idea is it if they are scoped by a div with a unique ID?

I have an webpage which shows a single item for sale with an add to basket button. The page makes use of alot of javascript to allow the user to customise the item.I now need to modify the page to show multiples of similar items on the same page, each additional item also customisable in the same way by the user. The javascript makes heavy use of id's in the markup to find elements and manipulate them to provide the client side item customisation.
My 1st thought is to allow the html markup to repeat for each item,also allowing the IDs to repeat themselves but add an additional div with a unique ID around each items markup to separate the scope of the repeated ID's , making the repeated ID's unique within the containing div. This should allow the javascript to stay relatively the same with the exception that any references to repeated ID's will need to be scoped for a particular DIV ID
Bearing in mind I want the outcome to be cross browser compatible , IE6 -IE9 , Firefox 3+ , Chrome, Safari, Opera, how sensible does this approach sound? Will some browsers disallow repeated IDs or behave badly with them? Any advice as to a better approach or things I should look out for would be very welcomed.
Thanks
-----------------addendum----------------------------------------------------------------
It seems the overwhelming consensus is that it's a really really bad idea to repeat ID's mostly because the standards say id's should be unique and although some/most browsers support it now , there's no guarantee for the future. I agree with you all on this totally.
The class approach seemed to be the best route to take from the advice received but now it looks like older browsers won't support it, specifically IE6 and 7. Any advice on a way forwards from here?
-----------------addendum----------------------------------------------------------------
On balance getElementsByTagName seems to be the most compatible way forwards , covering a good spectrum of mobile browsers too.
Thanks for all your advice.
Don't reuse id's. Ever. It results in very unexpected behavior. If you need to reuse markers then make use of classes instead.
If you have the following syntax
<div id="container1"><span id="a"></span></div>
<div id="container2"><span id="a"></span></div>
What would you expect document.getElementById('a') to do?
Instead use:
<div id="container1"><span class="a"></span></div>
<div id="container2"><span class="a"></span></div>
Then you can access them via.
document.getElementsByClassName('a')
Beginning with HTML 4.01 specification dated Dec 24, 1999 it is invalid if the id attribute has a duplicate value.
You shoud use class, both referenced here:
http://www.w3.org/TR/html401/struct/global.html#h-7.5.2
For this sort of thing, I usually iterate through the childNodes or use getElementsByTagName with the retrieved element.
<div id="div_with_id">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
<script>
var div = document.getElementById("div_with_id");
var cNodes = div.getElementsByTagName("div");
for(var i = 0, l = cNodes.length; i < l; i++) {
console.log(cNodes[i].innerHTML);
}
</script>
I use getElementById only when necessary, which it turns out, isn't all that often ;)
Remember: getElementsByTagName gets all elements of a type, including elements within elements. childNodes gets only the top level, but gets everything in the element, including text nodes.
The DOM is not the supplied text. The ID is supposed to be unique in the DOM. If a browser allows more than one node with the same ID, there's no guarantee that it will do so in the future. So even if it works, you shouldn't take advantage of that fact. Just do the right thing and either use a unique ID, or a class as appropriate.

Accessing Objects inside HTML Object with JS

I am creating an array of div tags inside the player table div. I'm getting all div tags with class .players. The divs with class name .players have input fieds and a link field inside. I want to be able to manipulate these (remove, add class, etc...)
What I thought would work would be something like:
$(divarray[j]+' .link').hide();
$(divarray[j]+' a').remove('.link');
But it's not working. Any thoughts? I'm sure it's something simple but it's my first time at JS :)
var divarray = $('#player-table > .players');
for( var j = 0; j < 10; j++){
$(divarray[j]).removeClass("players");
$(divarray[j]).addClass("selected_players");
$('#debug').append(divarray[j]);
$(divarray[j]+' a').hide();
}
First of all, you cannot just concatenate jQuery objects or DOM nodes with strings to create new selectors. jQuery provides methods for this kind of situations, where you already have an object or DOM node and want to find other related nodes.
Second, with jQuery there are much better ways to process a set of elements. Here is your code in more jQuery-like way. This is just an example, because I don't know the HTML structure. You have to adjust it so that it selects and applies to the correct elements.
$('#player-table > .players').slice(0,10) // gets the first 10 elements
.removeClass("players") // removes the class from all of them
.addClass("selected_players") // adds the class
.find('a').hide().end() // finds all descendant links and hides them
.appendTo('#debug'); // appends all elements to `#debug`
As you maybe see, there is only one semicolon at the last line. That means this whole code block is just one statement, but splitting it up over several lines increases readability.
It works because of the fluent interface, a concept which jQuery heavily makes use of. It lets you avoid creating jQuery objects over and over again, like you do ($(divarray[j])).
Another advantage is that you can work on the whole set of elements at once and don't have to iterate over every element explicitly like you have to do with "normal" DOM manipulation methods.
For learning JavaScript, I recommend the MDN JavaScript Guide.
jQuery has a couple of tutorials and a very good API documentation.
Read them thoroughly to understand the basics. You cannot expect to be able to use a tool without reading its instructions first.
Try this istructions
$(divarray[j]).find('.link').hide();
$(divarray[j]).find('a').remove('.link');
Try also
$(divarray[j]).find('.link:first').hide();
If you need to work only on the first element
Hope it helps

how to determine how many jQuery objects are on a page?

Can I determine the number of jQuery objects on a page?
I want to use the number of elements as a sort of weak benchmark for page complexity. I'm thinking that if I can reduce the number of elements that jQuery knows about , the page might run more efficiently.
Does this make sense?
Is it as simple as doing a * select and counting the results?
related:
How can I clear content without getting the dreaded “stop running this script?” dialog?
http://api.jquery.com/size/
var elementCount = $('*').size();
Although this might be more what you want:
var elementCount = $('body').find('*').size()
var n= 0;
for (var i in jQuery.cache)
n++;
Now n holds the number of elements jQuery has ‘touched’ (added data to, such as event handlers).
Previously this used to be a whole lot, as it would ‘touch’ every element it was even checking for data. This unpleasantness is fixed in jQuery 1.4.
As for clearing content, yes, you can use innerHTML= '' to remove all the content without giving jQuery the chance to detach its data so very slowly. If you know there are no ‘touched’ elements inside the element that's a win, but otherwise it's a potential memory leak until the page is reloaded, as unused jQuery.cache data stays around.
Using live()/delegate() event binding avoids adding data to its target elements, which can allow you to use this short-cut more freely. However if you have a lot of events to bind and they're not selectors that are very easy to match quickly, this can make event handling slow.
(Because there is no browser-native speedup like querySelectorAll for matching elements against a particular selector as delegation needs to do; this is proposed for Selectors-API level 2.)
var elementCount = $('*').length;
This doesn't really have much to do with jQuery except insofar as it's a handy way to get the answer.

What is the fastest way to get a dom element?

I'm performance-tuning my code, and am surprised to find that the bottleneck is not dom node insert, but selection.
This is fast:
var row = jquery(rowHTML).appendTo(oThis.parentTable);
but the subsequent getting of an element inside "row" is slow:
var checkbox = jquery(".checkbox input", row);
I need to get the checkbox in every row so I can attach an event handler to it. Selecting the checkbox is ALMOST 10X AS SLOW as inserting the entire parent row.
What am I doing wrong here?
DOM manipulation uses native functions to perform simple operations. Browser vendors optimize these. You are building the row from HTML. Internally jQuery is using .innerHTML to build the collection which then patches into the browser's mega-fast parser.
Selection is slow in comparison because JS code needs to loop through the DOM repeatedly. Newer browsers have native selection handling which provides dramatic speedups to selector based JS. As time moves on this will be less of a problem.
Here is how the query in question, $(".checkbox input", row), breaks down:
row.getElementsByTagName('*');
for-loop through every element returned (all elements within the row) and test elements[i].className with /(\s|^)checkbox(\s|$)/.
for-loop every element still remaining and collect matched[i].getElementsByTagName('input');
unique the final collection.
This is different for jQuery 1.3 as it's engine moves through the selector the other way around, beginning with getting all input elements and then testing the parent elements.
Rremember that the JS selector engines implement a lot more of the CSS selector spec than is actually usable with CSS (or implemented by current browsers). Exploiting this, and knowledge of the engines, we can optimize selector can be optimized in a few different ways:
If you know what element type the .checkbox is:
$("td.checkbox input", row);
It is faster for filter first for type and then for the class for only those matches. This doesn't apply for a very small subset of elements, but that is almost never the case in praxis.
The single class test is the slowest of the common selectors people actually use.
Simpler selection:
$("input[type=checkbox]", row);
One loop is faster than two loops. This only finds input elements and then directly filters them by type attribute. Since sub/child-elements are never used, unique may also be skipped (and smart engines will try to do this because unique is slow).
A more direct selector:
$("td:first.checkbox input", row);
A more complex selector may actually be faster if it is more direct (YMMV).
If possible, move the search context up to the table level:
By this I mean that instead of looping through the rows, and searching for the checkbox in every one, leave them alone until after the loop and then select them all at a time:
$("tr td:first.checkbox input", table);
The point of this is to eliminate the overhead of firing the selector engine up repeatedly, but instead do everything in one haul. This is presented here for completeness rather than something that I think would return massive speedups.
Don't select:
Build the row from bits, assigning events as you go.
var row = $( '<tr></tr>' );
var cell = $( '<td class="checkbox"></td>' ).appendTo( row );
$( '<input type="checkbox" name="..."/>' ).appendTo( cell ).click(/* ... */);
This may be impossible for reasons of Ajax or other templates out of your control. Additionally, the speed may not be worth turning your code into this sort of mess, but sometimes this may make sense.
Or, if none of these work for you, or return too performance gain, it may be time to rethink the method entirely. You can assign an event listener higher up the tree and grab the events there, instead of per-element instance:
$('table').change(function(e){
// you may want a faster check...
if ( $(e.target).is('input[type=checkbox]') ) {
// do some stuff ...
}
});
This way you don't do anything unless, and until, the user actually requests it. Fastest. :-)
var checkbox = jquery(".checkbox input", row);
This is traversing the entire dom tree to find the checkbox. You could possibly speed it up by changing the selector to an ID which can use the browsers native getElementById functionality.
var checkbox = jquery("#checkbox input", row);
You could also use your row as a starting point for the DOM search like the following example. Now your not parsing through the entire DOM tree again to find the matched element.
var row = jquery(rowHTML).appendTo(oThis.parentTable);
row.children(".checkbox input");
Use event delegation and add a single handler to a parent element and not the checkboxes themselves.
jQuery supports this via the live() function.
Try putting a class name on the input field itself. That may prove to be faster.
The reason for that is your code goes through all .checkbox classes, tries to find the input child of that element and returns that. I think that action might be your culprit.
By simply just looking for all elements with the class the input field has, you might see some speedup.
Try using Sly, it has an emphasis on performance.
If you're looking for performance, jQuery selectors are very slow. In the example there, it has to scan the full DOM tree and check CSS classes and so on to find the relevant nodes.
It is significantly faster to use native DOM methods. There are some interesting library performance comparisons here:
http://ajaxian.com/archives/taskspeed-more-benchmarks-for-the-libraries-and-browsers
The fastest way to get the DOM element is to use pure JavaScript and calling it by ID.
var element = document.getElementById('element);

Categories

Resources