UNPKG

webgme

Version:

Web-based Generic Modeling Environment

1,647 lines (1,445 loc) 378 kB
/** * @summary DataTables * @description Paginate, search and sort HTML tables * @version 1.9.4 * @file jquery.dataTables.js * @author Allan Jardine (www.sprymedia.co.uk) * @contact www.sprymedia.co.uk/contact * * @copyright Copyright 2008-2012 Allan Jardine, all rights reserved. * * This source file is free software, under either the GPL v2 license or a * BSD style license, available at: * http://datatables.net/license_gpl2 * http://datatables.net/license_bsd * * This source file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. * * For details please refer to: http://www.datatables.net */ /*jslint evil: true, undef: true, browser: true */ /*globals $, jQuery,define,_fnExternApiFunc,_fnInitialise,_fnInitComplete,_fnLanguageCompat,_fnAddColumn,_fnColumnOptions,_fnAddData,_fnCreateTr,_fnGatherData,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnServerParams,_fnAddOptionsHtml,_fnFeatureHtmlTable,_fnScrollDraw,_fnAdjustColumnSizing,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnBuildSearchArray,_fnBuildSearchRow,_fnFilterCreateSearch,_fnDataToSearch,_fnSort,_fnSortAttachListener,_fnSortingClasses,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnFeatureHtmlLength,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnNodeToDataIndex,_fnVisbleColumns,_fnCalculateEnd,_fnConvertToWidth,_fnCalculateColumnWidths,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnDetectType,_fnSettingsFromNode,_fnGetDataMaster,_fnGetTrNodes,_fnGetTdNodes,_fnEscapeRegex,_fnDeleteIndex,_fnReOrderIndex,_fnColumnOrdering,_fnLog,_fnClearTable,_fnSaveState,_fnLoadState,_fnCreateCookie,_fnReadCookie,_fnDetectHeader,_fnGetUniqueThs,_fnScrollBarWidth,_fnApplyToChildren,_fnMap,_fnGetRowData,_fnGetCellData,_fnSetCellData,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnApplyColumnDefs,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnJsonString,_fnRender,_fnNodeToColumnIndex,_fnInfoMacros,_fnBrowserDetect,_fnGetColumns*/ (/** @lends <global> */function( window, document, undefined ) { (function( factory ) { "use strict"; // Define as an AMD module if possible if ( typeof define === 'function' && define.amd ) { define( ['jquery'], factory ); } /* Define using browser globals otherwise * Prevent multiple instantiations if the script is loaded twice */ else if ( jQuery && !jQuery.fn.dataTable ) { factory( jQuery ); } } (/** @lends <global> */function( $ ) { "use strict"; /** * DataTables is a plug-in for the jQuery Javascript library. It is a * highly flexible tool, based upon the foundations of progressive * enhancement, which will add advanced interaction controls to any * HTML table. For a full list of features please refer to * <a href="http://datatables.net">DataTables.net</a>. * * Note that the <i>DataTable</i> object is not a global variable but is * aliased to <i>jQuery.fn.DataTable</i> and <i>jQuery.fn.dataTable</i> through which * it may be accessed. * * @class * @param {object} [oInit={}] Configuration object for DataTables. Options * are defined by {@link DataTable.defaults} * @requires jQuery 1.3+ * * @example * // Basic initialisation * $(document).ready( function { * $('#example').dataTable(); * } ); * * @example * // Initialisation with configuration options - in this case, disable * // pagination and sorting. * $(document).ready( function { * $('#example').dataTable( { * "bPaginate": false, * "bSort": false * } ); * } ); */ var DataTable = function( oInit ) { /** * Add a column to the list used for the table with default values * @param {object} oSettings dataTables settings object * @param {node} nTh The th element for this column * @memberof DataTable#oApi */ function _fnAddColumn( oSettings, nTh ) { var oDefaults = DataTable.defaults.columns; var iCol = oSettings.aoColumns.length; var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { "sSortingClass": oSettings.oClasses.sSortable, "sSortingClassJUI": oSettings.oClasses.sSortJUI, "nTh": nTh ? nTh : document.createElement('th'), "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], "mData": oDefaults.mData ? oDefaults.oDefaults : iCol } ); oSettings.aoColumns.push( oCol ); /* Add a column specific filter */ if ( oSettings.aoPreSearchCols[ iCol ] === undefined || oSettings.aoPreSearchCols[ iCol ] === null ) { oSettings.aoPreSearchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch ); } else { var oPre = oSettings.aoPreSearchCols[ iCol ]; /* Don't require that the user must specify bRegex, bSmart or bCaseInsensitive */ if ( oPre.bRegex === undefined ) { oPre.bRegex = true; } if ( oPre.bSmart === undefined ) { oPre.bSmart = true; } if ( oPre.bCaseInsensitive === undefined ) { oPre.bCaseInsensitive = true; } } /* Use the column options function to initialise classes etc */ _fnColumnOptions( oSettings, iCol, null ); } /** * Apply options for a column * @param {object} oSettings dataTables settings object * @param {int} iCol column index to consider * @param {object} oOptions object with sType, bVisible and bSearchable etc * @memberof DataTable#oApi */ function _fnColumnOptions( oSettings, iCol, oOptions ) { var oCol = oSettings.aoColumns[ iCol ]; /* User specified column options */ if ( oOptions !== undefined && oOptions !== null ) { /* Backwards compatibility for mDataProp */ if ( oOptions.mDataProp && !oOptions.mData ) { oOptions.mData = oOptions.mDataProp; } if ( oOptions.sType !== undefined ) { oCol.sType = oOptions.sType; oCol._bAutoType = false; } $.extend( oCol, oOptions ); _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); /* iDataSort to be applied (backwards compatibility), but aDataSort will take * priority if defined */ if ( oOptions.iDataSort !== undefined ) { oCol.aDataSort = [ oOptions.iDataSort ]; } _fnMap( oCol, oOptions, "aDataSort" ); } /* Cache the data get and set functions for speed */ var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; var mData = _fnGetObjectDataFn( oCol.mData ); oCol.fnGetData = function (oData, sSpecific) { var innerData = mData( oData, sSpecific ); if ( oCol.mRender && (sSpecific && sSpecific !== '') ) { return mRender( innerData, sSpecific, oData ); } return innerData; }; oCol.fnSetData = _fnSetObjectDataFn( oCol.mData ); /* Feature sorting overrides column specific when off */ if ( !oSettings.oFeatures.bSort ) { oCol.bSortable = false; } /* Check that the class assignment is correct for sorting */ if ( !oCol.bSortable || ($.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1) ) { oCol.sSortingClass = oSettings.oClasses.sSortableNone; oCol.sSortingClassJUI = ""; } else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1 ) { oCol.sSortingClass = oSettings.oClasses.sSortable; oCol.sSortingClassJUI = oSettings.oClasses.sSortJUI; } else if ( $.inArray('asc', oCol.asSorting) != -1 && $.inArray('desc', oCol.asSorting) == -1 ) { oCol.sSortingClass = oSettings.oClasses.sSortableAsc; oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIAscAllowed; } else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) != -1 ) { oCol.sSortingClass = oSettings.oClasses.sSortableDesc; oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIDescAllowed; } } /** * Adjust the table column widths for new data. Note: you would probably want to * do a redraw after calling this function! * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnAdjustColumnSizing ( oSettings ) { /* Not interested in doing column width calculation if auto-width is disabled */ if ( oSettings.oFeatures.bAutoWidth === false ) { return false; } _fnCalculateColumnWidths( oSettings ); for ( var i=0 , iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { oSettings.aoColumns[i].nTh.style.width = oSettings.aoColumns[i].sWidth; } } /** * Covert the index of a visible column to the index in the data array (take account * of hidden columns) * @param {object} oSettings dataTables settings object * @param {int} iMatch Visible column index to lookup * @returns {int} i the data index * @memberof DataTable#oApi */ function _fnVisibleToColumnIndex( oSettings, iMatch ) { var aiVis = _fnGetColumns( oSettings, 'bVisible' ); return typeof aiVis[iMatch] === 'number' ? aiVis[iMatch] : null; } /** * Covert the index of an index in the data array and convert it to the visible * column index (take account of hidden columns) * @param {int} iMatch Column index to lookup * @param {object} oSettings dataTables settings object * @returns {int} i the data index * @memberof DataTable#oApi */ function _fnColumnIndexToVisible( oSettings, iMatch ) { var aiVis = _fnGetColumns( oSettings, 'bVisible' ); var iPos = $.inArray( iMatch, aiVis ); return iPos !== -1 ? iPos : null; } /** * Get the number of visible columns * @param {object} oSettings dataTables settings object * @returns {int} i the number of visible columns * @memberof DataTable#oApi */ function _fnVisbleColumns( oSettings ) { return _fnGetColumns( oSettings, 'bVisible' ).length; } /** * Get an array of column indexes that match a given property * @param {object} oSettings dataTables settings object * @param {string} sParam Parameter in aoColumns to look for - typically * bVisible or bSearchable * @returns {array} Array of indexes with matched properties * @memberof DataTable#oApi */ function _fnGetColumns( oSettings, sParam ) { var a = []; $.map( oSettings.aoColumns, function(val, i) { if ( val[sParam] ) { a.push( i ); } } ); return a; } /** * Get the sort type based on an input string * @param {string} sData data we wish to know the type of * @returns {string} type (defaults to 'string' if no type can be detected) * @memberof DataTable#oApi */ function _fnDetectType( sData ) { var aTypes = DataTable.ext.aTypes; var iLen = aTypes.length; for ( var i=0 ; i<iLen ; i++ ) { var sType = aTypes[i]( sData ); if ( sType !== null ) { return sType; } } return 'string'; } /** * Figure out how to reorder a display list * @param {object} oSettings dataTables settings object * @returns array {int} aiReturn index list for reordering * @memberof DataTable#oApi */ function _fnReOrderIndex ( oSettings, sColumns ) { var aColumns = sColumns.split(','); var aiReturn = []; for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { for ( var j=0 ; j<iLen ; j++ ) { if ( oSettings.aoColumns[i].sName == aColumns[j] ) { aiReturn.push( j ); break; } } } return aiReturn; } /** * Get the column ordering that DataTables expects * @param {object} oSettings dataTables settings object * @returns {string} comma separated list of names * @memberof DataTable#oApi */ function _fnColumnOrdering ( oSettings ) { var sNames = ''; for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { sNames += oSettings.aoColumns[i].sName+','; } if ( sNames.length == iLen ) { return ""; } return sNames.slice(0, -1); } /** * Take the column definitions and static columns arrays and calculate how * they relate to column indexes. The callback function will then apply the * definition found for a column to a suitable configuration object. * @param {object} oSettings dataTables settings object * @param {array} aoColDefs The aoColumnDefs array that is to be applied * @param {array} aoCols The aoColumns array that defines columns individually * @param {function} fn Callback function - takes two parameters, the calculated * column index and the definition for that column. * @memberof DataTable#oApi */ function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn ) { var i, iLen, j, jLen, k, kLen; // Column definitions with aTargets if ( aoColDefs ) { /* Loop over the definitions array - loop in reverse so first instance has priority */ for ( i=aoColDefs.length-1 ; i>=0 ; i-- ) { /* Each definition can target multiple columns, as it is an array */ var aTargets = aoColDefs[i].aTargets; if ( !$.isArray( aTargets ) ) { _fnLog( oSettings, 1, 'aTargets must be an array of targets, not a '+(typeof aTargets) ); } for ( j=0, jLen=aTargets.length ; j<jLen ; j++ ) { if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 ) { /* Add columns that we don't yet know about */ while( oSettings.aoColumns.length <= aTargets[j] ) { _fnAddColumn( oSettings ); } /* Integer, basic index */ fn( aTargets[j], aoColDefs[i] ); } else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 ) { /* Negative integer, right to left column counting */ fn( oSettings.aoColumns.length+aTargets[j], aoColDefs[i] ); } else if ( typeof aTargets[j] === 'string' ) { /* Class name matching on TH element */ for ( k=0, kLen=oSettings.aoColumns.length ; k<kLen ; k++ ) { if ( aTargets[j] == "_all" || $(oSettings.aoColumns[k].nTh).hasClass( aTargets[j] ) ) { fn( k, aoColDefs[i] ); } } } } } } // Statically defined columns array if ( aoCols ) { for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) { fn( i, aoCols[i] ); } } } /** * Add a data array to the table, creating DOM node etc. This is the parallel to * _fnGatherData, but for adding rows from a Javascript source, rather than a * DOM source. * @param {object} oSettings dataTables settings object * @param {array} aData data array to be added * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed * @memberof DataTable#oApi */ function _fnAddData ( oSettings, aDataSupplied ) { var oCol; /* Take an independent copy of the data source so we can bash it about as we wish */ var aDataIn = ($.isArray(aDataSupplied)) ? aDataSupplied.slice() : $.extend( true, {}, aDataSupplied ); /* Create the object for storing information about this new row */ var iRow = oSettings.aoData.length; var oData = $.extend( true, {}, DataTable.models.oRow ); oData._aData = aDataIn; oSettings.aoData.push( oData ); /* Create the cells */ var nTd, sThisType; for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { oCol = oSettings.aoColumns[i]; /* Use rendered data for filtering / sorting */ if ( typeof oCol.fnRender === 'function' && oCol.bUseRendered && oCol.mData !== null ) { _fnSetCellData( oSettings, iRow, i, _fnRender(oSettings, iRow, i) ); } else { _fnSetCellData( oSettings, iRow, i, _fnGetCellData( oSettings, iRow, i ) ); } /* See if we should auto-detect the column type */ if ( oCol._bAutoType && oCol.sType != 'string' ) { /* Attempt to auto detect the type - same as _fnGatherData() */ var sVarType = _fnGetCellData( oSettings, iRow, i, 'type' ); if ( sVarType !== null && sVarType !== '' ) { sThisType = _fnDetectType( sVarType ); if ( oCol.sType === null ) { oCol.sType = sThisType; } else if ( oCol.sType != sThisType && oCol.sType != "html" ) { /* String is always the 'fallback' option */ oCol.sType = 'string'; } } } } /* Add to the display array */ oSettings.aiDisplayMaster.push( iRow ); /* Create the DOM information */ if ( !oSettings.oFeatures.bDeferRender ) { _fnCreateTr( oSettings, iRow ); } return iRow; } /** * Read in the data from the target table from the DOM * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnGatherData( oSettings ) { var iLoop, i, iLen, j, jLen, jInner, nTds, nTrs, nTd, nTr, aLocalData, iThisIndex, iRow, iRows, iColumn, iColumns, sNodeName, oCol, oData; /* * Process by row first * Add the data object for the whole table - storing the tr node. Note - no point in getting * DOM based data if we are going to go and replace it with Ajax source data. */ if ( oSettings.bDeferLoading || oSettings.sAjaxSource === null ) { nTr = oSettings.nTBody.firstChild; while ( nTr ) { if ( nTr.nodeName.toUpperCase() == "TR" ) { iThisIndex = oSettings.aoData.length; nTr._DT_RowIndex = iThisIndex; oSettings.aoData.push( $.extend( true, {}, DataTable.models.oRow, { "nTr": nTr } ) ); oSettings.aiDisplayMaster.push( iThisIndex ); nTd = nTr.firstChild; jInner = 0; while ( nTd ) { sNodeName = nTd.nodeName.toUpperCase(); if ( sNodeName == "TD" || sNodeName == "TH" ) { _fnSetCellData( oSettings, iThisIndex, jInner, $.trim(nTd.innerHTML) ); jInner++; } nTd = nTd.nextSibling; } } nTr = nTr.nextSibling; } } /* Gather in the TD elements of the Table - note that this is basically the same as * fnGetTdNodes, but that function takes account of hidden columns, which we haven't yet * setup! */ nTrs = _fnGetTrNodes( oSettings ); nTds = []; for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) { nTd = nTrs[i].firstChild; while ( nTd ) { sNodeName = nTd.nodeName.toUpperCase(); if ( sNodeName == "TD" || sNodeName == "TH" ) { nTds.push( nTd ); } nTd = nTd.nextSibling; } } /* Now process by column */ for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ ) { oCol = oSettings.aoColumns[iColumn]; /* Get the title of the column - unless there is a user set one */ if ( oCol.sTitle === null ) { oCol.sTitle = oCol.nTh.innerHTML; } var bAutoType = oCol._bAutoType, bRender = typeof oCol.fnRender === 'function', bClass = oCol.sClass !== null, bVisible = oCol.bVisible, nCell, sThisType, sRendered, sValType; /* A single loop to rule them all (and be more efficient) */ if ( bAutoType || bRender || bClass || !bVisible ) { for ( iRow=0, iRows=oSettings.aoData.length ; iRow<iRows ; iRow++ ) { oData = oSettings.aoData[iRow]; nCell = nTds[ (iRow*iColumns) + iColumn ]; /* Type detection */ if ( bAutoType && oCol.sType != 'string' ) { sValType = _fnGetCellData( oSettings, iRow, iColumn, 'type' ); if ( sValType !== '' ) { sThisType = _fnDetectType( sValType ); if ( oCol.sType === null ) { oCol.sType = sThisType; } else if ( oCol.sType != sThisType && oCol.sType != "html" ) { /* String is always the 'fallback' option */ oCol.sType = 'string'; } } } if ( oCol.mRender ) { // mRender has been defined, so we need to get the value and set it nCell.innerHTML = _fnGetCellData( oSettings, iRow, iColumn, 'display' ); } else if ( oCol.mData !== iColumn ) { // If mData is not the same as the column number, then we need to // get the dev set value. If it is the column, no point in wasting // time setting the value that is already there! nCell.innerHTML = _fnGetCellData( oSettings, iRow, iColumn, 'display' ); } /* Rendering */ if ( bRender ) { sRendered = _fnRender( oSettings, iRow, iColumn ); nCell.innerHTML = sRendered; if ( oCol.bUseRendered ) { /* Use the rendered data for filtering / sorting */ _fnSetCellData( oSettings, iRow, iColumn, sRendered ); } } /* Classes */ if ( bClass ) { nCell.className += ' '+oCol.sClass; } /* Column visibility */ if ( !bVisible ) { oData._anHidden[iColumn] = nCell; nCell.parentNode.removeChild( nCell ); } else { oData._anHidden[iColumn] = null; } if ( oCol.fnCreatedCell ) { oCol.fnCreatedCell.call( oSettings.oInstance, nCell, _fnGetCellData( oSettings, iRow, iColumn, 'display' ), oData._aData, iRow, iColumn ); } } } } /* Row created callbacks */ if ( oSettings.aoRowCreatedCallback.length !== 0 ) { for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) { oData = oSettings.aoData[i]; _fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [oData.nTr, oData._aData, i] ); } } } /** * Take a TR element and convert it to an index in aoData * @param {object} oSettings dataTables settings object * @param {node} n the TR element to find * @returns {int} index if the node is found, null if not * @memberof DataTable#oApi */ function _fnNodeToDataIndex( oSettings, n ) { return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null; } /** * Take a TD element and convert it into a column data index (not the visible index) * @param {object} oSettings dataTables settings object * @param {int} iRow The row number the TD/TH can be found in * @param {node} n The TD/TH element to find * @returns {int} index if the node is found, -1 if not * @memberof DataTable#oApi */ function _fnNodeToColumnIndex( oSettings, iRow, n ) { var anCells = _fnGetTdNodes( oSettings, iRow ); for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { if ( anCells[i] === n ) { return i; } } return -1; } /** * Get an array of data for a given row from the internal data cache * @param {object} oSettings dataTables settings object * @param {int} iRow aoData row id * @param {string} sSpecific data get type ('type' 'filter' 'sort') * @param {array} aiColumns Array of column indexes to get data from * @returns {array} Data array * @memberof DataTable#oApi */ function _fnGetRowData( oSettings, iRow, sSpecific, aiColumns ) { var out = []; for ( var i=0, iLen=aiColumns.length ; i<iLen ; i++ ) { out.push( _fnGetCellData( oSettings, iRow, aiColumns[i], sSpecific ) ); } return out; } /** * 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') * @returns {*} Cell data * @memberof DataTable#oApi */ function _fnGetCellData( oSettings, iRow, iCol, sSpecific ) { var sData; var oCol = oSettings.aoColumns[iCol]; var oData = oSettings.aoData[iRow]._aData; if ( (sData=oCol.fnGetData( oData, sSpecific )) === undefined ) { if ( oSettings.iDrawError != oSettings.iDraw && oCol.sDefaultContent === null ) { _fnLog( oSettings, 0, "Requested unknown parameter "+ (typeof oCol.mData=='function' ? '{mData function}' : "'"+oCol.mData+"'")+ " from the data source for row "+iRow ); oSettings.iDrawError = oSettings.iDraw; } return oCol.sDefaultContent; } /* When the data source is null, we can use default column data */ if ( sData === null && oCol.sDefaultContent !== null ) { sData = oCol.sDefaultContent; } else if ( typeof sData === 'function' ) { /* If the data source is a function, then we run it and use the return */ return sData(); } if ( sSpecific == 'display' && sData === null ) { return ''; } return sData; } /** * 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 {*} val Value to set * @memberof DataTable#oApi */ function _fnSetCellData( oSettings, iRow, iCol, val ) { var oCol = oSettings.aoColumns[iCol]; var oData = oSettings.aoData[iRow]._aData; oCol.fnSetData( oData, val ); } // Private variable that is used to match array syntax in the data property object var __reArray = /\[.*?\]$/; /** * Return a function that can be used to get data from a source object, taking * into account the ability to use nested objects as a source * @param {string|int|function} mSource The data source for the object * @returns {function} Data get function * @memberof DataTable#oApi */ function _fnGetObjectDataFn( mSource ) { if ( mSource === null ) { /* Give an empty string for rendering / sorting etc */ return function (data, type) { return null; }; } else if ( typeof mSource === 'function' ) { return function (data, type, extra) { return mSource( data, type, extra ); }; } else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1) ) { /* If there is a . in the source string then the data source is in a * nested object so we loop over the data for each level to get the next * level down. On each loop we test for undefined, and if found immediately * return. This allows entire objects to be missing and sDefaultContent to * be used if defined, rather than throwing an error */ var fetchData = function (data, type, src) { var a = src.split('.'); var arrayNotation, out, innerSrc; if ( src !== "" ) { for ( var i=0, iLen=a.length ; i<iLen ; i++ ) { // Check if we are dealing with an array notation request arrayNotation = a[i].match(__reArray); if ( arrayNotation ) { a[i] = a[i].replace(__reArray, ''); // Condition allows simply [] to be passed in if ( a[i] !== "" ) { data = data[ a[i] ]; } out = []; // Get the remainder of the nested object to get a.splice( 0, i+1 ); innerSrc = a.join('.'); // Traverse each entry in the array getting the properties requested for ( var j=0, jLen=data.length ; j<jLen ; j++ ) { out.push( fetchData( data[j], type, innerSrc ) ); } // If a string is given in between the array notation indicators, that // is used to join the strings together, otherwise an array is returned var join = arrayNotation[0].substring(1, arrayNotation[0].length-1); data = (join==="") ? out : out.join(join); // The inner call to fetchData has already traversed through the remainder // of the source requested, so we exit from the loop break; } if ( data === null || data[ a[i] ] === undefined ) { return undefined; } data = data[ a[i] ]; } } return data; }; return function (data, type) { return fetchData( data, type, mSource ); }; } else { /* Array or flat object mapping */ return function (data, type) { return data[mSource]; }; } } /** * Return a function that can be used to set data from a source object, taking * into account the ability to use nested objects as a source * @param {string|int|function} mSource The data source for the object * @returns {function} Data set function * @memberof DataTable#oApi */ function _fnSetObjectDataFn( mSource ) { if ( mSource === null ) { /* Nothing to do when the data source is null */ return function (data, val) {}; } else if ( typeof mSource === 'function' ) { return function (data, val) { mSource( data, 'set', val ); }; } else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1) ) { /* Like the get, we need to get data from a nested object */ var setData = function (data, val, src) { var a = src.split('.'), b; var arrayNotation, o, innerSrc; for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ ) { // Check if we are dealing with an array notation request arrayNotation = a[i].match(__reArray); if ( arrayNotation ) { a[i] = a[i].replace(__reArray, ''); data[ a[i] ] = []; // Get the remainder of the nested object to set so we can recurse b = a.slice(); b.splice( 0, i+1 ); innerSrc = b.join('.'); // Traverse each entry in the array setting the properties requested for ( var j=0, jLen=val.length ; j<jLen ; j++ ) { o = {}; setData( o, val[j], innerSrc ); data[ a[i] ].push( o ); } // The inner call to setData has already traversed through the remainder // of the source and has set the data, thus we can exit here return; } // If the nested object doesn't currently exist - since we are // trying to set the value - create it if ( data[ a[i] ] === null || data[ a[i] ] === undefined ) { data[ a[i] ] = {}; } data = data[ a[i] ]; } // If array notation is used, we just want to strip it and use the property name // and assign the value. If it isn't used, then we get the result we want anyway data[ a[a.length-1].replace(__reArray, '') ] = val; }; return function (data, val) { return setData( data, val, mSource ); }; } else { /* Array or flat object mapping */ return function (data, val) { data[mSource] = val; }; } } /** * Return an array with the full table data * @param {object} oSettings dataTables settings object * @returns array {array} aData Master data array * @memberof DataTable#oApi */ function _fnGetDataMaster ( oSettings ) { var aData = []; var iLen = oSettings.aoData.length; for ( var i=0 ; i<iLen; i++ ) { aData.push( oSettings.aoData[i]._aData ); } return aData; } /** * Nuke the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnClearTable( oSettings ) { oSettings.aoData.splice( 0, oSettings.aoData.length ); oSettings.aiDisplayMaster.splice( 0, oSettings.aiDisplayMaster.length ); oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length ); _fnCalculateEnd( oSettings ); } /** * Take an array of integers (index array) and remove a target integer (value - not * the key!) * @param {array} a Index array to target * @param {int} iTarget value to find * @memberof DataTable#oApi */ function _fnDeleteIndex( a, iTarget ) { var iTargetIndex = -1; for ( var i=0, iLen=a.length ; i<iLen ; i++ ) { if ( a[i] == iTarget ) { iTargetIndex = i; } else if ( a[i] > iTarget ) { a[i]--; } } if ( iTargetIndex != -1 ) { a.splice( iTargetIndex, 1 ); } } /** * Call the developer defined fnRender function for a given cell (row/column) with * the required parameters and return the result. * @param {object} oSettings dataTables settings object * @param {int} iRow aoData index for the row * @param {int} iCol aoColumns index for the column * @returns {*} Return of the developer's fnRender function * @memberof DataTable#oApi */ function _fnRender( oSettings, iRow, iCol ) { var oCol = oSettings.aoColumns[iCol]; return oCol.fnRender( { "iDataRow": iRow, "iDataColumn": iCol, "oSettings": oSettings, "aData": oSettings.aoData[iRow]._aData, "mDataProp": oCol.mData }, _fnGetCellData(oSettings, iRow, iCol, 'display') ); } /** * Create a new TR element (and it's TD children) for a row * @param {object} oSettings dataTables settings object * @param {int} iRow Row to consider * @memberof DataTable#oApi */ function _fnCreateTr ( oSettings, iRow ) { var oData = oSettings.aoData[iRow]; var nTd; if ( oData.nTr === null ) { oData.nTr = document.createElement('tr'); /* Use a private property on the node to allow reserve mapping from the node * to the aoData array for fast look up */ oData.nTr._DT_RowIndex = iRow; /* Special parameters can be given by the data source to be used on the row */ if ( oData._aData.DT_RowId ) { oData.nTr.id = oData._aData.DT_RowId; } if ( oData._aData.DT_RowClass ) { oData.nTr.className = oData._aData.DT_RowClass; } /* Process each column */ for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { var oCol = oSettings.aoColumns[i]; nTd = document.createElement( oCol.sCellType ); /* Render if needed - if bUseRendered is true then we already have the rendered * value in the data source - so can just use that */ nTd.innerHTML = (typeof oCol.fnRender === 'function' && (!oCol.bUseRendered || oCol.mData === null)) ? _fnRender( oSettings, iRow, i ) : _fnGetCellData( oSettings, iRow, i, 'display' ); /* Add user defined class */ if ( oCol.sClass !== null ) { nTd.className = oCol.sClass; } if ( oCol.bVisible ) { oData.nTr.appendChild( nTd ); oData._anHidden[i] = null; } else { oData._anHidden[i] = nTd; } if ( oCol.fnCreatedCell ) { oCol.fnCreatedCell.call( oSettings.oInstance, nTd, _fnGetCellData( oSettings, iRow, i, 'display' ), oData._aData, iRow, i ); } } _fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [oData.nTr, oData._aData, iRow] ); } } /** * Create the HTML header for the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnBuildHead( oSettings ) { var i, nTh, iLen, j, jLen; var iThs = $('th, td', oSettings.nTHead).length; var iCorrector = 0; var jqChildren; /* If there is a header in place - then use it - otherwise it's going to get nuked... */ if ( iThs !== 0 ) { /* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { nTh = oSettings.aoColumns[i].nTh; nTh.setAttribute('role', 'columnheader'); if ( oSettings.aoColumns[i].bSortable ) { nTh.setAttribute('tabindex', oSettings.iTabIndex); nTh.setAttribute('aria-controls', oSettings.sTableId); } if ( oSettings.aoColumns[i].sClass !== null ) { $(nTh).addClass( oSettings.aoColumns[i].sClass ); } /* Set the title of the column if it is user defined (not what was auto detected) */ if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML ) { nTh.innerHTML = oSettings.aoColumns[i].sTitle; } } } else { /* We don't have a header in the DOM - so we are going to have to create one */ var nTr = document.createElement( "tr" ); for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { nTh = oSettings.aoColumns[i].nTh; nTh.innerHTML = oSettings.aoColumns[i].sTitle; nTh.setAttribute('tabindex', '0'); if ( oSettings.aoColumns[i].sClass !== null ) { $(nTh).addClass( oSettings.aoColumns[i].sClass ); } nTr.appendChild( nTh ); } $(oSettings.nTHead).html( '' )[0].appendChild( nTr ); _fnDetectHeader( oSettings.aoHeader, oSettings.nTHead ); } /* ARIA role for the rows */ $(oSettings.nTHead).children('tr').attr('role', 'row'); /* Add the extra markup needed by jQuery UI's themes */ if ( oSettings.bJUI ) { for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { nTh = oSettings.aoColumns[i].nTh; var nDiv = document.createElement('div'); nDiv.className = oSettings.oClasses.sSortJUIWrapper; $(nTh).contents().appendTo(nDiv); var nSpan = document.createElement('span'); nSpan.className = oSettings.oClasses.sSortIcon; nDiv.appendChild( nSpan ); nTh.appendChild( nDiv ); } } if ( oSettings.oFeatures.bSort ) { for ( i=0 ; i<oSettings.aoColumns.length ; i++ ) { if ( oSettings.aoColumns[i].bSortable !== false ) { _fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i ); } else { $(oSettings.aoColumns[i].nTh).addClass( oSettings.oClasses.sSortableNone ); } } } /* Deal with the footer - add classes if required */ if ( oSettings.oClasses.sFooterTH !== "" ) { $(oSettings.nTFoot).children('tr').children('th').addClass( oSettings.oClasses.sFooterTH ); } /* Cache the footer elements */ if ( oSettings.nTFoot !== null ) { var anCells = _fnGetUniqueThs( oSettings, null, oSettings.aoFooter ); for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { if ( anCells[i] ) { oSettings.aoColumns[i].nTf = anCells[i]; if ( oSettings.aoColumns[i].sClass ) { $(anCells[i]).addClass( oSettings.aoColumns[i].sClass ); } } } } } /** * Draw the header (or footer) element based on the column visibility states. The * methodology here is to use the layout array from _fnDetectHeader, modified for * the instantaneous column visibility, to construct the new layout. The grid is * traversed over cell at a time in a rows x columns grid fashion, although each * cell insert can cover multiple elements in the grid - which is tracks using the * aApplied array. Cell inserts in the grid will only occur where there isn't * already a cell in that position. * @param {object} oSettings dataTables settings object * @param array {objects} aoSource Layout array from _fnDetectHeader * @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc, * @memberof DataTable#oApi */ function _fnDrawHead( oSettings, aoSource, bIncludeHidden ) { var i, iLen, j, jLen, k, kLen, n, nLocalTr; var aoLocal = []; var aApplied = []; var iColumns = oSettings.aoColumns.length; var iRowspan, iColspan; if ( bIncludeHidden === undefined ) { bIncludeHidden = false; } /* Make a copy of the master layout array, but without the visible columns in it */ for ( i=0, iLen=aoSource.length ; i<iLen ; i++ ) { aoLocal[i] = aoSource[i].slice(); aoLocal[i].nTr = aoSource[i].nTr; /* Remove any columns which are currently hidden */ for ( j=iColumns-1 ; j>=0 ; j-- ) { if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden ) { aoLocal[i].splice( j, 1 ); } } /* Prep the applied array - it needs an element for each row */ aApplied.push( [] ); } for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ ) { nLocalTr = aoLocal[i].nTr; /* All cells are going to be replaced, so empty out the row */ if ( nLocalTr ) { while( (n = nLocalTr.firstChild) ) { nLocalTr.removeChild( n ); } } for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ ) { iRowspan = 1; iColspan = 1; /* Check to see if there is already a cell (row/colspan) covering our target * insert point. If there is, then there is nothing to do. */ if ( aApplied[i][j] === undefined ) { nLocalTr.appendChild( aoLocal[i][j].cell ); aApplied[i][j] = 1; /* Expand the cell to cover as many rows as needed */ while ( aoLocal[i+iRowspan] !== undefined && aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell ) { aApplied[i+iRowspan][j] = 1; iRowspan++; } /* Expand the cell to cover as many columns as needed */ while ( aoLocal[i][j+iColspan] !== undefined && aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell ) { /* Must update the applied array over the rows for the columns */ for ( k=0 ; k<iRowspan ; k++ ) { aApplied[i+k][j+iColspan] = 1; } iColspan++; } /* Do the actual expansion in the DOM */ aoLocal[i][j].cell.rowSpan = iRowspan; aoLocal[i][j].cell.colSpan = iColspan; } } } } /** * Insert the required TR nodes into the table for display * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnDraw( oSettings ) { /* Provide a pre-callback function which can be used to cancel the draw is false is returned */ var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] ); if ( $.inArray( false, aPreDraw ) !== -1 ) { _fnProcessingDisplay( oSettings, false ); return; } var i, iLen, n; var anRows = []; var iRowCount = 0; var iStripes = oSettings.asStripeClasses.length; var iOpenRows = oSettings.aoOpenRows.length; oSettings.bDrawing = true; /* Check and see if we have an initial draw position from state saving */ if ( oSettings.iInitDisplayStart !== undefined && oSettings.iInitDisplayStart != -1 ) { if ( oSettings.oFeatures.bServerSide ) { oSettings._iDisplayStart = oSettings.iInitDisplayStart; } else { oSettings._iDisplayStart = (oSettings.iInitDisplayStart >= oSettings.fnRecordsDisplay()) ? 0 : oSettings.iInitDisplayStart; } oSettings.iInitDisplayStart = -1; _fnCalculateEnd( oSettings ); } /* Server-side processing draw intercept */ if ( oSettings.bDeferLoading ) { oSettings.bDeferLoading = false; oSettings.iDraw++; } else if ( !oSettings.oFeatures.bServerSide ) { oSettings.iDraw++; } else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) ) { return; } if ( oSettings.aiDisplay.length !== 0 ) { var iStart = oSettings._iDisplayStart; var iEnd = oSettings._iDisplayEnd; if ( oSettings.oFeatures.bServerSide ) { iStart = 0; iEnd = oSettings.aoData.length; } for ( var j=iStart ; j<iEnd ; j++ ) { var aoData = oSettings.aoData[ oSettings.aiDisplay[j] ]; if ( aoData.nTr === null ) { _fnCreateTr( oSettings, oSettings.aiDisplay[j] ); } var nRow = aoData.nTr; /* Remove the old striping classes and then add the new one */ if ( iStripes !== 0 ) { var sStripe = oSettings.asStripeClasses[ iRowCount % iStripes ]; if ( aoData._sRowStripe != sStripe ) { $(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe ); aoData._sRowStripe = sStripe; } } /* Row callback functions - might want to manipulate the row */ _fnCallbackFire( oSettings, 'aoRowCallback', null, [nRow, oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j] ); anRows.push( nRow ); iRowCount++; /* If there is an open row - and it is attached to this parent - attach it on redraw */ if ( iOpenRows !== 0 ) { for ( var k=0 ; k<iOpenRows ; k++ ) { if ( nRow == oSettings.aoOpenRows[k].nParent ) { anRows.push( oSettings.aoOpenRows[k].nTr ); break; } } } } } else { /* Table is empty - create a row with an empty message in it */ anRows[ 0 ] = document.createElement( 'tr' ); if ( oSettings.asStripeClasses[0] ) { anRows[ 0 ].className = oSettings.asStripeClasses[0]; } var oLang = oSettings.oLanguage; var sZero = oLang.sZeroRecords; if ( oSettings.iDraw == 1 && oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide ) { sZero = oLang.sLoadingRecords; } else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 ) { sZero = oLang.sEmptyTable; } var nTd = document.createElement( 'td' ); nTd.setAttribute( 'valign', "top" ); nTd.colSpan = _fnVisbleColumns( oSettings ); nTd.className = oSettings.oClasses.sRowEmpty; nTd.innerHTML = _fnInfoMacros( oSettings, sZero ); anRows[ iRowCount ].appendChild( nTd ); } /* Header and footer callbacks */ _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0], _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(), oSettings.aiDisplay ] ); _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0], _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(), oSettings.aiDisplay ] ); /* * Need to remove any old row from the display - note we can't just empty the tbody using * $().html('') since this will unbind the jQuery event handlers (even although the node * still exists!) - equally we can't use innerHTML, since IE throws an exception. */ var nAddFrag = document.createDocumentFragment(), nRemoveFrag = document.createDocumentFragment(), nBodyPar, nTrs; if ( oSettings.nTBody ) { nBodyPar = oSettings.nTBody.parentNode; nRemoveFrag.appendChild( oSettings.nTBody ); /* When doing infinite scrolling, only remove child rows when sorting, filtering or start * up. When not infinite scroll, always do it. */ if ( !oSettings.oScroll.bInfinite || !oSettings._bInitComplete || oSettings.bSorted || oSettings.bFiltered ) { while( (n = oSettings.nTBody.firstChild) ) { oSettings.nTBody.removeChild( n ); } } /* Put the draw table into the dom */ for ( i=0, iLen=anRows.length ; i<iLen ; i++ ) { nAddFrag.appendChild( anRows[i] ); } oSettings.nTBody.appendChild( nAddFrag ); if ( nBodyPar !== null ) { nBodyPar.appendChild( oSettings.nTBody ); } } /* Call all required callback functions for the end of a draw */ _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] ); /* Draw is complete, sorting and filtering must be as well */ oSettings.bSorted = false; oSettings.bFiltered = false; oSettings.bDrawing = false; if ( oSettings.oFeatures.bServerSide ) { _fnProcessingDisplay( oSettings, false ); if ( !oSettings._bInitComplete ) { _fnInitComplete( oSettings ); } } } /** * Redraw the table - taking account of the various features which are enabled * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnReDraw( oSettings ) { if ( oSettings.oFeatures.bSort ) { /* Sorting will refilter and draw for us */ _fnSort( oSettings, oSettings.oPreviousSearch ); } else if ( oSettings.oFeatures.bFilter ) { /* Filtering will redraw for us */ _fnFilterComplete( oSettings, oSettings.oPreviousSearch ); } else { _fnCalculateEnd( oSettings ); _fnDraw( oSettings ); } } /** * Add the options to the page HTML for the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnAddOptionsHtml ( oSettings ) { /* * Create a temporary, empty, div which we can later on replace with what we have generated * we do it this way to rendering the 'options' html offline - speed :-) */ var nHolding = $('<div></div>')[0]; oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable ); /* * All DataTables are wrapped in a div */ oSettings.nTableWrapper = $('<div id="'+oSettings.sTableId+'_wrapper" class="'+oSettings.oClasses.sWrapper+'" role="grid"></div>')[0]; oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling; /* Track where we want to insert the option */ var nInsertNode = oSettings.nTableWrapper; /* Loop over the user set positioning and place the elements as needed */ var aDom = oSettings.sDom.split(''); var nTmp, iPushFeature, cOption, nNewNode, cNext, sAttr, j; for ( var i=0 ; i<aDom.length ; i++ ) { iPushFeature = 0; cOption = aDom[i]; if ( cOption == '<' ) { /* New container div */ nNewNode = $('<div></div>')[0]; /* Check to see if we should append an id and/or a class name to the container */ cNext = aDom[i+1]; if ( cN