* @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();
+ }
+
} );
},
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,
}
);
+// 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(
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.
$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.
}
);
+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 ) {