Reshapable Tables #

Despite all the ranting about tables vs. CSS, one place where they are unequivocally suitable is when displaying tabular data (though some people will mysteriously argue even about that). However, despite the fact that native OS controls for displaying such data offer all sorts of features, (X)HTML tables only provide the most basic functionality. Some enhancements have been made, but they are mostly for the sake of semantics or accessibility (<thead>, <tbody>, etc.) The W3C has only given us weakly worded proposals such as "User agents may exploit the head/body/foot division to support scrolling of body sections independently of the head and foot sections." (emphasis mine).

For an ongoing project I need a little more, and so I have come up with this more capable approach. Though it is still feature-poor compared to a native control, it is still a step forward (if we can have a wrapper for NSSearchField perhaps one for the data browser/NSTableView is warranted too?).

Firstly, column headers now stay put while the rest of the table is scrolled. This is primarily accomplished via the position: fixed CSS attribute. However, since IE lacks support for it, an alternative approach (suggested by this page) using dynamic properties had to be used. This basically sets the top attribute to a script snippet that is dynamically (re)evaluated to the document's vertical scroll offset. This does have the drawback that the header row had to be put in a separate table from the <tbody> portion, and in order to get the cells of these two sections to line up, the table-layout: fixed attribute had to be used.

However, there is a good reason to use fixed table layout anyway, since it allows us to implement the second feature, resizable columns. Making the dividers draggable was done by overlaying on top of their borders some <div>s with onmousedown/up/move handlers in a similar matter to the magnifier. However, instead of moving a <div>, we compute the horizontal percentage of the mouse's position and use that to change the table's column widths*. Another trick was to have the draggable column divider <div> change its width when a drag begins. Normally it is only ten pixels wide, centered on the column border. However, once a drag begins it expands to fill up the entire width of the page. The illusion of movement is accomplished by shifting the background position. This widening is to prevent the mouse cursor from escaping from outside its boundaries if it's moving too quickly. Attaching the onmousemove handler to the document's body didn't work with Mozilla/Firefox, since the handler wasn't invoked when it was on top of the bare body (as opposed to another node). Finally, text selections were disabled since the effect was distracting when dragging a column divider. To accomplish this required the use of the -moz-user-select CSS property in Mozilla-based browsers (based in turn on the user-select property in a CSS3 draft). Safari and IE do not implement it, but they do support the onselectstart handler, and setting it to a function that returns false prevents selections in those browsers.

Speaking of specific browsers, it should be clear that I made all efforts to make sure that this table implementation is compatible with Safari, Mozilla, Firefox and IE 6. Since the JavaScript simply adds behaviors to a static table, the page degrades well when scripting is disabled. Its behavior in older or accessibility-oriented browsers should also be minimally impacted.

Incidentally, a bit of Googling turned up the fact that apparently the resizable columns feature (as implemented for IE only) is worth $24.95, so clearly this entry is quite a bargain.

* For performance reasons only the header widths are updated dynamically, with the rest of the table being refreshed only at the end of the drag. To update everything dynamically, it is only necessary to set the argument to UpdateColumns in ColumnGrabberMouseMove to true.

1 Comment

Hello Mihai,
I just want to say thanks for this example. I'm using it as the basis (I'll be adding pagination, sorting, filtering, and column hiding to it or around it) for a replacement for the extjs framework in an inventory application I'm working on. With extjs when I allowed over 100 rows to be viewable at once in a 10+ column editable form there were browser lockup/delay problems. I'm hoping this "light" approach will perform much better.

Post a Comment