This post is going back to basics, but it's a useful trick for many applications that's easy to implement and can make a big difference in usability. Quite frequently in an application you might have a server generated list of items to display that can be displayed in a reversible or other sortable order. While you can build the logic into a server side application to let you switch the sort order by re-reading content from the server, it's actually quite easy to make the server generated list client side sortable. Client side sorting is nearly instant and doesn't require server round trips and it's pretty easy to implement.
An Example: Messages in a Thread
For example, I've recently re-written the Message Board application I use for support on my Web site, and in it I show a list of message in a thread. One of the first requests that came up was: Can I see the list in reverse order with the last messages posted showing first.
Yup I can relate: I use my message board to support customers, and I frequently deal with long threads and it is useful to be able to see the last messages without having to scroll past a million messages that I have previously read and replied to. Not unexpected at all.
The UI I ended up with is a sort button on the top of the thread header with the message list below:
The server side code in this application simply returns the HTML of the sorted list in ascending/default order, which is what is the most common scenario. Most people prefer to read messages chronologically. The only people who usually want to only read the last messages are moderators or support people like myself who need to sift through lots of posts and reply to answer messages.
Making the List Client Side Reversible
It's actually quite easy to make any list client side sortable or reversible, by adding an initial sort order indicator to the sortable elements in the page. So for example in my app I generate my list items like this using ASP.NET MVC and Razor on the server:
<div id="ThreadMessageList">
@{
int counter = 0;
foreach (var msg in Model.Messages)
{
counter++;
<div class="message-list-item" data-id="@msg.MsgId" data-sort="@counter">
... message content item here
</div>
}
}
</div>
The key here is the data-sort="@counter" attribute which effectively defines an initial sort order for the messages in this thread which generates:
<div id="ThreadMessageList">
<article class="message-list-item" data-id="4I3212L1" data-sort="1">
...
</article>
<article class="message-list-item" data-id="4I10J4WL9" data-sort="2">
...
</article>
</div>
At this point you have an ascending sorted list of messages.
Toggling the List to make it Reversible
The data-sort key is important even though it's not used in the ascending order, but it gives a basic comparer value to each DOM element that you can then use to sort the DOM elements. And how do we sort DOM elements you might ask?
jQuery actually makes this pretty damn simple because you can simply select all of your list elements and then run JavaScript's sort() function over the resulting element array. The steps for this are:
- Keep a static variable that holds the sort state (asc/desc)
- Select all the child elements with jQuery
- Run sort() over the result jQuery result set
- Detach and re-attach the elements to the parent node
Here's what this looks like:
wwthreads.sortAscending = true; // initialize globally for page
// handle sorting of thread messages
$(".main-content").on("click","#ReverseMessageOrder",function () {
wwthreads.sortAscending = !wwthreads.sortAscending;
var $msgList = $(".message-list-item");
$msgList.sort(function (a, b) {
var sort = a.getAttribute('data-sort') * 1;
var sort2 = b.getAttribute('data-sort') * 1;
var mult = 1;
if (!wwthreads.sortAscending)
mult = -1
if (sort > sort2)
return 1 * mult;
if (sort < sort2)
return -1 * mult;
return 0;
});
$msgList.detach().appendTo("#ThreadMessageList");
});
How it Works
The code starts by declaring a static variable where it's globally accessible. I use wwthreads.sortAscending which represents the toggle state of the sort option. To toggle the order all I do is not the value which effectively reverses the flag. As a side note I also load topics via AJAX requests, and whenever a topic is loaded the sortAscending flag is reset to true to make sure the initial message is displayed in the right order before it can be toggled.
The sort operation is initiated by the click on the #ReverseMessageOrder icon button which is also generated as part of the server side message. The first thing that happens is that I toggle the ascending order flag, so whatever the order currently is we're going to reverse it.
Next I capture all the message elements into a jQuery selected set. The nice thing about jQuery selectors is that they produce an array (actually and array-like list) that you can treat like an array using array functions. Which means we can use the JavaScript Array.sort() method on the result set. Sort iterates over the DOM elements and uses a Comparer function to receive two parameters for DOM elements to compare. Sort is internal and it shuffles elements asking your code to provide the logic to compare the two elements and return 1, –1, 0 to determine if the value is greater than, smaller than or equal.
So in our case, I look at the data-sort attribute's value for both elements passed, turn that value into a number (by multiplying by 1), and then decide depending on the sort order which value is 'larger'. Depending on whether we're doing ascending or descending order we then add a multiplier of 1 or –1 respectively. For ascending (natural) order we multiple by 1 which leaves the original order intact. For descending/reverse order we multiply by –1 which effectively reverses the sort logic. And that's it for the Comparer function.
When the .sort() completes the list has been resorted, but this doesn't affect the DOM as the list is only pointing at the existing DOM elements – although we sorted the list the DOM elements haven't changed. In order to update the DOM we have to actually detach the existing list, and reattach it to the parent element using this simple line:
$msgList.detach().appendTo("#ThreadMessageList")
And voila – the list is instantly updated.
This code is very simple and fairly generic to plug into any application easily, and it's a great client side enhancement feature you can easily add to server side application as an easy value add.
Bonus: Generating the Client Side data-sort Order
In the example above I used server side rendering to generate the initial sortorder and data-sort attribute, but that's not actually necessary (but easier if you can do it!). If you can't control the server side generated code for your sortable list you can generate the data-sort attributes yourself when the page loads (or when it refreshes as in my AJAX reloads). Assuming the initial list is in a specific order you can simply add the data-id using a little jQuery code:
// create initial data-sort elements
$(".message-list-item").each(function (index, el) {
$(el).attr("data-sort", index );
});
This will work the same as server generated code but you have to be careful if you reload content via AJAX as to make sure the list is updated each time the data is loaded. But, this is a good way to handle the list sort entirely on the client side – the server doesn't need to contribute anything to the behavior.
Summary
This is certainly not a new trick, but it's something that I do quite frequently in my applications, and I'm often surprised that this functionality is not provided in popular Web sites or customer implementations when this functionality is so easy to implement and add to any kind of application. Choice is good and your users will thank you for the option to quickly view things in a different order.
Other Posts you might also like