JQuery_ Novice to Ninja - Earle Castledine [126]
Figure 8.5. Fixed header row
If your table is the only element on the page, position: fixed can be used to affix the thead element in place. However, position: fixed can only position an element relative to the viewport, rather than its containing element. This means that for tables contained inside other elements (which will almost always be the case), we need to turn to jQuery.
Let’s have a look at how we can achieve this effect. Our markup is the same Celebrities table we added zebra-striping to back in Chapter 2:
chapter_08/06_fixed_table_headers/index.html (excerpt)
| Id | Name | Occupation | Approx. Location | Price |
|---|
Moving the thead around is tricky. Some browsers let you move it with impunity, while in others it’s surprisingly resistant to styling. So we’ll employ a trick: we’ll duplicate the contents of the thead in the form of an unordered list, styled to look exactly like the thead. Then we’ll give that position: absolute;, and move it around the screen as the user scrolls.
We start by creating a TABLE widget to hold our code, with a fixHeader method that we’ll use for our fixed headers effect. The method will expect to receive a selector string pointing at a table on the page. We start by storing a few selections inside variables and in data, to speed up our subsequent code:
chapter_08/06_fixed_table_headers/script.js (excerpt)
var TABLE = {};
TABLE.fixHeader = function(table) {
$(table).each(function() {
var $table = $(this);
var $thead = $table.find('thead');
var $ths = $thead.find('th');
$table.data('top', $thead.offset().top);
$table.data('left', $thead.offset().left);
$table.data('bottom', $table.data('top') + $table.height() -
↵$thead.height());
…
We first declare a closure to hold on to our widget’s context. Then we select any tables that match the selector passed in to our method. We loop over them with each, and store a reference to the table itself ($table), the thead ($thead), and the collection of th elements it contains ($ths). Finally, we store the left and top offsets of the $thead, as well as the location of the bottom of the table (to avoid the header moving past the bottom!).
Tip: Use each When Writing Selector-based Functions
When writing this sort of utility function, you should always anticipate the possibility of your selector returning more than one element. In this case, our page only has one table on it, so the method would work fine if we omitted the each loop. But in the interests of preparing for the future, and making our code reusable, we’ve included the loop anyway; even if the table selector returns multiple tables, our function will handle them all with grace.
Next, we create our faux header—a ul—and copy over the contents of the table header:
chapter_08/06_fixed_table_headers/script.js (excerpt)
var $list = $('
$ths.each(function(i) {
_th = $(this);
$list.append($("
").addClass(_th.attr("class"))
.html(_th.html())
.width(_th.width())
.click(function() {
_th.click()
})
).hide().css({left: _table.left});
});
$('body').append($list);
With the real th elements collected in $ths, we use an each action to go through each one and craft our mimics. We copy the original elements’ class, HTML contents, width, and click event handlers. Some of this is unnecessary in our simple example, but it’s good to be prepared! After the list is fully populated, we hide it and position it directly over our real thead before slipping it into the page.
Note: Append as Infrequently as Possible
You may wonder why we wait until the list is fully constructed before appending it to the page. While appending the list to the page first, and subsequently appending each item to it would have the same desired effect, the method we’ve chosen to adopt executes much more quickly in the browser.
Every time you insert a new element into the DOM, the browser needs to recalculate