What's the best way to find tbody rows in javascript? Regex or ...?
What I want to do is to filter a table to only show the tbody that contains a given value against a value entered into a textbox, and to show the filtered rows in a zebra stripe pattern.
The zebra striping is fast, the filtering is generally fast, except on the first filter on a table with a lot of tbodys (say 2000 tbody? ... I haven't measured for the first visible slowdown, and haven't tested for speed by the number, but it's slow in Firefox AND Chrome)
First the JS:
开发者_运维技巧//filter results based on query
function filter(selector, query) {
var regex = new RegExp( query, "i" ); // I did this from memory, may be incorrect, but I know the thing works, the problem is the next part, cos on 5 rows it's fast
$(selector).each(function() {
( regex.test( $(this).text() ) ) < 0) ? $(this).hide().removeClass('visible') : $(this).show().addClass('visible');
});
}
// then after this I recall the zebra function, which is fast.
Then the sample data:
<table>
<thead>
<tr>
<th>value to find 1</th>
<th>value to find 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>12345</td>
<td>67890</td>
</tr>
<tr>
<td>empty for now, while testing</td>
<td>may contain other info later</td>
</tr>
</tbody>
<tbody>
<tr>
<td>23456</td>
<td>78901</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
<tbody>
<tr>
<td>45678</td>
<td>90123</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
... /ad nauseum, for 2000 rows +
<tfoot>
</tfoot>
</table>
So for instance, trying to match value 123
would return the first and third rows of this sample data, but I think you already figured that out ...
HELP?
Any time you work with this many DOM elements your main performance hit is generally rendering since every time you modify something in the DOM a browser will re-render the page. This will turn your rendering performance into O(n^2) unless you modify the elements outside of the DOM. There are a few ways to do this like cloning and DOM arrays. To use cloning you clone
the elements you wish to modify, modify the cloned elements and then insert them into the DOM with replaceWith
. DOM arrays are just standard JavaScript arrays holding DOM elements. You can pass an array of DOM elements to jQuery instead of a selector. Here's the output of my test using copies of your html. I'm using jQuery 1.4.2.
// Using chrome 6.0.472.62
Number of <td> elements: 9524
Optimized Time: 232ms
Normal Time: 21669ms
Other modern browsers have different performance characteristics.
// Using IE8
Number of <td> elements: 9524
Optimized Time: 1506ms
Normal Time: 4179ms
// Using Firefox 4 Beta
Number of <td> elements: 9524
Optimized Time: 698ms
Normal Time: 2644ms
You can see how the O(n^2) rendering really starts to add up. Here's my test program minus the thousands of copied html elements.
$(document).ready(function(){
console.log("Number of <td> elements: " + $("td").length);
$('input[value=clone]').click(function(){
function filter(selector, query) {
var regex = new RegExp( query, "i" );
var temp = $("table").clone();
var hide = [];
var show = [];
$(selector, temp).each(function() {
if (regex.test($(this).text())) {
hide.push(this);
} else {
show.push(this);
}
});
$(hide).hide().removeClass('visible');
$(show).show().addClass('visible');
$("table").replaceWith(temp);
}
var start = (new Date).getTime();
/* Run a test. */
filter("td","12345");
var diff = (new Date).getTime() - start;
console.log("Optimized Time: " + diff + "ms");
});
$('input[value=normal]').click(function(){
function filter(selector, query) {
var regex = new RegExp( query, "i" );
$(selector).each(function() {
if (regex.test($(this).text())) {
$(this).hide().removeClass('visible');
} else {
$(this).show().addClass('visible');
}
});
}
var start = (new Date).getTime();
/* Run a test. */
filter("td","12345");
var diff = (new Date).getTime() - start;
console.log("Normal Time: " + diff + "ms");
});
});
Another option if you don't want to clone is to detach the elements you're working on and then reattach them once your done. See detach.
I think it would be better to use a filter function on a set of data, which would be an array of objects. Then you reconstruct the table from the filtered data provider.
There are inherent problems with showing/hiding table rows, not the least of which is the fact that different browsers (I'm looking at you, IE) do the hiding different. Merely setting a row to visibility="hidden" won't do what you want. Setting it to display="none" will, but then you have a problem getting it back. What do you set the display style to in that case? Not "block", certainly. And setting it to table-row behaves differently cross-browser.
Just an idea, is this (pun intended) faster?
//filter results based on query
function filter(selector, query) {
var regex = new RegExp( query, "i" ); // I did this from memory, may be incorrect, but I know the thing works, the problem is the next part, cos on 5 rows it's fast
$(selector).each(function() {
me = $(this);
( regex.test( me.text() ) ) < 0) ? me.hide().removeClass('visible') : me.show().addClass('visible');
});
}
// then after this I recall the zebra function, which is fast.
Should cut down at least one by row constant by 2/3rds.
Also, do you really need to remove and add a class on each row -- if it is hidden you can check for that if you need to know if it is visible or or not.
use jQuery selectory:
//filter results based on query
// all elements in selector must not have class visible set
function filter(selector, query) {
var newSel = selectory+":contains('"+query+"')";
$(newSel).show().addClass('visible');
}
//filter results based on query
// safe version... hides all elements first.
function filter(selector, query) {
$(selector).hide().removeClass('visible');
var newSel = selector+":contains('"+query+"')";
$(newSel).show().addClass('visible');
}
精彩评论