1
0
mirror of https://github.com/DataTables/DataTables.git synced 2024-12-01 13:24:10 +01:00

Internal: Refactoring the sort functions for invalidation

- invalidation is going to play an important part in the new API, with
  the ability to invalid the cached data for sorting, filtering, display
  etc so we need to be able tos upport this in the core.

- In fairness the sorting didn't actually need invalidation because it
  would always get data on every sort, which is bad for performance -
  proper invalidation and caching will resolve this. Which is what has
  been implemented here.

- I had expected (hoped) to be able to save a bit of space in the
  refactor, but only aorund 100 bytes (compressed) saved, which will
  probably be lost when the invalidation is fully implemented in the
  API. Still, better performance, tidier code, and no extra space...

Fix: Sorting classes now show multi column sorting when a column is
defined to sort over multiple columns (sortData). Previously it would
only show the first column.
This commit is contained in:
Allan Jardine 2013-05-16 17:16:04 +01:00
parent 67d19ac9e4
commit 4913226456
5 changed files with 127 additions and 118 deletions

View File

@ -45,8 +45,14 @@ _api.register( 'row().data()', function ( data ) {
// return undefined; // return undefined;
// @todo - Set operator // @todo - Set operator
ctx[0].aoData[ this[0] ]._aData = data;
// Invalidate the row
} ); } );
// Should row object have an invalidated flag? Scan the array before doing
// anything with the data? or better to update at the invalidation point
_api.register( 'row().index()', function () { _api.register( 'row().index()', function () {
return this.length ? this[0] : undefined; return this.length ? this[0] : undefined;

View File

@ -1,3 +1,37 @@
function _fnSortFlatten ( settings )
{
var
i, iLen, k, kLen,
aSort = [],
aiOrig = [],
aoColumns = settings.aoColumns,
aDataSort, iCol, sType,
nestedSort = settings.aaSortingFixed.concat( settings.aaSorting );
for ( i=0 ; i<nestedSort.length ; i++ )
{
aDataSort = aoColumns[ nestedSort[i][0] ].aDataSort;
for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
{
iCol = aDataSort[k];
sType = aoColumns[ iCol ].sType || 'string';
aSort.push( {
col: iCol,
dir: nestedSort[i][1],
index: nestedSort[i][2],
type: sType,
formatter: DataTable.ext.oSort[ sType+"-pre" ]
} );
}
}
return aSort;
}
/** /**
* Change the order of the table * Change the order of the table
* @param {object} oSettings dataTables settings object * @param {object} oSettings dataTables settings object
@ -8,107 +42,42 @@
function _fnSort ( oSettings, bApplyClasses ) function _fnSort ( oSettings, bApplyClasses )
{ {
var var
i, iLen, j, jLen, k, kLen, i, ien, iLen, j, jLen, k, kLen,
sDataType, nTh, sDataType, nTh,
aSort = [], aSort = [],
aiOrig = [], aiOrig = [],
oExtSort = DataTable.ext.oSort,
aoData = oSettings.aoData, aoData = oSettings.aoData,
aoColumns = oSettings.aoColumns, aoColumns = oSettings.aoColumns,
oAria = oSettings.oLanguage.oAria, oAria = oSettings.oLanguage.oAria,
fnFormatter, aDataSort, data, iCol, sType, oSort, aDataSort, data, iCol, sType, oSort,
iFormatters = 0, formatters = 0,
aaNestedSort = ( oSettings.aaSortingFixed !== null ) ? nestedSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting ),
oSettings.aaSortingFixed.concat( oSettings.aaSorting ) : sortCol,
oSettings.aaSorting.slice(); displayMaster = oSettings.aiDisplayMaster;
/* Flatten the aDataSort inner arrays into a single array, otherwise we have nested aDataSort = _fnSortFlatten( oSettings );
* loops in multiple locations
*/
for ( i=0 ; i<aaNestedSort.length ; i++ )
{
aDataSort = aoColumns[ aaNestedSort[i][0] ].aDataSort;
for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ ) for ( i=0, ien=aDataSort.length ; i<ien ; i++ ) {
{ sortCol = aDataSort[i];
iCol = aDataSort[k];
sType = aoColumns[ iCol ].sType || 'string';
fnFormatter = oExtSort[ sType+"-pre" ];
aSort.push( { // Track if we can use the fast sort algorithm
col: iCol, if ( sortCol.formatter ) {
dir: aaNestedSort[i][1], formatters++;
index: aaNestedSort[i][2],
type: sType,
format: fnFormatter
} );
// Track if we can use the formatter method
if ( fnFormatter )
{
iFormatters++;
}
} }
// Load the data needed for the sort, for each cell
_fnSortColumn( oSettings, sortCol.col );
} }
/* No sorting required if server-side or no sorting array */ /* No sorting required if server-side or no sorting array */
if ( !oSettings.oFeatures.bServerSide && aSort.length !== 0 ) if ( !oSettings.oFeatures.bServerSide && aSort.length !== 0 )
{ {
/* If there is a sorting data type, and a function belonging to it, then we need to // Create a value - key array of the current row positions such that we can use their
* get the data from the developer's function and apply it for this column // current position during the sort, if values match, in order to perform stable sorting
*/ for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) {
for ( i=0 ; i<aSort.length ; i++ ) aiOrig[ displayMaster[i] ] = i;
{
var iColumn = aSort[i].col;
var iVisColumn = _fnColumnIndexToVisible( oSettings, iColumn );
sDataType = oSettings.aoColumns[ iColumn ].sSortDataType;
if ( DataTable.ext.afnSortData[sDataType] )
{
var aData = DataTable.ext.afnSortData[sDataType].call(
oSettings.oInstance, oSettings, iColumn, iVisColumn
);
if ( aData.length === aoData.length )
{
for ( j=0, jLen=aoData.length ; j<jLen ; j++ )
{
_fnSetCellData( oSettings, j, iColumn, aData[j] );
}
}
else
{
_fnLog( oSettings, 0, "Returned data sort array (col "+iColumn+") is the wrong length" );
}
}
} }
/* Create a value - key array of the current row positions such that we can use their
* current position during the sort, if values match, in order to perform stable sorting
*/
for ( i=0, iLen=oSettings.aiDisplayMaster.length ; i<iLen ; i++ )
{
aiOrig[ oSettings.aiDisplayMaster[i] ] = i;
}
/* Build an internal data array which is specific to the sort, so we can get and prep
* the data to be sorted only once, rather than needing to do it every time the sorting
* function runs. This make the sorting function a very simple comparison
*/
for ( j=0 ; j<aSort.length ; j++ )
{
oSort = aSort[j];
for ( i=0, iLen=aoData.length ; i<iLen ; i++ )
{
data = _fnGetCellData( oSettings, i, oSort.col, 'sort' );
aoData[i]._aSortData[ oSort.col ] = oSort.format ?
oSort.format( data ) :
data;
}
}
/* Do the sort - here we want multi-column sorting based on a given data source (column) /* Do the sort - here we want multi-column sorting based on a given data source (column)
* and sorting function (from oSort) in a certain direction. It's reasonably complex to * and sorting function (from oSort) in a certain direction. It's reasonably complex to
* follow on it's own, but this is what we want (example two column sorting): * follow on it's own, but this is what we want (example two column sorting):
@ -130,29 +99,27 @@ function _fnSort ( oSettings, bApplyClasses )
* 15% faster, so the second is only maintained for backwards compatibility with sorting * 15% faster, so the second is only maintained for backwards compatibility with sorting
* methods which do not have a pre-sort formatting function. * methods which do not have a pre-sort formatting function.
*/ */
if ( iFormatters === aSort.length ) { if ( formatters === aSort.length ) {
// All sort types have formatting functions // All sort types have formatting functions
oSettings.aiDisplayMaster.sort( function ( a, b ) { displayMaster.sort( function ( a, b ) {
var var
x, y, k, test, sort, x, y, k, test, sort,
len=aSort.length, len=aSort.length,
dataA = aoData[a]._aSortData, dataA = aoData[a]._aSortData,
dataB = aoData[b]._aSortData; dataB = aoData[b]._aSortData;
for ( k=0 ; k<len ; k++ ) for ( k=0 ; k<len ; k++ ) {
{
sort = aSort[k]; sort = aSort[k];
x = dataA[ sort.col ]; x = dataA[ sort.col ];
y = dataB[ sort.col ]; y = dataB[ sort.col ];
test = x<y ? -1 : x>y ? 1 : 0; test = x<y ? -1 : x>y ? 1 : 0;
if ( test !== 0 ) if ( test !== 0 ) {
{
return sort.dir === 'asc' ? test : -test; return sort.dir === 'asc' ? test : -test;
} }
} }
x = aiOrig[a]; x = aiOrig[a];
y = aiOrig[b]; y = aiOrig[b];
return x<y ? -1 : x>y ? 1 : 0; return x<y ? -1 : x>y ? 1 : 0;
@ -162,34 +129,32 @@ function _fnSort ( oSettings, bApplyClasses )
// Depreciated - remove in 1.11 (providing a plug-in option) // Depreciated - remove in 1.11 (providing a plug-in option)
// Not all sort types have formatting methods, so we have to call their sorting // Not all sort types have formatting methods, so we have to call their sorting
// methods. // methods.
oSettings.aiDisplayMaster.sort( function ( a, b ) { displayMaster.sort( function ( a, b ) {
var var
x, y, k, l, test, sort, x, y, k, l, test, sort,
len=aSort.length, len=aSort.length,
dataA = aoData[a]._aSortData, dataA = aoData[a]._aSortData,
dataB = aoData[b]._aSortData; dataB = aoData[b]._aSortData;
for ( k=0 ; k<len ; k++ ) for ( k=0 ; k<len ; k++ ) {
{
sort = aSort[k]; sort = aSort[k];
x = dataA[ sort.col ]; x = dataA[ sort.col ];
y = dataB[ sort.col ]; y = dataB[ sort.col ];
test = oExtSort[ sort.type+"-"+sort.dir ]( x, y ); test = oExtSort[ sort.type+"-"+sort.dir ]( x, y );
if ( test !== 0 ) if ( test !== 0 ) {
{
return test; return test;
} }
} }
x = aiOrig[a]; x = aiOrig[a];
y = aiOrig[b]; y = aiOrig[b];
return x<y ? -1 : x>y ? 1 : 0; return x<y ? -1 : x>y ? 1 : 0;
} ); } );
} }
} }
/* Alter the sorting classes to take account of the changes */ /* Alter the sorting classes to take account of the changes */
if ( (bApplyClasses === undefined || bApplyClasses) && !oSettings.oFeatures.bDeferRender ) if ( (bApplyClasses === undefined || bApplyClasses) && !oSettings.oFeatures.bDeferRender )
{ {
@ -202,14 +167,14 @@ function _fnSort ( oSettings, bApplyClasses )
nTh = aoColumns[i].nTh; nTh = aoColumns[i].nTh;
nTh.removeAttribute('aria-sort'); nTh.removeAttribute('aria-sort');
nTh.removeAttribute('aria-label'); nTh.removeAttribute('aria-label');
/* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */ /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
if ( aoColumns[i].bSortable ) if ( aoColumns[i].bSortable )
{ {
if ( aSort.length > 0 && aSort[0].col == i ) if ( aSort.length > 0 && aSort[0].col == i )
{ {
nTh.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" ); nTh.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" );
var nextSort = (aoColumns[i].asSorting[ aSort[0].index+1 ]) ? var nextSort = (aoColumns[i].asSorting[ aSort[0].index+1 ]) ?
aoColumns[i].asSorting[ aSort[0].index+1 ] : aoColumns[i].asSorting[0]; aoColumns[i].asSorting[ aSort[0].index+1 ] : aoColumns[i].asSorting[0];
nTh.setAttribute('aria-label', sTitle+ nTh.setAttribute('aria-label', sTitle+
@ -226,7 +191,7 @@ function _fnSort ( oSettings, bApplyClasses )
nTh.setAttribute('aria-label', sTitle); nTh.setAttribute('aria-label', sTitle);
} }
} }
/* Tell the draw function that we have sorted the data */ /* Tell the draw function that we have sorted the data */
oSettings.bSorted = true; oSettings.bSorted = true;
$(oSettings.oInstance).trigger('sort', oSettings); $(oSettings.oInstance).trigger('sort', oSettings);
@ -374,14 +339,7 @@ function _fnSortingClasses( oSettings )
} }
} }
if ( oSettings.aaSortingFixed !== null ) aaSort = _fnSortFlatten( oSettings );
{
aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
}
else
{
aaSort = oSettings.aaSorting.slice();
}
/* Apply the required classes to the header */ /* Apply the required classes to the header */
for ( i=0 ; i<oSettings.aoColumns.length ; i++ ) for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
@ -392,9 +350,9 @@ function _fnSortingClasses( oSettings )
iFound = -1; iFound = -1;
for ( j=0 ; j<aaSort.length ; j++ ) for ( j=0 ; j<aaSort.length ; j++ )
{ {
if ( aaSort[j][0] == i ) if ( aaSort[j].col == i )
{ {
sClass = ( aaSort[j][1] == "asc" ) ? sClass = ( aaSort[j].dir == "asc" ) ?
oClasses.sSortAsc : oClasses.sSortDesc; oClasses.sSortAsc : oClasses.sSortDesc;
iFound = j; iFound = j;
break; break;
@ -414,7 +372,7 @@ function _fnSortingClasses( oSettings )
{ {
sSpanClass = oSettings.aoColumns[i].sSortingClassJUI; sSpanClass = oSettings.aoColumns[i].sSortingClassJUI;
} }
else if ( aaSort[iFound][1] == "asc" ) else if ( aaSort[iFound].dir == "asc" )
{ {
sSpanClass = oClasses.sSortJUIAsc; sSpanClass = oClasses.sSortJUIAsc;
} }
@ -506,3 +464,48 @@ function _fnSortingClasses( oSettings )
} }
} }
// Get the data to sort a column, be it from cache, fresh (populating the
// cache), or from a sort formatter
function _fnSortColumn( settings, idx )
{
// Custom sorting function - provided by the sort data type
var column = settings.aoColumns[ idx ];
var customSort = DataTable.ext.afnSortData[ column.sSortDataType ];
var customData;
if ( customSort ) {
customData = customSort.call( settings.oInstance, settings, idx,
_fnColumnIndexToVisible( settings, idx )
);
}
// Use / populate cache
var row, cellData;
var formatter = DataTable.ext.oSort[ column.sType+"-pre" ];
for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
row = settings.aoData[i];
if ( ! row._aSortData ) {
row._aSortData = [];
}
if ( ! row._aSortData[idx] || customSort ) {
cellData = customSort ?
customData : // If there was a custom sort function, use data from there
_fnGetCellData( settings, i, idx, 'sort' );
row._aSortData[ idx ] = formatter ?
formatter( cellData ) :
cellData;
}
}
}
function _fnSortInvalidate( settings, row )
{
row._aSortData = false;
}

View File

@ -133,7 +133,7 @@ DataTable.defaults = {
* } ); * } );
* } ) * } )
*/ */
"aaSortingFixed": null, "aaSortingFixed": [],
/** /**

View File

@ -43,10 +43,10 @@ DataTable.models.oRow = {
* per sort. This array should not be read from or written to by anything * per sort. This array should not be read from or written to by anything
* other than the master sorting methods. * other than the master sorting methods.
* @type array * @type array
* @default [] * @default null
* @private * @private
*/ */
"_aSortData": [], "_aSortData": null,
/** /**
* Cache of the class name that DataTables has applied to the row, so we * Cache of the class name that DataTables has applied to the row, so we

View File

@ -360,10 +360,10 @@ DataTable.models.oSettings = {
* aaSorting). * aaSorting).
* Note that this parameter will be set by the initialisation routine. To * Note that this parameter will be set by the initialisation routine. To
* set a default use {@link DataTable.defaults}. * set a default use {@link DataTable.defaults}.
* @type array|null * @type array
* @default null * @default []
*/ */
"aaSortingFixed": null, "aaSortingFixed": [],
/** /**
* Classes to use for the striping of a table. * Classes to use for the striping of a table.