views:

136

answers:

6

I have a table which needs to be sorted by several columns - let's say music which is sorted by genre, then by artist, album and so on. Or you can sort by artist/album/track. This is what I came up with in jquery:

function tableSort (by) {                                                                             
    var mylist = $('#mainList');                                                                      
    var listitems = $('.row').get();                                                                                                                                               
    listitems.sort(function(a, b) {                                                                   
        var cidA=$(a).attr('id');                                                       
        var cidB=$(b).attr('id');                                                       
        var i=[1,2,4,3];                                                                              
        var compA=[];                                                                                 
        var compB=[];                                                                                 
        compA[0]=$(a).find('.album').html();                                     
        compB[0]=$(b).find('.album').html();;                                    
        compA[1]=$(a).find('.artist').html();             
        compB[1]=$(b).find('.artist').html();             
        compA[2]=$(a).find('.genre').html();                                                             
        compB[2]=$(b).find('.genre').html();                                                             
        compA[3]=$(a).find('.track').val();                                                          
        compB[3]=$(b).find('.track').val();                                                          
        switch (by) {                                                                                 
            case 'genre->artist->album':
                i=[2,0,1,3];                                                                          
                break;                                                                                
            case 'genre->album->artist':                                                                             
                i=[2,1,0,3];                                                                          
                break;                                                                                
            default:                                                                                  
                compA='';                                                                             
                compB='';                                                                             
        }                                                                                             
        var comp=(compA[i[0]] < compB[i[0]]) ? -1 : (compA[i[0]] > compB[i[0]]) ? 1 : 0;              
        if (comp==0) comp=(compA[i[1]] < compB[i[1]]) ? -1 : (compA[i[1]] > compB[i[1]]) ? 1 : 0;     
        if (comp==0) comp=(compA[i[2]] < compB[i[2]]) ? -1 : (compA[i[2]] > compB[i[2]]) ? 1 : 0;     
        if (comp==0) comp=(compA[i[3]] < compB[i[3]]) ? -1 : (compA[i[3]] > compB[i[3]]) ? 1 : 0;     
        return comp;                                                                                  
    });                                                                                               

$.each(listitems, function(i, v) {                                                                    
    var id=$(v).attr('id').split('_')[1];                                                             
    mylist.append(v);                                                                                 
    });                                                                                               
}

This works for me with 3-4 orders, but there must be a more elegant and scalable way!

+1  A: 

If you do not mind using a plugin try tablesorter or the lightweight tinytable

redsquare
+2  A: 

Not tested but it should work:

listitems.sort(function(a, b) { 
    var $a=$(a),$b=$(b),
        i=[1,2,4,3],  
        compA=[],   
        compB=[],
        cols=['album', 'artist', 'genre', 'track'];
    for(var c=0; c<cols.length; c++)
    {
        var fn=cols[c]==="track" ? "val" : "html";
        compA.push($a.find("."+cols[c])[fn]());
        compB.push($b.find("."+cols[c])[fn]());
    }
    switch (by) { 
        case 'genre->artist->album':
            i=[2,0,1,3];
            break;
        case 'genre->album->artist':
            i=[2,1,0,3];
            break;
        /*default: 
            compA='';   //WHY???
            compB='';*/
    }
    for(c=0; c<i.length; c++)
    {
        if(compA[i[c]]<compB[i[c]]) return -1;
        else if(compA[i[c]]>compB[i[c]]) return 1;
    }
    return 0;
});
mck89
+1  A: 

I would recommend using a plugin for something like this.

A quick google search shows tablesorter, which I believe does everything you were looking for. The minified version is only 12kb too, so its not a huge issue to load into the site.

Ryan French
A: 

I second the recommendation of a plugin to handle sorting and offload potential bugs discovery to a community. jqGrid pops to mind, but i don't think you'll have problems with too few choices :)

samy
+1  A: 

mck89 has a great answer, but I fixed the numeric sort & tried cleaning it all up a bit more (demo):

HTML

<table>
 <tr><td colspan=2>Sort:</td></tr>
 <tr><td>Direction:</td><td><select class="dir"><option value="asc">Ascending</option><option value="desc">Descending</option></select></td></tr>
 <tr><td>Order:</td><td><ul class="order"><li>Album <span>-&gt;</span></li><li>Artist <span>-&gt;</span></li><li>Genre <span>-&gt;</span></li><li>Track <span style="display:none">-&gt;</span></li></ul></td></tr>
 <tr><td colspan=2><button>Sort</button></td></tr>
</table>
<br>
<table id="mainList">
 <thead>
  <tr><th>Album</th><th>Artist</th><th>Genre</th><th>Track</th></tr>
 </thead>
 <tbody>
  <tr><td>...</td><td>...</td><td>...</td><td>#</td></tr>
  ...
 </tbody>
</table>

Script

$(document).ready(function(){
 $('.order').sortable({
  update: function(e,ui){
   $('.order span').show().filter(':last').hide(); // hide last arrow
  }
 });

 $(':button').click(function(){
  var table = ['Album', 'Artist', 'Genre', 'Track'],
   order = [];
  $('.order li').each(function(){
   order.push( $.inArray( $(this).text().replace(' ->',''), table ) );
  });
  tableSort(order, $('.dir').val());
 })
})

 function tableSort(by,dir){
  var mylist = $('#mainList'),
   listitems = mylist.find('tbody tr').get();

  listitems.sort(function(a, b) {
   var $a = $(a),
    $b = $(b),
    i = by,
    compA = [],
    compB = [],
    c;

   for(c=0; c<3; c++){
    compA.push($a.find('td').eq(c).html()); // Alphabetical
    compB.push($b.find('td').eq(c).html());
   }
    compA.push(('0' + $a.find('td:last').html()).slice(-2)); // Numerical (2 digit numbers)
    compB.push(('0' + $b.find('td:last').html()).slice(-2)); // use '00' + ....slice(-3) for 3 digit numbers, etc...

   for(c=0; c<i.length; c++){
    return (dir == 'asc') ?
     ((compA[i[c]] < compB[i[c]]) ? -1 : (compA[i[c]] > compB[i[c]]) ? 1 : 0) : // ascending sort
     ((compA[i[c]] > compB[i[c]]) ? -1 : (compA[i[c]] < compB[i[c]]) ? 1 : 0)   // descending sort
   }
  });

  $.each(listitems, function(i, v) {
   mylist.append(v);
  });
 }
fudgey
A: 

My javascript / jQuery isn't great, but a general technique that might be useful here is building a composite sort key from the selected columns, then sorting on that.

To do this, you identify the maximum possible length of each column, pad the value in each sort column to its maximum, then concatenate the values in the sort columns to produce a single composite sort key.

For example, if the sort order is genre / album / artist, and the maximum length of these fields is 20 / 100 / 100, then for each row the composite sort key is

pad(genre, 20) + pad(album, 100) + pad(artist, 100)

where pad is a function that pads the given text to the given length, with spaces.

For a given set of sort columns, you could define a function that takes a row as its input and returns the composite sort key. Then just sort on those (single) values. The padding and concatenation ensures that each row's sort key is of the same length, and that natural string sorting will give the order you want.

AakashM