From: Brad Jorsch Date: Mon, 19 Nov 2012 14:47:11 +0000 (-0500) Subject: jquery.tablesorter: Fix explodeRowspans X-Git-Tag: 1.31.0-rc.0~20309 X-Git-Url: https://git.heureux-cyclage.org/?a=commitdiff_plain;h=4cc5048ce11fef7f0983604f1f542fb3cce1a7f1;p=lhc%2Fweb%2Fwiklou.git jquery.tablesorter: Fix explodeRowspans $.tablesorter's explodeRowspans works ok for simple tables, but row headers or unusual spanning structures will confuse it. This rewrite makes it more robust. Bug: 41889 Change-Id: Icb674f7eece053435ca9525d45709579df14cc74 --- diff --git a/RELEASE-NOTES-1.21 b/RELEASE-NOTES-1.21 index 2e3cf72c2d..295eb7bfc5 100644 --- a/RELEASE-NOTES-1.21 +++ b/RELEASE-NOTES-1.21 @@ -204,6 +204,7 @@ production. script in includes/DefaultSettings.php * (bug 45143) jquery.badge: Treat non-Latin variants of zero as zero as well. * (bug 46151) mwdocgen.php should not ignore exit code of doxygen command. +* (bug 41889) Fix $.tablesorter rowspan exploding for complex cases. === API changes in 1.21 === * prop=revisions can now report the contentmodel and contentformat. diff --git a/resources/jquery/jquery.tablesorter.js b/resources/jquery/jquery.tablesorter.js index 8bf1f614c2..e08c9aafb4 100644 --- a/resources/jquery/jquery.tablesorter.js +++ b/resources/jquery/jquery.tablesorter.js @@ -431,24 +431,86 @@ } + /** + * Replace all rowspanned cells in the body with clones in each row, so sorting + * need not worry about them. + * + * @param $table jQuery object for a + */ function explodeRowspans( $table ) { - // Split multi row cells into multiple cells with the same content - $table.find( '> tbody > tr > [rowspan]' ).each(function () { - var rowSpan = this.rowSpan; - this.rowSpan = 1; - var cell = $( this ); - var next = cell.parent().nextAll(); + var rowspanCells = $table.find( '> tbody > tr > [rowspan]' ).get(); + + // Short circuit + if ( !rowspanCells.length ) { + return; + } + + // First, we need to make a property like cellIndex but taking into + // account colspans. We also cache the rowIndex to avoid having to take + // cell.parentNode.rowIndex in the sorting function below. + $table.find( '> tbody > tr' ).each( function () { + var col = 0; + var l = this.cells.length; + for ( var i = 0; i < l; i++ ) { + this.cells[i].realCellIndex = col; + this.cells[i].realRowIndex = this.rowIndex; + col += this.cells[i].colSpan; + } + } ); + + // Split multi row cells into multiple cells with the same content. + // Sort by column then row index to avoid problems with odd table structures. + // Re-sort whenever a rowspanned cell's realCellIndex is changed, because it + // might change the sort order. + function resortCells() { + rowspanCells = rowspanCells.sort( function ( a, b ) { + var ret = a.realCellIndex - b.realCellIndex; + if ( !ret ) { + ret = a.realRowIndex - b.realRowIndex; + } + return ret; + } ); + $.each( rowspanCells, function () { + this.needResort = false; + } ); + } + resortCells(); + + var spanningRealCellIndex, rowSpan, colSpan; + function filterfunc() { + return this.realCellIndex >= spanningRealCellIndex; + } + + function fixTdCellIndex() { + this.realCellIndex += colSpan; + if ( this.rowSpan > 1 ) { + this.needResort = true; + } + } + + while ( rowspanCells.length ) { + if ( rowspanCells[0].needResort ) { + resortCells(); + } + + var cell = rowspanCells.shift(); + rowSpan = cell.rowSpan; + colSpan = cell.colSpan; + spanningRealCellIndex = cell.realCellIndex; + cell.rowSpan = 1; + var $nextRows = $( cell ).parent().nextAll(); for ( var i = 0; i < rowSpan - 1; i++ ) { - var td = next.eq( i ).children( 'td' ); - if ( !td.length ) { - next.eq( i ).append( cell.clone() ); - } else if ( this.cellIndex === 0 ) { - td.eq( this.cellIndex ).before( cell.clone() ); + var $tds = $( $nextRows[i].cells ).filter( filterfunc ); + var $clone = $( cell ).clone(); + $clone[0].realCellIndex = spanningRealCellIndex; + if ( $tds.length ) { + $tds.each( fixTdCellIndex ); + $tds.first().before( $clone ); } else { - td.eq( this.cellIndex - 1 ).after( cell.clone() ); + $nextRows.eq( i ).append( $clone ); } } - }); + } } function buildCollationTable() { diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js index deff5b03b0..307b04405d 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js @@ -86,6 +86,34 @@ } ); } + /** + * Run a table test by building a table with the given HTML, + * running some callback on it, then checking the results. + * + * @param {String} msg text to pass on to qunit for the comparison + * @param {String} HTML to make the table + * @param {String[][]} expected rows/cols to compare against at end + * @param {function($table)} callback something to do with the table before we compare + */ + function tableTestHTML( msg, html, expected, callback ) { + QUnit.test( msg, 1, function ( assert ) { + var $table = $( html ); + + // Give caller a chance to set up sorting and manipulate the table. + if ( callback ) { + callback( $table ); + } else { + $table.tablesorter(); + $table.find( '#sortme' ).click(); + } + + // Table sorting is done synchronously; if it ever needs to change back + // to asynchronous, we'll need a timeout or a callback here. + var extracted = tableExtract( $table ); + assert.deepEqual( extracted, expected, msg ); + } ); + } + function reversed( arr ) { // Clone array var arr2 = arr.slice( 0 ); @@ -981,4 +1009,120 @@ 'Applied correct sorting order' ); } ); + + // bug 41889 - exploding rowspans in more complex cases + tableTestHTML( + 'Rowspan exploding with row headers', + '
' + + '' + + '' + + '' + + '' + + '
nfoobarbaz
1foobarbaz
2baz
', + [ + [ '1', 'foo', 'bar', 'baz' ], + [ '2', 'foo', 'bar', 'baz' ] + ] + ); + + tableTestHTML( + 'Rowspan exploding with colspanned cells', + '' + + '' + + '' + + '' + + '' + + '
nfoobarbaz
1foobarbaz
2foobar
', + [ + [ '1', 'foo', 'bar', 'baz' ], + [ '2', 'foobar', 'baz' ] + ] + ); + + tableTestHTML( + 'Rowspan exploding with colspanned cells (2)', + '' + + '' + + '' + + '' + + '' + + '
nfoobarbazquux
1foobarbazquux
2foobarquux
', + [ + [ '1', 'foo', 'bar', 'baz', 'quux' ], + [ '2', 'foobar', 'baz', 'quux' ] + ] + ); + + tableTestHTML( + 'Rowspan exploding with rightmost rows spanning most', + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
nfoobar
1foobar
2
3foo
4
', + [ + [ '1', 'foo', 'bar' ], + [ '2', 'foo', 'bar' ], + [ '3', 'foo', 'bar' ], + [ '4', 'foo', 'bar' ] + ] + ); + + tableTestHTML( + 'Rowspan exploding with rightmost rows spanning most (2)', + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
nfoobarbaz
1foobarbaz
2baz
3foobaz
4baz
', + [ + [ '1', 'foo', 'bar', 'baz' ], + [ '2', 'foo', 'bar', 'baz' ], + [ '3', 'foo', 'bar', 'baz' ], + [ '4', 'foo', 'bar', 'baz' ] + ] + ); + + tableTestHTML( + 'Rowspan exploding with row-and-colspanned cells', + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
nfoo1foo2barbaz
1foo1foo2barbaz
2baz
3foobaz
4baz
', + [ + [ '1', 'foo1', 'foo2', 'bar', 'baz' ], + [ '2', 'foo1', 'foo2', 'bar', 'baz' ], + [ '3', 'foo', 'bar', 'baz' ], + [ '4', 'foo', 'bar', 'baz' ] + ] + ); + + tableTestHTML( + 'Rowspan exploding with uneven rowspan layout', + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
nfoo1foo2foo3barbaz
1foo1foo2foo3barbaz
2barbaz
3foo1foo2foo3baz
4baz
', + [ + [ '1', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ], + [ '2', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ], + [ '3', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ], + [ '4', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ] + ] + ); + }( jQuery, mediaWiki ) );