MatthiasDD has uploaded a new change for review.
https://gerrit.wikimedia.org/r/233168
Change subject: jquery.tablesorter: Add ability for cells with colspan in tbody
......................................................................
jquery.tablesorter: Add ability for cells with colspan in tbody
* Add manageColspans() to handle colspaned cells in table body.
Add config.columns - number of colums with extended colspan, inclusive
unsortable columns
buildParserCache() iterate for all columns (not only the first body row, Bug:
T105731)
buildCache() also (Bug: T74534)
Rows with not enougth cells get after first click additional empty cells.
* Add $.tablesorter.getParsers() for better table diagnosis.
fix 3 litle bugs:
* Improve multi column sorting with colspan in header (add columnToHeader[..]
to s[0] )
* Unsortable headers get after sorting no title tag. ($headers contain only
sortable headers)
* Parser detection in tables with max. 5 rows and empty cells works now.
Bug: T105731
Bug: T74534
Change-Id: I518029616d4c10a48eeaad8e92962f4e580f9413
---
M resources/src/jquery/jquery.tablesorter.js
M tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
2 files changed, 209 insertions(+), 88 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core
refs/changes/68/233168/1
diff --git a/resources/src/jquery/jquery.tablesorter.js
b/resources/src/jquery/jquery.tablesorter.js
index 8efbb1c..c504de8 100644
--- a/resources/src/jquery/jquery.tablesorter.js
+++ b/resources/src/jquery/jquery.tablesorter.js
@@ -105,18 +105,25 @@
}
}
- function detectParserForColumn( table, rows, cellIndex ) {
+ function detectParserForColumn( table, rows, column ) {
var l = parsers.length,
+ cellIndex,
nodeValue,
// Start with 1 because 0 is the fallback parser
i = 1,
+ lastRowIndex = -1,
rowIndex = 0,
concurrent = 0,
+ empty = 0,
needed = ( rows.length > 4 ) ? 5 : rows.length;
while ( i < l ) {
- if ( rows[rowIndex] && rows[rowIndex].cells[cellIndex]
) {
- nodeValue = $.trim( getElementSortKey(
rows[rowIndex].cells[cellIndex] ) );
+ if ( rows[rowIndex] ) {
+ if ( rowIndex != lastRowIndex) {
+ lastRowIndex = rowIndex;
+ cellIndex = $(rows[rowIndex]).data(
'columnToCell' )[column];
+ nodeValue = $.trim( getElementSortKey(
rows[rowIndex].cells[cellIndex] ) );
+ }
} else {
nodeValue = '';
}
@@ -134,13 +141,22 @@
i++;
rowIndex = 0;
concurrent = 0;
+ empty = 0;
}
} else {
// Empty cell
+ empty++;
rowIndex++;
- if ( rowIndex > rows.length ) {
- rowIndex = 0;
+ if ( rowIndex >= rows.length ) {
+ if ( concurrent >= rows.length - empty)
{
+ //Confirmed the parser for all
filled cells
+ return parsers[i];
+ }
+ // Check next parser, reset rows
i++;
+ rowIndex = 0;
+ concurrent = 0;
+ empty = 0;
}
}
}
@@ -150,24 +166,23 @@
}
function buildParserCache( table, $headers ) {
- var sortType, cells, len, i, parser,
+ var sortType, len, j, parser,
rows = table.tBodies[0].rows,
+ config = $(table).data( 'tablesorter' ).config,
parsers = [];
if ( rows[0] ) {
- cells = rows[0].cells;
- len = cells.length;
-
- for ( i = 0; i < len; i++ ) {
+ len = config.columns;
+ for ( j = 0; j < len; j++ ) {
parser = false;
- sortType = $headers.eq( i ).data( 'sortType' );
+ sortType = $headers.eq(
config.columnToHeader[j] ).data( 'sortType' );
if ( sortType !== undefined ) {
parser = getParserById( sortType );
}
if ( parser === false ) {
- parser = detectParserForColumn( table,
rows, i );
+ parser = detectParserForColumn( table,
rows, j );
}
parsers.push( parser );
@@ -181,15 +196,16 @@
function buildCache( table ) {
var i, j, $row, cols,
totalRows = ( table.tBodies[0] &&
table.tBodies[0].rows.length ) || 0,
- totalCells = ( table.tBodies[0].rows[0] &&
table.tBodies[0].rows[0].cells.length ) || 0,
config = $( table ).data( 'tablesorter' ).config,
parsers = config.parsers,
+ len = parsers.length,
+ cellIndex,
cache = {
row: [],
normalized: []
};
- for ( i = 0; i < totalRows; ++i ) {
+ for ( i = 0; i < totalRows; i++ ) {
// Add the table data to main data array
$row = $( table.tBodies[0].rows[i] );
@@ -205,8 +221,9 @@
cache.row.push( $row );
- for ( j = 0; j < totalCells; ++j ) {
- cols.push( parsers[j].format(
getElementSortKey( $row[0].cells[j] ), table, $row[0].cells[j] ) );
+ for ( j = 0; j < len; j++ ) {
+ cellIndex = $row.data( 'columnToCell' )[j];
+ cols.push( parsers[j].format(
getElementSortKey( $row[0].cells[cellIndex] ) ) );
}
cols.push( cache.normalized.length ); // add position
for rowCache
@@ -283,7 +300,7 @@
maxSeen = 0,
colspanOffset = 0,
columns,
- i,
+ k,
$cell,
rowspan,
colspan,
@@ -337,29 +354,17 @@
// as each header can span over multiple columns (using
colspan=N),
// we have to bidirectionally map headers to their columns and
columns to their headers
- $tableHeaders.each( function ( headerIndex ) {
+ headerIndex = 0;
+ $tableHeaders.each( function () {
$cell = $( this );
columns = [];
- for ( i = 0; i < this.colSpan; i++ ) {
- config.columnToHeader[ colspanOffset + i ] =
headerIndex;
- columns.push( colspanOffset + i );
- }
-
- config.headerToColumns[ headerIndex ] = columns;
- colspanOffset += this.colSpan;
-
- $cell.data( {
- headerIndex: headerIndex,
- order: 0,
- count: 0
- } );
-
+ //this data is not used, delete it?
if ( $cell.hasClass( config.unsortableClass ) ) {
$cell.data( 'sortDisabled', true );
}
- if ( !$cell.data( 'sortDisabled' ) ) {
+ if ( !$cell.hasClass( config.unsortableClass ) ) {
$cell
.addClass( config.cssHeader )
.prop( 'tabIndex', 0 )
@@ -367,14 +372,32 @@
role: 'columnheader button',
title: msg[1]
} );
+
+ for ( k = 0; k < this.colSpan; k++ ) {
+ config.columnToHeader[ colspanOffset +
k ] = headerIndex;
+ columns.push( colspanOffset + k );
+ }
+
+ config.headerToColumns[ headerIndex ] = columns;
+
+ $cell.data( {
+ headerIndex: headerIndex,
+ order: 0,
+ count: 0
+ } );
+
+ // add only sortable cells to headerList //MK
+ config.headerList[ headerIndex ] = this;
+ headerIndex++;
}
-
- // add cell to headerList
- config.headerList[headerIndex] = this;
+
+ colspanOffset += this.colSpan;
} );
+ /*number of columns with extended colspan, inclusive unsortable
+ parsers[j], cache[][j], columnToHeader[j], columnToCell[j]
have so many elements */
+ config.columns = colspanOffset; //MK
- return $tableHeaders;
-
+ return $tableHeaders.not( '.' + config.unsortableClass );
}
/**
@@ -551,6 +574,7 @@
var spanningRealCellIndex, rowSpan, colSpan,
cell, cellData, i, $tds, $clone, $nextRows,
rowspanCells = $table.find( '> tbody > tr > [rowspan]'
).get();
+ //rowspanCells = $table.find( '> tbody > tr >' ).not(
'[rowspan="1"]' ).get(); //better?
// Short circuit
if ( !rowspanCells.length ) {
@@ -637,6 +661,48 @@
}
}
+ /**
+ * Colspanned cells in the body would not cloned,
+ * only the cell index is multipe set in a array for every row,
+ * columnToCell[collumnIndex] point at the real cell in this row.
+ *
+ * @param $table jQuery object for a <table>
+ */
+function manageColspans( $table ) {
+ var i, j, k, $row,
+ $rows = $table.find( '> tbody > tr' ),
+ totalRows = $rows.length || 0 ,
+ config = $table.data( 'tablesorter' ).config,
+ columns = config.columns,
+ columnToCell, cellsInRow, index;
+
+ for ( i = 0; i < totalRows; i++ ) {
+
+ $row = $rows.eq(i);
+ // if this is a child row, continue to the next row (as
buildCache())
+ if ( $row.hasClass( config.cssChildRow ) ) {
+ // go to the next for loop
+ continue;
+ }
+
+ columnToCell = [];
+ cellsInRow = ( $row[0].cells.length ) || 0; //all
cells in this row
+ index = 0; //real cell index in this row
+ for ( j = 0; j < columns; index++) {
+ if ( index === cellsInRow) {
+ //row with cells less than columns: add
empty cell
+ $row.append( '<td></td>' );
+ cellsInRow++;
+ }
+ for (k = 0; k < $row[0].cells[index].colSpan;
k++ ) {
+ columnToCell[j++] = index;
+ }
+ }
+ //Store it in $row
+ $row.data( 'columnToCell', columnToCell );
+ }
+ }
+
function buildCollationTable() {
ts.collationTable = mw.config.get( 'tableSorterCollation' );
ts.collationRegex = null;
@@ -714,12 +780,13 @@
cssChildRow: 'expand-child',
sortMultiSortKey: 'shiftKey',
unsortableClass: 'unsortable',
- parsers: {},
+ parsers: [],
cancelSelection: true,
sortList: [],
headerList: [],
headerToColumns: [],
- columnToHeader: []
+ columnToHeader: [],
+ columns: 0
},
dateRegex: [],
@@ -797,6 +864,7 @@
}
explodeRowspans( $table );
+ manageColspans( $table );
// Try to auto detect column
type, and store in tables config
config.parsers =
buildParserCache( table, $headers );
@@ -804,7 +872,7 @@
// Apply event handling to headers
// this is too big, perhaps break it
out?
- $headers.not( '.' +
config.unsortableClass ).on( 'keypress click', function ( e ) {
+ $headers.on( 'keypress click', function
( e ) {
var cell, $cell, columns,
newSortList, i,
totalRows,
j, s, o;
@@ -866,7 +934,7 @@
//
Reverse the sorting direction for all tables.
for ( j
= 0; j < config.sortList.length; j++ ) {
s = config.sortList[j];
-
o = config.headerList[s[0]];
+
o = config.headerList[config.columnToHeader[s[0]]];
if ( isValueInArray( s[0], newSortList ) ) {
$( o ).data( 'count', s[1] + 1 );
s[1] = $( o ).data( 'count' ) % 2;
@@ -937,7 +1005,7 @@
// sort initially
if ( config.sortList.length > 0 ) {
- setupForFirstSort();
+ //setupForFirstSort(); is in
sort()
config.sortList =
convertSortList( config.sortList );
$table.data( 'tablesorter'
).sort();
}
@@ -998,6 +1066,10 @@
buildCollationTable();
return getParserById( id );
+ },
+
+ getParsers: function () { //for table diagnosis
+ return parsers;
}
};
diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
index acd98a6..5391ff5 100644
--- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
+++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
@@ -1,24 +1,5 @@
( function ( $, mw ) {
- var header,
-
- // Data set "simple"
- a1 = [ 'A', '1' ],
- a2 = [ 'A', '2' ],
- a3 = [ 'A', '3' ],
- b1 = [ 'B', '1' ],
- b2 = [ 'B', '2' ],
- b3 = [ 'B', '3' ],
- simple = [a2, b3, a1, a3, b2, b1],
- simpleAsc = [a1, a2, a3, b1, b2, b3],
- simpleDescasc = [b1, b2, b3, a1, a2, a3],
-
- // Data set "colspan"
- aaa1 = [ 'A', 'A', 'A', '1' ],
- aab5 = [ 'A', 'A', 'B', '5' ],
- abc3 = [ 'A', 'B', 'C', '3' ],
- bbc2 = [ 'B', 'B', 'C', '2' ],
- caa4 = [ 'C', 'A', 'A', '4' ],
- colspanInitial = [ aab5, aaa1, abc3, bbc2, caa4 ],
+ var header = [ 'Planet', 'Radius (km)'],
// Data set "planets"
mercury = [ 'Mercury', '2439.7' ],
@@ -33,6 +14,26 @@
planetsRowspan,
planetsRowspanII,
planetsAscNameLegacy,
+
+ // Data set "simple"
+ a1 = [ 'A', '1' ],
+ a2 = [ 'A', '2' ],
+ a3 = [ 'A', '3' ],
+ b1 = [ 'B', '1' ],
+ b2 = [ 'B', '2' ],
+ b3 = [ 'B', '3' ],
+ simple = [a2, b3, a1, a3, b2, b1],
+ simpleAsc = [a1, a2, a3, b1, b2, b3],
+ simpleDescasc = [b1, b2, b3, a1, a2, a3],
+
+ // Data set "colspan"
+ header4 = [ 'column1a', 'column1b', 'column1c', 'column2' ];
+ aaa1 = [ 'A', 'A', 'A', '1' ],
+ aab5 = [ 'A', 'A', 'B', '5' ],
+ abc3 = [ 'A', 'B', 'C', '3' ],
+ bbc2 = [ 'B', 'B', 'C', '2' ],
+ caa4 = [ 'C', 'A', 'A', '4' ],
+ colspanInitial = [ aab5, aaa1, abc3, bbc2, caa4 ],
// Data set "ipv4"
ipv4 = [
@@ -302,7 +303,6 @@
}
// Sample data set using planets named and their radius
- header = [ 'Planet', 'Radius (km)'];
tableTest(
'Basic planet table: sorting initially - ascending by name',
@@ -388,9 +388,6 @@
$table.find( '.headerSort:eq(1)' ).click().click();
}
);
-
- header = [ 'column1', 'column2' ];
-
tableTest(
'Sorting multiple columns by passing sort list',
header,
@@ -500,10 +497,9 @@
} );
// Sorting with colspans
- header = [ 'column1a', 'column1b', 'column1c', 'column2' ];
tableTest( 'Sorting with colspanned headers: spanned column',
- header,
+ header4,
colspanInitial,
[ aaa1, aab5, abc3, bbc2, caa4 ],
function ( $table ) {
@@ -516,7 +512,7 @@
}
);
tableTest( 'Sorting with colspanned headers: sort spanned column twice',
- header,
+ header4,
colspanInitial,
[ caa4, bbc2, abc3, aab5, aaa1 ],
function ( $table ) {
@@ -530,7 +526,7 @@
}
);
tableTest( 'Sorting with colspanned headers: subsequent column',
- header,
+ header4,
colspanInitial,
[ aaa1, bbc2, abc3, caa4, aab5 ],
function ( $table ) {
@@ -543,7 +539,7 @@
}
);
tableTest( 'Sorting with colspanned headers: sort subsequent column
twice',
- header,
+ header4,
colspanInitial,
[ aab5, caa4, abc3, bbc2, aaa1 ],
function ( $table ) {
@@ -557,18 +553,36 @@
}
);
- tableTest(
- 'Basic planet table: one unsortable column',
- header,
- planets,
- planets,
- function ( $table ) {
- $table.find( 'tr:eq(0) > th:eq(0)' ).addClass(
'unsortable' );
+ QUnit.test( 'Basic planet table: one unsortable column', 3, function (
assert ) {
+ var $table = tableCreate( header, planets ),
+ $cell;
+ $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' );
- $table.tablesorter();
- $table.find( 'tr:eq(0) > th:eq(0)' ).click();
- }
- );
+ $table.tablesorter();
+ $table.find( 'tr:eq(0) > th:eq(0)' ).click();
+
+ assert.deepEqual(
+ tableExtract( $table ),
+ planets,
+ 'table not sorted'
+ );
+
+ $cell = $table.find( 'tr:eq(0) > th:eq(0)' );
+ $table.find( 'tr:eq(0) > th:eq(1)' ).click();
+
+ assert.equal(
+ $cell.hasClass('headerSortUp') ||
$cell.hasClass('headerSortDown'),
+ false,
+ 'after sort: no class headerSortUp or headerSortDown'
+ );
+
+ assert.equal(
+ $cell.attr('title'),
+ undefined,
+ 'after sort: no title tag added'
+ );
+
+ } );
// Regression tests!
tableTest(
@@ -1262,14 +1276,14 @@
tableTestHTML(
'Rowspan exploding with colspanned cells (2)',
'<table class="sortable">' +
- '<thead><tr><th
id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th><th>quux</th></tr></thead>'
+
+
'<thead><tr><th>n</th><th>foo</th><th>bar</th><th>baz</th><th
id="sortme">n2</th></tr></thead>' +
'<tbody>' +
- '<tr><td>1</td><td>foo</td><td>bar</td><td
rowspan="2">baz</td><td>quux</td></tr>' +
- '<tr><td>2</td><td
colspan="2">foobar</td><td>quux</td></tr>' +
+ '<tr><td>1</td><td>foo</td><td>bar</td><td
rowspan="2">baz</td><td>2</td></tr>' +
+ '<tr><td>2</td><td
colspan="2">foobar</td><td>1</td></tr>' +
'</tbody></table>',
[
- [ '1', 'foo', 'bar', 'baz', 'quux' ],
- [ '2', 'foobar', 'baz', 'quux' ]
+ [ '2', 'foobar', 'baz', '1' ],
+ [ '1', 'foo', 'bar', 'baz', '2' ]
]
);
@@ -1345,4 +1359,39 @@
]
);
+ QUnit.test( 'bug 105731 - incomplete rows in table body', 3, function (
assert ) {
+ var $table, parsers;
+ $table = $(
+ '<table class="sortable">' +
+ '<tr><th>A</th><th>B</th></tr>' +
+ '<tr><td>3</td></tr>' +
+ '<tr><td>1</td><td>2</td></tr>' +
+ '</table>'
+ );
+ $table.tablesorter();
+ $table.find( '.headerSort:eq(0)' ).click();
+ //now the first row have 2 columns
+ $table.find( '.headerSort:eq(1)' ).click();
+
+ parsers = $table.data( 'tablesorter' ).config.parsers;
+
+ assert.equal(
+ parsers.length,
+ 2,
+ 'detectParserForColumn() detect 2 parsers'
+ );
+
+ assert.equal(
+ parsers[1].id,
+ 'number',
+ 'detectParserForColumn() detect parser.id "number" for
second column'
+ );
+
+ assert.equal(
+ parsers[1].format( $table.find( 'tbody > tr > td:eq(1)'
).text() ),
+ 0,
+ 'empty cell is sorted as number 0'
+ );
+
+ } );
}( jQuery, mediaWiki ) );
--
To view, visit https://gerrit.wikimedia.org/r/233168
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I518029616d4c10a48eeaad8e92962f4e580f9413
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: MatthiasDD <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits