onsdag, juli 06, 2011

Extending the YUI 2 DataTable to have frozen columns

Time and time again I've found myself needing a table widget with support for frozen columns (and headers). I admit that it may not be a good thing to just put a lot of table rows and columns in the DOM for performance reasons. But it does make sense to a certain extent... But I have never found a good implementation in any of the frameworks I've been working with. So I took it upon myself to do this in YUI which was the framework used for the current project.

YUI already has an extension of the DataTable, called ScrollingDataTable which had the ability to keep the header row(s) frozen while scrolling the table vertically. "Great, that's a good starting point", I thought... The ScrollingDataTable extended the DataTable by putting the headers in a separate table like this:


<div id="tablecontainer">
<div class="yui-dt-hd">
<table>
<colgroup></colgroup>
<thead></thead>
</table>
</div>
<div class="yui-dt-bd">
<table>
<thead></thead>
<tbody class="message"></tbody>
<tbody class="data"></tbody>
</table>
</div>
</div>

Expanding on this idea I decided to try and end up with something like this:

<div id="tablecontainer">
<table>
<tbody> </tbody>
<tbody>
<tr>
<td><div class="yui-dt-hd-fr"></div></td><td><div class="yui-dt-hd"></div></td>
</tr>
<tr>
<td><div class="yui-dt-bd-fr"></div></td><td><div class="yui-dt-bd"></div></td>
</tr>
</tbody>
</table>
</div>



That is, four areas kept in a 2x2 table. Second column holding the tables described above for the ScrollingDataTable. First column holding similar tables for the frozen columns part of data. I will refer to this structure as the "frame" for my frozen-columns table.

So the plan was to take the ScrollingDataTable and have it display only the set of columns that were not to be frozen, and then put another table to the left of it, displaying only the frozen columns.

I really had no idea if this was going to work or not. But I did know that I had discarded support for screen-readers by going down this road. From the DataTable source code I could see that the YUI folks were taking such things very serious. But I decided not to worry about this.

Not being a big fan of deep inheritance and to make things easier for myself I decided not to extend the ScrollingDataTable but instead just to expand it. I also decided to add an extra columndefs object to the constructor defining the columns to keep frozen. I figured I wouldn't have to mangle with the datasource, since the DataTable only displays data concerning columns in the columndef.

Constructor:



_initFrozenColumnSet is basically blindly redoing what it's base counterpart is doing except working on the frozen columnDefs and initializing the frozen columnset _oFrColumnSet.


One of the first thing happening in the DataTable is that the "frame" of DOM elements are initialized. Table, Thead, Tbody etc are created and various event listeners are hooked up. I would of course have to hook into this in order to make my new frame.


_initDomElements

_initContainerEl

_initTableEl

_initColgroupEl

_initTheadEl

_initFrTheadEl

_initThEl

_initBdTheadEl

_initFrBdTheadEl

_initBdThEl

_initTbodyEl


After that I was hoping that I would simply have to override the methods responsible for the rendering and add just repeat the base logic, but on the frozen area...


_addFrTrEl creates a new DOM table row by cloning a template row and then formatting it with data.


_getFrTrTemplateEl

Returns a DOM table row template element based on the frozen column set.


_updateFrTrEl

Updates a frozen table row element with data from a record


_getFirstFrTrEl

Returns the first row in the frozen body table.


_getLastFrTrEl

Returns the last row in the frozen body table.


_unsetFirstRow / _setFirstRow

_unsetLastRow / _setLastRow


_onScroll scrolls header table when body table scrolls horizontally and scrolls frozen body when body table scolls vertically



render : merged with base render to prevent running the render chain more than once. But could be accomplished nicer by using the "beforeRenderEvent" and add the frozen stuff here.


After the rendering cycle the method validateColumnWidths is called. It's main job in this context is to sync the column widths between header and body area. Easily handled by adding a dynamic style for the div liner element for every column. This is of course repeated in the frozen side.

_validateFrColumnWidths

_setFrColumnWidth

_setFrColumnWidthDynFunction (for IE: generates function to set width on all cells - don't why its not enough to just set it on cells in one row....?)


As part of the "postRenderEvent" adjustWidth is called.

adjustWidth checks the width of the frozen area and and sets the width of the non-frozen area to any remaining width inside the table-container. This ensures that my table always takes up full width of container - but this can easily be changed or made configurable.

This of course may result in the calculated columnwidths being no longer accurate, but their weights will ensure that the header and body columns remain aligned. It can cause other problems though - such as text alignment inside the cells. I have not yet addressed this problem, but it could be done by expanding the columnwidths by their weights...



In end the syncScroll methods do various adjustments in regard to when scrollbars are visible.

_syncScrollY Accounts for y-scrollbar by decreasing width of header and body tables by the width of the scrollbar (currently hardcoded to 18 px)


_syncScrollX accounts for cases with auto height in IE 6/7 where any x-scrollbar overlays table contents unless the table height is decreased by the height of the scrollbar (18px hardcoded). And if a fixed height is given and x-scrollbar is visible the frozen body table height needs to decrease by the height of the x-scrollbar.


_synScrollOverhang takes care of situations where the rightmost body headercell needs adjustment when both x and y scrolling are visble. This is handled by adding a right border of....you guessed it: 18px.





I have not attempted to implement logic for resizing and moving columns as this is functionality I am not using. So this will most likely not work as is, but it should definitely be do-able.

That's it for now - I'll expand on this shortly


2 kommentarer:

Pawan Pipalwa sagde ...

Very first, Thank you so much for sharing it.

I am using YUI 2 and using YAHOO.APEX.DataTableManager for list and grid.

I would like to Freeze first column and header row.

Could i do that using it ? I am pretty new for YUI and don't know much...i will appreciate if you can share an working demo or any source file.

I have very limited time and bandwidth to complete...your help will be job saver...!

Please.

Thanks
Pawan Pipalwa.

Pawan Pipalwa sagde ...

Very first, Thank you so much for sharing it.

I am using YUI 2 and using YAHOO.APEX.DataTableManager for list and grid.

I would like to Freeze first column and header row.

Could i do that using it ? I am pretty new for YUI and don't know much...i will appreciate if you can share an working demo or any source file.

I have very limited time and bandwidth to complete...your help will be job saver...!

Please.

Thanks
Pawan Pipalwa.