From 72b13cb3359393275c287a06c73db80275ce3ce8 Mon Sep 17 00:00:00 2001 From: lukas-heiligenbrunner Date: Fri, 27 Mar 2020 19:27:07 +0100 Subject: [PATCH] readded main datatables lib --- src/resources/wwwroot/ShedulePickUp.html | 1 + src/resources/wwwroot/adminpanel.html | 1 + src/resources/wwwroot/dashboard.html | 1 + src/resources/wwwroot/device.html | 1 + .../plugins/datatables/jquery.dataTables.js | 14819 ++++++++++++++++ .../datatables/jquery.dataTables.min.js | 166 + src/resources/wwwroot/user.html | 1 + 7 files changed, 14990 insertions(+) create mode 100644 src/resources/wwwroot/lib/AdminLTE/plugins/datatables/jquery.dataTables.js create mode 100644 src/resources/wwwroot/lib/AdminLTE/plugins/datatables/jquery.dataTables.min.js diff --git a/src/resources/wwwroot/ShedulePickUp.html b/src/resources/wwwroot/ShedulePickUp.html index efcd736..26aff71 100644 --- a/src/resources/wwwroot/ShedulePickUp.html +++ b/src/resources/wwwroot/ShedulePickUp.html @@ -455,6 +455,7 @@ + diff --git a/src/resources/wwwroot/adminpanel.html b/src/resources/wwwroot/adminpanel.html index ad8cfc4..0ace60a 100644 --- a/src/resources/wwwroot/adminpanel.html +++ b/src/resources/wwwroot/adminpanel.html @@ -254,6 +254,7 @@ + diff --git a/src/resources/wwwroot/dashboard.html b/src/resources/wwwroot/dashboard.html index 1ba2bed..5221f8f 100644 --- a/src/resources/wwwroot/dashboard.html +++ b/src/resources/wwwroot/dashboard.html @@ -439,6 +439,7 @@ + diff --git a/src/resources/wwwroot/device.html b/src/resources/wwwroot/device.html index a975fe4..3330a91 100644 --- a/src/resources/wwwroot/device.html +++ b/src/resources/wwwroot/device.html @@ -269,6 +269,7 @@ + diff --git a/src/resources/wwwroot/lib/AdminLTE/plugins/datatables/jquery.dataTables.js b/src/resources/wwwroot/lib/AdminLTE/plugins/datatables/jquery.dataTables.js new file mode 100644 index 0000000..8fd97ad --- /dev/null +++ b/src/resources/wwwroot/lib/AdminLTE/plugins/datatables/jquery.dataTables.js @@ -0,0 +1,14819 @@ +/*! DataTables 1.10.20 + * ©2008-2019 SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary DataTables + * @description Paginate, search and order HTML tables + * @version 1.10.20 + * @file jquery.dataTables.js + * @author SpryMedia Ltd + * @contact www.datatables.net + * @copyright Copyright 2008-2019 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()), 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 = $.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\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 = /[',$£€¥%\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) { + // Note the use of the Hungarian notation for the parameters in this method as + // this is called after the mapping of camelCase to Hungarian + var defaults = DataTable.defaults.oLanguage; + + // Default mapping + var defaultDecimal = defaults.sDecimal; + if (defaultDecimal) { + _addNumericSort(defaultDecimal); + } + + if (lang) { + 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 && defaultDecimal !== 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, true); + + /* 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, create; + + 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]; + create = nTrIn ? false : true; + + nTd = create ? document.createElement(oCol.sCellType) : anTds[i]; + 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 (create || ((!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, cells]); + } + + // 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, iDataIndex]); + + 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 = typeof ajaxData === 'function' ? + 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 = typeof ajaxData === 'function' && 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 (typeof ajax === 'function') { + // 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($('