Core & Filter: Add "entire column colspan" support. See #746 & #1047

This commit is contained in:
Rob Garrison 2015-10-17 15:15:36 -05:00
parent c7d4395c85
commit 55d19b3a95
2 changed files with 142 additions and 98 deletions

View File

@ -253,7 +253,7 @@
};
function buildParserCache( c, $tbodies ) {
var rows, list, l, i, h, ch, np, p, e, time, tb, len,
var rows, list, span, max, colIndex, i, h, ch, np, p, e, time, tb, len,
table = c.table,
j = 0,
debug = {};
@ -274,39 +274,48 @@
while (j < len) {
rows = tb[j].rows;
if (rows.length) {
l = c.columns; // rows[j].cells.length;
for (i = 0; i < l; i++) {
h = c.$headerIndexed[i];
// get column indexed table cell
ch = ts.getColumnData( table, c.headers, i );
// get column parser/extractor
e = ts.getParserById( ts.getData(h, ch, 'extractor') );
p = ts.getParserById( ts.getData(h, ch, 'sorter') );
np = ts.getData(h, ch, 'parser') === 'false';
// empty cells behaviour - keeping emptyToBottom for backwards compatibility
c.empties[i] = ( ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' ) ).toLowerCase();
// text strings behaviour in numerical sorts
c.strings[i] = ( ts.getData(h, ch, 'string') || c.stringTo || 'max' ).toLowerCase();
if (np) {
p = ts.getParserById('no-parser');
colIndex = 0;
max = c.columns; // rows[j].cells.length;
for (i = 0; i < max; i++) {
h = c.$headerIndexed[ colIndex ];
if ( h && h.length ) {
// get column indexed table cell
ch = ts.getColumnData( table, c.headers, colIndex );
// get column parser/extractor
e = ts.getParserById( ts.getData(h, ch, 'extractor') );
p = ts.getParserById( ts.getData(h, ch, 'sorter') );
np = ts.getData(h, ch, 'parser') === 'false';
// empty cells behaviour - keeping emptyToBottom for backwards compatibility
c.empties[colIndex] = ( ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' ) ).toLowerCase();
// text strings behaviour in numerical sorts
c.strings[colIndex] = ( ts.getData(h, ch, 'string') || c.stringTo || 'max' ).toLowerCase();
if (np) {
p = ts.getParserById('no-parser');
}
if (!e) {
// For now, maybe detect someday
e = false;
}
if (!p) {
p = detectParserForColumn(c, rows, -1, i);
}
if (c.debug) {
debug[ '(' + colIndex + ') ' + h.text() ] = {
parser : p.id,
extractor : e ? e.id : 'none',
string : c.strings[colIndex],
empty : c.empties[colIndex]
};
}
list.parsers[colIndex] = p;
list.extractors[colIndex] = e;
span = h[0].colSpan - 1;
if ( span > 0 ) {
colIndex += span;
max += span;
}
}
if (!e) {
// For now, maybe detect someday
e = false;
}
if (!p) {
p = detectParserForColumn(c, rows, -1, i);
}
if (c.debug) {
debug[ '(' + i + ') ' + h.text() ] = {
parser : p.id,
extractor : e ? e.id : 'none',
string : c.strings[i],
empty : c.empties[i]
};
}
list.parsers[i] = p;
list.extractors[i] = e;
colIndex++;
}
}
j += (list.parsers.length) ? len : 1;
@ -326,8 +335,8 @@
/* utils */
function buildCache(table, callback, $tbodies) {
var cc, t, v, i, j, k, $tb, $row, cols, cacheTime,
totalRows, rowData, prevRowData, colMax,
var cc, t, v, i, j, k, $tb, $row, cols, cell, cacheTime,
totalRows, rowData, prevRowData, colMax, span, cacheIndex, max,
c = table.config,
parsers = c.parsers;
// update tbody variable
@ -379,29 +388,52 @@
t = prevRowData.child.length;
prevRowData.child[ t ] = [];
// child row content does not account for colspans/rowspans; so indexing may be off
for ( j = 0; j < c.columns; j++ ) {
prevRowData.child[ t ][ j ] = ts.getParsedText( c, v[ j ], j );
cacheIndex = 0;
max = c.columns;
for ( j = 0; j < max; j++ ) {
cell = v[ j ];
if ( cell ) {
prevRowData.child[ t ][ j ] = ts.getParsedText( c, cell, j );
span = v[ j ].colSpan - 1;
if ( span > 0 ) {
cacheIndex += span;
max += span
}
}
cacheIndex++;
}
// go to the next for loop
continue;
}
rowData.$row = $row;
rowData.order = i; // add original row position to rowCache
for ( j = 0; j < c.columns; ++j ) {
if (typeof parsers[ j ] === 'undefined') {
if ( c.debug ) {
console.warn( 'No parser found for cell:', $row[ 0 ].cells[ j ], 'does it have a header?' );
cacheIndex = 0;
max = c.columns;
for ( j = 0; j < max; ++j ) {
cell = $row[ 0 ].cells[ j ];
if ( cell ) {
if (typeof parsers[ cacheIndex ] === 'undefined') {
if ( c.debug ) {
console.warn( 'No parser found for cell:', cell, 'does it have a header?' );
}
continue;
}
t = ts.getElementText( c, cell, cacheIndex );
rowData.raw[ cacheIndex ] = t; // save original row text
v = ts.getParsedText( c, cell, cacheIndex, t );
cols[ cacheIndex ] = v;
if ( ( parsers[ cacheIndex ].type || '' ).toLowerCase() === 'numeric' ) {
// determine column max value (ignore sign)
colMax[ j ] = Math.max( Math.abs( v ) || 0, colMax[ cacheIndex ] || 0 );
}
// allow colSpan in tbody
span = cell.colSpan - 1;
if ( span > 0 ) {
cacheIndex += span;
max += span;
}
continue;
}
t = ts.getElementText( c, $row[ 0 ].cells[j], j );
rowData.raw.push( t ); // save original row text
v = ts.getParsedText( c, $row[ 0 ].cells[ j ], j, t );
cols.push( v );
if ( ( parsers[ j ].type || '' ).toLowerCase() === 'numeric' ) {
// determine column max value (ignore sign)
colMax[ j ] = Math.max( Math.abs( v ) || 0, colMax[ j ] || 0 );
}
cacheIndex++;
}
// ensure rowData is always in the same location (after the last column)
cols[ c.columns ] = rowData;
@ -487,7 +519,9 @@
$t = c.$headers.filter('[data-column="' + indx + '"]');
// target sortable column cells, unless there are none, then use non-sortable cells
// .last() added in jQuery 1.4; use .filter(':last') to maintain compatibility with jQuery v1.2.6
c.$headerIndexed[indx] = $t.not('.sorter-false').length ? $t.not('.sorter-false').filter(':last') : $t.filter(':last');
c.$headerIndexed[indx] = $t.length ?
$t.not('.sorter-false').length ? $t.not('.sorter-false').filter(':last') : $t.filter(':last') :
$();
}
c.$table.find(c.selectorHeaders).attr({
scope: 'col',

View File

@ -647,71 +647,81 @@
return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter;
},
buildRow: function( table, c, wo ) {
var col, column, $header, makeSelect, disabled, name, ffxn, tmp,
var $filter, col, column, $header, makeSelect, disabled, name, ffxn, tmp,
// c.columns defined in computeThIndexes()
cellFilter = wo.filter_cellFilter,
columns = c.columns,
arry = $.isArray( cellFilter ),
buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">';
for ( column = 0; column < columns; column++ ) {
buildFilter += '<td';
if ( arry ) {
buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' );
} else {
buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' );
if ( c.$headerIndexed[ column ].length ) {
buildFilter += '<td data-column="' + column + '"';
// account for entire column set with colspan. See #1047
tmp = c.$headerIndexed[ column ] && c.$headerIndexed[ column ][0].colSpan || 0;
if ( tmp > 1 ) {
buildFilter += ' colspan="' + tmp + '"';
}
if ( arry ) {
buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' );
} else {
buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' );
}
buildFilter += '></td>';
}
buildFilter += '></td>';
}
c.$filters = $( buildFilter += '</tr>' )
.appendTo( c.$table.children( 'thead' ).eq( 0 ) )
.find( 'td' );
.children( 'td' );
// build each filter input
for ( column = 0; column < columns; column++ ) {
disabled = false;
// assuming last cell of a column is the main column
$header = c.$headerIndexed[ column ];
ffxn = ts.getColumnData( table, wo.filter_functions, column );
makeSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) ||
$header.hasClass( 'filter-select' );
// get data from jQuery data, metadata, headers option or header class name
col = ts.getColumnData( table, c.headers, column );
disabled = ts.getData( $header[0], col, 'filter' ) === 'false' ||
ts.getData( $header[0], col, 'parser' ) === 'false';
if ( $header && $header.length ) {
$filter = c.$filters.filter( '[data-column="' + column + '"]' );
ffxn = ts.getColumnData( table, wo.filter_functions, column );
makeSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) ||
$header.hasClass( 'filter-select' );
// get data from jQuery data, metadata, headers option or header class name
col = ts.getColumnData( table, c.headers, column );
disabled = ts.getData( $header[0], col, 'filter' ) === 'false' ||
ts.getData( $header[0], col, 'parser' ) === 'false';
if ( makeSelect ) {
buildFilter = $( '<select>' ).appendTo( c.$filters.eq( column ) );
} else {
ffxn = ts.getColumnData( table, wo.filter_formatter, column );
if ( ffxn ) {
wo.filter_formatterCount++;
buildFilter = ffxn( c.$filters.eq( column ), column );
// no element returned, so lets go find it
if ( buildFilter && buildFilter.length === 0 ) {
buildFilter = c.$filters.eq( column ).children( 'input' );
}
// element not in DOM, so lets attach it
if ( buildFilter && ( buildFilter.parent().length === 0 ||
( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) {
c.$filters.eq( column ).append( buildFilter );
}
if ( makeSelect ) {
buildFilter = $( '<select>' ).appendTo( $filter );
} else {
buildFilter = $( '<input type="search">' ).appendTo( c.$filters.eq( column ) );
ffxn = ts.getColumnData( table, wo.filter_formatter, column );
if ( ffxn ) {
wo.filter_formatterCount++;
buildFilter = ffxn( $filter, column );
// no element returned, so lets go find it
if ( buildFilter && buildFilter.length === 0 ) {
buildFilter = $filter.children( 'input' );
}
// element not in DOM, so lets attach it
if ( buildFilter && ( buildFilter.parent().length === 0 ||
( buildFilter.parent().length && buildFilter.parent()[0] !== $filter[0] ) ) ) {
$filter.append( buildFilter );
}
} else {
buildFilter = $( '<input type="search">' ).appendTo( $filter );
}
if ( buildFilter ) {
tmp = $header.data( 'placeholder' ) ||
$header.attr( 'data-placeholder' ) ||
wo.filter_placeholder.search || '';
buildFilter.attr( 'placeholder', tmp );
}
}
if ( buildFilter ) {
tmp = $header.data( 'placeholder' ) ||
$header.attr( 'data-placeholder' ) ||
wo.filter_placeholder.search || '';
buildFilter.attr( 'placeholder', tmp );
}
}
if ( buildFilter ) {
// add filter class name
name = ( $.isArray( wo.filter_cssFilter ) ?
( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) :
wo.filter_cssFilter ) || '';
buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column );
if ( disabled ) {
buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true;
// add filter class name
name = ( $.isArray( wo.filter_cssFilter ) ?
( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) :
wo.filter_cssFilter ) || '';
buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column );
if ( disabled ) {
buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true;
}
}
}
}
@ -1261,7 +1271,7 @@
// ( '> -10' => '> -100' will ignore hidden rows )
!( regex.isNeg1.test( val ) || regex.isNeg2.test( val ) ) &&
// if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
!( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length &&
!( val !== '' && c.$filters && c.$filters.filter( '[data-column="' + indx + '"]' ).find( 'select' ).length &&
!c.$headerIndexed[indx].hasClass( 'filter-match' ) );
}
}