sort method for jquery.tablesorter
authorHenning Snater <henning.snater@wikimedia.de>
Tue, 4 Sep 2012 14:27:14 +0000 (16:27 +0200)
committerDerk-Jan Hartman <hartman@videolan.org>
Wed, 17 Oct 2012 19:13:50 +0000 (21:13 +0200)
the sort method allows programmatic sorting as well as instantly sorting
the table when initialising the tablesorter; furthermore, the method can
be used to reset sorting (e.g. if rows have been added later via JS);
sortEnd event may be used to reapply alternating table row colours or other purposes

patch set 2: fixed mentioned issues and added another test
patch set 3: addressed all further issues, introduced more obvious way to
specify sorting
patch set 4: implemented sort method instead of using an event; reprashed whole commit message; introduced 'sortEnd' event
patch set 5: fixed white space error
patch set 6: Add release notes and rebase

Change-Id: Id14862100cd27ebd6980c48dcf497db229c4301f

RELEASE-NOTES-1.21
resources/jquery/jquery.tablesorter.js
tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js

index d8f6506..e072974 100644 (file)
@@ -26,6 +26,8 @@ production.
 * jQuery UI upgraded from 1.8.23 to 1.8.24.
 * Added separate fa_sha1 field to filearchive table. This allows sha1
   searches with the api in miser mode for deleted files.
+* Add initial and programmatic sorting for tablesorter.
+* Add the event "sortEnd.tablesorter", triggered after sorting has completed.
 
 === Bug fixes in 1.21 ===
 * (bug 40353) SpecialDoubleRedirect should support interwiki redirects.
index 2940d6f..75dd2f1 100644 (file)
@@ -19,6 +19,9 @@
  * @example $( 'table' ).tablesorter();
  * @desc Create a simple tablesorter interface.
  *
+ * @example $( 'table' ).tablesorter( { sortList: [ { 0: 'desc' }, { 1: 'asc' } ] } );
+ * @desc Create a tablesorter interface initially sorting on the first and second column.
+ *
  * @option String cssHeader ( optional ) A string of the class name to be appended
  *         to sortable tr elements in the thead of the table. Default value:
  *         "header"
  *         tablesorter should cancel selection of the table headers text.
  *         Default value: true
  *
+ * @option Array sortList ( optional ) An array containing objects specifying sorting.
+ *         By passing more than one object, multi-sorting will be applied. Object structure:
+ *         { <Integer column index>: <String 'asc' or 'desc'> }
+ *         Default value: []
+ *
  * @option Boolean debug ( optional ) Boolean flag indicating if tablesorter
  *         should display debuging information usefull for development.
  *
+ * @event sortEnd.tablesorter: Triggered as soon as any sorting has been applied.
+ *
  * @type jQuery
  *
  * @name tablesorter
 
                }
                table.tBodies[0].appendChild( fragment );
+
+               $( table ).trigger( 'sortEnd.tablesorter' );
        }
 
        /**
        }
 
        function setHeadersCss( table, $headers, list, css, msg ) {
-               // Remove all header information
-               $headers.removeClass( css[0] ).removeClass( css[1] );
+               // Remove all header information and reset titles to default message
+               $headers.removeClass( css[0] ).removeClass( css[1] ).attr( 'title', msg[1] );
 
                var h = [];
                $headers.each( function ( offset ) {
                };
        }
 
+       /**
+        * Converts sort objects [ { Integer: String }, ... ] to the internally used nested array
+        * structure [ [ Integer , Integer ], ... ]
+        *
+        * @param sortObjects {Array} List of sort objects.
+        * @return {Array} List of internal sort definitions.
+        */
+
+       function convertSortList( sortObjects ) {
+               var sortList = [];
+               $.each( sortObjects, function( i, sortObject ) {
+                       $.each ( sortObject, function( columnIndex, order ) {
+                               var orderIndex = ( order === 'desc' ) ? 1 : 0;
+                               sortList.push( [columnIndex, orderIndex] );
+                       } );
+               } );
+               return sortList;
+       }
+
        /* Public scope */
 
        $.tablesorter = {
                                        // Declare and cache.
                                        var $document, $headers, cache, config, sortOrder,
                                                $table = $( table ),
-                                               shiftDown = 0,
-                                               firstTime = true;
+                                               shiftDown = 0;
 
                                        // Quit if no tbody
                                        if ( !table.tBodies ) {
                                        }
                                        $table.addClass( "jquery-tablesorter" );
 
+                                       // FIXME config should probably not be stored in the plain table node
                                        // New config object.
                                        table.config = {};
 
                                        config = $.extend( table.config, $.tablesorter.defaultOptions, settings );
 
                                        // Save the settings where they read
-                                       $.data( table, 'tablesorter', config );
+                                       $.data( table, 'tablesorter', { config: config } );
 
                                        // Get the CSS class names, could be done else where.
                                        var sortCSS = [ config.cssDesc, config.cssAsc ];
                                        // performance improvements in some browsers.
                                        cacheRegexs();
 
+                                       // Legacy fix of .sortbottoms
+                                       // Wrap them inside inside a tfoot (because that's what they actually want to be) &
+                                       // and put the <tfoot> at the end of the <table>
+                                       var $sortbottoms = $table.find( '> tbody > tr.sortbottom' );
+                                       if ( $sortbottoms.length ) {
+                                               var $tfoot = $table.children( 'tfoot' );
+                                               if ( $tfoot.length ) {
+                                                       $tfoot.eq(0).prepend( $sortbottoms );
+                                               } else {
+                                                       $table.append( $( '<tfoot>' ).append( $sortbottoms ) );
+                                               }
+                                       }
+
+                                       explodeRowspans( $table );
+
+                                       // try to auto detect column type, and store in tables config
+                                       table.config.parsers = buildParserCache( table, $headers );
+
+                                       // initially build the cache for the tbody cells (to be able to sort initially)
+                                       cache = buildCache( table );
+
                                        // Apply event handling to headers
                                        // this is too big, perhaps break it out?
-                                       $headers.click( function ( e ) {
+                                       $headers.filter( ':not(.unsortable)' ).click( function ( e ) {
                                                if ( e.target.nodeName.toLowerCase() === 'a' ) {
                                                        // The user clicked on a link inside a table header
                                                        // Do nothing and let the default link click action continue
                                                        return true;
                                                }
 
-                                               if ( firstTime ) {
-                                                       firstTime = false;
-
-                                                       // Legacy fix of .sortbottoms
-                                                       // Wrap them inside inside a tfoot (because that's what they actually want to be) &
-                                                       // and put the <tfoot> at the end of the <table>
-                                                       var $sortbottoms = $table.find( '> tbody > tr.sortbottom' );
-                                                       if ( $sortbottoms.length ) {
-                                                               var $tfoot = $table.children( 'tfoot' );
-                                                               if ( $tfoot.length ) {
-                                                                       $tfoot.eq(0).prepend( $sortbottoms );
-                                                               } else {
-                                                                       $table.append( $( '<tfoot>' ).append( $sortbottoms ) );
-                                                               }
-                                                       }
-
-                                                       explodeRowspans( $table );
-                                                       // try to auto detect column type, and store in tables config
-                                                       table.config.parsers = buildParserCache( table, $headers );
-                                               }
-
                                                // Build the cache for the tbody cells
                                                // to share between calculations for this sort action.
                                                // Re-calculated each time a sort action is performed due to possiblity
                                                        return false;
                                                }
                                        } );
+
+                                       /**
+                                        * Sorts the table. If no sorting is specified by passing a list of sort
+                                        * objects, the table is sorted according to the initial sorting order.
+                                        * Passing an empty array will reset sorting (basically just reset the headers
+                                        * making the table appear unsorted).
+                                        *
+                                        * @param sortList {Array} (optional) List of sort objects.
+                                        */
+                                       $table.data( 'tablesorter' ).sort = function( sortList ) {
+
+                                               if ( sortList === undefined ) {
+                                                       sortList = config.sortList;
+                                               } else if ( sortList.length > 0 ) {
+                                                       sortList = convertSortList( sortList );
+                                               }
+
+                                               // re-build the cache for the tbody cells
+                                               cache = buildCache( table );
+
+                                               // set css for headers
+                                               setHeadersCss( table, $headers, sortList, sortCSS, sortMsg );
+
+                                               // sort the table and append it to the dom
+                                               appendToTable( table, multisort( table, sortList, cache ) );
+                                       };
+
+                                       // sort initially
+                                       if ( config.sortList.length > 0 ) {
+                                               explodeRowspans( $table );
+                                               config.sortList = convertSortList( config.sortList );
+                                               $table.data( 'tablesorter' ).sort();
+                                       }
+
                                } );
                        },
 
index 16d8170..291c6b8 100644 (file)
@@ -107,6 +107,24 @@ var planets         = [mercury, venus, earth, mars, jupiter, saturn];
 var ascendingName   = [earth, jupiter, mars, mercury, saturn, venus];
 var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter];
 
+tableTest(
+       'Basic planet table: sorting initially - ascending by name',
+       header,
+       planets,
+       ascendingName,
+       function ( $table ) {
+               $table.tablesorter( { sortList: [ { 0: 'asc' } ] } );
+       }
+);
+tableTest(
+       'Basic planet table: sorting initially - descending by radius',
+       header,
+       planets,
+       reversed(ascendingRadius),
+       function ( $table ) {
+               $table.tablesorter( { sortList: [ { 1: 'desc' } ] } );
+       }
+);
 tableTest(
        'Basic planet table: ascending by name',
        header,
@@ -158,6 +176,81 @@ tableTest(
        }
 );
 
+// Sample data set to test multiple column sorting
+var header  = [ 'column1' , 'column2'],
+       a1 = [ 'A', '1' ],
+       a2 = [ 'A', '2' ],
+       a3 = [ 'A', '3' ],
+       b1 = [ 'B', '1' ],
+       b2 = [ 'B', '2' ],
+       b3 = [ 'B', '3' ];
+var initial = [a2, b3, a1, a3, b2, b1];
+var asc = [a1, a2, a3, b1, b2, b3];
+var descasc = [b1, b2, b3, a1, a2, a3];
+
+tableTest(
+       'Sorting multiple columns by passing sort list',
+       header,
+       initial,
+       asc,
+       function ( $table ) {
+               $table.tablesorter(
+                       { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] }
+               );
+       }
+);
+tableTest(
+       'Sorting multiple columns by programmatically triggering sort()',
+       header,
+       initial,
+       descasc,
+       function ( $table ) {
+               $table.tablesorter();
+               $table.data( 'tablesorter' ).sort(
+                       [ { 0: 'desc' }, { 1: 'asc' } ]
+               );
+       }
+);
+tableTest(
+       'Reset to initial sorting by triggering sort() without any parameters',
+       header,
+       initial,
+       asc,
+       function ( $table ) {
+               $table.tablesorter(
+                       { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] }
+               );
+               $table.data( 'tablesorter' ).sort(
+                       [ { 0: 'desc' }, { 1: 'asc' } ]
+               );
+               $table.data( 'tablesorter' ).sort();
+       }
+);
+QUnit.test( 'Reset sorting making table appear unsorted', 3, function ( assert ) {
+       var $table = tableCreate( header, initial );
+       $table.tablesorter(
+               { sortList: [ { 0: 'desc' }, { 1: 'asc' } ] }
+       );
+       $table.data( 'tablesorter' ).sort( [] );
+
+       assert.equal(
+               $table.find( 'th.headerSortUp' ).length + $table.find( 'th.headerSortDown' ).length,
+               0,
+               'No sort specific sort classes addign to header cells'
+       );
+
+       assert.equal(
+               $table.find( 'th' ).first().attr( 'title' ),
+               mw.msg( 'sort-ascending' ),
+               'First header cell has default title'
+       );
+
+       assert.equal(
+               $table.find( 'th' ).first().attr( 'title' ),
+               $table.find( 'th' ).last().attr( 'title' ),
+               'Both header cells\' titles match'
+       );
+} );
 
 // Regression tests!
 tableTest(
@@ -306,7 +399,7 @@ tableTest(
        planets,
        planetsRowspan,
        function ( $table ) {
-               // Modify the table to have a multiuple-row-spanning cell:
+               // Modify the table to have a multiple-row-spanning cell:
                // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
                $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
                // - Set rowspan for 2nd cell of 3rd row to 3.
@@ -317,13 +410,29 @@ tableTest(
                $table.find( '.headerSort:eq(0)' ).click();
        }
 );
+tableTest(
+       'Basic planet table: same value for multiple rows via rowspan (sorting initially)',
+       header,
+       planets,
+       planetsRowspan,
+       function ( $table ) {
+               // Modify the table to have a multiple-row-spanning cell:
+               // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
+               $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
+               // - Set rowspan for 2nd cell of 3rd row to 3.
+               //   This covers the removed cell in the 4th and 5th row.
+               $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
+
+               $table.tablesorter( { sortList: [ { 0: 'asc' } ] } );
+       }
+);
 tableTest(
        'Basic planet table: Same value for multiple rows via rowspan II',
        header,
        planets,
        planetsRowspanII,
        function ( $table ) {
-               // Modify the table to have a multiuple-row-spanning cell:
+               // Modify the table to have a multiple-row-spanning cell:
                // - Remove 1st cell of 4th row, and, 1st cell or 5th row.
                $table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove();
                // - Set rowspan for 1st cell of 3rd row to 3.
@@ -414,6 +523,24 @@ tableTest(
        }
 );
 
+QUnit.test( 'Test detection routine', function ( assert ) {
+       var $table;
+       $table = $(
+               '<table class="sortable">' +
+               '<caption>CAPTION</caption>' +
+               '<tr><th>THEAD</th></tr>' +
+               '<tr><td>1</td></tr>' +
+               '<tr class="sortbottom"><td>text</td></tr>' +
+               '</table>'
+       );
+       $table.tablesorter();
+
+       assert.equal(
+               $table.data( 'tablesorter' ).config.parsers[0].id,
+               'number',
+               'Correctly detected column content skipping sortbottom'
+       );
+} );
 
 /** FIXME: the diff output is not very readeable. */
 QUnit.test( 'bug 32047 - caption must be before thead', function ( assert ) {