While the web design community gradually moves away from using
tables to lay out the structure of a page, tables really do have a
vital use, their original use; they're for laying out tabular data. For
instance, imagine a table of employees.
| Name | Salary | Extension | Start date |
|---|---|---|---|
| Bloggs, Fred | $12000.00 | 1353 | 18/08/2003 |
| Turvey, Kevin | $191200.00 | 2342 | 02/05/1979 |
| Mbogo, Arnold | $32010.12 | 2755 | 09/08/1998 |
| Shakespeare, Bill | $122000.00 | 3211 | 12/11/1961 |
| Shakespeare, Hamnet | $9000 | 9005 | 01/01/2002 |
| Fitz, Marvin | $3300 | 5554 | 22/05/1995 |
Pretty simple. But if you saw that table in a client-side
application, you'd expect to be able to click on the headers and have
the table sort, would you not? I know it always annoys me when you
can't. A fair few web applications do allow this; most of them, which
are pulling this data by submitting a SQL query to a relational
database (an environment eminently suited to tabular data) implement
this by resubmitting the whole page with something like
ordercolumn=4 in the URL, and then adding an ORDER clause to their SQL query to return the data from the DB
BY
ordered by the specified column.
Resubmit the page? Just to sort data we already have? I'm sure we
can do better than that.
| Name | Salary | Extension | Start date |
|---|---|---|---|
| Bloggs, Fred | $12000.00 | 1353 | 18/08/2003 |
| Turvey, Kevin | $191200.00 | 2342 | 02/05/1979 |
| Mbogo, Arnold | $32010.12 | 2755 | 09/08/1998 |
| Shakespeare, Bill | $122000.00 | 3211 | 12/11/1961 |
| Shakespeare, Hamnet | $9000 | 9005 | 01/01/2002 |
| Fitz, Marvin | $3300 | 5554 | 22/05/1995 |
As you can see, the above table now has clickable headers that sort
the table by the clicked column. Note how the numeric and date columns
all sort properly, too, rather than sorting alphanumerically.
This is not a new trick, sorting a table using the DOM. However,
this mini-library has two nice attributes; the first is,
unsurprisingly, that it follows my principles of
unobtrusive DHTML, as you'll see below. The second is that, as
mentioned above, it knows how to sort a variety of different data
types, and it works them out for itself -- you don't have to tell
it.
First, how to use it, then we'll get on to How It Works for those of
you who don't want to just take the code and use it (like all the other
JS libraries here, it's under the MIT
licence). To make a table of your
choice sortable, there are three steps:
<script src="sorttable.js"></script>
<table class="sortable">
<table class="sortable" id="unique_id">
And that's all you need. Your table will now have column sorting
available by clicking the headers. For niceness, you might want to add
the following styles to your stylesheet, or make up some of your own
based on this:
/* Sortable tables */
table.sortable a.sortheader {
background-color:#eee;
color:#666666;
font-weight: bold;
text-decoration: none;
display: block;
}
table.sortable span.sortarrow {
color: black;
text-decoration: none;
}
If you just want to use the code, that's it; take the JS library,
link to it as above, and mark your tables as sortable. Remember to give
them IDs, too. For those of you with enquiring minds who want to know
how it works, read "How It Works" below.
Lots of people ask, "how do I make sorttable sort the table the first
time the page is loaded?" The answer is: you don't. Sorttable is about
changing the HTML that is served from your server without a
page refresh. When the page is first served from the server, you have
to incur the wait for it to be served anyway. So, if you want
the table sorted when a page is first displayed, serve the
table in sorted order. Tables often come out of a database; get
the data from the database in a sorted order with an ORDER BY clause in
your SQL.
The other thing that people say to that is: no, no, no, you've
got
it wrong, I want people who have visited the page before and sorted the
data to get the data sorted that way again on their next visit, so I
need sorttable to run when the page loads. No, you
don't. Any solution
which involves you running sorttable as soon as the page loads (i.e.,
without user input) is a wrong solution. To do what you want,
what you should do is:
This procedure is left as an exercise for the reader. It would be a
useful addition, but I can't add it to the library because I don't know
how your server code works, and I won't add something that fires
sorttable to do sorting as soon as the page loads. That's the wrong
solution; if you're determined to do it then I can't stop you, but
you're on your own.
As said above (briefly) using the DOM to sort a table on the fly
(rather than doing a new server request) is not a new idea. A table
object in the DOM has a rows attribute which is basically
a two dimensional array defining the table; each item in the
rows list has a cells list, so
rows is essentially a TR and each
cell in a row is essentially a TD within that
row. So, sorting a table means sorting the rows list by
the value in a particular column, i.e., the value in
row.cells[n] where n is the column number.
JavaScript arrays have a sort() method. Calling
myarray.sort() sorts the array in place. You can also pass
a sort function to this, so myarray.sort(sortfn) will use
sortfn() to sort the array. We can use this to sort 2d
arrays (where each array element is itself an array);
sortfn() is passed two things, and much return whether the
first is larger, smaller, or the same as the second. So, if we're
sorting an array of arrays, sortfn() will be repeatedly
called with two parameters, both of which are arrays themselves. Clear
as mud? Thought so. A few examples*:
myarray = [4,2,6,5,3,1];
function mysortfn(a,b) {
WScript.Echo("Comparing "+a+" and "+b);
if (a<b) return -1;
if (a>b) return 1;
return 0;
}
WScript.Echo(myarray);
myarray.sort(mysortfn);
WScript.Echo(myarray);
4,2,6,5,3,1
Comparing 1 and 6
Comparing 5 and 3
Comparing 5 and 2
Comparing 5 and 6
Comparing 6 and 4
Comparing 1 and 4
Comparing 5 and 4
Comparing 5 and 1
Comparing 2 and 3
Comparing 3 and 1
Comparing 3 and 4
Comparing 4 and 1
Comparing 3 and 1
Comparing 3 and 2
Comparing 2 and 1
1,2,3,4,5,6
As you can see, the mysortfn() function is called a lot
to work out how to sort. Now, let's try:
// This time, use a multi-dimensional array, and sort by column 2
myarray = [
['Fred','Bloggs','$12000.00',1353],
['Kevin','Turvey','$191200.00',2342],
['Arnold','Mbogo','$32010.12',2755],
['Bill','Shakespeare','$122000.00',3211],
['Hamnet','Shakespeare','$9000',9005],
['Marvin','Fitz','$3300',5554]
];
function mysortfn(a,b) {
WScript.Echo("Comparing "+a+" and "+b);
// Note that each thing we are passed is an array, so we don't compare the things
// we're passed; instead, we compare their second column
if (a[1]<b[1]) return -1;
if (a[1]>b[1]) return 1;
return 0;
}
WScript.Echo(myarray);
myarray.sort(mysortfn);
WScript.Echo(myarray);
Fred,Bloggs,$12000.00,1353,Kevin,Turvey,$191200.00,2342,Arnold,Mbogo,$32010.12,2755,Bill,Shakespeare,$122000.00,3211,Hamnet,Shakespeare,$9000,9005,Marvin,Fitz,$3300,5554
Comparing Marvin,Fitz,$3300,5554 and Arnold,Mbogo,$32010.12,2755
Comparing Bill,Shakespeare,$122000.00,3211 and Hamnet,Shakespeare,$9000,9005
Comparing Hamnet,Shakespeare,$9000,9005 and Kevin,Turvey,$191200.00,2342
Comparing Kevin,Turvey,$191200.00,2342 and Arnold,Mbogo,$32010.12,2755
Comparing Kevin,Turvey,$191200.00,2342 and Fred,Bloggs,$12000.00,1353
Comparing Bill,Shakespeare,$122000.00,3211 and Hamnet,Shakespeare,$9000,9005
Comparing Hamnet,Shakespeare,$9000,9005 and Fred,Bloggs,$12000.00,1353
Comparing Hamnet,Shakespeare,$9000,9005 and Arnold,Mbogo,$32010.12,2755
Comparing Hamnet,Shakespeare,$9000,9005 and Marvin,Fitz,$3300,5554
Comparing Bill,Shakespeare,$122000.00,3211 and Fred,Bloggs,$12000.00,1353
Comparing Bill,Shakespeare,$122000.00,3211 and Marvin,Fitz,$3300,5554
Comparing Bill,Shakespeare,$122000.00,3211 and Arnold,Mbogo,$32010.12,2755
Comparing Bill,Shakespeare,$122000.00,3211 and Fred,Bloggs,$12000.00,1353
Comparing Marvin,Fitz,$3300,5554 and Fred,Bloggs,$12000.00,1353
Comparing Marvin,Fitz,$3300,5554 and Arnold,Mbogo,$32010.12,2755
Comparing Arnold,Mbogo,$32010.12,2755 and Fred,Bloggs,$12000.00,1353
Comparing Marvin,Fitz,$3300,5554 and Fred,Bloggs,$12000.00,1353
Fred,Bloggs,$12000.00,1353,Marvin,Fitz,$3300,5554,Arnold,Mbogo,$32010.1,2755,Bill,Shakespeare,$122000.00,3211,Hamnet,Shakespeare,$9000,9005,Kevin,Turvy,$191200.00,2342
This time, we told our sort function to explicitly use column 2 to
compare each of the arrays we were passed, and it worked.
That being the case, it's relatively easy to see how the sortable
table is implemented. Each of the clickable headers needs to call a
function to sort the table's rows array by that column, so
the first clickable header calls the function and tells it to sort this
table using column 1.
There are, naturally, a few minor wrinkles. You might think that you
could just sort table.rows in place with
table.rows.sort(sortfn). Nuh-uh. Can't do that. So, we
invent a newRows array and copy table.rows
into it, and then sort that, and then re-add those sorted rows
to the page. One of the neat things about the DOM is that if you insert
into the document an element that is already in the document,
then it moves it from where it is to where you inserted it. That's nice
for this, because we don't have to delete the table and then
reconstruct it from newRows; instead, we just walk through
the sorted newRows and insert each row in it into the
table, and because we're inserting them in sorted order, the table gets
swapped around to be in sorted order. Very handy.
There's also a little bit of code to do with showing an arrow in the
header that you've clicked on to show which way it's sorted, and to
allow us to toggle the sort to ascending or descending. This uses
innerHTML, slightly naughtily, but it seems to work OK in
my testing.
The next important part is diagnosing field types; this is a
canonical problem for sorting, because numeric fields must be sorted in
numeric order, not alphanumeric order (20,153,11 should sort as
11,20,153, not 11,153,20). The code does this by looking at the data in
the first row and then choosing a sort function based on what the data
looks like; it matches the data against a series of regular
expressions, one that looks for dates, one for numbers, one for
currency fields, and so on. Each regex is paired with a special sort
function written particularly for that sort of data (so the numeric
sort function sorts numbers numerically, for example). If no regexes
match, then the code defaults to a generic function which sorts
alphanumerically and case-insensitively. (Note that the date function
is keyed towards UK format dates (dd/mm/[cc]yy) and will need poking
for dates in other formats.)
The code also treats rows with class sortbottom
specially; they always appear at the bottom of the table and don't take
their place in the sort order. It does this by sorting them into the
sort order along with everything else, but then re-inserting them into
the table last. This is useful if you have a row of totals,
for example, that should remain at the bottom of the table regardless
of sorting.
Finally, we return to something we glossed over earlier; how the
clickable sort headers are created. This is the unobtrusive DHTML part;
the code adds an onload event to the page which calls the
sortables_init() function. This walks through all tables
on the page, and for any that have class sortable, walks
through each of the cells in the first row of that table and makes the
contents of those cells into clickable links.
I pinched the getInnerText() function from Erik over at
WebFX. Their sortable
table implementation does the same sort of thing as mine, but it
requires you to add some JavaScript to instantiate the table, and you
also have to explicitly specify the types of fields, which is a bit
obtrusive.
This was partially developed for a project at work, so thanks to my
boss. Thanks also to C. David
Eagle for pointing out a bug where I didn't close the
script tag in the example.
Stuart Langridge, November 2003
Sort by vocabulary
The module this comes from has too much hard-coded knowledge of my site for contribution, but here's how I produce my directory pages:
<?phpfunction bynodetype_page() {
if (strncasecmp(arg(1), 'flexinode-', 10)) {
$pagetitle = t(arg(1));
} else {
$ctype_id = substr(arg(1), 10);
$ctype = flexinode_load_content_type($ctype_id);
$pagetitle = t($ctype->name);
}
// Hardcode my special types for now
if ($pagetitle == 'News') {
$query = "SELECT nid, type FROM {node} WHERE status = 1 AND type = '" .
arg(1) . "' " . 'ORDER BY sticky desc,created DESC';
} else {
$query = "SELECT n.nid, type, td.name FROM {node} n ".
"INNER JOIN {term_node} tn ON n.nid=tn.nid ".
"INNER JOIN {term_data} td ON tn.tid=td.tid and td.vid=3 ".
"WHERE status = 1 AND type = '" .
arg(1) . "' " . 'ORDER BY td.name, n.title';
}
$result = pager_query($query,
variable_get('default_nodes_main', 10));
$output = '';
$last_category = '';
while ($node = db_fetch_object($result)) {
if ($node->name != $last_category) {
$output .= "<hr />\n<h2 class='dir-heading'>$node->name</h2>\n";
$last_category = $node->name;
}
$output .= node_view(node_load(array('nid' => $node->nid, 'type' => $node->type)), 1);
}
$output .= theme('pager', NULL, 10);
print theme('page', $output, t($pagetitle));
}
?>
Example
Fenway Views
sort flexinode nodes in category overview in general
first: this is not what it should be - "drupal-style". hacking the core is no real solution on long terms imho.
In taxonomy.module go to
function taxonomy_select_nodes(about line 850) and find$joins .= ' INNER JOIN {term_node} tn'. $index .' ON n.nid = tn'. $index .'.nid';.i added
$joins .= ' INNER JOIN {flexinode_data} flex ON n.nid = flex.nid ';afterwards (next line) and changed thesql = ...some lines below to$sql = 'SELECT n.* FROM {node} n '. $joins .' WHERE n.status = 1 and flex.field_id = 22 '. $wheres .' ORDER BY flex.textual_data ASC';. replaceflex.field_idwith the id of the field you want to sort the node-listings for the taxonomy category.i'm still looking for a way to do this without changing the core.
any ideas on this?
Create a theme for a specific flexinode type
go to this page for “Create a theme for a specific flexinode type”