client/lanes/components/grid/vendor/jquery.dataTables.js in lanes-0.1.2 vs client/lanes/components/grid/vendor/jquery.dataTables.js in lanes-0.1.5

- old
+ new

@@ -1,13 +1,13 @@ -/*! DataTables 1.10.0 +/*! DataTables 1.10.4 * ©2008-2014 SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables - * @version 1.10.0 + * @version 1.10.4 * @file jquery.dataTables.js * @author SpryMedia Ltd (www.sprymedia.co.uk) * @contact www.sprymedia.co.uk/contact * @copyright Copyright 2008-2014 SpryMedia Ltd. * @@ -20,11 +20,11 @@ * * For details please refer to: http://www.datatables.net */ /*jslint evil: true, undef: true, browser: true */ -/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidateRow,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnScrollBarWidth,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ +/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnScrollBarWidth,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ (/** @lends <global> */function( window, document, undefined ) { (function( factory ) { "use strict"; @@ -36,11 +36,10 @@ else if ( typeof exports === 'object' ) { // Node/CommonJS factory( require( 'jquery' ) ); } else if ( jQuery && !jQuery.fn.dataTable ) { - // Define using browser globals otherwise // Prevent multiple instantiations if the script is loaded twice factory( jQuery ); } } @@ -104,22 +103,23 @@ var _api_registerPlural; // DataTable.Api.registerPlural var _re_dic = {}; var _re_new_lines = /[\r\n]/g; var _re_html = /<.*?>/g; - var _re_date_start = /^[\d\+\-a-zA-Z]/; + var _re_date_start = /^[\w\+\-]/; + var _re_date_end = /[\w\+\-]$/; // Escape regular expression special characters var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); // U+2009 is thin space and U+202F is narrow no-break space, both used in many // standards as thousands separators var _re_formatted_numeric = /[',$£€¥%\u2009\u202F]/g; var _empty = function ( d ) { - return !d || d === '-' ? true : false; + return !d || d === true || d === '-' ? true : false; }; var _intVal = function ( s ) { var integer = parseInt( s, 10 ); @@ -131,11 +131,11 @@ var _numToDecimal = function ( num, decimalPoint ) { // Cache created regular expressions for speed as this function is called often if ( ! _re_dic[ decimalPoint ] ) { _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); } - return typeof num === 'string' ? + return typeof num === 'string' && decimalPoint !== '.' ? num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : num; }; @@ -148,17 +148,17 @@ if ( formatted && strType ) { d = d.replace( _re_formatted_numeric, '' ); } - return !d || d==='-' || (!isNaN( parseFloat(d) ) && isFinite( d )); + return _empty( d ) || (!isNaN( parseFloat(d) ) && isFinite( d )); }; // A string without HTML in it can be considered to be HTML still var _isHtml = function ( d ) { - return !d || typeof d === 'string'; + return _empty( d ) || typeof d === 'string'; }; var _htmlNumeric = function ( d, decimalPoint, formatted ) { if ( _empty( d ) ) { @@ -208,11 +208,13 @@ // Could have the test in the loop for slightly smaller code, but speed // is essential here if ( prop2 !== undefined ) { for ( ; i<ien ; i++ ) { - out.push( a[ order[i] ][ prop ][ prop2 ] ); + if ( a[ order[i] ][ prop ] ) { + out.push( a[ order[i] ][ prop ][ prop2 ] ); + } } } else { for ( ; i<ien ; i++ ) { out.push( a[ order[i] ][ prop ] ); @@ -243,10 +245,24 @@ return out; }; + var _removeEmpty = function ( a ) + { + var out = []; + + for ( var i=0, ien=a.length ; i<ien ; i++ ) { + if ( a[i] ) { // careful - will remove all falsy values! + out.push( a[i] ); + } + } + + return out; + }; + + var _stripHtml = function ( d ) { return d.replace( _re_html, '' ); }; @@ -308,11 +324,10 @@ if ( match && hungarian.indexOf(match[1]+' ') !== -1 ) { newKey = key.replace( match[0], match[2].toLowerCase() ); map[ newKey ] = key; - //console.log( key, match ); if ( match[1] === 'o' ) { _fnHungarianMap( o[key] ); } } @@ -434,10 +449,22 @@ _fnCompatMap( init, 'orderFixed', 'aaSortingFixed' ); _fnCompatMap( init, 'paging', 'bPaginate' ); _fnCompatMap( init, 'pagingType', 'sPaginationType' ); _fnCompatMap( init, 'pageLength', 'iDisplayLength' ); _fnCompatMap( init, 'searching', 'bFilter' ); + + // Column search objects are in an array, so it needs to be converted + // element by element + var searchCols = init.aoSearchCols; + + if ( searchCols ) { + for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) { + if ( searchCols[i] ) { + _fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] ); + } + } + } } /** * Provide backwards compatibility for column options. Note that the new options @@ -590,11 +617,11 @@ if ( ! oCol.sWidthOrig ) { // Width attribute oCol.sWidthOrig = th.attr('width') || null; // Style attribute - var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%])/); + var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); if ( t ) { oCol.sWidthOrig = t[1]; } } @@ -648,21 +675,27 @@ }; oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && ( attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) ); - oCol.fnGetData = function (oData, sSpecific) { - var innerData = mData( oData, sSpecific ); + oCol.fnGetData = function (rowData, type, meta) { + var innerData = mData( rowData, type, undefined, meta ); - if ( oCol.mRender && (sSpecific && sSpecific !== '') ) - { - return mRender( innerData, sSpecific, oData ); - } - return innerData; + return mRender && type ? + mRender( innerData, type, rowData, meta ) : + innerData; }; - oCol.fnSetData = _fnSetObjectDataFn( mDataSrc ); + oCol.fnSetData = function ( rowData, val, meta ) { + return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta ); + }; + // Indicate if DataTables should read DOM data as an object or array + // Used in _fnGetRowElements + if ( typeof mDataSrc !== 'number' ) { + oSettings._rowReadObject = true; + } + /* Feature sorting overrides column specific when off */ if ( !oSettings.oFeatures.bSort ) { oCol.bSortable = false; th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called @@ -823,17 +856,24 @@ cache[k] = _fnGetCellData( settings, k, i, 'type' ); } detectedType = types[j]( cache[k], settings ); - // Doesn't match, so break early, since this type can't - // apply to this column. Also, HTML is a special case since - // it is so similar to `string`. Just a single match is - // needed for a column to be html type - if ( ! detectedType || detectedType === 'html' ) { + // If null, then this type can't apply to this column, so + // rather than testing all cells, break out. There is an + // exception for the last type which is `html`. We need to + // scan all rows since it is possible to mix string and HTML + // types + if ( ! detectedType && j !== types.length-1 ) { break; } + + // Only a single match is needed for html type since it is + // bottom of the pile and very similar to string + if ( detectedType === 'html' ) { + break; + } } // Type is valid for all data points in the column - use this // type if ( detectedType ) { @@ -968,12 +1008,12 @@ } /* Add to the display array */ oSettings.aiDisplayMaster.push( iRow ); - /* Create the DOM information */ - if ( !oSettings.oFeatures.bDeferRender ) + /* Create the DOM information, or register it if already present */ + if ( nTr || ! oSettings.oFeatures.bDeferRender ) { _fnCreateTr( oSettings, iRow, nTr, anTds ); } return iRow; @@ -1033,68 +1073,74 @@ } /** * Get the data for a given cell from the internal cache, taking into account data mapping - * @param {object} oSettings dataTables settings object - * @param {int} iRow aoData row id - * @param {int} iCol Column index - * @param {string} sSpecific data get type ('display', 'type' 'filter' 'sort') + * @param {object} settings dataTables settings object + * @param {int} rowIdx aoData row id + * @param {int} colIdx Column index + * @param {string} type data get type ('display', 'type' 'filter' 'sort') * @returns {*} Cell data * @memberof DataTable#oApi */ - function _fnGetCellData( oSettings, iRow, iCol, sSpecific ) + function _fnGetCellData( settings, rowIdx, colIdx, type ) { - var oCol = oSettings.aoColumns[iCol]; - var oData = oSettings.aoData[iRow]._aData; - var sData = oCol.fnGetData( oData, sSpecific ); + var draw = settings.iDraw; + var col = settings.aoColumns[colIdx]; + var rowData = settings.aoData[rowIdx]._aData; + var defaultContent = col.sDefaultContent; + var cellData = col.fnGetData( rowData, type, { + settings: settings, + row: rowIdx, + col: colIdx + } ); - if ( sData === undefined ) - { - if ( oSettings.iDrawError != oSettings.iDraw && oCol.sDefaultContent === null ) - { - _fnLog( oSettings, 0, "Requested unknown parameter "+ - (typeof oCol.mData=='function' ? '{function}' : "'"+oCol.mData+"'")+ - " for row "+iRow, 4 ); - oSettings.iDrawError = oSettings.iDraw; + if ( cellData === undefined ) { + if ( settings.iDrawError != draw && defaultContent === null ) { + _fnLog( settings, 0, "Requested unknown parameter "+ + (typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+ + " for row "+rowIdx, 4 ); + settings.iDrawError = draw; } - return oCol.sDefaultContent; + return defaultContent; } /* When the data source is null, we can use default column data */ - if ( (sData === oData || sData === null) && oCol.sDefaultContent !== null ) - { - sData = oCol.sDefaultContent; + if ( (cellData === rowData || cellData === null) && defaultContent !== null ) { + cellData = defaultContent; } - else if ( typeof sData === 'function' ) - { - // If the data source is a function, then we run it and use the return - return sData(); + else if ( typeof cellData === 'function' ) { + // If the data source is a function, then we run it and use the return, + // executing in the scope of the data object (for instances) + return cellData.call( rowData ); } - if ( sData === null && sSpecific == 'display' ) - { + if ( cellData === null && type == 'display' ) { return ''; } - return sData; + return cellData; } /** * Set the value for a specific cell, into the internal data cache - * @param {object} oSettings dataTables settings object - * @param {int} iRow aoData row id - * @param {int} iCol Column index + * @param {object} settings dataTables settings object + * @param {int} rowIdx aoData row id + * @param {int} colIdx Column index * @param {*} val Value to set * @memberof DataTable#oApi */ - function _fnSetCellData( oSettings, iRow, iCol, val ) + function _fnSetCellData( settings, rowIdx, colIdx, val ) { - var oCol = oSettings.aoColumns[iCol]; - var oData = oSettings.aoData[iRow]._aData; + var col = settings.aoColumns[colIdx]; + var rowData = settings.aoData[rowIdx]._aData; - oCol.fnSetData( oData, val ); + col.fnSetData( rowData, val, { + settings: settings, + row: rowIdx, + col: colIdx + } ); } // Private variable that is used to match action syntax in the data property object var __reArray = /\[.*?\]$/; @@ -1106,11 +1152,11 @@ * @return {array} Split string */ function _fnSplitObjNotation( str ) { return $.map( str.match(/(\\.|[^\.])+/g), function ( s ) { - return s.replace('\\.', '.'); + return s.replace(/\\./g, '.'); } ); } /** @@ -1130,28 +1176,28 @@ if ( val ) { o[key] = _fnGetObjectDataFn( val ); } } ); - return function (data, type, extra) { + return function (data, type, row, meta) { var t = o[type] || o._; return t !== undefined ? - t(data, type, extra) : + t(data, type, row, meta) : data; }; } else if ( mSource === null ) { /* Give an empty string for rendering / sorting etc */ - return function (data, type) { + return function (data) { // type, row and meta also passed, but not used return data; }; } else if ( typeof mSource === 'function' ) { - return function (data, type, extra) { - return mSource( data, type, extra ); + return function (data, type, row, meta) { + return mSource( data, type, row, meta ); }; } else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) ) { @@ -1220,18 +1266,18 @@ } return data; }; - return function (data, type) { + return function (data, type) { // row and meta also passed, but not used return fetchData( data, type, mSource ); }; } else { /* Array or flat object mapping */ - return function (data, type) { + return function (data, type) { // row and meta also passed, but not used return data[mSource]; }; } } @@ -1255,16 +1301,16 @@ return _fnSetObjectDataFn( mSource._ ); } else if ( mSource === null ) { /* Nothing to do when the data source is null */ - return function (data, val) {}; + return function () {}; } else if ( typeof mSource === 'function' ) { - return function (data, val) { - mSource( data, 'set', val ); + return function (data, val, meta) { + mSource( data, 'set', val, meta ); }; } else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) ) { @@ -1330,18 +1376,18 @@ // and assign the value. If it isn't used, then we get the result we want anyway data[ aLast.replace(__reArray, '') ] = val; } }; - return function (data, val) { + return function (data, val) { // meta is also passed in, but not used return setData( data, val, mSource ); }; } else { /* Array or flat object mapping */ - return function (data, val) { + return function (data, val) { // meta is also passed in, but not used data[mSource] = val; }; } } @@ -1404,121 +1450,164 @@ /** * Mark cached data as invalid such that a re-read of the data will occur when * the cached data is next requested. Also update from the data source object. * * @param {object} settings DataTables settings object - * @param {int} rowIdx Row index to invalidate + * @param {int} rowIdx Row index to invalidate + * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom' + * or 'data' + * @param {int} [colIdx] Column index to invalidate. If undefined the whole + * row will be invalidated * @memberof DataTable#oApi * * @todo For the modularisation of v1.11 this will need to become a callback, so * the sort and filter methods can subscribe to it. That will required * initialisation options for sorting, which is why it is not already baked in */ - function _fnInvalidateRow( settings, rowIdx, src, column ) + function _fnInvalidate( settings, rowIdx, src, colIdx ) { var row = settings.aoData[ rowIdx ]; var i, ien; + var cellWrite = function ( cell, col ) { + // This is very frustrating, but in IE if you just write directly + // to innerHTML, and elements that are overwritten are GC'ed, + // even if there is a reference to them elsewhere + while ( cell.childNodes.length ) { + cell.removeChild( cell.firstChild ); + } + cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' ); + }; + // Are we reading last data from DOM or the data object? if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) { // Read the data from the DOM - row._aData = _fnGetRowElements( settings, row ).data; + row._aData = _fnGetRowElements( + settings, row, colIdx, colIdx === undefined ? undefined : row._aData + ) + .data; } else { // Reading from data object, update the DOM var cells = row.anCells; if ( cells ) { - for ( i=0, ien=cells.length ; i<ien ; i++ ) { - cells[i].innerHTML = _fnGetCellData( settings, rowIdx, i, 'display' ); + if ( colIdx !== undefined ) { + cellWrite( cells[colIdx], colIdx ); } + else { + for ( i=0, ien=cells.length ; i<ien ; i++ ) { + cellWrite( cells[i], i ); + } + } } } + // For both row and cell invalidation, the cached data for sorting and + // filtering is nulled out row._aSortData = null; row._aFilterData = null; // Invalidate the type for a specific column (if given) or all columns since // the data might have changed var cols = settings.aoColumns; - if ( column !== undefined ) { - cols[ column ].sType = null; + if ( colIdx !== undefined ) { + cols[ colIdx ].sType = null; } else { for ( i=0, ien=cols.length ; i<ien ; i++ ) { cols[i].sType = null; } - } - // Update DataTables special `DT_*` attributes for the row - _fnRowAttributes( row ); + // Update DataTables special `DT_*` attributes for the row + _fnRowAttributes( row ); + } } /** * Build a data source object from an HTML row, reading the contents of the * cells that are in the row. * * @param {object} settings DataTables settings object * @param {node|object} TR element from which to read data or existing row * object from which to re-read the data from the cells + * @param {int} [colIdx] Optional column index + * @param {array|object} [d] Data source object. If `colIdx` is given then this + * parameter should also be given and will be used to write the data into. + * Only the column in question will be written * @returns {object} Object with two parameters: `data` the data read, in * document order, and `cells` and array of nodes (they can be useful to the * caller, so rather than needing a second traversal to get them, just return * them from here). * @memberof DataTable#oApi */ - function _fnGetRowElements( settings, row ) + function _fnGetRowElements( settings, row, colIdx, d ) { var - d = [], tds = [], td = row.firstChild, name, col, o, i=0, contents, - columns = settings.aoColumns; + columns = settings.aoColumns, + objectRead = settings._rowReadObject; - var attr = function ( str, data, td ) { + // Allow the data object to be passed in, or construct + d = d || objectRead ? {} : []; + + var attr = function ( str, td ) { if ( typeof str === 'string' ) { var idx = str.indexOf('@'); if ( idx !== -1 ) { - var src = str.substring( idx+1 ); - o[ '@'+src ] = td.getAttribute( src ); + var attr = str.substring( idx+1 ); + var setter = _fnSetObjectDataFn( str ); + setter( d, td.getAttribute( attr ) ); } } }; + // Read data from a cell and store into the data object var cellProcess = function ( cell ) { - col = columns[i]; - contents = $.trim(cell.innerHTML); + if ( colIdx === undefined || colIdx === i ) { + col = columns[i]; + contents = $.trim(cell.innerHTML); - if ( col && col._bAttrSrc ) { - o = { - display: contents - }; + if ( col && col._bAttrSrc ) { + var setter = _fnSetObjectDataFn( col.mData._ ); + setter( d, contents ); - attr( col.mData.sort, o, cell ); - attr( col.mData.type, o, cell ); - attr( col.mData.filter, o, cell ); - - d.push( o ); + attr( col.mData.sort, cell ); + attr( col.mData.type, cell ); + attr( col.mData.filter, cell ); + } + else { + // Depending on the `data` option for the columns the data can + // be read to either an object or an array. + if ( objectRead ) { + if ( ! col._setter ) { + // Cache the setter function + col._setter = _fnSetObjectDataFn( col.mData ); + } + col._setter( d, contents ); + } + else { + d[i] = contents; + } + } } - else { - d.push( contents ); - } - tds.push( cell ); i++; }; if ( td ) { - // `tr` element passed in + // `tr` element was passed in while ( td ) { name = td.nodeName.toUpperCase(); if ( name == "TD" || name == "TH" ) { cellProcess( td ); + tds.push( td ); } td = td.nextSibling; } } @@ -1601,11 +1690,11 @@ } if ( oCol.fnCreatedCell ) { oCol.fnCreatedCell.call( oSettings.oInstance, - nTd, _fnGetCellData( oSettings, iRow, i, 'display' ), rowData, iRow, i + nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i ); } } _fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow] ); @@ -1920,11 +2009,13 @@ $(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe ); aoData._sRowStripe = sStripe; } } - /* Row callback functions - might want to manipulate the row */ + // Row callback functions - might want to manipulate the row + // iRowCount and j are not currently documented. Are they at all + // useful? _fnCallbackFire( oSettings, 'aoRowCallback', null, [nRow, aoData._aData, iRowCount, j] ); anRows.push( nRow ); iRowCount++; @@ -2001,11 +2092,17 @@ if ( holdPosition !== true ) { settings._iDisplayStart = 0; } + // Let any modules know about the draw hold position state (used by + // scrolling internally) + settings._drawHold = holdPosition; + _fnDraw( settings ); + + settings._drawHold = false; } /** * Add the options to the page HTML for the table @@ -2403,26 +2500,27 @@ } /** * Update the table using an Ajax call - * @param {object} oSettings dataTables settings object + * @param {object} settings dataTables settings object * @returns {boolean} Block the table drawing or not * @memberof DataTable#oApi */ - function _fnAjaxUpdate( oSettings ) + function _fnAjaxUpdate( settings ) { - if ( oSettings.bAjaxDataGet ) - { - oSettings.iDraw++; - _fnProcessingDisplay( oSettings, true ); - var iColumns = oSettings.aoColumns.length; - var aoData = _fnAjaxParameters( oSettings ); + if ( settings.bAjaxDataGet ) { + settings.iDraw++; + _fnProcessingDisplay( settings, true ); - _fnBuildAjax( oSettings, aoData, function(json) { - _fnAjaxUpdateDraw( oSettings, json ); - }, oSettings ); + _fnBuildAjax( + settings, + _fnAjaxParameters( settings ), + function(json) { + _fnAjaxUpdateDraw( settings, json ); + } + ); return false; } return true; } @@ -2552,15 +2650,14 @@ // v1.10 uses camelCase variables, while 1.9 uses Hungarian notation. // Support both var compat = function ( old, modern ) { return json[old] !== undefined ? json[old] : json[modern]; }; - var data = _fnAjaxDataSrc( settings, json ); - var draw = compat( 'sEcho', 'draw' ); - var recordsTotal = compat( 'totaliTotalRecords', 'recordsTotal' ); - var rocordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' ); + var draw = compat( 'sEcho', 'draw' ); + var recordsTotal = compat( 'iTotalRecords', 'recordsTotal' ); + var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' ); if ( draw ) { // Protect against out of sequence returns if ( draw*1 < settings.iDraw ) { return; @@ -2568,12 +2665,13 @@ settings.iDraw = draw * 1; } _fnClearTable( settings ); settings._iRecordsTotal = parseInt(recordsTotal, 10); - settings._iRecordsDisplay = parseInt(rocordsFiltered, 10); + settings._iRecordsDisplay = parseInt(recordsFiltered, 10); + var data = _fnAjaxDataSrc( settings, json ); for ( var i=0, ien=data.length ; i<ien ; i++ ) { _fnAddData( settings, data[i] ); } settings.aiDisplay = settings.aiDisplayMaster.slice(); @@ -2623,15 +2721,16 @@ */ function _fnFeatureHtmlFilter ( settings ) { var classes = settings.oClasses; var tableId = settings.sTableId; + var language = settings.oLanguage; var previousSearch = settings.oPreviousSearch; var features = settings.aanFeatures; var input = '<input type="search" class="'+classes.sFilterInput+'"/>'; - var str = settings.oLanguage.sSearch; + var str = language.sSearch; str = str.match(/_INPUT_/) ? str.replace('_INPUT_', input) : str+input; var filter = $('<div/>', { @@ -2657,16 +2756,24 @@ // Need to redraw, without resorting settings._iDisplayStart = 0; _fnDraw( settings ); } }; + + var searchDelay = settings.searchDelay !== null ? + settings.searchDelay : + _fnDataSource( settings ) === 'ssp' ? + 400 : + 0; + var jqFilter = $('input', filter) - .val( previousSearch.sSearch.replace('"','&quot;') ) + .val( previousSearch.sSearch ) + .attr( 'placeholder', language.sSearchPlaceholder ) .bind( 'keyup.DT search.DT input.DT paste.DT cut.DT', - _fnDataSource( settings ) === 'ssp' ? - _fnThrottle( searchFn, 400 ): + searchDelay ? + _fnThrottle( searchFn, searchDelay ) : searchFn ) .bind( 'keypress.DT', function(e) { /* Prevent form submission */ if ( e.keyCode == 13 ) { @@ -2674,19 +2781,21 @@ } } ) .attr('aria-controls', tableId); // Update the input elements whenever the table is filtered - $(settings.nTable).on( 'filter.DT', function () { - // IE9 throws an 'unknown error' if document.activeElement is used - // inside an iframe or frame... - try { - if ( jqFilter[0] !== document.activeElement ) { - jqFilter.val( previousSearch.sSearch ); + $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) { + if ( settings === s ) { + // IE9 throws an 'unknown error' if document.activeElement is used + // inside an iframe or frame... + try { + if ( jqFilter[0] !== document.activeElement ) { + jqFilter.val( previousSearch.sSearch ); + } } + catch ( e ) {} } - catch ( e ) {} } ); return filter[0]; } @@ -2755,19 +2864,27 @@ { var filters = DataTable.ext.search; var displayRows = settings.aiDisplay; var row, rowIdx; - for ( var i=0, iLen=filters.length ; i<iLen ; i++ ) { - for ( var j=displayRows.length-1 ; j>=0 ; j-- ) { + for ( var i=0, ien=filters.length ; i<ien ; i++ ) { + var rows = []; + + // Loop over each row and see if it should be included + for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) { rowIdx = displayRows[ j ]; row = settings.aoData[ rowIdx ]; - if ( ! filters[i]( settings, row._aFilterData, rowIdx, row._aData ) ) { - displayRows.splice( j, 1 ); + if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) { + rows.push( rowIdx ); } } + + // So the array reference doesn't break set the results into the + // existing array + displayRows.length = 0; + displayRows.push.apply( displayRows, rows ); } } /** @@ -2875,24 +2992,27 @@ * generate: * * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ */ var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || '', function ( word ) { - return word.charAt(0) === '"' ? - word.match( /^"(.*)"$/ )[1] : - word; + if ( word.charAt(0) === '"' ) { + var m = word.match( /^"(.*)"$/ ); + word = m ? m[1] : word; + } + + return word.replace('"', ''); } ); search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$'; } return new RegExp( search, caseInsensitive ? 'i' : '' ); } /** - * scape a string such that it can be used in a regular expression + * Escape a string such that it can be used in a regular expression * @param {string} sVal string to escape * @returns {string} escaped string * @memberof DataTable#oApi */ function _fnEscapeRegex ( sVal ) @@ -2924,15 +3044,23 @@ column = columns[j]; if ( column.bSearchable ) { cellData = _fnGetCellData( settings, i, j, 'filter' ); - cellData = fomatters[ column.sType ] ? - fomatters[ column.sType ]( cellData ) : - cellData !== null ? - cellData : - ''; + if ( fomatters[ column.sType ] ) { + cellData = fomatters[ column.sType ]( cellData ); + } + + // Search in DataTables 1.10 is string based. In 1.11 this + // should be altered to also allow strict type checking. + if ( cellData === null ) { + cellData = ''; + } + + if ( typeof cellData !== 'string' && cellData.toString ) { + cellData = cellData.toString(); + } } else { cellData = ''; } @@ -2961,11 +3089,48 @@ } return wasInvalidated; } + /** + * Convert from the internal Hungarian notation to camelCase for external + * interaction + * @param {object} obj Object to convert + * @returns {object} Inverted object + * @memberof DataTable#oApi + */ + function _fnSearchToCamel ( obj ) + { + return { + search: obj.sSearch, + smart: obj.bSmart, + regex: obj.bRegex, + caseInsensitive: obj.bCaseInsensitive + }; + } + + + + /** + * Convert from camelCase notation to the internal Hungarian. We could use the + * Hungarian convert function here, but this is cleaner + * @param {object} obj Object to convert + * @returns {object} Inverted object + * @memberof DataTable#oApi + */ + function _fnSearchToHung ( obj ) + { + return { + sSearch: obj.search, + bSmart: obj.smart, + bRegex: obj.regex, + bCaseInsensitive: obj.caseInsensitive + }; + } + + /** * Generate the node required for the info display * @param {object} oSettings dataTables settings object * @returns {node} Information element * @memberof DataTable#oApi */ @@ -3204,27 +3369,28 @@ var div = $('<div><label/></div>').addClass( classes.sLength ); if ( ! settings.aanFeatures.l ) { div[0].id = tableId+'_length'; } - var a = settings.oLanguage.sLengthMenu.split(/(_MENU_)/); - div.children().append( a.length > 1 ? - [ a[0], select, a[2] ] : - a[0] + div.children().append( + settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML ) ); - // Can't use `select` variable, as user might provide their own select menu + // Can't use `select` variable as user might provide their own and the + // reference is broken by the use of outerHTML $('select', div) .val( settings._iDisplayLength ) .bind( 'change.DT', function(e) { _fnLengthChange( settings, $(this).val() ); _fnDraw( settings ); } ); // Update node value whenever anything changes the table's length $(settings.nTable).bind( 'length.dt.DT', function (e, s, len) { - $('select', div).val( len ); + if ( settings === s ) { + $('select', div).val( len ); + } } ); return div[0]; } @@ -3482,16 +3648,16 @@ } ) .append( headerClone .removeAttr('id') .css( 'margin-left', 0 ) + .append( captionSide === 'top' ? caption : null ) .append( table.children('thead') ) ) ) - .append( captionSide === 'top' ? caption : null ) ) .append( $(_div, { 'class': classes.sScrollBody } ) .css( { overflow: 'auto', @@ -3513,16 +3679,16 @@ $(_div, { 'class': classes.sScrollFootInner } ) .append( footerClone .removeAttr('id') .css( 'margin-left', 0 ) + .append( captionSide === 'bottom' ? caption : null ) .append( table.children('tfoot') ) ) ) - .append( captionSide === 'bottom' ? caption : null ) ); } var children = scroller.children(); var scrollHead = children[0]; @@ -3842,12 +4008,13 @@ } /* Adjust the position of the header in case we loose the y-scrollbar */ divBody.scroll(); - /* If sorting or filtering has occurred, jump the scrolling back to the top */ - if ( settings.bSorted || settings.bFiltered ) { + // If sorting or filtering has occurred, jump the scrolling back to the top + // only if we aren't holding the position + if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) { divBodyEl.scrollTop = 0; } } @@ -3943,11 +4110,12 @@ else { // Otherwise construct a single row table with the widest node in the // data, assign any user defined widths, then insert it into the DOM and // allow the browser to do all the hard work of calculating table widths - var tmpTable = $( table.cloneNode( false ) ) + var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table + .empty() .css( 'visibility', 'hidden' ) .removeAttr( 'id' ) .append( $(oSettings.nTHead).clone( false ) ) .append( $(oSettings.nTFoot).clone( false ) ) .append( $('<tbody><tr/></tbody>') ); @@ -4071,11 +4239,11 @@ * @returns {function} wrapped function * @memberof DataTable#oApi */ function _fnThrottle( fn, freq ) { var - frequency = freq || 200, + frequency = freq !== undefined ? freq : 200, last, timer; return function () { var @@ -4312,15 +4480,19 @@ for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ ) { iCol = aDataSort[k]; sType = aoColumns[ iCol ].sType || 'string'; + if ( nestedSort[i]._idx === undefined ) { + nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting ); + } + aSort.push( { src: srcCol, col: iCol, dir: nestedSort[i][1], - index: nestedSort[i][2], + index: nestedSort[i]._idx, type: sType, formatter: DataTable.ext.type.order[ sType+"-pre" ] } ); } } @@ -4519,30 +4691,44 @@ { var col = settings.aoColumns[ colIdx ]; var sorting = settings.aaSorting; var asSorting = col.asSorting; var nextSortIdx; - var next = function ( a ) { + var next = function ( a, overflow ) { var idx = a._idx; if ( idx === undefined ) { idx = $.inArray( a[1], asSorting ); } - return idx+1 >= asSorting.length ? 0 : idx+1; + return idx+1 < asSorting.length ? + idx+1 : + overflow ? + null : + 0; }; + // Convert to 2D array if needed + if ( typeof sorting[0] === 'number' ) { + sorting = settings.aaSorting = [ sorting ]; + } + // If appending the sort then we are multi-column sorting if ( append && settings.oFeatures.bSortMulti ) { // Are we already doing some kind of sort on this column? var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') ); if ( sortIdx !== -1 ) { // Yes, modify the sort - nextSortIdx = next( sorting[sortIdx] ); + nextSortIdx = next( sorting[sortIdx], true ); - sorting[sortIdx][1] = asSorting[ nextSortIdx ]; - sorting[sortIdx]._idx = nextSortIdx; + if ( nextSortIdx === null ) { + sorting.splice( sortIdx, 1 ); + } + else { + sorting[sortIdx][1] = asSorting[ nextSortIdx ]; + sorting[sortIdx]._idx = nextSortIdx; + } } else { // No sort on this column yet sorting.push( [ colIdx, asSorting[0], 0 ] ); sorting[sorting.length-1]._idx = 0; @@ -4693,98 +4879,111 @@ /** * Save the state of a table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ - function _fnSaveState ( oSettings ) + function _fnSaveState ( settings ) { - if ( !oSettings.oFeatures.bStateSave || oSettings.bDestroying ) + if ( !settings.oFeatures.bStateSave || settings.bDestroying ) { return; } /* Store the interesting variables */ - var i, iLen; - var oState = { - "iCreate": +new Date(), - "iStart": oSettings._iDisplayStart, - "iLength": oSettings._iDisplayLength, - "aaSorting": $.extend( true, [], oSettings.aaSorting ), - "oSearch": $.extend( true, {}, oSettings.oPreviousSearch ), - "aoSearchCols": $.extend( true, [], oSettings.aoPreSearchCols ), - "abVisCols": _pluck( oSettings.aoColumns, 'bVisible' ) + var state = { + time: +new Date(), + start: settings._iDisplayStart, + length: settings._iDisplayLength, + order: $.extend( true, [], settings.aaSorting ), + search: _fnSearchToCamel( settings.oPreviousSearch ), + columns: $.map( settings.aoColumns, function ( col, i ) { + return { + visible: col.bVisible, + search: _fnSearchToCamel( settings.aoPreSearchCols[i] ) + }; + } ) }; - _fnCallbackFire( oSettings, "aoStateSaveParams", 'stateSaveParams', [oSettings, oState] ); + _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] ); - oSettings.fnStateSaveCallback.call( oSettings.oInstance, oSettings, oState ); + settings.oSavedState = state; + settings.fnStateSaveCallback.call( settings.oInstance, settings, state ); } /** * Attempt to load a saved table state * @param {object} oSettings dataTables settings object * @param {object} oInit DataTables init object so we can override settings * @memberof DataTable#oApi */ - function _fnLoadState ( oSettings, oInit ) + function _fnLoadState ( settings, oInit ) { var i, ien; - var columns = oSettings.aoColumns; + var columns = settings.aoColumns; - if ( ! oSettings.oFeatures.bStateSave ) { + if ( ! settings.oFeatures.bStateSave ) { return; } - var oData = oSettings.fnStateLoadCallback.call( oSettings.oInstance, oSettings ); - if ( !oData ) { + var state = settings.fnStateLoadCallback.call( settings.oInstance, settings ); + if ( ! state || ! state.time ) { return; } /* Allow custom and plug-in manipulation functions to alter the saved data set and * cancelling of loading by returning false */ - var abStateLoad = _fnCallbackFire( oSettings, 'aoStateLoadParams', 'stateLoadParams', [oSettings, oData] ); + var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, state] ); if ( $.inArray( false, abStateLoad ) !== -1 ) { return; } /* Reject old data */ - var duration = oSettings.iStateDuration; - if ( duration > 0 && oData.iCreate < +new Date() - (duration*1000) ) { + var duration = settings.iStateDuration; + if ( duration > 0 && state.time < +new Date() - (duration*1000) ) { return; } // Number of columns have changed - all bets are off, no restore of settings - if ( columns.length !== oData.aoSearchCols.length ) { + if ( columns.length !== state.columns.length ) { return; } - /* Store the saved state so it might be accessed at any time */ - oSettings.oLoadedState = $.extend( true, {}, oData ); + // Store the saved state so it might be accessed at any time + settings.oLoadedState = $.extend( true, {}, state ); - /* Restore key features */ - oSettings._iDisplayStart = oData.iStart; - oSettings.iInitDisplayStart = oData.iStart; - oSettings._iDisplayLength = oData.iLength; - oSettings.aaSorting = $.map( oData.aaSorting, function ( col, i ) { - return col[0] >= columns.length ? + // Restore key features - todo - for 1.11 this needs to be done by + // subscribed events + settings._iDisplayStart = state.start; + settings.iInitDisplayStart = state.start; + settings._iDisplayLength = state.length; + settings.aaSorting = []; + + // Order + $.each( state.order, function ( i, col ) { + settings.aaSorting.push( col[0] >= columns.length ? [ 0, col[1] ] : - col; + col + ); } ); - /* Search filtering */ - $.extend( oSettings.oPreviousSearch, oData.oSearch ); - $.extend( true, oSettings.aoPreSearchCols, oData.aoSearchCols ); + // Search + $.extend( settings.oPreviousSearch, _fnSearchToHung( state.search ) ); - /* Column visibility state */ - var visColumns = oData.abVisCols; - for ( i=0, ien=visColumns.length ; i<ien ; i++ ) { - columns[i].bVisible = visColumns[i]; + // Columns + for ( i=0, ien=state.columns.length ; i<ien ; i++ ) { + var col = state.columns[i]; + + // Visibility + columns[i].bVisible = col.visible; + + // Search + $.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) ); } - _fnCallbackFire( oSettings, 'aoStateLoaded', 'stateLoaded', [oSettings, oData] ); + _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, state] ); } /** * Return the settings object for a particular table @@ -4978,22 +5177,22 @@ * trigger is fired * @param {array} args Array of arguments to pass to the callback function / * trigger * @memberof DataTable#oApi */ - function _fnCallbackFire( settings, callbackArr, event, args ) + function _fnCallbackFire( settings, callbackArr, e, args ) { var ret = []; if ( callbackArr ) { ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) { return val.fn.apply( settings.oInstance, args ); } ); } - if ( event !== null ) { - $(settings.nTable).trigger( event+'.dt', args ); + if ( e !== null ) { + $(settings.nTable).trigger( e+'.dt', args ); } return ret; } @@ -5004,15 +5203,18 @@ start = settings._iDisplayStart, end = settings.fnDisplayEnd(), len = settings._iDisplayLength; /* If we have space to show extra rows (backing up from the end point - then do so */ - if ( end === settings.fnRecordsDisplay() ) + if ( start >= end ) { start = end - len; } + // Keep the start record on the current page + start -= (start % len); + if ( len === -1 || start < 0 ) { start = 0; } @@ -5980,10 +6182,11 @@ "bSortCellsTop", "iTabIndex", "fnStateLoadCallback", "fnStateSaveCallback", "renderer", + "searchDelay", [ "iCookieDuration", "iStateDuration" ], // backwards compat [ "oSearch", "oPreviousSearch" ], [ "aoSearchCols", "aoPreSearchCols" ], [ "iDisplayLength", "_iDisplayLength" ], [ "bJQueryUI", "bJUI" ] @@ -6061,31 +6264,36 @@ oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading; oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading; } /* Language definitions */ - if ( oInit.oLanguage.sUrl !== "" ) + var oLanguage = oSettings.oLanguage; + $.extend( true, oLanguage, oInit.oLanguage ); + + if ( oLanguage.sUrl !== "" ) { /* Get the language definitions from a file - because this Ajax call makes the language * get async to the remainder of this function we use bInitHandedOff to indicate that * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor */ - oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl; - $.getJSON( oSettings.oLanguage.sUrl, null, function( json ) { - _fnLanguageCompat( json ); - _fnCamelToHungarian( defaults.oLanguage, json ); - $.extend( true, oSettings.oLanguage, oInit.oLanguage, json ); - _fnInitialise( oSettings ); + $.ajax( { + dataType: 'json', + url: oLanguage.sUrl, + success: function ( json ) { + _fnLanguageCompat( json ); + _fnCamelToHungarian( defaults.oLanguage, json ); + $.extend( true, oLanguage, json ); + _fnInitialise( oSettings ); + }, + error: function () { + // Error occurred loading language file, continue on as best we can + _fnInitialise( oSettings ); + } } ); bInitHandedOff = true; } - else - { - $.extend( true, oSettings.oLanguage, oInit.oLanguage ); - } - /* * Stripes */ if ( oInit.asStripeClasses === null ) { @@ -6469,11 +6677,11 @@ * * @example * // Initialisation as a constructor * var api = new $.fn.DataTable.Api( 'table.dataTable' ); */ - DataTable.Api = _Api = function ( context, data ) + _Api = function ( context, data ) { if ( ! this instanceof _Api ) { throw 'DT API must be constructed as a new object'; // or should it do the 'new' for the caller? // return new _Api.apply( this, arguments ); @@ -6512,10 +6720,11 @@ }; _Api.extend( this, this, __apiStruct ); }; + DataTable.Api = _Api; _Api.prototype = /** @lends DataTables.Api */{ /** * Return a new Api instance, comprised of the data held in the current * instance, join with the other array(s) and/or value(s). @@ -6534,21 +6743,13 @@ context: [], // array of table settings objects each: function ( fn ) { - if ( __arrayProto.forEach ) { - // Where possible, use the built-in forEach - __arrayProto.forEach.call( this, fn, this ); + for ( var i=0, ien=this.length ; i<ien; i++ ) { + fn.call( this, this[i], i, this ); } - else { - // Compatibility for browsers without EMCA-252-5 (JS 1.6) - for ( var i=0, ien=this.length ; i<ien; i++ ) { - // In strict mode the execution scope is the passed value - fn.call( this, this[i], i, this ); - } - } return this; }, @@ -6600,37 +6801,40 @@ } } return -1; }, - // Internal only at the moment - relax? - iterator: function ( flatten, type, fn ) { + // Note that `alwaysNew` is internal - use iteratorNew externally + iterator: function ( flatten, type, fn, alwaysNew ) { var a = [], ret, i, ien, j, jen, context = this.context, rows, items, item, selector = this.selector; // Argument shifting if ( typeof flatten === 'string' ) { + alwaysNew = fn; fn = type; type = flatten; flatten = false; } for ( i=0, ien=context.length ; i<ien ; i++ ) { + var apiInst = new _Api( context[i] ); + if ( type === 'table' ) { - ret = fn( context[i], i ); + ret = fn.call( apiInst, context[i], i ); if ( ret !== undefined ) { a.push( ret ); } } else if ( type === 'columns' || type === 'rows' ) { // this has same length as context - one entry for each table - ret = fn( context[i], this[i], i ); + ret = fn.call( apiInst, context[i], this[i], i ); if ( ret !== undefined ) { a.push( ret ); } } @@ -6645,24 +6849,24 @@ for ( j=0, jen=items.length ; j<jen ; j++ ) { item = items[j]; if ( type === 'cell' ) { - ret = fn( context[i], item.row, item.column, i, j ); + ret = fn.call( apiInst, context[i], item.row, item.column, i, j ); } else { - ret = fn( context[i], item, i, j, rows ); + ret = fn.call( apiInst, context[i], item, i, j, rows ); } if ( ret !== undefined ) { a.push( ret ); } } } } - if ( a.length ) { + if ( a.length || alwaysNew ) { var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a ); var apiSelector = api.selector; apiSelector.rows = selector.rows; apiSelector.cols = selector.cols; apiSelector.opts = selector.opts; @@ -6779,11 +6983,11 @@ var i, ien, j, jen, struct, inner, - methodScoping = function ( fn, struc ) { + methodScoping = function ( scope, fn, struc ) { return function () { var ret = fn.apply( scope, arguments ); // Method extension _Api.extend( ret, ret, struc.methodExt ); @@ -6794,11 +6998,11 @@ for ( i=0, ien=ext.length ; i<ien ; i++ ) { struct = ext[i]; // Value obj[ struct.name ] = typeof struct.val === 'function' ? - methodScoping( struct.val, struct ) : + methodScoping( scope, struct.val, struct ) : $.isPlainObject( struct.val ) ? {} : struct.val; obj[ struct.name ].__dt_wrapper = true; @@ -6890,15 +7094,10 @@ struct = method ? src.methodExt : src.propExt; } } - - // Rebuild the API with the new construct - if ( _Api.ready ) { - DataTable.api.build(); - } }; _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) { _Api.register( pluralName, val ); @@ -6990,36 +7189,43 @@ _api_registerPlural( 'tables().nodes()', 'table().node()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTable; - } ); + }, 1 ); } ); _api_registerPlural( 'tables().body()', 'table().body()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTBody; - } ); + }, 1 ); } ); _api_registerPlural( 'tables().header()', 'table().header()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTHead; - } ); + }, 1 ); } ); _api_registerPlural( 'tables().footer()', 'table().footer()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTFoot; - } ); + }, 1 ); } ); + _api_registerPlural( 'tables().containers()', 'table().container()' , function () { + return this.iterator( 'table', function ( ctx ) { + return ctx.nTableWrapper; + }, 1 ); + } ); + + /** * Redraw the tables in the current context. * * @param {boolean} [reset=true] Reset (default) or hold the current paging * position. A full re-sort and re-filter is performed when this method is @@ -7283,15 +7489,16 @@ var _selector_run = function ( selector, select ) { var out = [], res, - a, i, ien, j, jen; + a, i, ien, j, jen, + selectorType = typeof selector; // Can't just check for isArray here, as an API or jQuery instance might be // given with their array like look - if ( ! selector || typeof selector === 'string' || selector.length === undefined ) { + if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) { selector = [ selector ]; } for ( i=0, ien=selector.length ; i<ien ; i++ ) { a = selector[i] && selector[i].split ? @@ -7397,11 +7604,11 @@ } else { // applied | removed tmp = $.inArray( i, displayFiltered ); if ((tmp === -1 && search == 'removed') || - (tmp === 1 && search == 'applied') ) + (tmp >= 0 && search == 'applied') ) { a.push( i ); } } } @@ -7425,10 +7632,11 @@ var __row_selector = function ( settings, selector, opts ) { return _selector_run( selector, function ( sel ) { var selInt = _intVal( sel ); + var i, ien; // Short cut - selector is a number and no options provided (default is // all records, so no need to check if the index is in there, since it // must be - dev error if the index doesn't exist). if ( selInt !== null && ! opts ) { @@ -7444,21 +7652,28 @@ else if ( ! sel ) { // Selector - none return rows; } - // Get nodes in the order from the `rows` array (can't use `pluck`) @todo - use pluck_order - var nodes = []; - for ( var i=0, ien=rows.length ; i<ien ; i++ ) { - nodes.push( settings.aoData[ rows[i] ].nTr ); + // Selector - function + if ( typeof sel === 'function' ) { + return $.map( rows, function (idx) { + var row = settings.aoData[ idx ]; + return sel( idx, row._aData, row.nTr ) ? idx : null; + } ); } + // Get nodes in the order from the `rows` array with null values removed + var nodes = _removeEmpty( + _pluck_order( settings.aoData, rows, 'nTr' ) + ); + + // Selector - node if ( sel.nodeName ) { - // Selector - node if ( $.inArray( sel, nodes ) !== -1 ) { - return [ sel._DT_RowIndex ];// sel is a TR node that is in the table - // and DataTables adds a prop for fast lookup + return [ sel._DT_RowIndex ]; // sel is a TR node that is in the table + // and DataTables adds a prop for fast lookup } } // Selector - jQuery selector string, array of nodes or jQuery object/ // As jQuery's .filter() allows jQuery objects to be passed in filter, @@ -7488,11 +7703,11 @@ opts = _selector_opts( opts ); var inst = this.iterator( 'table', function ( settings ) { return __row_selector( settings, selector, opts ); - } ); + }, 1 ); // Want argument shifting here and in __row_selector? inst.selector.rows = selector; inst.selector.opts = opts; @@ -7501,36 +7716,36 @@ _api_register( 'rows().nodes()', function () { return this.iterator( 'row', function ( settings, row ) { return settings.aoData[ row ].nTr || undefined; - } ); + }, 1 ); } ); _api_register( 'rows().data()', function () { return this.iterator( true, 'rows', function ( settings, rows ) { return _pluck_order( settings.aoData, rows, '_aData' ); - } ); + }, 1 ); } ); _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) { return this.iterator( 'row', function ( settings, row ) { var r = settings.aoData[ row ]; return type === 'search' ? r._aFilterData : r._aSortData; - } ); + }, 1 ); } ); _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) { return this.iterator( 'row', function ( settings, row ) { - _fnInvalidateRow( settings, row, src ); + _fnInvalidate( settings, row, src ); } ); } ); _api_registerPlural( 'rows().indexes()', 'row().index()', function () { return this.iterator( 'row', function ( settings, row ) { return row; - } ); + }, 1 ); } ); _api_registerPlural( 'rows().remove()', 'row().remove()', function () { var that = this; @@ -7575,11 +7790,11 @@ out.push( _fnAddData( settings, row ) ); } } return out; - } ); + }, 1 ); // Return an Api.rows() extended instance, so rows().nodes() etc can be used var modRows = this.rows( -1 ); modRows.pop(); modRows.push.apply( modRows, newRows.toArray() ); @@ -7611,11 +7826,11 @@ // Set ctx[0].aoData[ this[0] ]._aData = data; // Automatically invalidate - _fnInvalidateRow( ctx[0], this[0], 'data' ); + _fnInvalidate( ctx[0], this[0], 'data' ); return this; } ); @@ -7658,11 +7873,11 @@ if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) { rows.push( r ); } else { // Otherwise create a row with a wrapper - var created = $('<tr><td/></tr>'); + var created = $('<tr><td/></tr>').addClass( k ); $('td', created) .addClass( k ) .html( r ) [0].colSpan = _fnVisbleColumns( ctx ); @@ -7690,110 +7905,174 @@ row._details.insertAfter( row.nTr ); } }; - var __details_display = function ( show ) { - var ctx = this.context; + var __details_remove = function ( api, idx ) + { + var ctx = api.context; - if ( ctx.length && this.length ) { - var row = ctx[0].aoData[ this[0] ]; + if ( ctx.length ) { + var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ]; if ( row._details ) { + row._details.remove(); + + row._detailsShow = undefined; + row._details = undefined; + } + } + }; + + + var __details_display = function ( api, show ) { + var ctx = api.context; + + if ( ctx.length && api.length ) { + var row = ctx[0].aoData[ api[0] ]; + + if ( row._details ) { row._detailsShow = show; + if ( show ) { row._details.insertAfter( row.nTr ); } else { - row._details.remove(); + row._details.detach(); } __details_events( ctx[0] ); } } - - return this; }; var __details_events = function ( settings ) { var api = new _Api( settings ); var namespace = '.dt.DT_details'; var drawEvent = 'draw'+namespace; var colvisEvent = 'column-visibility'+namespace; + var destroyEvent = 'destroy'+namespace; + var data = settings.aoData; - api.off( drawEvent +' '+ colvisEvent ); + api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent ); - if ( _pluck( settings.aoData, '_details' ).length > 0 ) { + if ( _pluck( data, '_details' ).length > 0 ) { // On each draw, insert the required elements into the document - api.on( drawEvent, function () { + api.on( drawEvent, function ( e, ctx ) { + if ( settings !== ctx ) { + return; + } + api.rows( {page:'current'} ).eq(0).each( function (idx) { // Internal data grab - var row = settings.aoData[ idx ]; + var row = data[ idx ]; if ( row._detailsShow ) { row._details.insertAfter( row.nTr ); } } ); } ); // Column visibility change - update the colspan - api.on( colvisEvent, function ( e, settings, idx, vis ) { + api.on( colvisEvent, function ( e, ctx, idx, vis ) { + if ( settings !== ctx ) { + return; + } + // Update the colspan for the details rows (note, only if it already has // a colspan) - var row, visible = _fnVisbleColumns( settings ); + var row, visible = _fnVisbleColumns( ctx ); - for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { - row = settings.aoData[i]; + for ( var i=0, ien=data.length ; i<ien ; i++ ) { + row = data[i]; if ( row._details ) { row._details.children('td[colspan]').attr('colspan', visible ); } } } ); + + // Table destroyed - nuke any child rows + api.on( destroyEvent, function ( e, ctx ) { + if ( settings !== ctx ) { + return; + } + + for ( var i=0, ien=data.length ; i<ien ; i++ ) { + if ( data[i]._details ) { + __details_remove( api, i ); + } + } + } ); } }; + // Strings for the method names to help minification + var _emp = ''; + var _child_obj = _emp+'row().child'; + var _child_mth = _child_obj+'()'; + // data can be: // tr // string // jQuery or array of any of the above - _api_register( 'row().child()', function ( data, klass ) { + _api_register( _child_mth, function ( data, klass ) { var ctx = this.context; if ( data === undefined ) { // get return ctx.length && this.length ? ctx[0].aoData[ this[0] ]._details : undefined; } + else if ( data === true ) { + // show + this.child.show(); + } + else if ( data === false ) { + // remove + __details_remove( this ); + } else if ( ctx.length && this.length ) { // set __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass ); } return this; } ); + _api_register( [ - 'row().child.show()', - 'row().child().show()' - ], function () { - __details_display.call( this, true ); + _child_obj+'.show()', + _child_mth+'.show()' // only when `child()` was called with parameters (without + ], function ( show ) { // it returns an object and this method is not executed) + __details_display( this, true ); return this; } ); + _api_register( [ - 'row().child.hide()', - 'row().child().hide()' - ], function () { - __details_display.call( this, false ); + _child_obj+'.hide()', + _child_mth+'.hide()' // only when `child()` was called with parameters (without + ], function () { // it returns an object and this method is not executed) + __details_display( this, false ); return this; } ); - _api_register( 'row().child.isShown()', function () { + + _api_register( [ + _child_obj+'.remove()', + _child_mth+'.remove()' // only when `child()` was called with parameters (without + ], function () { // it returns an object and this method is not executed) + __details_remove( this ); + return this; + } ); + + + _api_register( _child_obj+'.isShown()', function () { var ctx = this.context; if ( ctx.length && this.length ) { // _detailsShown as false or undefined will fall through to return false return ctx[0].aoData[ this[0] ]._detailsShow || false; @@ -7815,79 +8094,102 @@ */ // can be an array of these items, comma separated list, or an array of comma // separated lists - var __re_column_selector = /^(.*):(name|visIdx|visible)$/; + var __re_column_selector = /^(.+):(name|visIdx|visible)$/; + + // r1 and r2 are redundant - but it means that the parameters match for the + // iterator callback in columns().data() + var __columnData = function ( settings, column, r1, r2, rows ) { + var a = []; + for ( var row=0, ien=rows.length ; row<ien ; row++ ) { + a.push( _fnGetCellData( settings, rows[row], column ) ); + } + return a; + }; + + var __column_selector = function ( settings, selector, opts ) { var columns = settings.aoColumns, names = _pluck( columns, 'sName' ), nodes = _pluck( columns, 'nTh' ); return _selector_run( selector, function ( s ) { var selInt = _intVal( s ); + // Selector - all if ( s === '' ) { - // All columns return _range( columns.length ); } - else if ( selInt !== null ) { - // Integer selector + + // Selector - index + if ( selInt !== null ) { return [ selInt >= 0 ? selInt : // Count from left columns.length + selInt // Count from right (+ because its a negative value) ]; } - else { - var match = typeof s === 'string' ? - s.match( __re_column_selector ) : - ''; - if ( match ) { - switch( match[2] ) { - case 'visIdx': - case 'visible': - var idx = parseInt( match[1], 10 ); - // Visible index given, convert to column index - if ( idx < 0 ) { - // Counting from the right - var visColumns = $.map( columns, function (col,i) { - return col.bVisible ? i : null; - } ); - return [ visColumns[ visColumns.length + idx ] ]; - } - // Counting from the left - return [ _fnVisibleToColumnIndex( settings, idx ) ]; + // Selector = function + if ( typeof s === 'function' ) { + var rows = _selector_row_indexes( settings, opts ); - case 'name': - // match by name. `names` is column index complete and in order - return $.map( names, function (name, i) { - return name === match[1] ? i : null; + return $.map( columns, function (col, idx) { + return s( + idx, + __columnData( settings, idx, 0, 0, rows ), + nodes[ idx ] + ) ? idx : null; + } ); + } + + // jQuery or string selector + var match = typeof s === 'string' ? + s.match( __re_column_selector ) : + ''; + + if ( match ) { + switch( match[2] ) { + case 'visIdx': + case 'visible': + var idx = parseInt( match[1], 10 ); + // Visible index given, convert to column index + if ( idx < 0 ) { + // Counting from the right + var visColumns = $.map( columns, function (col,i) { + return col.bVisible ? i : null; } ); - } + return [ visColumns[ visColumns.length + idx ] ]; + } + // Counting from the left + return [ _fnVisibleToColumnIndex( settings, idx ) ]; + + case 'name': + // match by name. `names` is column index complete and in order + return $.map( names, function (name, i) { + return name === match[1] ? i : null; + } ); } - else { - // jQuery selector on the TH elements for the columns - return $( nodes ) - .filter( s ) - .map( function () { - return $.inArray( this, nodes ); // `nodes` is column index complete and in order - } ) - .toArray(); - } } + else { + // jQuery selector on the TH elements for the columns + return $( nodes ) + .filter( s ) + .map( function () { + return $.inArray( this, nodes ); // `nodes` is column index complete and in order + } ) + .toArray(); + } } ); }; - - - - var __setColumnVis = function ( settings, column, vis ) { + var __setColumnVis = function ( settings, column, vis, recalc ) { var cols = settings.aoColumns, col = cols[ column ], data = settings.aoData, row, cells, i, ien, tr; @@ -7919,29 +8221,25 @@ } } else { // Remove column $( _pluck( settings.aoData, 'anCells', column ) ).detach(); - - col.bVisible = false; - _fnDrawHead( settings, settings.aoHeader ); - _fnDrawHead( settings, settings.aoFooter ); - - _fnSaveState( settings ); } // Common actions col.bVisible = vis; _fnDrawHead( settings, settings.aoHeader ); _fnDrawHead( settings, settings.aoFooter ); - // Automatically adjust column sizing - _fnAdjustColumnSizing( settings ); + if ( recalc === undefined || recalc ) { + // Automatically adjust column sizing + _fnAdjustColumnSizing( settings ); - // Realign columns for scrolling - if ( settings.oScroll.sX || settings.oScroll.sY ) { - _fnScrollDraw( settings ); + // Realign columns for scrolling + if ( settings.oScroll.sX || settings.oScroll.sY ) { + _fnScrollDraw( settings ); + } } _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis] ); _fnSaveState( settings ); @@ -7963,11 +8261,11 @@ opts = _selector_opts( opts ); var inst = this.iterator( 'table', function ( settings ) { return __column_selector( settings, selector, opts ); - } ); + }, 1 ); // Want argument shifting here and in _row_selector? inst.selector.cols = selector; inst.selector.opts = opts; @@ -7979,71 +8277,73 @@ * */ _api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].nTh; - } ); + }, 1 ); } ); /** * */ _api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].nTf; - } ); + }, 1 ); } ); /** * */ _api_registerPlural( 'columns().data()', 'column().data()', function () { - return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { - var a = []; - for ( var row=0, ien=rows.length ; row<ien ; row++ ) { - a.push( _fnGetCellData( settings, rows[row], column, '' ) ); - } - return a; - } ); + return this.iterator( 'column-rows', __columnData, 1 ); } ); + _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () { + return this.iterator( 'column', function ( settings, column ) { + return settings.aoColumns[column].mData; + }, 1 ); + } ); + + _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) { return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { return _pluck_order( settings.aoData, rows, type === 'search' ? '_aFilterData' : '_aSortData', column ); - } ); + }, 1 ); } ); _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () { return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { return _pluck_order( settings.aoData, rows, 'anCells', column ) ; - } ); + }, 1 ); } ); - _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis ) { + _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) { return this.iterator( 'column', function ( settings, column ) { - return vis === undefined ? - settings.aoColumns[ column ].bVisible : - __setColumnVis( settings, column, vis ); + if ( vis === undefined ) { + return settings.aoColumns[ column ].bVisible; + } // else + __setColumnVis( settings, column, vis, calc ); } ); } ); _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) { return this.iterator( 'column', function ( settings, column ) { return type === 'visible' ? _fnColumnIndexToVisible( settings, column ) : column; - } ); + }, 1 ); } ); // _api_register( 'columns().show()', function () { // var selector = this.selector; @@ -8059,11 +8359,11 @@ _api_register( 'columns.adjust()', function () { return this.iterator( 'table', function ( settings ) { _fnAdjustColumnSizing( settings ); - } ); + }, 1 ); } ); // Convert from one column index type, to another type _api_register( 'column.index()', function ( type, idx ) { @@ -8089,39 +8389,56 @@ var __cell_selector = function ( settings, selector, opts ) { var data = settings.aoData; var rows = _selector_row_indexes( settings, opts ); - var cells = _pluck_order( data, rows, 'anCells' ); + var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) ); var allCells = $( [].concat.apply([], cells) ); var row; var columns = settings.aoColumns.length; - var a, i, ien, j; + var a, i, ien, j, o, host; return _selector_run( selector, function ( s ) { - if ( ! s ) { - // All cells + var fnSelector = typeof s === 'function'; + + if ( s === null || s === undefined || fnSelector ) { + // All cells and function selectors a = []; for ( i=0, ien=rows.length ; i<ien ; i++ ) { row = rows[i]; for ( j=0 ; j<columns ; j++ ) { - a.push( { + o = { row: row, column: j - } ); + }; + + if ( fnSelector ) { + // Selector - function + host = settings.aoData[ row ]; + + if ( s( o, _fnGetCellData(settings, row, j), host.anCells[j] ) ) { + a.push( o ); + } + } + else { + // Selector - all + a.push( o ); + } } } return a; } - else if ( $.isPlainObject( s ) ) { + + // Selector - index + if ( $.isPlainObject( s ) ) { return [s]; } - // jQuery filtered cells + // Selector - jQuery filtered cells return allCells .filter( s ) .map( function (i, el) { row = el.parentNode._DT_RowIndex; @@ -8138,12 +8455,12 @@ _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) { // Argument shifting if ( $.isPlainObject( rowSelector ) ) { - // If passing in a cell index - if ( rowSelector.row ) { + // Indexes + if ( typeof rowSelector.row !== undefined ) { opts = columnSelector; columnSelector = null; } else { opts = rowSelector; @@ -8178,11 +8495,11 @@ } ); } } return a; - } ); + }, 1 ); $.extend( cells.selector, { cols: columnSelector, rows: rowSelector, opts: opts @@ -8192,65 +8509,65 @@ } ); _api_registerPlural( 'cells().nodes()', 'cell().node()', function () { return this.iterator( 'cell', function ( settings, row, column ) { - return settings.aoData[ row ].anCells[ column ]; - } ); + var cells = settings.aoData[ row ].anCells; + return cells ? + cells[ column ] : + undefined; + }, 1 ); } ); _api_register( 'cells().data()', function () { return this.iterator( 'cell', function ( settings, row, column ) { return _fnGetCellData( settings, row, column ); - } ); + }, 1 ); } ); _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) { type = type === 'search' ? '_aFilterData' : '_aSortData'; return this.iterator( 'cell', function ( settings, row, column ) { return settings.aoData[ row ][ type ][ column ]; - } ); + }, 1 ); } ); + _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) { + return this.iterator( 'cell', function ( settings, row, column ) { + return _fnGetCellData( settings, row, column, type ); + }, 1 ); + } ); + + _api_registerPlural( 'cells().indexes()', 'cell().index()', function () { return this.iterator( 'cell', function ( settings, row, column ) { return { row: row, column: column, columnVisible: _fnColumnIndexToVisible( settings, column ) }; - } ); + }, 1 ); } ); - _api_register( [ - 'cells().invalidate()', - 'cell().invalidate()' - ], function ( src ) { - var selector = this.selector; - - // Use the rows method of the instance to perform the invalidation, rather - // than doing it here. This avoids needing to handle duplicate rows from - // the cells. - this.rows( selector.rows, selector.opts ).invalidate( src ); - - return this; + _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) { + return this.iterator( 'cell', function ( settings, row, column ) { + _fnInvalidate( settings, row, src, column ); + } ); } ); - _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) { return _selector_first( this.cells( rowSelector, columnSelector, opts ) ); } ); - _api_register( 'cell().data()', function ( data ) { var ctx = this.context; var cell = this[0]; if ( data === undefined ) { @@ -8260,11 +8577,11 @@ undefined; } // Set _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data ); - _fnInvalidateRow( ctx[0], cell[0].row, 'data', cell[0].column ); + _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column ); return this; } ); @@ -8385,40 +8702,73 @@ } ), 1 ); } ); } ); - _api_register( [ + _api_registerPlural( 'columns().search()', - 'column().search()' - ], function ( input, regex, smart, caseInsen ) { - return this.iterator( 'column', function ( settings, column ) { - var preSearch = settings.aoPreSearchCols; + 'column().search()', + function ( input, regex, smart, caseInsen ) { + return this.iterator( 'column', function ( settings, column ) { + var preSearch = settings.aoPreSearchCols; - if ( input === undefined ) { - // get - return preSearch[ column ].sSearch; - } + if ( input === undefined ) { + // get + return preSearch[ column ].sSearch; + } - // set - if ( ! settings.oFeatures.bFilter ) { - return; - } + // set + if ( ! settings.oFeatures.bFilter ) { + return; + } - $.extend( preSearch[ column ], { - "sSearch": input+"", - "bRegex": regex === null ? false : regex, - "bSmart": smart === null ? true : smart, - "bCaseInsensitive": caseInsen === null ? true : caseInsen + $.extend( preSearch[ column ], { + "sSearch": input+"", + "bRegex": regex === null ? false : regex, + "bSmart": smart === null ? true : smart, + "bCaseInsensitive": caseInsen === null ? true : caseInsen + } ); + + _fnFilterComplete( settings, settings.oPreviousSearch, 1 ); } ); + } + ); - _fnFilterComplete( settings, settings.oPreviousSearch, 1 ); + /* + * State API methods + */ + + _api_register( 'state()', function () { + return this.context.length ? + this.context[0].oSavedState : + null; + } ); + + + _api_register( 'state.clear()', function () { + return this.iterator( 'table', function ( settings ) { + // Save an empty object + settings.fnStateSaveCallback.call( settings.oInstance, settings, {} ); } ); } ); + _api_register( 'state.loaded()', function () { + return this.context.length ? + this.context[0].oLoadedState : + null; + } ); + + _api_register( 'state.save()', function () { + return this.iterator( 'table', function ( settings ) { + _fnSaveState( settings ); + } ); + } ); + + + /** * Provide a common method for plug-ins to check the version of DataTables being * used, in order to ensure compatibility. * * @param {string} version Version string to check for, in the format "X.Y.Z". @@ -8501,19 +8851,51 @@ * $(table).DataTable().columns.adjust(); * } ); */ DataTable.tables = DataTable.fnTables = function ( visible ) { - return jQuery.map( DataTable.settings, function (o) { + return $.map( DataTable.settings, function (o) { if ( !visible || (visible && $(o.nTable).is(':visible')) ) { return o.nTable; } } ); }; /** + * DataTables utility methods + * + * This namespace provides helper methods that DataTables uses internally to + * create a DataTable, but which are not exclusively used only for DataTables. + * These methods can be used by extension authors to save the duplication of + * code. + * + * @namespace + */ + DataTable.util = { + /** + * Throttle the calls to a function. Arguments and context are maintained + * for the throttled function. + * + * @param {function} fn Function to be called + * @param {integer} freq Call frequency in mS + * @return {function} Wrapped function + */ + throttle: _fnThrottle, + + + /** + * Escape a string such that it can be used in a regular expression + * + * @param {string} sVal string to escape + * @returns {string} escaped string + */ + escapeRegex: _fnEscapeRegex + }; + + + /** * Convert from camel case parameters to Hungarian notation. This is made public * for the extensions to provide the same ability as DataTables core to accept * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase * parameters. * @@ -8547,11 +8929,11 @@ $.each( [ 'on', 'one', 'off' ], function (i, key) { _api_register( key+'()', function ( /* event, handler */ ) { var args = Array.prototype.slice.call(arguments); // Add the `dt` namespace automatically if it isn't already present - if ( args[0].indexOf( '.dt' ) === -1 ) { + if ( ! args[0].match(/\.dt\b/) ) { args[0] += '.dt'; } var inst = $( this.tables().nodes() ); inst[key].apply( inst, args ); @@ -8688,11 +9070,11 @@ * only for non-release builds. See http://semver.org/ for more information. * @member * @type string * @default Version number */ - DataTable.version = "1.10.0"; + DataTable.version = "1.10.4"; /** * Private data store, containing all of the settings objects that are * created for the tables on a given page. * @@ -10952,10 +11334,21 @@ */ "sSearch": "Search:", /** + * Assign a `placeholder` attribute to the search `input` element + * @type string + * @default + * + * @dtopt Language + * @name DataTable.defaults.language.searchPlaceholder + */ + "sSearchPlaceholder": "", + + + /** * All of the language information can be stored in a file on the * server-side, which DataTables will look up if this parameter is passed. * It must store the URL of the language file, which is in a JSON format, * and the object has the same properties as the oLanguage object in the * initialiser object (i.e. the above parameters). Please refer to one of @@ -11118,10 +11511,30 @@ */ "sDom": "lfrtip", /** + * Search delay option. This will throttle full table searches that use the + * DataTables provided search input element (it does not effect calls to + * `dt-api search()`, providing a delay before the search is made. + * @type integer + * @default 0 + * + * @dtopt Options + * @name DataTable.defaults.searchDelay + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "searchDelay": 200 + * } ); + * } ) + */ + "searchDelay": null, + + + /** * DataTables features four different built-in options for the buttons to * display for pagination control: * * * `simple` - 'Previous' and 'Next' buttons only * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers @@ -12634,10 +13047,17 @@ * @default null */ "sDom": null, /** + * Search delay (in mS) + * @type integer + * @default null + */ + "searchDelay": null, + + /** * Which type of pagination should be used. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string * @default two_button @@ -12680,10 +13100,17 @@ * @default [] */ "aoStateLoad": [], /** + * State that was saved. Useful for back reference + * @type object + * @default null + */ + "oSavedState": null, + + /** * State that was loaded. Useful for back reference * @type object * @default null */ "oLoadedState": null, @@ -13666,10 +14093,11 @@ // For testing and plug-ins to use _numbers: _numbers, numbers_length: 7 } ); + $.extend( true, DataTable.ext.renderer, { pageButton: { _: function ( settings, host, idx, buttons, page, pages ) { var classes = settings.oClasses; var lang = settings.oLanguage.oPaginate; @@ -13750,29 +14178,120 @@ } } } }; - // Because this approach is destroying and recreating the paging - // elements, focus is lost on the select button which is bad for - // accessibility. So we want to restore focus once the draw has - // completed - var activeEl = $(document.activeElement).data('dt-idx'); + // IE9 throws an 'unknown error' if document.activeElement is used + // inside an iframe or frame. Try / catch the error. Not good for + // accessibility, but neither are frames. + try { + // Because this approach is destroying and recreating the paging + // elements, focus is lost on the select button which is bad for + // accessibility. So we want to restore focus once the draw has + // completed + var activeEl = $(document.activeElement).data('dt-idx'); - attach( $(host).empty(), buttons ); + attach( $(host).empty(), buttons ); - if ( activeEl !== null ) { - $(host).find( '[data-dt-idx='+activeEl+']' ).focus(); + if ( activeEl !== null ) { + $(host).find( '[data-dt-idx='+activeEl+']' ).focus(); + } } + catch (e) {} } } } ); + // Built in type detection. See model.ext.aTypes for information about + // what is required from this methods. + $.extend( DataTable.ext.type.detect, [ + // Plain numbers - first since V8 detects some plain numbers as dates + // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). + function ( d, settings ) + { + var decimal = settings.oLanguage.sDecimal; + return _isNumber( d, decimal ) ? 'num'+decimal : null; + }, + + // Dates (only those recognised by the browser's Date.parse) + function ( d, settings ) + { + // V8 will remove any unknown characters at the start and end of the + // expression, leading to false matches such as `$245.12` or `10%` being + // a valid date. See forum thread 18941 for detail. + if ( d && !(d instanceof Date) && ( ! _re_date_start.test(d) || ! _re_date_end.test(d) ) ) { + return null; + } + var parsed = Date.parse(d); + return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; + }, + + // Formatted numbers + function ( d, settings ) + { + var decimal = settings.oLanguage.sDecimal; + return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null; + }, + + // HTML numeric + function ( d, settings ) + { + var decimal = settings.oLanguage.sDecimal; + return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null; + }, + + // HTML numeric, formatted + function ( d, settings ) + { + var decimal = settings.oLanguage.sDecimal; + return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null; + }, + + // HTML (this is strict checking - there must be html) + function ( d, settings ) + { + return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ? + 'html' : null; + } + ] ); + + + + // Filter formatting functions. See model.ext.ofnSearch for information about + // what is required from these methods. + // + // Note that additional search methods are added for the html numbers and + // html formatted numbers by `_addNumericSort()` when we know what the decimal + // place is + + + $.extend( DataTable.ext.type.search, { + html: function ( data ) { + return _empty(data) ? + data : + typeof data === 'string' ? + data + .replace( _re_new_lines, " " ) + .replace( _re_html, "" ) : + ''; + }, + + string: function ( data ) { + return _empty(data) ? + data : + typeof data === 'string' ? + data.replace( _re_new_lines, " " ) : + data; + } + } ); + + + var __numericReplace = function ( d, decimalPlace, re1, re2 ) { - if ( !d || d === '-' ) { + if ( d !== 0 && (!d || d === '-') ) { return -Infinity; } // If a decimal place other than `.` is used, it needs to be given to the // function so we can detect it and replace with a `.` which is the only @@ -13793,12 +14312,12 @@ return d * 1; }; - // Add the numeric 'deformatting' functions for sorting. This is done in a - // function to provide an easy ability for the language options to add + // Add the numeric 'deformatting' functions for sorting and search. This is done + // in a function to provide an easy ability for the language options to add // additional methods if a non-period decimal place is used. function _addNumericSort ( decimalPlace ) { $.each( { // Plain numbers @@ -13820,11 +14339,17 @@ "html-num-fmt": function ( d ) { return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); } }, function ( key, fn ) { + // Add the ordering method _ext.type.order[ key+decimalPlace+'-pre' ] = fn; + + // For HTML types add a search formatter that will strip the HTML + if ( key.match(/^html\-/) ) { + _ext.type.search[ key+decimalPlace ] = _ext.type.search.html; + } } ); } @@ -13835,24 +14360,28 @@ return Date.parse( d ) || 0; }, // html "html-pre": function ( a ) { - return ! a ? + return _empty(a) ? '' : a.replace ? a.replace( /<.*?>/g, "" ).toLowerCase() : a+''; }, // string "string-pre": function ( a ) { - return typeof a === 'string' ? - a.toLowerCase() : - ! a || ! a.toString ? - '' : - a.toString(); + // This is a little complex, but faster than always calling toString, + // http://jsperf.com/tostring-v-check + return _empty(a) ? + '' : + typeof a === 'string' ? + a.toLowerCase() : + ! a.toString ? + '' : + a.toString(); }, // string-asc and -desc are retained only for compatibility with the old // sort methods "string-asc": function ( x, y ) { @@ -13867,100 +14396,23 @@ // Numeric sorting types - order doesn't matter here _addNumericSort( '' ); - // Built in type detection. See model.ext.aTypes for information about - // what is required from this methods. - $.extend( DataTable.ext.type.detect, [ - // Plain numbers - first since V8 detects some plain numbers as dates - // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _isNumber( d, decimal ) ? 'num'+decimal : null; - }, - - // Dates (only those recognised by the browser's Date.parse) - function ( d, settings ) - { - // V8 will remove any unknown characters at the start of the expression, - // leading to false matches such as `$245.12` being a valid date. See - // forum thread 18941 for detail. - if ( d && ! _re_date_start.test(d) ) { - return null; - } - var parsed = Date.parse(d); - return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; - }, - - // Formatted numbers - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null; - }, - - // HTML numeric - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null; - }, - - // HTML numeric, formatted - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null; - }, - - // HTML (this is strict checking - there must be html) - function ( d, settings ) - { - return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ? - 'html' : null; - } - ] ); - - - - // Filter formatting functions. See model.ext.ofnSearch for information about - // what is required from these methods. - - - $.extend( DataTable.ext.type.search, { - html: function ( data ) { - return _empty(data) ? - '' : - typeof data === 'string' ? - data - .replace( _re_new_lines, " " ) - .replace( _re_html, "" ) : - ''; - }, - - string: function ( data ) { - return _empty(data) ? - '' : - typeof data === 'string' ? - data.replace( _re_new_lines, " " ) : - data; - } - } ); - - - $.extend( true, DataTable.ext.renderer, { header: { _: function ( settings, cell, column, classes ) { // No additional mark-up required // Attach a sort listener to update on sort - note that using the // `DT` namespace will allow the event to be removed automatically // on destroy, while the `dt` namespaced event is the one we are // listening for - $(settings.nTable).on( 'order.dt.DT', function ( e, settings, sorting, columns ) { + $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { + if ( settings !== ctx ) { // need to check this this is the host + return; // table, not a nested one + } + var colIdx = column.idx; cell .removeClass( column.sSortingClass +' '+ @@ -13974,22 +14426,26 @@ ); } ); }, jqueryui: function ( settings, cell, column, classes ) { - var colIdx = column.idx; - $('<div/>') .addClass( classes.sSortJUIWrapper ) .append( cell.contents() ) .append( $('<span/>') .addClass( classes.sSortIcon+' '+column.sSortingClassJUI ) ) .appendTo( cell ); // Attach a sort listener to update on sort - $(settings.nTable).on( 'order.dt.DT', function ( e, settings, sorting, columns ) { + $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { + if ( settings !== ctx ) { + return; + } + + var colIdx = column.idx; + cell .removeClass( classes.sSortAsc +" "+classes.sSortDesc ) .addClass( columns[ colIdx ] == 'asc' ? classes.sSortAsc : columns[ colIdx ] == 'desc' ? classes.sSortDesc : @@ -14047,17 +14503,19 @@ */ DataTable.render = { number: function ( thousands, decimal, precision, prefix ) { return { display: function ( d ) { - d = parseFloat( d ); + var negative = d < 0 ? '-' : ''; + d = Math.abs( parseFloat( d ) ); + var intPart = parseInt( d, 10 ); var floatPart = precision ? - (decimal+(d - intPart).toFixed( precision )).substring( 2 ): + decimal+(d - intPart).toFixed( precision ).substring( 2 ): ''; - return (prefix||'') + + return negative + (prefix||'') + intPart.toString().replace( /\B(?=(\d{3})+(?!\d))/g, thousands ) + floatPart; } @@ -14126,10 +14584,10 @@ _fnGetObjectDataFn: _fnGetObjectDataFn, _fnSetObjectDataFn: _fnSetObjectDataFn, _fnGetDataMaster: _fnGetDataMaster, _fnClearTable: _fnClearTable, _fnDeleteIndex: _fnDeleteIndex, - _fnInvalidateRow: _fnInvalidateRow, + _fnInvalidate: _fnInvalidate, _fnGetRowElements: _fnGetRowElements, _fnCreateTr: _fnCreateTr, _fnBuildHead: _fnBuildHead, _fnDrawHead: _fnDrawHead, _fnDraw: _fnDraw,