/* * This combined file was created by the DataTables downloader builder: * https://datatables.net/download * * To rebuild or modify this file with the latest versions of the included * software please visit: * https://datatables.net/download/#bs4/dt-1.10.16 * * Included libraries: * DataTables 1.10.16 */ /*! DataTables 1.10.16 * ©2008-2017 SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables * @version 1.10.16 * @file jquery.dataTables.js * @author SpryMedia Ltd * @contact www.datatables.net * @copyright Copyright 2008-2017 SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license * * 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 $,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,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ (function (factory) { "use strict"; if (typeof define === "function" && define.amd) { // AMD define(["jquery"], function ($) { return factory($, window, document); }); } else if (typeof exports === "object") { // CommonJS module.exports = function (root, $) { if (!root) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if (!$) { $ = typeof window !== "undefined" // jQuery's factory checks for a global window ? require("jquery") : require("jquery")(root); } return factory($, root, root.document); }; } else { // Browser factory(jQuery, window, document); } })(function ($, window, document, undefined) { "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 * [DataTables.net](href="http://datatables.net). * * Note that the `DataTable` object is not a global variable but is aliased * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may * be accessed. * * @class * @param {object} [init={}] Configuration object for DataTables. Options * are defined by {@link DataTable.defaults} * @requires jQuery 1.7+ * * @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( { * "paginate": false, * "sort": false * } ); * } ); */ var DataTable = function (options) { /** * Perform a jQuery selector action on the table's TR elements (from the tbody) and * return the resulting jQuery object. * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on * @param {object} [oOpts] Optional parameters for modifying the rows to be included * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter * criterion ("applied") or all TR elements (i.e. no filter). * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. * Can be either 'current', whereby the current sorting of the table is used, or * 'original' whereby the original order the data was read into the table is used. * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page * ("current") or not ("all"). If 'current' is given, then order is assumed to be * 'current' and filter is 'applied', regardless of what they might be given as. * @returns {object} jQuery object, filtered by the given selector. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Highlight every second row * oTable.$('tr:odd').css('backgroundColor', 'blue'); * } ); * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Filter to rows with 'Webkit' in them, add a background colour and then * // remove the filter, thus highlighting the 'Webkit' rows only. * oTable.fnFilter('Webkit'); * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); * oTable.fnFilter(''); * } ); */ this.$ = function (sSelector, oOpts) { return this.api(true).$(sSelector, oOpts); }; /** * Almost identical to $ in operation, but in this case returns the data for the matched * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes * rather than any descendants, so the data can be obtained for the row/cell. If matching * rows are found, the data returned is the original data array/object that was used to * create the row (or a generated array if from a DOM source). * * This method is often useful in-combination with $ where both functions are given the * same parameters and the array indexes will match identically. * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on * @param {object} [oOpts] Optional parameters for modifying the rows to be included * @param {string} [oOpts.filter=none] Select elements that meet the current filter * criterion ("applied") or all elements (i.e. no filter). * @param {string} [oOpts.order=current] Order of the data in the processed array. * Can be either 'current', whereby the current sorting of the table is used, or * 'original' whereby the original order the data was read into the table is used. * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page * ("current") or not ("all"). If 'current' is given, then order is assumed to be * 'current' and filter is 'applied', regardless of what they might be given as. * @returns {array} Data for the matched elements. If any elements, as a result of the * selector, were not TR, TD or TH elements in the DataTable, they will have a null * entry in the array. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Get the data from the first row in the table * var data = oTable._('tr:first'); * * // Do something useful with the data * alert( "First cell is: "+data[0] ); * } ); * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Filter to 'Webkit' and get all data for * oTable.fnFilter('Webkit'); * var data = oTable._('tr', {"search": "applied"}); * * // Do something with the data * alert( data.length+" rows matched the search" ); * } ); */ this._ = function (sSelector, oOpts) { return this.api(true).rows(sSelector, oOpts).data(); }; /** * Create a DataTables Api instance, with the currently selected tables for * the Api's context. * @param {boolean} [traditional=false] Set the API instance's context to be * only the table referred to by the `DataTable.ext.iApiIndex` option, as was * used in the API presented by DataTables 1.9- (i.e. the traditional mode), * or if all tables captured in the jQuery object should be used. * @return {DataTables.Api} */ this.api = function (traditional) { return traditional ? new _Api(_fnSettingsFromNode(this[_ext.iApiIndex])) : new _Api(this); }; /** * Add a single new row or multiple rows of data to the table. Please note * that this is suitable for client-side processing only - if you are using * server-side processing (i.e. "bServerSide": true), then to add data, you * must add it to the data source, i.e. the server-side, through an Ajax call. * @param {array|object} data The data to be added to the table. This can be: * * @param {bool} [redraw=true] redraw the table or not * @returns {array} An array of integers, representing the list of indexes in * aoData ({@link DataTable.models.oSettings}) that have been added to * the table. * @dtopt API * @deprecated Since v1.10 * * @example * // Global var for counter * var giCount = 2; * * $(document).ready(function() { * $('#example').dataTable(); * } ); * * function fnClickAddRow() { * $('#example').dataTable().fnAddData( [ * giCount+".1", * giCount+".2", * giCount+".3", * giCount+".4" ] * ); * * giCount++; * } */ this.fnAddData = function (data, redraw) { var api = this.api(true); /* Check if we want to add multiple rows or not */ var rows = $.isArray(data) && ($.isArray(data[0]) || $.isPlainObject(data[0])) ? api.rows.add(data) : api.row.add(data); if (redraw === undefined || redraw) { api.draw(); } return rows.flatten().toArray(); }; /** * This function will make DataTables recalculate the column sizes, based on the data * contained in the table and the sizes applied to the columns (in the DOM, CSS or * through the sWidth parameter). This can be useful when the width of the table's * parent element changes (for example a window resize). * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable( { * "sScrollY": "200px", * "bPaginate": false * } ); * * $(window).on('resize', function () { * oTable.fnAdjustColumnSizing(); * } ); * } ); */ this.fnAdjustColumnSizing = function (bRedraw) { var api = this.api(true).columns.adjust(); var settings = api.settings()[0]; var scroll = settings.oScroll; if (bRedraw === undefined || bRedraw) { api.draw(false); } else if (scroll.sX !== "" || scroll.sY !== "") { /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ _fnScrollDraw(settings); } }; /** * Quickly and simply clear a table * @param {bool} [bRedraw=true] redraw the table or not * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) * oTable.fnClearTable(); * } ); */ this.fnClearTable = function (bRedraw) { var api = this.api(true).clear(); if (bRedraw === undefined || bRedraw) { api.draw(); } }; /** * The exact opposite of 'opening' a row, this function will close any rows which * are currently 'open'. * @param {node} nTr the table row to 'close' * @returns {int} 0 on success, or 1 if failed (can't find the row) * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable; * * // 'open' an information row when a row is clicked on * $('#example tbody tr').click( function () { * if ( oTable.fnIsOpen(this) ) { * oTable.fnClose( this ); * } else { * oTable.fnOpen( this, "Temporary row opened", "info_row" ); * } * } ); * * oTable = $('#example').dataTable(); * } ); */ this.fnClose = function (nTr) { this.api(true).row(nTr).child.hide(); }; /** * Remove a row for the table * @param {mixed} target The index of the row from aoData to be deleted, or * the TR element you want to delete * @param {function|null} [callBack] Callback function * @param {bool} [redraw=true] Redraw the table or not * @returns {array} The row that was deleted * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Immediately remove the first row * oTable.fnDeleteRow( 0 ); * } ); */ this.fnDeleteRow = function (target, callback, redraw) { var api = this.api(true); var rows = api.rows(target); var settings = rows.settings()[0]; var data = settings.aoData[rows[0][0]]; rows.remove(); if (callback) { callback.call(this, settings, data); } if (redraw === undefined || redraw) { api.draw(); } return data; }; /** * Restore the table to it's original state in the DOM by removing all of DataTables * enhancements, alterations to the DOM structure of the table and event listeners. * @param {boolean} [remove=false] Completely remove the table from the DOM * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * // This example is fairly pointless in reality, but shows how fnDestroy can be used * var oTable = $('#example').dataTable(); * oTable.fnDestroy(); * } ); */ this.fnDestroy = function (remove) { this.api(true).destroy(remove); }; /** * Redraw the table * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) * oTable.fnDraw(); * } ); */ this.fnDraw = function (complete) { // Note that this isn't an exact match to the old call to _fnDraw - it takes // into account the new data, but can hold position. this.api(true).draw(complete); }; /** * Filter the input based on data * @param {string} sInput String to filter the table on * @param {int|null} [iColumn] Column to limit filtering to * @param {bool} [bRegex=false] Treat as regular expression or not * @param {bool} [bSmart=true] Perform smart filtering or not * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Sometime later - filter... * oTable.fnFilter( 'test string' ); * } ); */ this.fnFilter = function ( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive ) { var api = this.api(true); if (iColumn === null || iColumn === undefined) { api.search(sInput, bRegex, bSmart, bCaseInsensitive); } else { api.column(iColumn).search(sInput, bRegex, bSmart, bCaseInsensitive); } api.draw(); }; /** * Get the data for the whole table, an individual row or an individual cell based on the * provided parameters. * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as * a TR node then the data source for the whole row will be returned. If given as a * TD/TH cell node then iCol will be automatically calculated and the data for the * cell returned. If given as an integer, then this is treated as the aoData internal * data index for the row (see fnGetPosition) and the data for that row used. * @param {int} [col] Optional column index that you want the data of. * @returns {array|object|string} If mRow is undefined, then the data for all rows is * returned. If mRow is defined, just data for that row, and is iCol is * defined, only data for the designated cell is returned. * @dtopt API * @deprecated Since v1.10 * * @example * // Row data * $(document).ready(function() { * oTable = $('#example').dataTable(); * * oTable.$('tr').click( function () { * var data = oTable.fnGetData( this ); * // ... do something with the array / object of data for the row * } ); * } ); * * @example * // Individual cell data * $(document).ready(function() { * oTable = $('#example').dataTable(); * * oTable.$('td').click( function () { * var sData = oTable.fnGetData( this ); * alert( 'The cell clicked on had the value of '+sData ); * } ); * } ); */ this.fnGetData = function (src, col) { var api = this.api(true); if (src !== undefined) { var type = src.nodeName ? src.nodeName.toLowerCase() : ""; return col !== undefined || type == "td" || type == "th" ? api.cell(src, col).data() : api.row(src).data() || null; } return api.data().toArray(); }; /** * Get an array of the TR nodes that are used in the table's body. Note that you will * typically want to use the '$' API method in preference to this as it is more * flexible. * @param {int} [iRow] Optional row index for the TR element you want * @returns {array|node} If iRow is undefined, returns an array of all TR elements * in the table's body, or iRow is defined, just the TR element requested. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Get the nodes from the table * var nNodes = oTable.fnGetNodes( ); * } ); */ this.fnGetNodes = function (iRow) { var api = this.api(true); return iRow !== undefined ? api.row(iRow).node() : api.rows().nodes().flatten().toArray(); }; /** * Get the array indexes of a particular cell from it's DOM element * and column index including hidden columns * @param {node} node this can either be a TR, TD or TH in the table's body * @returns {int} If nNode is given as a TR, then a single index is returned, or * if given as a cell, an array of [row index, column index (visible), * column index (all)] is given. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * $('#example tbody td').click( function () { * // Get the position of the current data from the node * var aPos = oTable.fnGetPosition( this ); * * // Get the data array for this row * var aData = oTable.fnGetData( aPos[0] ); * * // Update the data array and return the value * aData[ aPos[1] ] = 'clicked'; * this.innerHTML = 'clicked'; * } ); * * // Init DataTables * oTable = $('#example').dataTable(); * } ); */ this.fnGetPosition = function (node) { var api = this.api(true); var nodeName = node.nodeName.toUpperCase(); if (nodeName == "TR") { return api.row(node).index(); } else if (nodeName == "TD" || nodeName == "TH") { var cell = api.cell(node).index(); return [cell.row, cell.columnVisible, cell.column]; } return null; }; /** * Check to see if a row is 'open' or not. * @param {node} nTr the table row to check * @returns {boolean} true if the row is currently open, false otherwise * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable; * * // 'open' an information row when a row is clicked on * $('#example tbody tr').click( function () { * if ( oTable.fnIsOpen(this) ) { * oTable.fnClose( this ); * } else { * oTable.fnOpen( this, "Temporary row opened", "info_row" ); * } * } ); * * oTable = $('#example').dataTable(); * } ); */ this.fnIsOpen = function (nTr) { return this.api(true).row(nTr).child.isShown(); }; /** * This function will place a new row directly after a row which is currently * on display on the page, with the HTML contents that is passed into the * function. This can be used, for example, to ask for confirmation that a * particular record should be deleted. * @param {node} nTr The table row to 'open' * @param {string|node|jQuery} mHtml The HTML to put into the row * @param {string} sClass Class to give the new TD cell * @returns {node} The row opened. Note that if the table row passed in as the * first parameter, is not found in the table, this method will silently * return. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable; * * // 'open' an information row when a row is clicked on * $('#example tbody tr').click( function () { * if ( oTable.fnIsOpen(this) ) { * oTable.fnClose( this ); * } else { * oTable.fnOpen( this, "Temporary row opened", "info_row" ); * } * } ); * * oTable = $('#example').dataTable(); * } ); */ this.fnOpen = function (nTr, mHtml, sClass) { return this.api(true).row(nTr).child(mHtml, sClass).show().child()[0]; }; /** * Change the pagination - provides the internal logic for pagination in a simple API * function. With this function you can have a DataTables table go to the next, * previous, first or last pages. * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" * or page number to jump to (integer), note that page 0 is the first page. * @param {bool} [bRedraw=true] Redraw the table or not * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * oTable.fnPageChange( 'next' ); * } ); */ this.fnPageChange = function (mAction, bRedraw) { var api = this.api(true).page(mAction); if (bRedraw === undefined || bRedraw) { api.draw(false); } }; /** * Show a particular column * @param {int} iCol The column whose display should be changed * @param {bool} bShow Show (true) or hide (false) the column * @param {bool} [bRedraw=true] Redraw the table or not * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Hide the second column after initialisation * oTable.fnSetColumnVis( 1, false ); * } ); */ this.fnSetColumnVis = function (iCol, bShow, bRedraw) { var api = this.api(true).column(iCol).visible(bShow); if (bRedraw === undefined || bRedraw) { api.columns.adjust().draw(); } }; /** * Get the settings for a particular table for external manipulation * @returns {object} DataTables settings object. See * {@link DataTable.models.oSettings} * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * var oSettings = oTable.fnSettings(); * * // Show an example parameter from the settings * alert( oSettings._iDisplayStart ); * } ); */ this.fnSettings = function () { return _fnSettingsFromNode(this[_ext.iApiIndex]); }; /** * Sort the table by a particular column * @param {int} iCol the data index to sort on. Note that this will not match the * 'display index' if you have hidden data entries * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Sort immediately with columns 0 and 1 * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); * } ); */ this.fnSort = function (aaSort) { this.api(true).order(aaSort).draw(); }; /** * Attach a sort listener to an element for a given column * @param {node} nNode the element to attach the sort listener to * @param {int} iColumn the column that a click on this node will sort on * @param {function} [fnCallback] callback function when sort is run * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Sort on column 1, when 'sorter' is clicked on * oTable.fnSortListener( document.getElementById('sorter'), 1 ); * } ); */ this.fnSortListener = function (nNode, iColumn, fnCallback) { this.api(true).order.listener(nNode, iColumn, fnCallback); }; /** * Update a table cell or row - this method will accept either a single value to * update the cell with, an array of values with one element for each column or * an object in the same format as the original data source. The function is * self-referencing in order to make the multi column updates easier. * @param {object|array|string} mData Data to update the cell/row with * @param {node|int} mRow TR element you want to update or the aoData index * @param {int} [iColumn] The column to update, give as null or undefined to * update a whole row. * @param {bool} [bRedraw=true] Redraw the table or not * @param {bool} [bAction=true] Perform pre-draw actions or not * @returns {int} 0 on success, 1 on error * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row * } ); */ this.fnUpdate = function (mData, mRow, iColumn, bRedraw, bAction) { var api = this.api(true); if (iColumn === undefined || iColumn === null) { api.row(mRow).data(mData); } else { api.cell(mRow, iColumn).data(mData); } if (bAction === undefined || bAction) { api.columns.adjust(); } if (bRedraw === undefined || bRedraw) { api.draw(); } return 0; }; /** * Provide a common method for plug-ins to check the version of DataTables being used, in order * to ensure compatibility. * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the * formats "X" and "X.Y" are also acceptable. * @returns {boolean} true if this version of DataTables is greater or equal to the required * version, or false if this version of DataTales is not suitable * @method * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * alert( oTable.fnVersionCheck( '1.9.0' ) ); * } ); */ this.fnVersionCheck = _ext.fnVersionCheck; var _that = this; var emptyInit = options === undefined; var len = this.length; if (emptyInit) { options = {}; } this.oApi = this.internal = _ext.internal; // Extend with old style plug-in API methods for (var fn in DataTable.ext.internal) { if (fn) { this[fn] = _fnExternApiFunc(fn); } } this.each(function () { // For each initialisation we want to give it a clean initialisation // object that can be bashed around var o = {}; var oInit = len > 1 // optimisation for single table case ? _fnExtend(o, options, true) : options; /*global oInit,_that,emptyInit*/ var i = 0, iLen, j, jLen, k, kLen; var sId = this.getAttribute("id"); var bInitHandedOff = false; var defaults = DataTable.defaults; var $this = $(this); /* Sanity check */ if (this.nodeName.toLowerCase() != "table") { _fnLog( null, 0, "Non-table node initialisation (" + this.nodeName + ")", 2 ); return; } /* Backwards compatibility for the defaults */ _fnCompatOpts(defaults); _fnCompatCols(defaults.column); /* Convert the camel-case defaults to Hungarian */ _fnCamelToHungarian(defaults, defaults, true); _fnCamelToHungarian(defaults.column, defaults.column, true); /* Setting up the initialisation object */ _fnCamelToHungarian(defaults, $.extend(oInit, $this.data())); /* Check to see if we are re-initialising a table */ var allSettings = DataTable.settings; for (i = 0, iLen = allSettings.length; i < iLen; i++) { var s = allSettings[i]; /* Base check on table node */ if ( s.nTable == this || s.nTHead.parentNode == this || (s.nTFoot && s.nTFoot.parentNode == this) ) { var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve; var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy; if (emptyInit || bRetrieve) { return s.oInstance; } else if (bDestroy) { s.oInstance.fnDestroy(); break; } else { _fnLog(s, 0, "Cannot reinitialise DataTable", 3); return; } } /* If the element we are initialising has the same ID as a table which was previously * initialised, but the table nodes don't match (from before) then we destroy the old * instance by simply deleting it. This is under the assumption that the table has been * destroyed by other methods. Anyone using non-id selectors will need to do this manually */ if (s.sTableId == this.id) { allSettings.splice(i, 1); break; } } /* Ensure the table has an ID - required for accessibility */ if (sId === null || sId === "") { sId = "DataTables_Table_" + DataTable.ext._unique++; this.id = sId; } /* Create the settings object for this table and set some of the default parameters */ var oSettings = $.extend(true, {}, DataTable.models.oSettings, { sDestroyWidth: $this[0].style.width, sInstance: sId, sTableId: sId, }); oSettings.nTable = this; oSettings.oApi = _that.internal; oSettings.oInit = oInit; allSettings.push(oSettings); // Need to add the instance after the instance after the settings object has been added // to the settings array, so we can self reference the table instance if more than one oSettings.oInstance = _that.length === 1 ? _that : $this.dataTable(); // Backwards compatibility, before we apply all the defaults _fnCompatOpts(oInit); if (oInit.oLanguage) { _fnLanguageCompat(oInit.oLanguage); } // If the length menu is given, but the init display length is not, use the length menu if (oInit.aLengthMenu && !oInit.iDisplayLength) { oInit.iDisplayLength = $.isArray(oInit.aLengthMenu[0]) ? oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0]; } // Apply the defaults and init options to make a single init object will all // options defined from defaults and instance options. oInit = _fnExtend($.extend(true, {}, defaults), oInit); // Map the initialisation options onto the settings object _fnMap(oSettings.oFeatures, oInit, [ "bPaginate", "bLengthChange", "bFilter", "bSort", "bSortMulti", "bInfo", "bProcessing", "bAutoWidth", "bSortClasses", "bServerSide", "bDeferRender", ]); _fnMap(oSettings, oInit, [ "asStripeClasses", "ajax", "fnServerData", "fnFormatNumber", "sServerMethod", "aaSorting", "aaSortingFixed", "aLengthMenu", "sPaginationType", "sAjaxSource", "sAjaxDataProp", "iStateDuration", "sDom", "bSortCellsTop", "iTabIndex", "fnStateLoadCallback", "fnStateSaveCallback", "renderer", "searchDelay", "rowId", ["iCookieDuration", "iStateDuration"], // backwards compat ["oSearch", "oPreviousSearch"], ["aoSearchCols", "aoPreSearchCols"], ["iDisplayLength", "_iDisplayLength"], ]); _fnMap(oSettings.oScroll, oInit, [ ["sScrollX", "sX"], ["sScrollXInner", "sXInner"], ["sScrollY", "sY"], ["bScrollCollapse", "bCollapse"], ]); _fnMap(oSettings.oLanguage, oInit, "fnInfoCallback"); /* Callback functions which are array driven */ _fnCallbackReg(oSettings, "aoDrawCallback", oInit.fnDrawCallback, "user"); _fnCallbackReg(oSettings, "aoServerParams", oInit.fnServerParams, "user"); _fnCallbackReg( oSettings, "aoStateSaveParams", oInit.fnStateSaveParams, "user" ); _fnCallbackReg( oSettings, "aoStateLoadParams", oInit.fnStateLoadParams, "user" ); _fnCallbackReg(oSettings, "aoStateLoaded", oInit.fnStateLoaded, "user"); _fnCallbackReg(oSettings, "aoRowCallback", oInit.fnRowCallback, "user"); _fnCallbackReg( oSettings, "aoRowCreatedCallback", oInit.fnCreatedRow, "user" ); _fnCallbackReg( oSettings, "aoHeaderCallback", oInit.fnHeaderCallback, "user" ); _fnCallbackReg( oSettings, "aoFooterCallback", oInit.fnFooterCallback, "user" ); _fnCallbackReg(oSettings, "aoInitComplete", oInit.fnInitComplete, "user"); _fnCallbackReg( oSettings, "aoPreDrawCallback", oInit.fnPreDrawCallback, "user" ); oSettings.rowIdFn = _fnGetObjectDataFn(oInit.rowId); /* Browser support detection */ _fnBrowserDetect(oSettings); var oClasses = oSettings.oClasses; $.extend(oClasses, DataTable.ext.classes, oInit.oClasses); $this.addClass(oClasses.sTable); if (oSettings.iInitDisplayStart === undefined) { /* Display start point, taking into account the save saving */ oSettings.iInitDisplayStart = oInit.iDisplayStart; oSettings._iDisplayStart = oInit.iDisplayStart; } if (oInit.iDeferLoading !== null) { oSettings.bDeferLoading = true; var tmp = $.isArray(oInit.iDeferLoading); oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading; oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading; } /* Language definitions */ 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 */ $.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; } /* * Stripes */ if (oInit.asStripeClasses === null) { oSettings.asStripeClasses = [oClasses.sStripeOdd, oClasses.sStripeEven]; } /* Remove row stripe classes if they are already on the table row */ var stripeClasses = oSettings.asStripeClasses; var rowOne = $this.children("tbody").find("tr").eq(0); if ( $.inArray( true, $.map(stripeClasses, function (el, i) { return rowOne.hasClass(el); }) ) !== -1 ) { $("tbody tr", this).removeClass(stripeClasses.join(" ")); oSettings.asDestroyStripes = stripeClasses.slice(); } /* * Columns * See if we should load columns automatically or use defined ones */ var anThs = []; var aoColumnsInit; var nThead = this.getElementsByTagName("thead"); if (nThead.length !== 0) { _fnDetectHeader(oSettings.aoHeader, nThead[0]); anThs = _fnGetUniqueThs(oSettings); } /* If not given a column array, generate one with nulls */ if (oInit.aoColumns === null) { aoColumnsInit = []; for (i = 0, iLen = anThs.length; i < iLen; i++) { aoColumnsInit.push(null); } } else { aoColumnsInit = oInit.aoColumns; } /* Add the columns */ for (i = 0, iLen = aoColumnsInit.length; i < iLen; i++) { _fnAddColumn(oSettings, anThs ? anThs[i] : null); } /* Apply the column definitions */ _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) { _fnColumnOptions(oSettings, iCol, oDef); } ); /* HTML5 attribute detection - build an mData object automatically if the * attributes are found */ if (rowOne.length) { var a = function (cell, name) { return cell.getAttribute("data-" + name) !== null ? name : null; }; $(rowOne[0]) .children("th, td") .each(function (i, cell) { var col = oSettings.aoColumns[i]; if (col.mData === i) { var sort = a(cell, "sort") || a(cell, "order"); var filter = a(cell, "filter") || a(cell, "search"); if (sort !== null || filter !== null) { col.mData = { _: i + ".display", sort: sort !== null ? i + ".@data-" + sort : undefined, type: sort !== null ? i + ".@data-" + sort : undefined, filter: filter !== null ? i + ".@data-" + filter : undefined, }; _fnColumnOptions(oSettings, i); } } }); } var features = oSettings.oFeatures; var loadedInit = function () { /* * Sorting * @todo For modularisation (1.11) this needs to do into a sort start up handler */ // If aaSorting is not defined, then we use the first indicator in asSorting // in case that has been altered, so the default sort reflects that option if (oInit.aaSorting === undefined) { var sorting = oSettings.aaSorting; for (i = 0, iLen = sorting.length; i < iLen; i++) { sorting[i][1] = oSettings.aoColumns[i].asSorting[0]; } } /* Do a first pass on the sorting classes (allows any size changes to be taken into * account, and also will apply sorting disabled classes if disabled */ _fnSortingClasses(oSettings); if (features.bSort) { _fnCallbackReg(oSettings, "aoDrawCallback", function () { if (oSettings.bSorted) { var aSort = _fnSortFlatten(oSettings); var sortedColumns = {}; $.each(aSort, function (i, val) { sortedColumns[val.src] = val.dir; }); _fnCallbackFire(oSettings, null, "order", [ oSettings, aSort, sortedColumns, ]); _fnSortAria(oSettings); } }); } _fnCallbackReg( oSettings, "aoDrawCallback", function () { if ( oSettings.bSorted || _fnDataSource(oSettings) === "ssp" || features.bDeferRender ) { _fnSortingClasses(oSettings); } }, "sc" ); /* * Final init * Cache the header, body and footer as required, creating them if needed */ // Work around for Webkit bug 83867 - store the caption-side before removing from doc var captions = $this.children("caption").each(function () { this._captionSide = $(this).css("caption-side"); }); var thead = $this.children("thead"); if (thead.length === 0) { thead = $("").appendTo($this); } oSettings.nTHead = thead[0]; var tbody = $this.children("tbody"); if (tbody.length === 0) { tbody = $("").appendTo($this); } oSettings.nTBody = tbody[0]; var tfoot = $this.children("tfoot"); if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) { // If we are a scrolling table, and no footer has been given, then we need to create // a tfoot element for the caption element to be appended to tfoot = $("").appendTo($this); } if (tfoot.length === 0 || tfoot.children().length === 0) { $this.addClass(oClasses.sNoFooter); } else if (tfoot.length > 0) { oSettings.nTFoot = tfoot[0]; _fnDetectHeader(oSettings.aoFooter, oSettings.nTFoot); } /* Check if there is data passing into the constructor */ if (oInit.aaData) { for (i = 0; i < oInit.aaData.length; i++) { _fnAddData(oSettings, oInit.aaData[i]); } } else if ( oSettings.bDeferLoading || _fnDataSource(oSettings) == "dom" ) { /* Grab the data from the page - only do this when deferred loading or no Ajax * source since there is no point in reading the DOM data if we are then going * to replace it with Ajax data */ _fnAddTr(oSettings, $(oSettings.nTBody).children("tr")); } /* Copy the data index array */ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); /* Initialisation complete - table can be drawn */ oSettings.bInitialised = true; /* Check if we need to initialise the table (it might not have been handed off to the * language processor) */ if (bInitHandedOff === false) { _fnInitialise(oSettings); } }; /* Must be done after everything which can be overridden by the state saving! */ if (oInit.bStateSave) { features.bStateSave = true; _fnCallbackReg(oSettings, "aoDrawCallback", _fnSaveState, "state_save"); _fnLoadState(oSettings, oInit, loadedInit); } else { loadedInit(); } }); _that = null; return this; }; /* * It is useful to have variables which are scoped locally so only the * DataTables functions can access them and they don't leak into global space. * At the same time these functions are often useful over multiple files in the * core and API, so we list, or at least document, all variables which are used * by DataTables as private variables here. This also ensures that there is no * clashing of variable names and that they can easily referenced for reuse. */ // Defined else where // _selector_run // _selector_opts // _selector_first // _selector_row_indexes var _ext; // DataTable.ext var _Api; // DataTable.Api var _api_register; // DataTable.Api.register var _api_registerPlural; // DataTable.Api.registerPlural var _re_dic = {}; var _re_new_lines = /[\r\n]/g; var _re_html = /<.*?>/g; // This is not strict ISO8601 - Date.parse() is quite lax, although // implementations differ between browsers. var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/; // Escape regular expression special characters var _re_escape_regex = new RegExp( "(\\" + [ "/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\", "$", "^", "-", ].join("|\\") + ")", "g" ); // http://en.wikipedia.org/wiki/Foreign_exchange_market // - \u20BD - Russian ruble. // - \u20a9 - South Korean Won // - \u20BA - Turkish Lira // - \u20B9 - Indian Rupee // - R - Brazil (R$) and South Africa // - fr - Swiss Franc // - kr - Swedish krona, Norwegian krone and Danish krone // - \u2009 is thin space and \u202F is narrow no-break space, both used in many // standards as thousands separators. var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi; var _empty = function (d) { return !d || d === true || d === "-" ? true : false; }; var _intVal = function (s) { var integer = parseInt(s, 10); return !isNaN(integer) && isFinite(s) ? integer : null; }; // Convert from a formatted number with characters other than `.` as the // decimal place, to a Javascript number 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" && decimalPoint !== "." ? num.replace(/\./g, "").replace(_re_dic[decimalPoint], ".") : num; }; var _isNumber = function (d, decimalPoint, formatted) { var strType = typeof d === "string"; // If empty return immediately so there must be a number if it is a // formatted string (this stops the string "k", or "kr", etc being detected // as a formatted number for currency if (_empty(d)) { return true; } if (decimalPoint && strType) { d = _numToDecimal(d, decimalPoint); } if (formatted && strType) { d = d.replace(_re_formatted_numeric, ""); } return !isNaN(parseFloat(d)) && isFinite(d); }; // A string without HTML in it can be considered to be HTML still var _isHtml = function (d) { return _empty(d) || typeof d === "string"; }; var _htmlNumeric = function (d, decimalPoint, formatted) { if (_empty(d)) { return true; } var html = _isHtml(d); return !html ? null : _isNumber(_stripHtml(d), decimalPoint, formatted) ? true : null; }; var _pluck = function (a, prop, prop2) { var out = []; var i = 0, ien = a.length; // Could have the test in the loop for slightly smaller code, but speed // is essential here if (prop2 !== undefined) { for (; i < ien; i++) { if (a[i] && a[i][prop]) { out.push(a[i][prop][prop2]); } } } else { for (; i < ien; i++) { if (a[i]) { out.push(a[i][prop]); } } } return out; }; // Basically the same as _pluck, but rather than looping over `a` we use `order` // as the indexes to pick from `a` var _pluck_order = function (a, order, prop, prop2) { var out = []; var i = 0, ien = order.length; // Could have the test in the loop for slightly smaller code, but speed // is essential here if (prop2 !== undefined) { for (; i < ien; i++) { if (a[order[i]][prop]) { out.push(a[order[i]][prop][prop2]); } } } else { for (; i < ien; i++) { out.push(a[order[i]][prop]); } } return out; }; var _range = function (len, start) { var out = []; var end; if (start === undefined) { start = 0; end = len; } else { end = start; start = len; } for (var i = start; i < end; i++) { out.push(i); } 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, ""); }; /** * Determine if all values in the array are unique. This means we can short * cut the _unique method at the cost of a single loop. A sorted array is used * to easily check the values. * * @param {array} src Source array * @return {boolean} true if all unique, false otherwise * @ignore */ var _areAllUnique = function (src) { if (src.length < 2) { return true; } var sorted = src.slice().sort(); var last = sorted[0]; for (var i = 1, ien = sorted.length; i < ien; i++) { if (sorted[i] === last) { return false; } last = sorted[i]; } return true; }; /** * Find the unique elements in a source array. * * @param {array} src Source array * @return {array} Array of unique items * @ignore */ var _unique = function (src) { if (_areAllUnique(src)) { return src.slice(); } // A faster unique method is to use object keys to identify used values, // but this doesn't work with arrays or objects, which we must also // consider. See jsperf.com/compare-array-unique-versions/4 for more // information. var out = [], val, i, ien = src.length, j, k = 0; again: for (i = 0; i < ien; i++) { val = src[i]; for (j = 0; j < k; j++) { if (out[j] === val) { continue again; } } out.push(val); k++; } return out; }; /** * 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: function (fn, freq) { var frequency = freq !== undefined ? freq : 200, last, timer; return function () { var that = this, now = +new Date(), args = arguments; if (last && now < last + frequency) { clearTimeout(timer); timer = setTimeout(function () { last = undefined; fn.apply(that, args); }, frequency); } else { last = now; fn.apply(that, args); } }; }, /** * Escape a string such that it can be used in a regular expression * * @param {string} val string to escape * @returns {string} escaped string */ escapeRegex: function (val) { return val.replace(_re_escape_regex, "\\$1"); }, }; /** * Create a mapping object that allows camel case parameters to be looked up * for their Hungarian counterparts. The mapping is stored in a private * parameter called `_hungarianMap` which can be accessed on the source object. * @param {object} o * @memberof DataTable#oApi */ function _fnHungarianMap(o) { var hungarian = "a aa ai ao as b fn i m o s ", match, newKey, map = {}; $.each(o, function (key, val) { match = key.match(/^([^A-Z]+?)([A-Z])/); if (match && hungarian.indexOf(match[1] + " ") !== -1) { newKey = key.replace(match[0], match[2].toLowerCase()); map[newKey] = key; if (match[1] === "o") { _fnHungarianMap(o[key]); } } }); o._hungarianMap = map; } /** * Convert from camel case parameters to Hungarian, based on a Hungarian map * created by _fnHungarianMap. * @param {object} src The model object which holds all parameters that can be * mapped. * @param {object} user The object to convert from camel case to Hungarian. * @param {boolean} force When set to `true`, properties which already have a * Hungarian value in the `user` object will be overwritten. Otherwise they * won't be. * @memberof DataTable#oApi */ function _fnCamelToHungarian(src, user, force) { if (!src._hungarianMap) { _fnHungarianMap(src); } var hungarianKey; $.each(user, function (key, val) { hungarianKey = src._hungarianMap[key]; if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) ) { // For objects, we need to buzz down into the object to copy parameters if (hungarianKey.charAt(0) === "o") { // Copy the camelCase options over to the hungarian if (!user[hungarianKey]) { user[hungarianKey] = {}; } $.extend(true, user[hungarianKey], user[key]); _fnCamelToHungarian(src[hungarianKey], user[hungarianKey], force); } else { user[hungarianKey] = user[key]; } } }); } /** * Language compatibility - when certain options are given, and others aren't, we * need to duplicate the values over, in order to provide backwards compatibility * with older language files. * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnLanguageCompat(lang) { var defaults = DataTable.defaults.oLanguage; var zeroRecords = lang.sZeroRecords; /* Backwards compatibility - if there is no sEmptyTable given, then use the same as * sZeroRecords - assuming that is given. */ if ( !lang.sEmptyTable && zeroRecords && defaults.sEmptyTable === "No data available in table" ) { _fnMap(lang, lang, "sZeroRecords", "sEmptyTable"); } /* Likewise with loading records */ if ( !lang.sLoadingRecords && zeroRecords && defaults.sLoadingRecords === "Loading..." ) { _fnMap(lang, lang, "sZeroRecords", "sLoadingRecords"); } // Old parameter name of the thousands separator mapped onto the new if (lang.sInfoThousands) { lang.sThousands = lang.sInfoThousands; } var decimal = lang.sDecimal; if (decimal) { _addNumericSort(decimal); } } /** * Map one parameter onto another * @param {object} o Object to map * @param {*} knew The new parameter name * @param {*} old The old parameter name */ var _fnCompatMap = function (o, knew, old) { if (o[knew] !== undefined) { o[old] = o[knew]; } }; /** * Provide backwards compatibility for the main DT options. Note that the new * options are mapped onto the old parameters, so this is an external interface * change only. * @param {object} init Object to map */ function _fnCompatOpts(init) { _fnCompatMap(init, "ordering", "bSort"); _fnCompatMap(init, "orderMulti", "bSortMulti"); _fnCompatMap(init, "orderClasses", "bSortClasses"); _fnCompatMap(init, "orderCellsTop", "bSortCellsTop"); _fnCompatMap(init, "order", "aaSorting"); _fnCompatMap(init, "orderFixed", "aaSortingFixed"); _fnCompatMap(init, "paging", "bPaginate"); _fnCompatMap(init, "pagingType", "sPaginationType"); _fnCompatMap(init, "pageLength", "iDisplayLength"); _fnCompatMap(init, "searching", "bFilter"); // Boolean initialisation of x-scrolling if (typeof init.sScrollX === "boolean") { init.sScrollX = init.sScrollX ? "100%" : ""; } if (typeof init.scrollX === "boolean") { init.scrollX = init.scrollX ? "100%" : ""; } // 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 * are mapped onto the old parameters, so this is an external interface change * only. * @param {object} init Object to map */ function _fnCompatCols(init) { _fnCompatMap(init, "orderable", "bSortable"); _fnCompatMap(init, "orderData", "aDataSort"); _fnCompatMap(init, "orderSequence", "asSorting"); _fnCompatMap(init, "orderDataType", "sortDataType"); // orderData can be given as an integer var dataSort = init.aDataSort; if (typeof dataSort === "number" && !$.isArray(dataSort)) { init.aDataSort = [dataSort]; } } /** * Browser feature detection for capabilities, quirks * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnBrowserDetect(settings) { // We don't need to do this every time DataTables is constructed, the values // calculated are specific to the browser and OS configuration which we // don't expect to change between initialisations if (!DataTable.__browser) { var browser = {}; DataTable.__browser = browser; // Scrolling feature / quirks detection var n = $("
") .css({ position: "fixed", top: 0, left: $(window).scrollLeft() * -1, // allow for scrolling height: 1, width: 1, overflow: "hidden", }) .append( $("
") .css({ position: "absolute", top: 1, left: 1, width: 100, overflow: "scroll", }) .append( $("
").css({ width: "100%", height: 10, }) ) ) .appendTo("body"); var outer = n.children(); var inner = outer.children(); // Numbers below, in order, are: // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth // // IE6 XP: 100 100 100 83 // IE7 Vista: 100 100 100 83 // IE 8+ Windows: 83 83 100 83 // Evergreen Windows: 83 83 100 83 // Evergreen Mac with scrollbars: 85 85 100 85 // Evergreen Mac without scrollbars: 100 100 100 100 // Get scrollbar width browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; // IE6/7 will oversize a width 100% element inside a scrolling element, to // include the width of the scrollbar, while other browsers ensure the inner // element is contained without forcing scrolling browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; // In rtl text layout, some browsers (most, but not all) will place the // scrollbar on the left, rather than the right. browser.bScrollbarLeft = Math.round(inner.offset().left) !== 1; // IE8- don't provide height and width for getBoundingClientRect browser.bBounding = n[0].getBoundingClientRect().width ? true : false; n.remove(); } $.extend(settings.oBrowser, DataTable.__browser); settings.oScroll.iBarWidth = DataTable.__browser.barWidth; } /** * Array.prototype reduce[Right] method, used for browsers which don't support * JS 1.6. Done this way to reduce code size, since we iterate either way * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnReduce(that, fn, init, start, end, inc) { var i = start, value, isSet = false; if (init !== undefined) { value = init; isSet = true; } while (i !== end) { if (!that.hasOwnProperty(i)) { continue; } value = isSet ? fn(value, that[i], i, that) : that[i]; isSet = true; i += inc; } return value; } /** * 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) { // Add column to aoColumns array var oDefaults = DataTable.defaults.column; var iCol = oSettings.aoColumns.length; var oCol = $.extend({}, DataTable.models.oColumn, oDefaults, { nTh: nTh ? nTh : document.createElement("th"), sTitle: oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : "", aDataSort: oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], mData: oDefaults.mData ? oDefaults.mData : iCol, idx: iCol, }); oSettings.aoColumns.push(oCol); // Add search object for column specific search. Note that the `searchCols[ iCol ]` // passed into extend can be undefined. This allows the user to give a default // with only some of the parameters defined, and also not give a default var searchCols = oSettings.aoPreSearchCols; searchCols[iCol] = $.extend({}, DataTable.models.oSearch, searchCols[iCol]); // Use the default column options function to initialise classes etc _fnColumnOptions(oSettings, iCol, $(nTh).data()); } /** * 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]; var oClasses = oSettings.oClasses; var th = $(oCol.nTh); // Try to get width information from the DOM. We can't get it from CSS // as we'd need to parse the CSS stylesheet. `width` option can override if (!oCol.sWidthOrig) { // Width attribute oCol.sWidthOrig = th.attr("width") || null; // Style attribute var t = (th.attr("style") || "").match(/width:\s*(\d+[pxem%]+)/); if (t) { oCol.sWidthOrig = t[1]; } } /* User specified column options */ if (oOptions !== undefined && oOptions !== null) { // Backwards compatibility _fnCompatCols(oOptions); // Map camel case parameters to their Hungarian counterparts _fnCamelToHungarian(DataTable.defaults.column, oOptions); /* Backwards compatibility for mDataProp */ if (oOptions.mDataProp !== undefined && !oOptions.mData) { oOptions.mData = oOptions.mDataProp; } if (oOptions.sType) { oCol._sManualType = oOptions.sType; } // `class` is a reserved word in Javascript, so we need to provide // the ability to use a valid name for the camel case input if (oOptions.className && !oOptions.sClass) { oOptions.sClass = oOptions.className; } if (oOptions.sClass) { th.addClass(oOptions.sClass); } $.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 mDataSrc = oCol.mData; var mData = _fnGetObjectDataFn(mDataSrc); var mRender = oCol.mRender ? _fnGetObjectDataFn(oCol.mRender) : null; var attrTest = function (src) { return typeof src === "string" && src.indexOf("@") !== -1; }; oCol._bAttrSrc = $.isPlainObject(mDataSrc) && (attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter)); oCol._setter = null; oCol.fnGetData = function (rowData, type, meta) { var innerData = mData(rowData, type, undefined, meta); return mRender && type ? mRender(innerData, type, rowData, meta) : innerData; }; 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 } /* Check that the class assignment is correct for sorting */ var bAsc = $.inArray("asc", oCol.asSorting) !== -1; var bDesc = $.inArray("desc", oCol.asSorting) !== -1; if (!oCol.bSortable || (!bAsc && !bDesc)) { oCol.sSortingClass = oClasses.sSortableNone; oCol.sSortingClassJUI = ""; } else if (bAsc && !bDesc) { oCol.sSortingClass = oClasses.sSortableAsc; oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; } else if (!bAsc && bDesc) { oCol.sSortingClass = oClasses.sSortableDesc; oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; } else { oCol.sSortingClass = oClasses.sSortable; oCol.sSortingClassJUI = oClasses.sSortJUI; } } /** * Adjust the table column widths for new data. Note: you would probably want to * do a redraw after calling this function! * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnAdjustColumnSizing(settings) { /* Not interested in doing column width calculation if auto-width is disabled */ if (settings.oFeatures.bAutoWidth !== false) { var columns = settings.aoColumns; _fnCalculateColumnWidths(settings); for (var i = 0, iLen = columns.length; i < iLen; i++) { columns[i].nTh.style.width = columns[i].sWidth; } } var scroll = settings.oScroll; if (scroll.sY !== "" || scroll.sX !== "") { _fnScrollDraw(settings); } _fnCallbackFire(settings, null, "column-sizing", [settings]); } /** * 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) { var vis = 0; // No reduce in IE8, use a loop for now $.each(oSettings.aoColumns, function (i, col) { if (col.bVisible && $(col.nTh).css("display") !== "none") { vis++; } }); return vis; } /** * 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; } /** * Calculate the 'type' of a column * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnColumnTypes(settings) { var columns = settings.aoColumns; var data = settings.aoData; var types = DataTable.ext.type.detect; var i, ien, j, jen, k, ken; var col, cell, detectedType, cache; // For each column, spin over the for (i = 0, ien = columns.length; i < ien; i++) { col = columns[i]; cache = []; if (!col.sType && col._sManualType) { col.sType = col._sManualType; } else if (!col.sType) { for (j = 0, jen = types.length; j < jen; j++) { for (k = 0, ken = data.length; k < ken; k++) { // Use a cache array so we only need to get the type data // from the formatter once (when using multiple detectors) if (cache[k] === undefined) { cache[k] = _fnGetCellData(settings, k, i, "type"); } detectedType = types[j](cache[k], settings); // 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) { col.sType = detectedType; break; } } // Fall back - if no type was detected, always use string if (!col.sType) { col.sType = "string"; } } } } /** * 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, def; var columns = oSettings.aoColumns; // 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--) { def = aoColDefs[i]; /* Each definition can target multiple columns, as it is an array */ var aTargets = def.targets !== undefined ? def.targets : def.aTargets; if (!$.isArray(aTargets)) { aTargets = [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 (columns.length <= aTargets[j]) { _fnAddColumn(oSettings); } /* Integer, basic index */ fn(aTargets[j], def); } else if (typeof aTargets[j] === "number" && aTargets[j] < 0) { /* Negative integer, right to left column counting */ fn(columns.length + aTargets[j], def); } else if (typeof aTargets[j] === "string") { /* Class name matching on TH element */ for (k = 0, kLen = columns.length; k < kLen; k++) { if ( aTargets[j] == "_all" || $(columns[k].nTh).hasClass(aTargets[j]) ) { fn(k, def); } } } } } } // 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 * @param {node} [nTr] TR element to add to the table - optional. If not given, * DataTables will create a row automatically * @param {array} [anTds] Array of TD|TH elements for the row - must be given * if nTr is. * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed * @memberof DataTable#oApi */ function _fnAddData(oSettings, aDataIn, nTr, anTds) { /* Create the object for storing information about this new row */ var iRow = oSettings.aoData.length; var oData = $.extend(true, {}, DataTable.models.oRow, { src: nTr ? "dom" : "data", idx: iRow, }); oData._aData = aDataIn; oSettings.aoData.push(oData); /* Create the cells */ var nTd, sThisType; var columns = oSettings.aoColumns; // Invalidate the column types as the new data needs to be revalidated for (var i = 0, iLen = columns.length; i < iLen; i++) { columns[i].sType = null; } /* Add to the display array */ oSettings.aiDisplayMaster.push(iRow); var id = oSettings.rowIdFn(aDataIn); if (id !== undefined) { oSettings.aIds[id] = oData; } /* Create the DOM information, or register it if already present */ if (nTr || !oSettings.oFeatures.bDeferRender) { _fnCreateTr(oSettings, iRow, nTr, anTds); } return iRow; } /** * Add one or more TR elements to the table. Generally we'd expect to * use this for reading data from a DOM sourced table, but it could be * used for an TR element. Note that if a TR is given, it is used (i.e. * it is not cloned). * @param {object} settings dataTables settings object * @param {array|node|jQuery} trs The TR element(s) to add to the table * @returns {array} Array of indexes for the added rows * @memberof DataTable#oApi */ function _fnAddTr(settings, trs) { var row; // Allow an individual node to be passed in if (!(trs instanceof $)) { trs = $(trs); } return trs.map(function (i, el) { row = _fnGetRowElements(settings, el); return _fnAddData(settings, row.data, el, row.cells); }); } /** * 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) { return $.inArray(n, oSettings.aoData[iRow].anCells); } /** * Get the data for a given cell from the internal cache, taking into account data mapping * @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(settings, rowIdx, colIdx, type) { 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 (cellData === undefined) { if (settings.iDrawError != draw && defaultContent === null) { _fnLog( settings, 0, "Requested unknown parameter " + (typeof col.mData == "function" ? "{function}" : "'" + col.mData + "'") + " for row " + rowIdx + ", column " + colIdx, 4 ); settings.iDrawError = draw; } return defaultContent; } // When the data source is null and a specific data type is requested (i.e. // not the original data), we can use default column data if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) { cellData = defaultContent; } 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 (cellData === null && type == "display") { return ""; } return cellData; } /** * Set the value for a specific cell, into the internal data cache * @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(settings, rowIdx, colIdx, val) { var col = settings.aoColumns[colIdx]; var rowData = settings.aoData[rowIdx]._aData; 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 = /\[.*?\]$/; var __reFn = /\(\)$/; /** * Split string on periods, taking into account escaped periods * @param {string} str String to split * @return {array} Split string */ function _fnSplitObjNotation(str) { return $.map(str.match(/(\\.|[^\.])+/g) || [""], function (s) { return s.replace(/\\\./g, "."); }); } /** * 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 ($.isPlainObject(mSource)) { /* Build an object of get functions, and wrap them in a single call */ var o = {}; $.each(mSource, function (key, val) { if (val) { o[key] = _fnGetObjectDataFn(val); } }); return function (data, type, row, meta) { var t = o[type] || o._; return t !== undefined ? t(data, type, row, meta) : data; }; } else if (mSource === null) { /* Give an empty string for rendering / sorting etc */ return function (data) { // type, row and meta also passed, but not used return data; }; } else if (typeof mSource === "function") { 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) ) { /* 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 arrayNotation, funcNotation, out, innerSrc; if (src !== "") { var a = _fnSplitObjNotation(src); for (var i = 0, iLen = a.length; i < iLen; i++) { // Check if we are dealing with special notation arrayNotation = a[i].match(__reArray); funcNotation = a[i].match(__reFn); if (arrayNotation) { // Array notation 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 if ($.isArray(data)) { 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; } else if (funcNotation) { // Function call a[i] = a[i].replace(__reFn, ""); data = data[a[i]](); continue; } if (data === null || data[a[i]] === undefined) { return undefined; } data = data[a[i]]; } } return data; }; 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) { // row and meta also passed, but not used 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 ($.isPlainObject(mSource)) { /* Unlike get, only the underscore (global) option is used for for * setting data since we don't know the type here. This is why an object * option is not documented for `mData` (which is read/write), but it is * for `mRender` which is read only. */ return _fnSetObjectDataFn(mSource._); } else if (mSource === null) { /* Nothing to do when the data source is null */ return function () {}; } else if (typeof mSource === "function") { return function (data, val, meta) { mSource(data, "set", val, meta); }; } else if ( typeof mSource === "string" && (mSource.indexOf(".") !== -1 || 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 = _fnSplitObjNotation(src), b; var aLast = a[a.length - 1]; var arrayNotation, funcNotation, 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); funcNotation = a[i].match(__reFn); 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 if ($.isArray(val)) { for (var j = 0, jLen = val.length; j < jLen; j++) { o = {}; setData(o, val[j], innerSrc); data[a[i]].push(o); } } else { // We've been asked to save data to an array, but it // isn't array data to be saved. Best that can be done // is to just save the value. data[a[i]] = val; } // 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; } else if (funcNotation) { // Function call a[i] = a[i].replace(__reFn, ""); data = data[a[i]](val); } // 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]]; } // Last item in the input - i.e, the actual set if (aLast.match(__reFn)) { // Function call data = data[aLast.replace(__reFn, "")](val); } else { // 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[aLast.replace(__reArray, "")] = 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) { // meta is also passed in, but not used 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(settings) { return _pluck(settings.aoData, "_aData"); } /** * Nuke the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnClearTable(settings) { settings.aoData.length = 0; settings.aiDisplayMaster.length = 0; settings.aiDisplay.length = 0; settings.aIds = {}; } /** * 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, splice) { 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 && splice === undefined) { a.splice(iTargetIndex, 1); } } /** * 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 {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 _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, colIdx, colIdx === undefined ? undefined : row._aData ).data; } else { // Reading from data object, update the DOM var cells = row.anCells; if (cells) { 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 (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(settings, 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, colIdx, d) { var tds = [], td = row.firstChild, name, col, o, i = 0, contents, columns = settings.aoColumns, objectRead = settings._rowReadObject; // Allow the data object to be passed in, or construct d = d !== undefined ? d : objectRead ? {} : []; var attr = function (str, td) { if (typeof str === "string") { var idx = str.indexOf("@"); if (idx !== -1) { 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) { if (colIdx === undefined || colIdx === i) { col = columns[i]; contents = $.trim(cell.innerHTML); if (col && col._bAttrSrc) { var setter = _fnSetObjectDataFn(col.mData._); setter(d, contents); 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; } } } i++; }; if (td) { // `tr` element was passed in while (td) { name = td.nodeName.toUpperCase(); if (name == "TD" || name == "TH") { cellProcess(td); tds.push(td); } td = td.nextSibling; } } else { // Existing row object passed in tds = row.anCells; for (var j = 0, jen = tds.length; j < jen; j++) { cellProcess(tds[j]); } } // Read the ID from the DOM if present var rowNode = row.firstChild ? row : row.nTr; if (rowNode) { var id = rowNode.getAttribute("id"); if (id) { _fnSetObjectDataFn(settings.rowId)(d, id); } } return { data: d, cells: tds, }; } /** * 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 * @param {node} [nTrIn] TR element to add to the table - optional. If not given, * DataTables will create a row automatically * @param {array} [anTds] Array of TD|TH elements for the row - must be given * if nTr is. * @memberof DataTable#oApi */ function _fnCreateTr(oSettings, iRow, nTrIn, anTds) { var row = oSettings.aoData[iRow], rowData = row._aData, cells = [], nTr, nTd, oCol, i, iLen; if (row.nTr === null) { nTr = nTrIn || document.createElement("tr"); row.nTr = nTr; row.anCells = cells; /* Use a private property on the node to allow reserve mapping from the node * to the aoData array for fast look up */ nTr._DT_RowIndex = iRow; /* Special parameters can be given by the data source to be used on the row */ _fnRowAttributes(oSettings, row); /* Process each column */ for (i = 0, iLen = oSettings.aoColumns.length; i < iLen; i++) { oCol = oSettings.aoColumns[i]; nTd = nTrIn ? anTds[i] : document.createElement(oCol.sCellType); nTd._DT_CellIndex = { row: iRow, column: i, }; cells.push(nTd); // Need to create the HTML if new, or if a rendering function is defined if ( (!nTrIn || oCol.mRender || oCol.mData !== i) && (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i + ".display") ) { nTd.innerHTML = _fnGetCellData(oSettings, iRow, i, "display"); } /* Add user defined class */ if (oCol.sClass) { nTd.className += " " + oCol.sClass; } // Visibility - add or remove as required if (oCol.bVisible && !nTrIn) { nTr.appendChild(nTd); } else if (!oCol.bVisible && nTrIn) { nTd.parentNode.removeChild(nTd); } if (oCol.fnCreatedCell) { oCol.fnCreatedCell.call( oSettings.oInstance, nTd, _fnGetCellData(oSettings, iRow, i), rowData, iRow, i ); } } _fnCallbackFire(oSettings, "aoRowCreatedCallback", null, [ nTr, rowData, iRow, ]); } // Remove once webkit bug 131819 and Chromium bug 365619 have been resolved // and deployed row.nTr.setAttribute("role", "row"); } /** * Add attributes to a row based on the special `DT_*` parameters in a data * source object. * @param {object} settings DataTables settings object * @param {object} DataTables row object for the row to be modified * @memberof DataTable#oApi */ function _fnRowAttributes(settings, row) { var tr = row.nTr; var data = row._aData; if (tr) { var id = settings.rowIdFn(data); if (id) { tr.id = id; } if (data.DT_RowClass) { // Remove any classes added by DT_RowClass before var a = data.DT_RowClass.split(" "); row.__rowc = row.__rowc ? _unique(row.__rowc.concat(a)) : a; $(tr).removeClass(row.__rowc.join(" ")).addClass(data.DT_RowClass); } if (data.DT_RowAttr) { $(tr).attr(data.DT_RowAttr); } if (data.DT_RowData) { $(tr).data(data.DT_RowData); } } } /** * Create the HTML header for the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnBuildHead(oSettings) { var i, ien, cell, row, column; var thead = oSettings.nTHead; var tfoot = oSettings.nTFoot; var createHeader = $("th, td", thead).length === 0; var classes = oSettings.oClasses; var columns = oSettings.aoColumns; if (createHeader) { row = $("").appendTo(thead); } for (i = 0, ien = columns.length; i < ien; i++) { column = columns[i]; cell = $(column.nTh).addClass(column.sClass); if (createHeader) { cell.appendTo(row); } // 1.11 move into sorting if (oSettings.oFeatures.bSort) { cell.addClass(column.sSortingClass); if (column.bSortable !== false) { cell .attr("tabindex", oSettings.iTabIndex) .attr("aria-controls", oSettings.sTableId); _fnSortAttachListener(oSettings, column.nTh, i); } } if (column.sTitle != cell[0].innerHTML) { cell.html(column.sTitle); } _fnRenderer(oSettings, "header")(oSettings, cell, column, classes); } if (createHeader) { _fnDetectHeader(oSettings.aoHeader, thead); } /* ARIA role for the rows */ $(thead).find(">tr").attr("role", "row"); /* Deal with the footer - add classes if required */ $(thead).find(">tr>th, >tr>td").addClass(classes.sHeaderTH); $(tfoot).find(">tr>th, >tr>td").addClass(classes.sFooterTH); // Cache the footer cells. Note that we only take the cells from the first // row in the footer. If there is more than one row the user wants to // interact with, they need to use the table().foot() method. Note also this // allows cells to be used for multiple columns using colspan if (tfoot !== null) { var cells = oSettings.aoFooter[0]; for (i = 0, ien = cells.length; i < ien; i++) { column = columns[i]; column.nTf = cells[i].cell; if (column.sClass) { $(column.nTf).addClass(column.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 (!aoSource) { return; } 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) .attr("rowspan", iRowspan) .attr("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 asStripeClasses = oSettings.asStripeClasses; var iStripes = asStripeClasses.length; var iOpenRows = oSettings.aoOpenRows.length; var oLang = oSettings.oLanguage; var iInitDisplayStart = oSettings.iInitDisplayStart; var bServerSide = _fnDataSource(oSettings) == "ssp"; var aiDisplay = oSettings.aiDisplay; oSettings.bDrawing = true; /* Check and see if we have an initial draw position from state saving */ if (iInitDisplayStart !== undefined && iInitDisplayStart !== -1) { oSettings._iDisplayStart = bServerSide ? iInitDisplayStart : iInitDisplayStart >= oSettings.fnRecordsDisplay() ? 0 : iInitDisplayStart; oSettings.iInitDisplayStart = -1; } var iDisplayStart = oSettings._iDisplayStart; var iDisplayEnd = oSettings.fnDisplayEnd(); /* Server-side processing draw intercept */ if (oSettings.bDeferLoading) { oSettings.bDeferLoading = false; oSettings.iDraw++; _fnProcessingDisplay(oSettings, false); } else if (!bServerSide) { oSettings.iDraw++; } else if (!oSettings.bDestroying && !_fnAjaxUpdate(oSettings)) { return; } if (aiDisplay.length !== 0) { var iStart = bServerSide ? 0 : iDisplayStart; var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd; for (var j = iStart; j < iEnd; j++) { var iDataIndex = aiDisplay[j]; var aoData = oSettings.aoData[iDataIndex]; if (aoData.nTr === null) { _fnCreateTr(oSettings, iDataIndex); } var nRow = aoData.nTr; /* Remove the old striping classes and then add the new one */ if (iStripes !== 0) { var sStripe = 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 // 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++; } } else { /* Table is empty - create a row with an empty message in it */ var sZero = oLang.sZeroRecords; if (oSettings.iDraw == 1 && _fnDataSource(oSettings) == "ajax") { sZero = oLang.sLoadingRecords; } else if (oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0) { sZero = oLang.sEmptyTable; } anRows[0] = $("", { class: iStripes ? asStripeClasses[0] : "", }).append( $("", { valign: "top", colSpan: _fnVisbleColumns(oSettings), class: oSettings.oClasses.sRowEmpty, }).html(sZero) )[0]; } /* Header and footer callbacks */ _fnCallbackFire(oSettings, "aoHeaderCallback", "header", [ $(oSettings.nTHead).children("tr")[0], _fnGetDataMaster(oSettings), iDisplayStart, iDisplayEnd, aiDisplay, ]); _fnCallbackFire(oSettings, "aoFooterCallback", "footer", [ $(oSettings.nTFoot).children("tr")[0], _fnGetDataMaster(oSettings), iDisplayStart, iDisplayEnd, aiDisplay, ]); var body = $(oSettings.nTBody); body.children().detach(); body.append($(anRows)); /* 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; } /** * Redraw the table - taking account of the various features which are enabled * @param {object} oSettings dataTables settings object * @param {boolean} [holdPosition] Keep the current paging position. By default * the paging is reset to the first page * @memberof DataTable#oApi */ function _fnReDraw(settings, holdPosition) { var features = settings.oFeatures, sort = features.bSort, filter = features.bFilter; if (sort) { _fnSort(settings); } if (filter) { _fnFilterComplete(settings, settings.oPreviousSearch); } else { // No filtering, so we want to just use the display master settings.aiDisplay = settings.aiDisplayMaster.slice(); } 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 * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnAddOptionsHtml(oSettings) { var classes = oSettings.oClasses; var table = $(oSettings.nTable); var holding = $("
").insertBefore(table); // Holding element for speed var features = oSettings.oFeatures; // All DataTables are wrapped in a div var insert = $("
", { id: oSettings.sTableId + "_wrapper", class: classes.sWrapper + (oSettings.nTFoot ? "" : " " + classes.sNoFooter), }); oSettings.nHolding = holding[0]; oSettings.nTableWrapper = insert[0]; oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling; /* Loop over the user set positioning and place the elements as needed */ var aDom = oSettings.sDom.split(""); var featureNode, cOption, nNewNode, cNext, sAttr, j; for (var i = 0; i < aDom.length; i++) { featureNode = null; cOption = aDom[i]; if (cOption == "<") { /* New container div */ nNewNode = $("
")[0]; /* Check to see if we should append an id and/or a class name to the container */ cNext = aDom[i + 1]; if (cNext == "'" || cNext == '"') { sAttr = ""; j = 2; while (aDom[i + j] != cNext) { sAttr += aDom[i + j]; j++; } /* Replace jQuery UI constants @todo depreciated */ if (sAttr == "H") { sAttr = classes.sJUIHeader; } else if (sAttr == "F") { sAttr = classes.sJUIFooter; } /* The attribute can be in the format of "#id.class", "#id" or "class" This logic * breaks the string into parts and applies them as needed */ if (sAttr.indexOf(".") != -1) { var aSplit = sAttr.split("."); nNewNode.id = aSplit[0].substr(1, aSplit[0].length - 1); nNewNode.className = aSplit[1]; } else if (sAttr.charAt(0) == "#") { nNewNode.id = sAttr.substr(1, sAttr.length - 1); } else { nNewNode.className = sAttr; } i += j; /* Move along the position array */ } insert.append(nNewNode); insert = $(nNewNode); } else if (cOption == ">") { /* End container div */ insert = insert.parent(); } // @todo Move options into their own plugins? else if (cOption == "l" && features.bPaginate && features.bLengthChange) { /* Length */ featureNode = _fnFeatureHtmlLength(oSettings); } else if (cOption == "f" && features.bFilter) { /* Filter */ featureNode = _fnFeatureHtmlFilter(oSettings); } else if (cOption == "r" && features.bProcessing) { /* pRocessing */ featureNode = _fnFeatureHtmlProcessing(oSettings); } else if (cOption == "t") { /* Table */ featureNode = _fnFeatureHtmlTable(oSettings); } else if (cOption == "i" && features.bInfo) { /* Info */ featureNode = _fnFeatureHtmlInfo(oSettings); } else if (cOption == "p" && features.bPaginate) { /* Pagination */ featureNode = _fnFeatureHtmlPaginate(oSettings); } else if (DataTable.ext.feature.length !== 0) { /* Plug-in features */ var aoFeatures = DataTable.ext.feature; for (var k = 0, kLen = aoFeatures.length; k < kLen; k++) { if (cOption == aoFeatures[k].cFeature) { featureNode = aoFeatures[k].fnInit(oSettings); break; } } } /* Add to the 2D features array */ if (featureNode) { var aanFeatures = oSettings.aanFeatures; if (!aanFeatures[cOption]) { aanFeatures[cOption] = []; } aanFeatures[cOption].push(featureNode); insert.append(featureNode); } } /* Built our DOM structure - replace the holding div with what we want */ holding.replaceWith(insert); oSettings.nHolding = null; } /** * Use the DOM source to create up an array of header cells. The idea here is to * create a layout grid (array) of rows x columns, which contains a reference * to the cell that that point in the grid (regardless of col/rowspan), such that * any column / row could be removed and the new grid constructed * @param array {object} aLayout Array to store the calculated layout in * @param {node} nThead The header/footer element for the table * @memberof DataTable#oApi */ function _fnDetectHeader(aLayout, nThead) { var nTrs = $(nThead).children("tr"); var nTr, nCell; var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan; var bUnique; var fnShiftCol = function (a, i, j) { var k = a[i]; while (k[j]) { j++; } return j; }; aLayout.splice(0, aLayout.length); /* We know how many rows there are in the layout - so prep it */ for (i = 0, iLen = nTrs.length; i < iLen; i++) { aLayout.push([]); } /* Calculate a layout array */ for (i = 0, iLen = nTrs.length; i < iLen; i++) { nTr = nTrs[i]; iColumn = 0; /* For every cell in the row... */ nCell = nTr.firstChild; while (nCell) { if ( nCell.nodeName.toUpperCase() == "TD" || nCell.nodeName.toUpperCase() == "TH" ) { /* Get the col and rowspan attributes from the DOM and sanitise them */ iColspan = nCell.getAttribute("colspan") * 1; iRowspan = nCell.getAttribute("rowspan") * 1; iColspan = !iColspan || iColspan === 0 || iColspan === 1 ? 1 : iColspan; iRowspan = !iRowspan || iRowspan === 0 || iRowspan === 1 ? 1 : iRowspan; /* There might be colspan cells already in this row, so shift our target * accordingly */ iColShifted = fnShiftCol(aLayout, i, iColumn); /* Cache calculation for unique columns */ bUnique = iColspan === 1 ? true : false; /* If there is col / rowspan, copy the information into the layout grid */ for (l = 0; l < iColspan; l++) { for (k = 0; k < iRowspan; k++) { aLayout[i + k][iColShifted + l] = { cell: nCell, unique: bUnique, }; aLayout[i + k].nTr = nTr; } } } nCell = nCell.nextSibling; } } } /** * Get an array of unique th elements, one for each column * @param {object} oSettings dataTables settings object * @param {node} nHeader automatically detect the layout from this node - optional * @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional * @returns array {node} aReturn list of unique th's * @memberof DataTable#oApi */ function _fnGetUniqueThs(oSettings, nHeader, aLayout) { var aReturn = []; if (!aLayout) { aLayout = oSettings.aoHeader; if (nHeader) { aLayout = []; _fnDetectHeader(aLayout, nHeader); } } for (var i = 0, iLen = aLayout.length; i < iLen; i++) { for (var j = 0, jLen = aLayout[i].length; j < jLen; j++) { if (aLayout[i][j].unique && (!aReturn[j] || !oSettings.bSortCellsTop)) { aReturn[j] = aLayout[i][j].cell; } } } return aReturn; } /** * Create an Ajax call based on the table's settings, taking into account that * parameters can have multiple forms, and backwards compatibility. * * @param {object} oSettings dataTables settings object * @param {array} data Data to send to the server, required by * DataTables - may be augmented by developer callbacks * @param {function} fn Callback function to run when data is obtained */ function _fnBuildAjax(oSettings, data, fn) { // Compatibility with 1.9-, allow fnServerData and event to manipulate _fnCallbackFire(oSettings, "aoServerParams", "serverParams", [data]); // Convert to object based for 1.10+ if using the old array scheme which can // come from server-side processing or serverParams if (data && $.isArray(data)) { var tmp = {}; var rbracket = /(.*?)\[\]$/; $.each(data, function (key, val) { var match = val.name.match(rbracket); if (match) { // Support for arrays var name = match[0]; if (!tmp[name]) { tmp[name] = []; } tmp[name].push(val.value); } else { tmp[val.name] = val.value; } }); data = tmp; } var ajaxData; var ajax = oSettings.ajax; var instance = oSettings.oInstance; var callback = function (json) { _fnCallbackFire(oSettings, null, "xhr", [ oSettings, json, oSettings.jqXHR, ]); fn(json); }; if ($.isPlainObject(ajax) && ajax.data) { ajaxData = ajax.data; var newData = $.isFunction(ajaxData) ? ajaxData(data, oSettings) // fn can manipulate data or return : ajaxData; // an object object or array to merge // If the function returned something, use that alone data = $.isFunction(ajaxData) && newData ? newData : $.extend(true, data, newData); // Remove the data property as we've resolved it already and don't want // jQuery to do it again (it is restored at the end of the function) delete ajax.data; } var baseAjax = { data: data, success: function (json) { var error = json.error || json.sError; if (error) { _fnLog(oSettings, 0, error); } oSettings.json = json; callback(json); }, dataType: "json", cache: false, type: oSettings.sServerMethod, error: function (xhr, error, thrown) { var ret = _fnCallbackFire(oSettings, null, "xhr", [ oSettings, null, oSettings.jqXHR, ]); if ($.inArray(true, ret) === -1) { if (error == "parsererror") { _fnLog(oSettings, 0, "Invalid JSON response", 1); } else if (xhr.readyState === 4) { _fnLog(oSettings, 0, "Ajax error", 7); } } _fnProcessingDisplay(oSettings, false); }, }; // Store the data submitted for the API oSettings.oAjaxData = data; // Allow plug-ins and external processes to modify the data _fnCallbackFire(oSettings, null, "preXhr", [oSettings, data]); if (oSettings.fnServerData) { // DataTables 1.9- compatibility oSettings.fnServerData.call( instance, oSettings.sAjaxSource, $.map(data, function (val, key) { // Need to convert back to 1.9 trad format return { name: key, value: val }; }), callback, oSettings ); } else if (oSettings.sAjaxSource || typeof ajax === "string") { // DataTables 1.9- compatibility oSettings.jqXHR = $.ajax( $.extend(baseAjax, { url: ajax || oSettings.sAjaxSource, }) ); } else if ($.isFunction(ajax)) { // Is a function - let the caller define what needs to be done oSettings.jqXHR = ajax.call(instance, data, callback, oSettings); } else { // Object to extend the base settings oSettings.jqXHR = $.ajax($.extend(baseAjax, ajax)); // Restore for next time around ajax.data = ajaxData; } } /** * Update the table using an Ajax call * @param {object} settings dataTables settings object * @returns {boolean} Block the table drawing or not * @memberof DataTable#oApi */ function _fnAjaxUpdate(settings) { if (settings.bAjaxDataGet) { settings.iDraw++; _fnProcessingDisplay(settings, true); _fnBuildAjax(settings, _fnAjaxParameters(settings), function (json) { _fnAjaxUpdateDraw(settings, json); }); return false; } return true; } /** * Build up the parameters in an object needed for a server-side processing * request. Note that this is basically done twice, is different ways - a modern * method which is used by default in DataTables 1.10 which uses objects and * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if * the sAjaxSource option is used in the initialisation, or the legacyAjax * option is set. * @param {object} oSettings dataTables settings object * @returns {bool} block the table drawing or not * @memberof DataTable#oApi */ function _fnAjaxParameters(settings) { var columns = settings.aoColumns, columnCount = columns.length, features = settings.oFeatures, preSearch = settings.oPreviousSearch, preColSearch = settings.aoPreSearchCols, i, data = [], dataProp, column, columnSearch, sort = _fnSortFlatten(settings), displayStart = settings._iDisplayStart, displayLength = features.bPaginate !== false ? settings._iDisplayLength : -1; var param = function (name, value) { data.push({ name: name, value: value }); }; // DataTables 1.9- compatible method param("sEcho", settings.iDraw); param("iColumns", columnCount); param("sColumns", _pluck(columns, "sName").join(",")); param("iDisplayStart", displayStart); param("iDisplayLength", displayLength); // DataTables 1.10+ method var d = { draw: settings.iDraw, columns: [], order: [], start: displayStart, length: displayLength, search: { value: preSearch.sSearch, regex: preSearch.bRegex, }, }; for (i = 0; i < columnCount; i++) { column = columns[i]; columnSearch = preColSearch[i]; dataProp = typeof column.mData == "function" ? "function" : column.mData; d.columns.push({ data: dataProp, name: column.sName, searchable: column.bSearchable, orderable: column.bSortable, search: { value: columnSearch.sSearch, regex: columnSearch.bRegex, }, }); param("mDataProp_" + i, dataProp); if (features.bFilter) { param("sSearch_" + i, columnSearch.sSearch); param("bRegex_" + i, columnSearch.bRegex); param("bSearchable_" + i, column.bSearchable); } if (features.bSort) { param("bSortable_" + i, column.bSortable); } } if (features.bFilter) { param("sSearch", preSearch.sSearch); param("bRegex", preSearch.bRegex); } if (features.bSort) { $.each(sort, function (i, val) { d.order.push({ column: val.col, dir: val.dir }); param("iSortCol_" + i, val.col); param("sSortDir_" + i, val.dir); }); param("iSortingCols", sort.length); } // If the legacy.ajax parameter is null, then we automatically decide which // form to use, based on sAjaxSource var legacy = DataTable.ext.legacy.ajax; if (legacy === null) { return settings.sAjaxSource ? data : d; } // Otherwise, if legacy has been specified then we use that to decide on the // form return legacy ? data : d; } /** * Data the data from the server (nuking the old) and redraw the table * @param {object} oSettings dataTables settings object * @param {object} json json data return from the server. * @param {string} json.sEcho Tracking flag for DataTables to match requests * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering * @param {array} json.aaData The data to display on this page * @param {string} [json.sColumns] Column ordering (sName, comma separated) * @memberof DataTable#oApi */ function _fnAjaxUpdateDraw(settings, json) { // 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("iTotalRecords", "recordsTotal"); var recordsFiltered = compat("iTotalDisplayRecords", "recordsFiltered"); if (draw) { // Protect against out of sequence returns if (draw * 1 < settings.iDraw) { return; } settings.iDraw = draw * 1; } _fnClearTable(settings); settings._iRecordsTotal = parseInt(recordsTotal, 10); settings._iRecordsDisplay = parseInt(recordsFiltered, 10); for (var i = 0, ien = data.length; i < ien; i++) { _fnAddData(settings, data[i]); } settings.aiDisplay = settings.aiDisplayMaster.slice(); settings.bAjaxDataGet = false; _fnDraw(settings); if (!settings._bInitComplete) { _fnInitComplete(settings, json); } settings.bAjaxDataGet = true; _fnProcessingDisplay(settings, false); } /** * Get the data from the JSON data source to use for drawing a table. Using * `_fnGetObjectDataFn` allows the data to be sourced from a property of the * source object, or from a processing function. * @param {object} oSettings dataTables settings object * @param {object} json Data source object / array from the server * @return {array} Array of data to use */ function _fnAjaxDataSrc(oSettings, json) { var dataSrc = $.isPlainObject(oSettings.ajax) && oSettings.ajax.dataSrc !== undefined ? oSettings.ajax.dataSrc : oSettings.sAjaxDataProp; // Compatibility with 1.9-. // Compatibility with 1.9-. In order to read from aaData, check if the // default has been changed, if not, check for aaData if (dataSrc === "data") { return json.aaData || json[dataSrc]; } return dataSrc !== "" ? _fnGetObjectDataFn(dataSrc)(json) : json; } /** * Generate the node required for filtering text * @returns {node} Filter control element * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ 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 = ''; var str = language.sSearch; str = str.match(/_INPUT_/) ? str.replace("_INPUT_", input) : str + input; var filter = $("
", { id: !features.f ? tableId + "_filter" : null, class: classes.sFilter, }).append($("