/*! DataTables 1.13.6 * ©2008-2023 SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables * @version 1.13.6 * @author SpryMedia Ltd * @contact www.datatables.net * @copyright 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 // jQuery's factory checks for a global window - if it isn't present then it // returns a factory function that expects the window object var jq = require('jquery'); if (typeof window === 'undefined') { 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 (!$) { $ = jq(root); } return factory($, root, root.document); }; } else { return factory(jq, window, window.document); } } else { // Browser window.DataTable = factory(jQuery, window, document); } } (function($, window, document, undefined) { "use strict"; var DataTable = function(selector, options) { // Check if called with a window or jQuery object for DOM less applications // This is for backwards compatibility if (DataTable.factory(selector, options)) { return DataTable; } // When creating with `new`, create a new DataTable, returning the API instance if (this instanceof DataTable) { return $(selector).DataTable(options); } else { // Argument switching options = selector; } /** * 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 = Array.isArray(data) && (Array.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()), true); /* 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 && 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); _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 = Array.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 = Array.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) { _fnCamelToHungarian(defaults.oLanguage, json); _fnLanguageCompat(json); $.extend(true, oLanguage, json, oSettings.oInit.oLanguage); _fnCallbackFire(oSettings, null, 'i18n', [oSettings]); _fnInitialise(oSettings); }, error: function() { // Error occurred loading language file, continue on as best we can _fnInitialise(oSettings); } }); bInitHandedOff = true; } else { _fnCallbackFire(oSettings, null, 'i18n', [oSettings]); } /* * 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) { _fnLog(oSettings, 0, 'Incorrect column count', 18); } 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 }; col._isArrayHost = true; _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 = $('').insertAfter(thead); } 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! */ _fnCallbackReg(oSettings, 'aoDrawCallback', _fnSaveState, 'state_save'); if (oInit.bStateSave) { features.bStateSave = true; _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\u2028]/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 // - Ƀ - Bitcoin // - Ξ - Ethereum // standards as thousands separators. var _re_formatted_numeric = /['\u00A0,$£€¥%\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 type = typeof d; var strType = type === 'string'; if (type === 'number' || type === 'bigint') { return true; } // 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, '') // Complete tags .replace(/