Sorting Values with JavaScript

Lists and tables are often the best way to display data on the web; but you shouldn’t have to worry about sorting that information manually. In today’s tutorial, you’re going to make a jQuery plugin that will put all your ducks in a row with JavaScript ease!


Preface

So, how exactly does sorting work in JavaScript? It’s not too complicated: any array object has a sort method. If you don’t pass it any parameters, it will convert the objects in the array to strings, sort them pseudo-alphabetically, and return them. Usually, this is terrible; consider sorting the numbers 0 – 10 alphabetically. You would get this: [0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]. Fortunately, we can pass a function to the sort method. That function should take two parameters (the two items to be compared): then, it will return 0 if they are equal, a negative number if the first parameter takes precedence, or a positive number of the second parameter should come first. So numbers are actually the simplest thing to sort “manually”:

numberArray.sort(function(a, b) {
    return a - b
});

Obviously, this will return 0 if the numbers are equal, a negative number if a should be first, and a positive number if b should be first.

We’re going to look at sorting several different types of data, a few in multiple formats; but this will all be much more useful if we wrap it in a jQuery plugin, so let’s start by setting up that shell!

The Plugin Shell

You still can't crate a jQuery Plugin

If you’re not familiar with writing jQuery plugins, check out Jeffrey Way’s Screencast “You still can’t create a jQuery Plugin?” It’ll get you up to speed in no time if you’re comfortable with jQuery! (true confession: I’d actually never written a plugin until I made this one).

We’ll set up our plugin, called datasort, this way: we’ll pass it an array of items to sort; we can specify four parameters.

  • datatype (the type of data you’re sorting)
  • sortElement (the child element you want to sort by, if desired)
  • sortAttr (the attribute you want to sort by, if desired)
  • reverse (the direction they should sort in)

So a fully-modified call to our plugin might look like this:

$('ul.names li).datasort({
    		datatype    : 'alpha',
    		sortElement : 'span.first',
    		sortAttr    : 'rel',
    		reverse     : true
    	});

Here’s the plugin shell:

(function ($) {
  $.fn.datasort = function(options) {
    var defaults = {
    	//set the default parameter values
          datatype    : 'alpha',
          sortElement : false,
          sortAttr    : false,
          reverse     : false
          },
    // combine the default and user's parameters, overriding defaults
        settings = $.extend({}, defaults, options), 
        datatypes = {},
        base = {},
        that = this;

    if (typeof settings.datatype === 'string') {
      that.sort(datatypes[settings.datatype]);
    }
    if (typeof settings.datatype === 'function') {
      that.sort(settings.datatype);
    }
    if(settings.reverse) {
      that = $($.makeArray(this).reverse());
    }
    $.each(that, function(index, element) { that.parent().append(element); });
  };
})(jQuery);

So here’s how it’ll work: we’ll set up all the variables at the beginning. Then, if the datatype parameter is a string, we’ll find the corresponding sort function in the datatypes object and sort with it; if the datatype parameter is a function, we’ll sort with it. Finally, if the reverse setting is set to true, we’ll reverse the order of the sorted items (since jQuery objects aren’t true JavaScript arrays, the reverse function won’t work on them; so we can use $.makeArray() to turn it into one; then, once it’s reversed, we re-jquery-fy it!).

Laying a Bit More Groundwork

At the very lowest level, you can sort almost any type of data in one of two ways: we’ll be calling them alphabetically and numerically. Let’s create these two functions as properties of your base object.

base = {
  alpha : function(a, b) {
    a = a.toUpperCase();
    b = b.toUpperCase();
    return (a < b) ? -1 : (a > b) : 1 : 0;
    //ternary operator: condition ? returnIfTrue : returnIfFalse
  },
  number : function(a, b) {
    a = parseFloat(a);
    b = parseFloat(b);
    return a - b;
  }
},

Pretty simple, eh? Simply normalize the two values, compare and return. The tricky part is parsing the data that we want to send to these functions; that’s what we’ll do now. However, there’s one more thing.

When sorting items in the array, we might not want to sort simply by the text of the element itself. The sortElement and sortAttr parameters of our plugin are to this end. For example, we will likely want to sort table rows based on a certain column of table cells. In that case, we’d use $(‘table tr’).datasort({ sortElement : ‘td.price’ }). Or perhaps we want to sort a list of images by their alt attributes: $(‘ul li’).datasort({sortElement : ‘img’, sortAttr : ‘alt’}). Because of all this, we need to add one more function to our base object:

base = {
  alpha : function (a, b) { ... },
  number : function (a, b) { ... },
  extract : function (a, b) {
  	var get = function (i) {
      var o = $(i);
      if (settings.sortElement) {
        o = o.children(settings.sortElement);
      }
      if (settings.sortAttr) {
        o = o.attr(settings.sortAttr);
      } else {
        o = o.text();
      }
      return o;
    };
    return {
      a : get(a),
      b : get(b)
    };
  }		
},

It may look complicated, but it’s not. We just create a jQuery object with each item; if sortElement is set, we use the children() method to get the right elements. Then, if a sortAttr is set, we get its value; if not, we get the element’s text. We’ve set all this to an inner function, and return an object with two properites; these properties are the values we must parse and send to the appropriate base sorting function.

This probably seemed like a lot of prep work, but what we were really doing is abstracting as much code as possible. This way, they’ll be much less repeat code, because the important actions have been bundled away as functions.

Sorting Words and Numbers

We’re finally here: the fun part! We’ll start by building two simple functions for our datatypes object. These will simple pass values to base.extract() and then pass those return values to the appropriate sorting class.

datatypes = {
  alpha : function (a, b) {
    var o = base.extract(a, b);
    return base.alpha(o.a, o.b);
  },
  number : function(a, b) {
    var o = base.extract(a, b);
    for (var e in o) {
      o[e] = o[e].replace(/[$]?(-?\d+.?\d+)/, '\$1');
    }
    return base.number(o.a, o.b);
  },
},

Our alphabetic sorter should be obvious. The number sorter does a bit more: before passing the extracted values on, it strips out a dollar sign at the front. I’ve kept this regular expression simple, but you could parse a lot of different number formats here if you wanted to get complex. Let’s give our evolving plugin a try; create a basic html page:

<!DOCTYPE html>
<html>
<head>
  <meta charset='utf-8' />
  <title>Data Sorting</title>
  <style type='text/css'>
  ul, table {
    display:table;
    float:left;
    background:#ececec;
    margin:10px;
    padding:0;
    border:1px solid #ccc;
  }
  li, tr {
    margin:0;
    padding:8px;
    border-top:1px solid #fff;
    border-bottom:1px solid #ccc;
    list-style-type:none;
  }
  li:first-child { border-top:0 }
  li:last-child { border-bottom:0 }
  </style>
</head>
<body>
  <table class='a'>
    <thead>
      <tr>
        <th rel='alpha' class='first'>First Name</th>
        <th rel='alpha' class='last'>Last Name</th>
      </tr>
    </thead>
    <tbody>
      <tr><td class="first">Jeffrey</td> <td class="last">Way</td></tr>
      <tr><td class="first">Sean</td> <td class="last">Hodge</td></tr>
      <tr><td class="first">Adam</td> <td class="last">Miller</td></tr>
      <tr><td class="first">Ian</td> <td class="last">Yates</td></tr>
      <tr><td class="first">Adrian</td> <td class="last">Try</td></tr>
      <tr><td class="first">Caleb</td> <td class="last">Aylsworth</td></tr>
    </tbody>
  </table>

  <ul class='n'>
  <li>4.09</li>
  <li>4.10</li>
  <li>67.8</li>
  <li>100</li>
  <li>-98</li>
  <li>67.7</li>
  <li>23</li>
  </ul> 

  <ul class="curr">
    <li>$299.66</li>
    <li>$299.57</li>
    <li>$0.14</li>
    <li>$80.00</li>
  </ul>

  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" /></script>
  <script src="jquery.datasort.js" /></script>
  <script type="text/javascript">
    $('table.a tbody tr').datasort({sortElement : 'td.last'});
    $('ul.n li').datasort({datatype: 'number', reverse: true});
    $('ul.curr li').datasort({ datatype: 'number' });
  </script>
</body>
</html>

I’ve included a table and two lists (and I’ve styled them briefly). Take note of our plugin calls: we’re using the default datatype for the table, but sorting by the table cells with a class of last; try changing this to ‘td.first.’ Then, we sort the lists numerically, and reverse one of them. Here’s the proof of our labours:

Sorting Alphabetically and Numerically

Pretty nice, but those were relatively simple values; what if we want to be able to sort multiple formats for one type?

Sorting Dates

There are a number of different ways to write dates, which makes it pretty tricky to parse them for sorting. However, we can cover most of them with this:

date : function(a, b) {
  var o = base.extract(a, b);
  for (var e in o) {
  o[e] = o[e].replace(/-/g, '')
             .replace(/january|jan/i, '01')
             .replace(/february|feb/i, '02')
             .replace(/march|mar/i, '03')
             .replace(/april|apr/i, '04')
             .replace(/may/i, '05')
             .replace(/june|jun/i, '06')
             .replace(/july|jul/i, '07')
             .replace(/august|aug/i, '08')
             .replace(/september|sept|sep/i, '09')
             .replace(/october|oct/i, '10')
             .replace(/november|nov/i, '11')
             .replace(/december|dec/i, '12')
             .replace(/(\d{2}) (\d{2}), (\d{4})/, '\$3\$1\$2')
             .replace(/(\d{2})\/(\d{2})\/(\d{4})/, '\$3\$2\$1');
  }
  return base.number(o.a, o.b);
},

So what are we doing here? First, here’s the logic: if all the dates are formatted YYYYMMDD, they will sort correctly with numerical sorting. Our parser can sort the following date formats:

  • YYYY-MM-DD
  • YYYYMMDD
  • DD/MM/YYYY
  • month DD, YYYY

First we strip our dashes, which will leave YYYY-MM-DD ready for parsing. Then, we replace every month name or abbreviation with its number value. Finally, we have to rearrange the numbers for DD/MM/YYY and month DD, YYYY. That’s what the last two expressions do. To give this a try, paste this list into our HTML:

<ul class='date'>
  <li>2009-10-06</li>
  <li>sept 25, 1995</li>
  <li>1990-06-18</li>
  <li>20100131</li>
  <li>June 18, 2009</li>
  <li>02/11/1993</li>
  <li>15941219</li>
  <li>1965-08-05</li>
  <li>1425-12-25</li>
</ul>

And call it with this:

    $('ul.date li').datasort({datatype: 'date'});
Sorting Dates

Is this a perfect date parser? Not by any means; we can’t sort DD/MM/YY, because there’s no way to know what century this is in. Also, we can’t tell the difference between DD/MM/YY and MM/DD/YY, so we just have to choose one.

Sorting Time

Sorting time values must be one of the most difficult values to sort: we need to be able to accept 12-hour time, 24-hour time, and values with or without AM/PM tags and seconds. I think it’s easiest to sort time alphabetically, even though its all numbers. Why? Consider these two timestamps: 00:15:37 and 12:15. The first one should come first, but if we sort them by number they’ll be parsed as floats, and end up like 1537 and 1215. Now, the second value will come first. Also, when sorting alphabetically, we don’t have to take out the colons (parseFloat() would choke on them). So here’s how it’s done.

time : function(a, b) {
  var o = base.extract(a, b),
      afternoon = /^(.+) PM$/i;
  for (var e in o) {
    o[e] = o[e].split(':');
    var last = o[e].length - 1;

    if(afternoon.test(o[e][last])) {
      o[e][0] = (parseInt(o[e][0]) + 12).toString();
      o[e][last] = o[e][last].replace(afternoon, '\$1');
    }
    if(parseInt(o[e][0]) < 10 && o[e][0].length === 1) {
      o[e][0] = '0' + o[e][0];
    }
    o[e][last] = o[e][last].replace(/^(.+) AM$/i, '\$1');

    o[e] = o[e].join('');
  }
  return base.alpha(o.a, o.b);
} 

Let's go through this line by line.

  var o = base.extract(a, b),
      afternoon = /^(.+) PM$/i;

We start with our variables: our extracted values and a regular expression to check for PM label.

  for (var e in o) {
    o[e] = o[e].split(':');
    var last = o[e].length - 1;

    if(afternoon.test(o[e][last])) {
      o[e][0] = (parseInt(o[e][0]) + 12).toString();
      o[e][last] = o[e][last].replace(afternoon, '\$1');
    }

Next, we'll start a for loop, going through each of the values we're sorting; first, we split it into an array at the colons. We create an easy way to get to the last items of the array: our 'last' variable. Then, we test our PM regex on the last item in our array; if it returns true, this value has the PM tag. Therefore, we'll add 12 to the first item in our array, which will be the hour value; we do this because we need all the values to be formatted in 24-hour time. (Note that to do this, we must convert it to a number, add 12, and then turn it back into a string). Finally, we use the PM regex again to remove that label from the last item in the array.

    if(parseInt(o[e][0]) < 10 && o[e][0].length === 1) {
      o[e][0] = '0' + o[e][0];
    }
   o[e][last] = o[e][last].replace(/^(.+) AM$/i, '\$1');

    o[e] = o[e].join('');
}
return base.alpha(o.a, o.b);

In this last chunk, we check the hour value for two conditions: is it less than 10? and does the string have only one character? This is important because a value like 08 will parse as 8 and be less than 10; but we're trying to see if we need to add a zero to the front. If the string has only one character, then we add the zero, so 3 becomes 03. This will keep things in order!

Before joining the array, we remove any AM labels. So now this . . .

<ul class='time'>
  <li>1:15:47</li>
  <li>3:45 PM</li>
  <li>12:00:17</li>
  <li>06:56</li>
  <li>19:39</li>
  <li>4:32 AM</li>
  <li>00:15:36</li>
</ul>

. . . can be sorted with this . . .

$('ul.time li').datasort({datatype: 'time'});

And we're done! Behold the fruits of our labour:

Sorting Time

More Random Values

We've set up our jQuery plugin so that users can pass sorting functions as the datatype parameter. This allows us to easily extend the plugin, although we don't have access to the base 'class' from the plugin call. We can easily write a function to sort psudeo-ratings:

$('ul.rating li').datasort({datatype: function(a, b) {
      var o  = {
      a : $(a).text(),
      b : $(b).text() 
      }
      for (var e in o) {
        o[e] = o[e].replace(/poor/i, 0)
                   .replace(/satisfactory/i, 1)
                   .replace(/good/i, 2)
                   .replace(/excellent/i, 3);
      }
      return o.a - o.b;
    }
});

This uses the simplest regular expressions possible to sort a list like this:

<ul class="rating">
  <li>Good</li>
  <li>Excellent</li>
  <li>Poor</li>
  <li>Satisfactory</li>
</ul>

That's a Wrap!

Now you're in the know: sorting values in JavaScript really isn't as hard as you might have thought. You can imagine this being useful to sort a table, with something like this:

$('table#myTable thead th').toggle(
  function() {
    var $this = $(this);
    $('table#myTable tbody tr').datasort({
      datatype: $this.attr('rel'),
      sortElement: 'td.' + $this.attr('class')
    });
  }, 
  function() {
    var $this = $(this);
    $('table#myTable tbody tr').datasort({
      datatype: $this.attr('rel'), 
      sortElement: 'td.' + $this.attr('class'),
      reverse: true 
      });
  }
);

(Try replacing the jQuery code for the table in the first example with this!)

Of course, we could improve this plugin a lot; for example, we could have it check the rel atttribute for a datatype if one isn't given as a parameter, and default to alpha if there is no rel. But that's aside from the sorting.

In sum, to sort with JavaScipt, we follow these steps:

  1. Determine the different formats you want to sort.
  2. Decide what format you want to sort in.
  3. Sort the array of items with the sort() method, passing in a function that will convert the two items to your desired format before comparing them

Have a datatype to add to our plugin? Have a better way of sorting one of these? Let's hear it in the comments!


Tags: jQuery
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • ShadowAssassin

    Thank you very very much :D

    • http://test test

      what’s thanks

  • Jason

    Great tutorial! You can also look at Adobe Spry – http://labs.adobe.com/technologies/spry/samples/data_region/SortSample.html :)

  • http://spotdex.com/ David Moreen

    I can for sure use this, in the future. For that I say think you.

  • Christophor S. Wilson

    I typically do sorting in PHP but this is great the end user does not have to wait for the page to reload plus you do not have to run another query just to resort the content. Great tut!

  • http://www.2gaoav.com gaoav

    既然进来了,就留个脚印

  • http://11heavens.com/ Caroline Schnapp

    What would you use to random-sort, that is, shuffle, an array?

    • http://andrewburgess.posterous.com Andrew Burgess
      Author

      Good Question! Had me stumped for a while, but after a bit of Googling, I create this: http://gist.github.com/239270. I’m sure it could be improved, but it works. Basically, you just make a copy of the array, and switch values between the original and the copy.

      Hope that helps!

      • dt

        goodstuff.

  • http://www.erenyagdiran.com eren yagdiran

    wow great..do you have any performance result according to sorting process?

    • http://tehkemo.com tehkemo

      basically, when you dont need to sort thousands of items I think there is no need to care about performance – all is done on user workstations and today all major browsers are more than fast with Js…

      Just my thought…

      now, kill me.. :P :)

    • http://andrewburgess.posterous.com Andrew Burgess
      Author

      I haven’t done any performance testing yet, but maybe I should :)

  • http://technology.johnsamuel.in John Samuel

    Thanks. Quite useful post

  • http://sonergonul.com Soner Gönül

    Great..!

  • http://cv-templates.info Maratonda

    I think I did not understand in which part the code identifies the arguments “a” and “b” which are then passed to the sorting functions.

    • http://andrewburgess.posterous.com Andrew Burgess
      Author

      That’s automatically done by the sort function. It will take the first and second items of the array, compare them, and return them in the correct order. Then it does the same with the second and third elements, and so on.

  • http://uid0.pl uid_0

    Another very useful article, thanks!

  • http://sasa.po.gs sasa

    I don’t understand, what to do if JavaScript is disabled in user browser.
    Now I try in my web move all basic things to server side.

  • http://www.deftjs.com Timothy

    You missed a quote in the second syntax block.

    Plugins are cool and whatnot, but do be careful if you use others’ plugins. Don’t trust code you don’t understand. It’s enough that people don’t really look into how jQuery, MooTools or a similar framework actually work.

    I’m actually considering pushing the whole ‘plugin’ aspect when my framework (www.deftjs.com) goes live. But for the reasons stated above I’m not sure if (for the end user) that is a good idea…

  • IgnacioRV

    Cool, it reminded me of java’s .compareTo() and .sort(), I overwrited them a lot of times xD

  • http://www.webhostdesignpost.com/website/ WebHostDesignPost

    Nice tutorial…..I love how it can sort on the fly.

  • http://vector.laroouse.com esranull

    very nice tutorial thanks

  • http://tutorialblog.info/ tutorial blog

    good code

    • http://11heavens.com/ Caroline Schnapp

      What the hell is that shit thing you call a tutorial blog? Nice avatar (nothing against that, actually) but your website alone makes you quite deserving of a full ban. P** off.

  • http://www.nouveller.com/ Benjamin Reid

    Anyone interested in a related jQuery plugin should check out http://tablesorter.com/docs/ A really quick way to sort out tabular data.

  • Raou

    Did you make a mistake in the 73rd line?

    71: number : function(a, b) {
    72: a = parseFloat(a);
    73: b – parseFloat(b);
    74: return a – b;
    75: },

  • http://sdf sdf

    Did not understand that at all, a video tutorial would be most apreciated. Trying to get a better understanding of oop to play with arduinos. But i figure learning js would be more practical for what i do at work.

  • Shimon

    Hi.
    Excelent tutorial, saved me a lot of time, thanks!
    But there is a problem when one wants to sort by some element which is not direct child of item being sorted, like this:

    lorem
    ipsum

    and you sort it with $(‘ul li’).datasort({sortElement:’div span’});
    In this case this
    8: o = o.children(settings.sortElement);
    will not work because children() gives you only immediate children..
    May be you should consider using ‘find()’ instead of ‘children()’?

  • http://www.pdeegii.blogspot.com deegii

    Good job thanks.
    Sorry for my English is bad.
    I have a one problem.How to sort many “ul” with same class name? It merges to one tag all data. It is really need me. Pls help me.

    • http://www.pdeegii.blogspot.com deegii

      In the last real line of the plugin (within the ‘each’) change that.parent() to $(element).parent(). should work now

  • http://bloggerzbible.blogspot.com/ Bloggerzbible

    thanks for the demo

  • http://www.fatlizardmedia.com Juan C. Rois

    This post is a few moths old, but if anybody (hopefully Andrew) out there can help me, I would really appreciate it.
    I’m using this tutorial as the base for a project that I’m working on, and unfortunately my java script is still very weak.
    I’m using it to sort values in a table and it works perfect when I sort the rows, however if I wrap each row in a tag (there is a reason for that), it stops sorting correctly. It reverses the order of the tags but not by the criteria that was passed (say each column has different data types).
    Any help would greatly appreciated.

    • http://www.fatlizardmedia.com Juan C. Rois

      Tags means tbody tags. Did not realized that I could not post code.

  • Gopal Veera

    Thanks for the tutorial. However, I did notice that the time sort code has a few bugs. It does not account for the 12:00 AM – 12:59 AM case and the 12:00 PM – 12:59 PM case. There is also a problem in using parseInt with out a base 10 qualifier. This would parse a ’08′ as 0 because of a default OCTAL parse. All you need to do is replace your parseInt’s with parseInt( , 10).

    Good job.