views:

51

answers:

3

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?

A: 

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');
}
Hogan
Using this as my inspiration: http://net.tutsplus.com/tutorials/javascript-ajax/using-jquery-to-manipulate-and-filter-data/ so that add/remove class was his idea. He uses the filter class to grab the data being zebra'd later. I'm thinking that may be the bottleneck, as it were.
drachenstern
for sure. regex might be kinda slow -- of course you don't really have any other options, except to try a jquery selector....
Hogan
I'll test that on Monday...
drachenstern
@drachenstern : note new code -- if no regex you don't need the callback function with jQuery.
Hogan
Time constraints being what they are ... I want to test this one, but haven't yet. When I do get a chance to I'll come back and update the status on this
drachenstern
A: 

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.

Robusto
Good commentary. I've considered rebuilding it from scratch each time, wanted to get some input since I've been considering this over the weekend. Got any thoughts on that?
drachenstern
It should be the case that jQuery handles the cross browser issues with show() and hide() right?
Hogan
@drachenstein: This is the only non-kludgey way I can think of to get the alternate row colors (zebra striping). Otherwise you have to iterate through them all each time, which is maybe more hassle than the rebuild. @Hogan: for all I know jQuery would handle that correctly, but it's still a pain and a drain, and the rendering likely wouldn't be much faster than the rebuild.
Robusto
@Robusto : I personally would not load 2000 rows into the browser and instead use AJAX to grab the data -- but the question is based on an article which is using jQuery to show and hide rows -- so it is a safe bet it works. jQuery is like that, it makes crazy cross browser stuff easy (and maybe in this case to easy.)
Hogan
@Hogan @Robusto ~ Loading up to 40k rows to be sorted ... figured it was faster to do all my re-rendering client side rather than wait for the potential laggy db connection all for the sake of grepping for something I already have in the browser. (Also, the host page is asp.net, and my co-devs aren't quite as up on AJAX so I need supportable code...)
drachenstern
A: 

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.

gradbot
Even with swapping you can't obviate the rendering issue. Big tables done as strings via innerHTML still take a while.
Robusto
I like this technique, I'll have to try this one...
drachenstern
@Robusto I updated my answer showing the performance different on a large table.
gradbot
@gradbot: That's fine, and I do like the idea of using `clone` in this context. Nice work. Nevertheless, at some point the hide/show issue is going to come into the equation. removing a few rows causes all the other rows to be re-rendered, which eats up cycles and time.
Robusto
So ... testing shows that this regex version is a lot faster. I have not tried Hogan's answer yet tho ...
drachenstern
I'd be shocked if mine got more performance than this because of the reasons above, but my changes might make this even faster!!
Hogan