We have now looked at some ways of ordering the rows
of data in a table to provide assistance to the user in finding the
desired information. It is often the case, though, that there is still a
lot of data to sift through after any sorting or paging is performed.
We can assist the user by manipulating not just the order and quantity
of displayed rows, but the appearance of those that are shown.
Row highlighting
One practical way to guide the
user's eye is to highlight rows to give a visual cue about what data is important. To
examine some highlighting strategies, we need a table to work with.
This time, we'll start with a table of news items. The table will be a
little more complicated than the last; it will include some rows used as subheadings,
in addition to a main heading row. The HTML structure is as follows:
<table>
<thead>
<tr>
<th>Date</th>
<th>Headline</th>
<th>Author</th>
<th>Topic</th>
</tr>
</thead>
<tbody>
<tr>
<th colspan="4">2008</th>
</tr>
<tr>
<td>Sep 28</td>
<td>jQuery, Microsoft, and Nokia</td>
<td>John Resig</td>
<td>third-party</td>
rows, table appearanceshighlighting</tr>
...
<tr>
<td>Jan 15</td>
<td>jQuery 1.2.2: 2nd Birthday Present</td>
<td>John Resig</td>
<td>release</td>
</tr>
</tbody>
<tbody>
<tr>
<th colspan="4">2007</th>
</tr>
<tr>
<td>Dec 8</td>
<td>jQuery Plugins site updated</td>
<td>Mike Hostetler</td>
<td>announcement</td>
</tr>
...
<tr>
<td>Jan 11</td>
<td>Selector Speeds</td>
<td>John Resig</td>
<td>source</td>
</tr>
</tbody>
...
</table>
Note the use of multiple<tbody> sections. This is valid HTML markup for grouping
sets of rows together. We have placed the section subheadings within
these groupings, using<th>
elements to set them off. With basic CSS added, this table renders as
follows:
Row striping
We have already seen one simple
example of row highlighting earlier in this chapter, and before that in
Chapter 2. Striping rows is a common way to guide the user's eye across multiple
columns accurately.
As we have seen, row
striping can be as simple as a couple of lines to add classes to odd and
even rows:
$(document).ready(function() {
$('table.striped tr:odd).addClass('odd');
$('table.striped tr:even).addClass('even');
});
While this code works fine for simple table structures, if we introduce
additional rows we do not want to be striped (such as the subheading
rows we are using for the years in our table), the basic odd-even
pattern no longer suffices. If, for example, the 2006 row would be
classified as even, the rows before and
after it would both be odd, which
is likely not desirable.
We can ensure that our
alternating-row pattern begins anew with each years' worth of news items
by using the :nth-child() pseudo-class
we learned about in Chapter 2:
$(document).ready(function() {
$('table.striped tr:nth-child(odd)').addClass('odd');
$('table.striped tr:nth-child(even)').addClass('even');
});
Each group of rows now begins with an odd row, but the subheading rows are included in this
calculation. So instead, we could exclude the subheading rows from
consideration with the :has() pseudo-class:
$(document).ready(function() {
$('table.striped tr:not(:has(th)):odd').addClass('odd');
$('table.striped tr:not(:has(th)):even').addClass('even');
});
Now the subheadings are
excluded, but groupings will begin with an odd or even row depending on which classification
applied to the previous data row. Reconciling these two behaviors can be
a bit tricky; one straightforward option is introducing some explicit iteration using the .each()
method.
$(document).ready(function() {
rows, table appearancesstriping$('table.striped tbody').each(function() {
$(this).find('tr:not(:has(th)):odd').addClass('odd');
$(this).find('tr:not(:has(th)):even').addClass('even');
});
});
Now each grouping
is striped independently, and subheading rows are excluded from the
calculations.
Advanced row striping
These manipulations of odd and
even rows have set us up for some more complicated techniques. In
particularly dense tables, even alternating row colors can be confusing
to the eye, and it can be beneficial to alternate colors at a larger
interval. As an example, we will modify the striping of our news table
to color its rows three-at-a-time.
In Chapter 2, we introduced
the .filter() method for selecting
page elements in a very flexible way. Recalling that .filter() can take not just a selector expression, but also a filter function, we can write:
$(document).ready(function() {
$('table.striped tbody').each(function() {
$(this).find('tr:not(:has(th))').filter(function(index) {
return (index % 6) < 3;
}).addClass('odd');
});
});
This code accomplishes a lot in a
small space, so let's break it down piece-by-piece.
First, we use the .each() method as before to segment our task neatly by row grouping.
We want our three-row stripes to begin anew after each subheading, so
this technique allows us to work one section at a time. Then, we use
.find() as in our last example to locate all
of the rows that do not have<th> elements (and thus are
not subheadings).
Now, we need to select
the first three elements of this set, skip three elements, and so forth.
This is where .filter() comes
into play. The filter function takes an argument containing the index of
the item within the matched set—that is, the row number in
the section of the table we're examining. If, and only if, our filter
function returns true, the element will
remain in the set.
The modulo operator
(%) provides us with the information we need. The expression index %
6 evaluates to the remainder of the row
number when divided by 6; if this remainder is 0, 1, or 2, then we'll
mark the row as odd; if it is 3, 4, or 5,
the row will be even.
The code, as presented, only
marks the odd sets of rows. To also
apply the even class, we could write
another filter that applied the opposite filter, or we can get a bit
more creative:
$(document).ready(function() {
$('table.striped tbody').each(function() {
$(this).find('tr:not(:has(th))').addClass('even')
.filter(function(index) {
return (index % 6) < 3;
}).removeClass('even').addClass('odd');
});
});
Here we apply the even
class to all of the rows, and remove it if we add the odd class. Our table now has stylish alternating row
groupings, which begin anew with each new section of the table.
Interactive row
highlighting
Another visual enhancement
that we can apply to our news article table is row highlighting based on
user interaction. Here we'll respond to clicking on an author's name by
highlighting all rows that have the same name in their Author cell. Just
as we did with the row striping, we can modify the appearance of these
highlighted rows by adding a class:
#content tr.highlight {
rows, table appearanceshighlighting, with user interactionbackground: #ff6;
}
It's important that we give
this new highlight class adequate
specificity, so that the background color
will override that of the even and odd classes.
Now we need to select the
appropriate cell and attach behavior to it using the .click()
method:
$(document).ready(function() {
rows, table appearanceshighlighting, with user interactionvar $authorCells = $('table.striped td:nth-child(3)');
$authorCells.click(function() {
// Perform our highlighting here.
});
});
Notice that we use the
:nth-child(n) pseudo-class as part of
the selector expression that points to the third column where the author
information is. In case the table structure were to later change, we
would want this constant 3 to be in
only one place in the code, so it could be easily updated. For this
reason, and for efficiency, we store the result of our selector in the
$authorCells variable rather than
repeating the selector each time it is needed.
Recall that unlike JavaScript indices, the
CSS-based :nth-child(n) pseudo-class begins numbering at 1, not
0.
When the user clicks a
cell in the third column, we want the cell's text to be compared to that
of the same column's cell in every other row. If it matches, the
highlight class will be toggled. In other
words, the class will be added if it isn't already there and removed if
it is. This way, we can click on an author cell to remove the row
highlighting if that cell or one with the same author has already been
clicked.
$(document).ready(function() {
var $authorCells = $('table.striped td:nth-child(3)');
$authorCells.click(function() {
var authorName = $(this).text();
$authorCells.each(function(index) {
if (authorName == $(this).text()) {
$(this).parent().toggleClass('highlight');
}
});
});
});
The code is working well at
this point, except when a user clicks on two authors' names in
succession. Rather than switching the highlighted rows from one author
to the next as we might expect, we end up with the highlight class on both groups of rows. To avoid this
behavior, we can add an else statement to the code, removing
the highlight class for any row that
does not have the same author name as the one clicked:
$(document).ready(function() {
var $authorCells = $('table.striped td:nth-child(3)');
$authorCells.click(function() {
var authorName = $(this).text();
$authorCells.each(function(index) {
if (authorName == $(this).text()) {
$(this).parent().toggleClass('highlight');
}
else {
$(this).parent().removeClass('highlight');
}
});
});
});
Now when we click on Rey Bango, for
example, we can see all of his articles much more easily:
If we then click on John Resig in any
one of the cells, the highlighting will be removed from Rey Bango's rows
and added to John's.