Projects‎ > ‎XBL‎ > ‎

Datatable Implementation Notes

Goals

The goal of this component is to provide a UI and features similar to the YUI datatable:
  • Sortable columns
  • Resizable columns
  • Pagination
  • Horizontal and vertical scrolling

Mismatch between XForms model and YUI datatable model

A first prototype has been developed based on the YUI component, however, this component implements high level features that overlap XForms features and is much higher level than the code generated by the XForms server. Most of the rather big JavaScript code that had been developed to do the interface between what is generated by the XForms server and the YUI component was attempting to kind of “disassemble” the code sent by the XForms server to guess the high level model and map it into the high level YUI component. This was clearly a kludge that has been useful to prototype this widget but it became clear that the component should rely on XForms rather than JavaScript for its final version.

With the current implementation of the datatable:
  • The component relies on the CSS definition from the YUI datatable, but does not use its JavaScript.
  • The YUI "decorations" that are added in JavaScript in the original YUI components are added by the XSLT transformation called by @xxbl:transform.
  • Pagination and sorts are performed in XForms.
  • Scrolling and resizing is done in JavaScript.

Resizing

The resizing is done using the same technique than the YUI ScrollingDataTable:
  • "Handles" are added in each resizable thead/tr/th cell. These handles are small div elements 5px large which are associated through CSS to a typical "resize column" pointer. They are positioned using position: absolute to the right of each resizable header cell.
  • These handles use the YUI Drag and Drop library and their drag event resizes the column according to their new position.

Scrolling

Vertical scrolling

Scrolling is a much harder problem than expected and interacts with the resizing. The main issue with vertical scrolling is that it is supported differently in Firefox and IE:
  • Firefox fully supports scrollable tbody elements and overflow-x/overflow-y CSS properties. It is thus quite easy to define tables with a fixed header and a body that scrolls vertically.
  • IE (up to 7.x included) doesn't support this method and requires to define the whole table as scrollable and fix its header using a proprietary CSS property: top: expression(offsetParent.scrollTop).
Brett Merkey provides a good example that shows how these two methods can be mixed support different browsers and the current implementation uses this method (with 3 JavaScript lines to resize elements and fix some glitches).

Horizontal scrolling

Horizontal scrolling is easier to achieve in a consistent way since you can just define a table that scrolls in its enclosing div. This is what is done in the current implementation with, here again a few (4) JavaScript lines to do some resizing and avoid that the table calculates its width to fit into the div in which it should scroll when its width isn't specified.

However, even this simple implementation has some downsides! For one thing, it doesn't play well with resizable columns in IE and if there are any resizable columns, the table body scrolls fine but the table header doesn't scroll. It took me quite a few cycles to discover that this is because the scroll handles use absolute positions and IE considers that this nails the handles and the header to which they belong.

Another issue is that you can't mix these two methods to implement tables that scroll both horizontally and vertically. Or rather, you can but the result isn't very interesting since the vertical scroll bar being part of the area that is horizontally scrolled is visible only when you scroll to the right end!

IE supports tables that nicely scroll in both directions through proprietary CSS properties, but Firefox seems to be hopeless unless you resort to use heavy JavaScript techniques.

Of course, the simple idea of scrolling the table body both horizontally and vertically isn't very user friendly since the table body scrolls nicely vertically but the header is fixes.

The technique that is proposed all over the web is also the one that has been used by the YUI for its datatable component:
  • The table is cloned, the table body of the clone is inserted before the real table and its body is removed.
  • The header of the original table is made invisible.
  • The original table is made scrollable in both direction.
  • A JavaScript handler synchronizes the position of the visible header (the cloned one which would be fixed otherwise) with the position of the table columns.
This appears to be the state of the art and there doesn't seem to be any cross browser alternatives. There are many examples of such implementations on the web including a jQuery one which is one of the few that isn't mixed with a bunch of other features.

Code overview

Initialization

On xforms-enabled:

Function
IE time
Style recalculations
Description
draw()2.2

Top-level function initializing the datatable.
  isDisplayed()0.9
getRegion()
Only proceed if the table is not rendered off-screen (e.g. within a switch). This uses YAHOO.util.Region.getRegion() , which calls _getXY().
  initProperties()0.3

Most of the time is spent in isSignificant(), which is called for every cell (1.5k times), each time checking for 3 classes.
    getAndSetColumns()0.1
 Goes through all the cells and stores them in this.headerColumns and this.bodyColumns.
  finish()1.0
Three times headerContainer. clientHeight (after initColumns())
 
    setSizes() 0.6 The table is split into 2 tables, one for the headers and one for the body, each in its own div.
      getActualOriginalTableWidth() 0.4 Using clientWidth to compute the width of the table.
    initColumns() 0.2Three times add style elementAll the time is spent in YAHOO.util.Selector.query(), called 660 times to get the div for a given cell.
resizeWhenStabilized() 1.3  
  draw() 0.6  
  isDisplayed() 0.4  
  reset() 0.3  


Performance

The following strategies can be used to improved the datatable performance:
  1. Reduce the of amount markup in cells — This is independent of the datatable itself. For instance, in the case where a lot of markup is non-relevant, this can be done by improving the full update mechanism not to sent non-relevant markup to the browser.

  2. Batch operations causing style recalculation — Some operations force the browser to so a relayout or style recalculation (SR) before it can proceed. Those can be expensive; they take more of 50% of the time on Chrome. When we have multiple datatable, by batching operations that require a SR we can reduce the number of SR. For instance, if we do A, B and C that require a SR:
    • If we have 1 datatable, we'll get: A, SR, B, SR, C, SR => 3 SR.
    • If we have 3 datatables, we'll get: A1, SR, B1, SR, C1, SR, A2, SR, B2, SR, C2, SR, A3, SR, B3, SR, C3, SR => 9 SR.
    • Instead, we could do: A1, A2, A3, SR, B1, B2, B3, SR, C1, C2, C3, SR => 3 SR.

Unit tests

We have 3 collections of unit tests for the datatable. Each one comes with its own XHTML and JavaScript file:

Tests collections
Browser
Tests failing
Comments
datatable-unittest (run it)Firefox 4b6
All tests passing
 
 IE 7
test314359
The column width are not synchronized on IE:

  testWidthsResizeable100pxRight
testWidthsResizeable50pxLeft

testWidthsResizeable10MorePxLeft
 
  test314210
 
datatable-dynamic-unittest (run it)Firefox 4b6
test314379Bug — The first column is too wide, the strange thing being that its width as seen in Firebug is not the one we set in JavaScript. See this illustration. This unit test has been temporarily disabled.
datatable-structure-unittest (run it)Firefox 4b6
All tests passing
 


Scrollable Table Resizing

Browsers do not overflow tables within a fixed size container if one doesn't specify a width.

In other words, a table with its width unset or set to "auto" or "100%" embedded within a div with a width of 500px and overflow set to auto will give something such as:



The table is dimensioned to fit within the div without overflowing at all and cells content is split into multiple lines where needed.

That's doesn't really look like a scrolling table! To get the scroll bars, you need to set the table width. For instance, the same table with a width set as 1000px will look like:



That's better, isn't it? But how can we determine the optimum table width?

A way to do so is to temporarily set the width of both the container and the table to auto:


Then, we can look at the actual width of the table, set the table width with this value and reset the container width to its original value.

That works well in many cases, but if the browser window is narrow we can still have something such as:

[This stupid system doesn't let me upload a fourth image!]

Here again, the value of a scrollable table isn't that obvious.

To solve this issue, the datatable tries several width to find the minimum width that minimizes the table height.


ą
Alessandro Vernet,
Jun 25, 2009, 9:08 AM
ą
Alessandro Vernet,
Jun 25, 2009, 9:17 AM
ą
Alessandro Vernet,
Jun 25, 2009, 9:20 AM
Comments