Add interactivity to your HTML tables by allowing users to sort table columns.
Stick with the table of the top 15 movies that's been used a few times already. Unlike the last recipe, where we started with an empty table body, this time we'll populate all 15 rows.
Start with the same table that was used in the past few recipes. Because columns will be sorted by clicking on the headers, add
<a>
elements to the table header text. Each<a>
element should have a class of sorter and a unique ID,column_n
, wheren
is the number of the column in the table, starting at0
.<table border="1" id="movies"> <thead> <tr> <th class="ranking"> <a href="#" class="sorter" id="column_0">Ranking</a> </th> <th class="title"> <a href="#" class="sorter" id="column_1">Movie</a> </th> <th class="releaseYear"> <a href="#" class="sorter" id="column_2">Release Year</a> </th> </tr> </thead>
Manually enter all of the movies into rows in the
tbody
, as follows:<tbody> <tr> <td>1</td> <td>Citizen Kane</td> <td>1941</td> </tr> <tr> <td>2</td> <td>The Godfather</td> <td>1972</td> </tr> … <tr> <td>15</td> <td>2001: A Space Odyssey</td> <td>1968</td> </tr> </tbody>
Finally, add the sorting script, as follows:
<script type="text/javascript"> $( document ).ready( function() { var tablerows = $( "#movies tbody tr" ).get(); $( "a.sorter" ).click( function( e ) { e.preventDefault(); $( "#movies tbody tr" ).remove(); // on which column are we sorting? var sort_pos = $( this ).attr( "id" ).split( "_" )[ 1 ]; tablerows.sort( function( a, b ) { var atext = $( a ).children( "td" ).eq (sort_pos ).text(); var btext = $( b ).children( "td" ).eq( sort_pos ).text(); var nums_only = /^\d+$/; if ( nums_only.test( atext ) ) atext *= 1; if ( nums_only.text( btext ) ) btext *= 1; if ( atext == btext ) return 0; return atext < btext ? -1 : 1; }); $.each( tablerows, function( index, tablerow ) { $( "#movies tbody" ).append( tablerow ); }); }); }); </script>
The first thing to do is to get an array of the table rows. Once again, jQuery makes this ridiculously simple with its built-in get()
function. Assign this array to a variable, since it will be referenced later.
var tablerows = $( "#movies tbody tr" ).get();
The selector itself should be familiar by now. The only thing that's new is the addition of .get()
, which will create an array of the elements retrieved.
Each of the table headers is surrounded by an <a href>
tag with a class attribute of sorter
. Listen for a click
event on those elements to kick off the function:
$( "a.sorter" ).click( function( e ) {
Because a link was clicked, prevent the default behavior via e.preventDefault()
. This was used in previous recipes, so it should be familiar.
As in the previous recipe, clear out all of the table rows within the table body. They'll be added back once they're properly sorted. But before that is done, they need to be removed.
$( "#movies.tbody tr" ).remove();
Now it starts to get a little bit trickier. Determine which column should be sorted. Each of the header links contains a unique ID whose value has a very specific format: column_n
, where n
is the index of the column. Start the index at 0
, so there are IDs column_0
, column_1
, and column_2
.
This specific format will allow you to get the ID via $( this ).attr( "id" )
. You can then use JavaScript's built-in split()
function to explode the string into an array. split()
takes a string argument, which is the substring or pattern on which to split the original string. Use the underscore character to create the array. Because the ID values are all in the column_n
format, split()
will result in a two element array. The first element, which is at the 0 position, will hold the literal string column
. The second element, which is at the 1 position, will hold the column number.
var sort_pos = $( this ).attr( "id" ).split( "_" )[ 1 ];
sort_pos
now holds the value 0, 1, or 2, for this table.
Now for the actual sort. JavaScript
provides a .sort()
method that works on arrays. This isn't jQuery, but standard JavaScript. It's a pretty straightforward algorithm that looks as follows:
array.sort( function( a, b ) { if ( a == b ) return 0; if ( a < b ) return -1; if ( a > b ) return 1; }
Kick off the sort function on the tablerows
array as follows:
tablerows.sort( function( a, b ) {
Unfortunately, that can't be used directly. The array holds table rows. Table row a
can't be compared to table row b
. The text is in a specific td
within table row a
and table row b
.
var atext = $( a ).children( "td" ).eq( sort_pos ).text(); var btext = $( b ).children( "td" ).eq( sort_pos ).text();
Since both a
and b
reference specific table rows, drill down into that table row to a specific <td>
, and grab the text using jQuery's .text()
method.
$( a )
and $( b )
are jQuery hooks into the table rows being compared. This returns an array of the child td
elements via .children( "td" )
, but a specific td
is needed. Remember that a variable sort_pos
was set earlier? This is where it gets implemented. Having traversed down to the appropriate td
element, use .text()
to get its value.
Now run the comparisons for the sort algorithm, but use atext
and btext
rather than a
and b
.
if ( atext == btext ) return 0; if ( atext < btext ) return -1; if ( atext > btext ) return 1;
With that, the tablerows
array is properly sorted. Loop over it and append each row back into the table body:
$.each( tablerows, function(index, tablerow) { $( "#movies tbody" ).append( tablerow ); });
Note that if the Ranking column is sorted, the results are not 1,2,3,...,15 as one might expect. Rather, the results are 1,10,11,12,13,14,15,2,3,...9. This is also a result of JavaScript being a typeless language. JavaScript is seeing the values as strings, and the string 10 comes before the string 2.
It's a bit of a kludge, but you can do a test to see if the values being compared are numeric. If they are, multiply them by 1. This will leave the value unchanged, but let JavaScript know that these are numeric values.
Prior to the comparison, do the following:
var nums_only = /^\d+$/;
The nums_only
variable is a regular expression that checks for one or more digits. Then use JavaScript's native .test()
method to test the values. If they're true, multiply the value by 1. This will address the issue of sorting the numeric values.
if ( nums_only.test( atext ) ) atext *= 1; if ( nums_only.test( btext ) ) btext *= 1;
This sort only sorts in one direction. Take this opportunity to allow a column heading to be clicked and re-sort the column in the opposite direction. If it was ascending, now sort it in descending order, and vice versa. As a hint, store some variables to allow you to determine which column is currently sorted, and in which direction. Then re-sort in the opposite direction, which would involve a small change to the sort algorithm.