Friday, July 21, 2006

Safari and the broken JavaScript TD.cellIndex property

There is a JavaScript property that Safari does not properly implement. It is the TD.cellIndex. It always returns a zero value.

Where would such a property would be used? Most commonly in sorting of HTML tables through JavaScript. Generally, the header cell of each column has an onClick="tableSort(this);" so within the sorting function, through the use of cellIndex the JavaScript will know what column the user wants to sort, and through multiple uses of parentElement the function can back out to the TBODY element of the TABLE to work with the contained nodes which contains the TR elements and each TR element contains more nodes which are the TD elements.

Simple? Make Sense? Good! Now you can probably figure out on your own how to perform a dynamic sort on an HTML table without relying upon any other JavaScript. That gives me an idea on another Blog Posting... Hyper Table Sorting! Coming soon to a browser near you!

This is a simple fix. If it is ever fixed in a future release of Safari, it will automatically disable itself (except when the true value of cellIndex is zero) to help ensure the best performance.

There are a few assumptions to keep this example simple:
1) the object that is passed in to this function will only be a TD object.
2) It is an acceptable performance hit to always go through the for loop when the true cellIndex is zero. The first element will be picked off, so the hit should not be too bad. The general consensus being that the first column of a table may be the default sort column so the odds of it be resorted on that column is reduced.
3) Safari browsers will be common. If they were not, may want to add a hook such as "isSafari && obj.cellIndex == 0" to allow other browsers to bypass the for loop.


function cellIndexFn( obj )
{
  var ci = ( obj.cellIndex == 0 ? -1 : obj.cellIndex );

  for ( var x = 0; ci == -1 && x < obj.parentElement.cells.length; x++ )
  {
    if ( obj === obj.parentElement.cells[x] ) {
      ci = x;
    }
  }
  return ci;
}


The following is a simple way to test for a Safari browser:

  var isSafari = navigator.userAgent.indexOf("Safari") != -1;



The modified code that will contain the check for Safari to minimize the performance hit:

function cellIndexFn( obj )
{
  var isSafari = navigator.userAgent.indexOf("Safari") != -1;
  var ci = ( isSafari && obj.cellIndex == 0 ? -1 : obj.cellIndex );

  for ( var x = 0; ci == -1 && x < obj.parentElement.cells.length; x++ )
  {
    if ( obj === obj.parentElement.cells[x] ) {
      ci = x;
    }
  }
  return ci;
}



Referances
This page covers many good ideas and topics, but see example 3.27 and the related code around it.
Great article in Object Detection. Can help prevent many pitfalls with future releases to any browser. A must read!

2 comments:

Gerry Power said...

Thanks Scott. Just what I needed! One small performance optimization:

In the for loop, the line ' ci = x;' could become ' ci = x; break;' to add a tad o' performance!

Cheers - Gerry

Scott Tabar said...

Thanks Gerry!

It really is not that big of a performance saving by adding the break; statement to the cellIndexFn() function since it will naturally break before starting the next loop due to the condition in the for statement.

Personally, I tend to avoid the use of goto, break, resume, or any other tag or statement in any language that disrupts or bypasses the normal flow of logic. I do feel that goto must never be used under any circumstances, including threat of death! It just is poorly structured!

But don't be too quick to sing the praises of break yet, I can understand the reasoning for the use of other tags such as break. I just feel that the savings in performance would be fairly trivial since in this example it will not process another trip through the for loop anyway. I do feel that the use of those kinds of statements or tags could lead to maintainability issues. Granted it may not appear as such with these small examples, but if you are dealing with a system of 200,000+ lines of code and their use is splattered all over, it could really make the program a bit more trying and could lead to some serious support issues.

I really don't like to use performance gaining techniques just for the sake of gaining performance. The code still must be readable, understandable, and above all, supportable by the next person! Good code can help reduce bugs, but not always!

It is a personal thing I guess. And anyway, that is a completely different subject! ;-)

Thanks for your feed back Gerry!

Scot Tabar