Makes any table with class="sortable", er, sortable. The user can click on a table header and change the sorting of the table rows.
Just include the JavaScript and it will work. No function calls are needed, everything is handled by an eventListener.
You can find a simple demo on https://tofsjonas.github.io/sortable/
- Factoids
- "Installation"
- Non-sortable field
- Indicators/arrows on the left side
- NOTE ABOUT CSS/SCSS
- Sorting sizes, dates and such
- Alternative sorting
- Colspans/Sort on specific column
- Concerning
rowspan
- Ascending sort
- Tiebreaker / secondary sort
- Empty/null rows always last
- Accessibility
- Sort Events
- Sort on load
- Thank you...
-
1.52KB minified. (795 bytes gzipped)
-
Works with JavaScript generated tables. (since we are using an eventListener)
-
Lightning fast. Huge tables will make it slow and may freeze the browser, especially for mobiles, so you know...
-
Requires thead and tbody.
-
rowspan is not supported 😢
-
cross browser,
ie9+No longer ie9 compatible. Then again, maybe it already wasn't 🤷 -
NOT tested with React, Angular, Vue, etc.
-
Works with Svelte!
There are three ways to use sortable, all of which have their pros and cons. S Anand and dkhgh had some interesting thoughts about it.
<table class="sortable">
<thead>
<tr>
<th><span>Role</span></th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>Genius</td>
<td>Rick</td>
</tr>
<tr>
<td><a href="javascript:alert('standalone javascript works!');">Sidekick</a></td>
<td>Morty</td>
</tr>
</tbody>
</table>
<link href="https://cdn.jsdelivr.net/gh/tofsjonas/sortable@latest/dist/sortable.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/gh/tofsjonas/sortable@latest/dist/sortable.min.js"></script>
The span
on line four is just there to prove that you can have elements inside th
!
Same as above, but link to your own files from the dist
directory
...
<link href="/assets/sortable.min.css" rel="stylesheet" />
<script src="/assets/sortable.min.js"></script>
...
First,
npm install sortable-tablesort
# yarn add sortable-tablesort
# pnpm install sortable-tablesort
Now you can:
Same as above, with links to files from the dist
directory
...
<link href="./node_modules/sortable-tablesort/dist/sortable.min.css" rel="stylesheet" />
<script src="./node_modules/sortable-tablesort/dist/sortable.min.js"></script>
...
or
// main.js
import 'sortable-tablesort/dist/sortable.min.css'
import 'sortable-tablesort/dist/sortable.min.js'
If you wish to disable sorting for a specific field, the easiest (and best) way is to add class="no-sort"
to it, like so:
<thead>
<tr>
<th class="no-sort">Role</th>
<th>Name</th>
</tr>
</thead>
Sorting will not be triggered if you click on "Role".
This is a bit trickier, but it doesn't require any changes to the html, so I guess it could be worth it in some cases.
/* the first column in every sortable table should not be sortable*/
.sortable th:nth-child(1) {
pointer-events: none;
}
/* the seventh column in the second .sortable table should not be sortable*/
.sortable:nth-of-type(2) th:nth-child(7) {
pointer-events: none;
}
The eventListener only triggers on th
, not td
, so this would disable sorting for "Role":
<thead>
<tr>
<td>Role</td>
<th>Name</th>
</tr>
</thead>
th
and td
are not the same thing, you would most likely still have to use CSS to make them look the way you want. (It might also mess with accessibility.) In some cases it could be worth it, but I recommend the .no-sort
alternative.
If you have text that is aligned on the right side, you may want to have the arrows on the left side.
This is solved by adding a class to the css and using ::before
instead of ::after
.
(You can of course use a pure css solution, without class names - just like with the non-sortable field - but that I will leave for you to figure out.)
.sortable th.indicator-left::after {
content: '';
}
.sortable th.indicator-left::before {
margin-right: 3px;
content: '▸';
}
/* etc. */
The css/scss
in this repo was only ever meant as an example. It was never intended to be actually used.
That said, if you're feeling lazy, here are two stylesheets you can use:
<!-- This will add arrows, as well as support for .no-sort and .indicator-left -->
<link href="https://cdn.jsdelivr.net/gh/tofsjonas/sortable@latest/sortable-base.min.css" rel="stylesheet" />
<!-- This will make it look like the tables in the example, with arrows, striped rows etc. -->
<link href="https://cdn.jsdelivr.net/gh/tofsjonas/sortable@latest/sortable.min.css" rel="stylesheet" />
I'm not sure if it's a good idea to have it in the main css, BUT if you are using the above sortable(.min).css
file (not the -base files) and want sticky headers, you can simply add the class sticky
to the table.
Blame razorkyle, it was his idea! 😜
<table class="sortable sticky">
...
</table>
If you are not using the css file, you can use the following css:
.sortable thead th {
position: sticky;
top: 0;
z-index: 1;
}
Using the data-sort
attribute in tbody
> td
you can have one visible value and one sortable value. This is useful in case you have for instance sizes like kb, Mb, GB, or really weird date formats. 😉
<table class="sortable">
<thead>
<tr>
<th>Movie Name</th>
<th>Size</th>
<th>Release date</th>
</tr>
</thead>
<tbody>
<tr>
<td>Zack Snyder's Justice League</td>
<td data-sort="943718400">900MB</td>
<td data-sort="20210318">03/18/2021</td>
</tr>
<tr>
<td>The Sound of Music</td>
<td data-sort="1610612736">1.5GB</td>
<td data-sort="19651209">12/09/1965</td>
</tr>
</tbody>
</table>
If you click on a table header while holding shift or alt an alternative
data-sort-alt
attribute will override data-sort
.
<table class="sortable">
<thead>
<tr>
<th>Movie Name</th>
<th>Size</th>
<th>Release date</th>
</tr>
</thead>
<tbody>
<tr>
<td>Something</td>
<td data-sort-alt="c" data-sort="a">A</td>
<td data-sort-alt="b" data-sort="c">B</td>
<td data-sort-alt="a" data-sort="b">C</td>
</tr>
<tr>
<td>Something else</td>
<td data-sort-alt="e" data-sort="f">D</td>
<td data-sort-alt="f" data-sort="e">E</td>
<td data-sort-alt="d" data-sort="d">F</td>
</tr>
</tbody>
</table>
Using the data-sort-col
attribute in thead
> th
, you can sort on a different column than the one that was clicked. For instance if you want to have colspans. Like so:
<thead>
<tr>
<th></th>
<th>Category</th>
<th class="show_name">Show</th>
<th colspan="2">Overall</th>
<th colspan="2" data-sort-col="5">On Our Dates</th>
<th data-sort-col="7">First Sold Out</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tags"> </td>
<td class="category">Comedy</td>
<td class="show_name">Show 1</td>
<td class="ratio all" data-sort="72">18/25</td>
<td class="pct all">72%</td>
<td class="ratio ours" data-sort="75">3/4</td>
<td class="pct ours">75%</td>
<td>2022-07-30</td>
</tr>
...
</tbody>
Rowspans are not supported. Maybe I could do a half-assed implementation, but I don't think it would be worth it. You can read my justification in Issue 71
If you have a good idea, please let me know!
By adding asc
to table
, the default sorting direction will be ascending instead of descending
<table class="sortable asc">
<thead>
...
</thead>
<tbody>
...
</tbody>
</table>
If you wish to sort by a different column when two values are equal, you can use the data-sort-tbr
attribute, like so:
<table class="sortable asc">
<thead>
<tr>
<th data-sort-tbr="1">Year</th>
<th>Month</th>
<th>Day</th>
</tr>
</thead>
<tbody>
<tr>
<td>2010</td>
<td>07</td>
<td>25</td>
</tr>
<tr>
<td>2010</td>
<td>11</td>
<td>12</td>
</tr>
<tr>
<td>2010</td>
<td>04</td>
<td>25</td>
</tr>
</tbody>
</table>
When clicking Year, if they are the same, we will sort on Month.
Adding class="n-last"
to <table class="sortable">
will make empty/null values always be sorted last, similar to what SQL does with ORDER BY foo NULLS LAST
.
<table class="sortable n-last">
<thead>
<tr>
<th>Text</th>
<th class="indicator-left">Number</th>
</tr>
</thead>
<tbody>
<tr>
<td>jkl</td>
<td>0.4</td>
</tr>
<tr>
<td>This will always be sorted after the others</td>
<td></td>
</tr>
<tr>
<td>abc</td>
<td>0</td>
</tr>
<tr>
<td>def</td>
<td>0.2</td>
</tr>
</tbody>
</table>
<td data-sort=" "></td>
will be sorted normally.
Sortable is not very accessible in its raw form. It does not support screen readers, and it does not have any keyboard support. Including sortable.a11y.min.js
in your project will add some basic accessibility features.
<table class="sortable">
...
</table>
<link href="dist/sortable.min.css" rel="stylesheet" />
<script src="dist/sortable.min.js"></script>
<script src="dist/sortable.a11y.min.js"></script>
By including the file the global function enhanceSortableAccessibility
will automatically run through all existing .sortable
tables, but you can also run it manually, like so:
enhanceSortableAccessibility([table1, table2,...etc.])
The function adds an aria-label
to each th, as well as tabindex="0"
to each th in the thead of each table, making it possible to tab through the headers. It updates the aria-label
depending on the direction.
If you want to import it instead:
import { enhanceSortableAccessibility } from 'sortable-tablesort/dist/esm/enhanceSortableAccessibility'
enhanceSortableAccessibility([table1, table2,...etc.])
The table element dispatches two custom events that bubble up the DOM tree:
sort-start
: Fired when sorting beginssort-end
: Fired when sorting is complete
You can listen for these events on any parent element, including the document itself:
// Listen for events from any sortable table
document.addEventListener('sort-start', function (e) {
console.log('Sorting started on:', e.target) // logs the table element
})
document.addEventListener('sort-end', function (e) {
console.log('Sorting complete on:', e.target) // logs the table element
})
// Or listen to a specific table
const table = document.querySelector('.sortable')
table.addEventListener('sort-start', () => console.log('Sorting started'))
table.addEventListener('sort-end', () => console.log('Sorting complete'))
If you wish to sort a table on load, I would recommend doing something like this:
<table class="sortable">
<thead>
<tr>
<th>Movie Name</th>
<th id="movie-size">Size</th>
<th>Release date</th>
</tr>
</thead>
<tbody>
...
</tbody>
</table>
<script>
window.addEventListener('load', function () {
const el = document.getElementById('movie-size')
// without id:
// const el = document.querySelector('.sortable th:first-child')
// const el = document.querySelector('.sortable th:nth-child(2)')
// const el = document.querySelectorAll('.sortable')[3].querySelector('th:nth-child(7)')
// etc.
if (el) {
el.click()
}
})
</script>
Combine this with <table class="sortable asc">
to reverse the sort order. Or do el.click()
twice!
-
...Nikita Dunajevs for the ascending sort idea!
-
...wodny for the alternative sorting idea!
-
...Nick Kocharhook for the colspan sorting idea!
-
...mxve for the nested elements inside
th
fix! -
...Christian Petersson and Abit Salihu for the sort on load example!
-
...GazHay for the idea to sort multiple
<tbody />
! -
...Gordan Ratkovic for the tiebreaker / secondary sort idea!
-
...chatcoda for the
<td></td>
/<td>0</td>
sorting bug fix! -
...Christian Garbs for fixing the
dataset
bug! -
...Witold Baryluk for pointing out some inefficiencies!
-
...Nick for raising a whole bunch of issues! 🤯️
-
...James Pudson for the empty last suggestion, AND for finding the "
data-sort
ignored when empty" bug! 🥳️ -
...Jojo-IO for the finding the "
parseFloat
messes up time values" bug! -
...Steve Wirt for lifting the Accessibility issue! 🦾️
-
...GazHay for the sort events idea!