Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
wp-content
/
plugins
/
the-events-calendar
/
common
/
vendor
/
datatables
:
datatables.js
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
/* * This combined file was created by the DataTables downloader builder: * https://datatables.net/download * * To rebuild or modify this file with the latest versions of the included * software please visit: * https://datatables.net/download/#dt/dt-1.13.8/af-2.6.0/b-2.4.2/cr-1.7.0/fc-4.3.0/fh-3.4.0/kt-2.11.0/r-2.5.0/rr-1.4.1/sc-2.3.0/sl-1.7.0 * * Included libraries: * DataTables 1.13.8, AutoFill 2.6.0, Buttons 2.4.2, ColReorder 1.7.0, FixedColumns 4.3.0, FixedHeader 3.4.0, KeyTable 2.11.0, Responsive 2.5.0, RowReorder 1.4.1, Scroller 2.3.0, Select 1.7.0 */ /*! DataTables 1.13.8 * ©2008-2023 SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables * @version 1.13.8 * @author SpryMedia Ltd * @contact www.datatables.net * @copyright SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - https://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: https://www.datatables.net */ /*jslint evil: true, undef: true, browser: true */ /*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ (function( factory ) { "use strict"; if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS // jQuery's factory checks for a global window - if it isn't present then it // returns a factory function that expects the window object var jq = require('jquery'); if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } return factory( $, root, root.document ); }; } else { module.exports = factory( jq, window, window.document ); } } else { // Browser window.DataTable = factory( jQuery, window, document ); } } (function( $, window, document, undefined ) { "use strict"; var DataTable = function ( selector, options ) { // Check if called with a window or jQuery object for DOM less applications // This is for backwards compatibility if (DataTable.factory(selector, options)) { return DataTable; } // When creating with `new`, create a new DataTable, returning the API instance if (this instanceof DataTable) { return $(selector).DataTable(options); } else { // Argument switching options = selector; } /** * Perform a jQuery selector action on the table's TR elements (from the tbody) and * return the resulting jQuery object. * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on * @param {object} [oOpts] Optional parameters for modifying the rows to be included * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter * criterion ("applied") or all TR elements (i.e. no filter). * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. * Can be either 'current', whereby the current sorting of the table is used, or * 'original' whereby the original order the data was read into the table is used. * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page * ("current") or not ("all"). If 'current' is given, then order is assumed to be * 'current' and filter is 'applied', regardless of what they might be given as. * @returns {object} jQuery object, filtered by the given selector. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Highlight every second row * oTable.$('tr:odd').css('backgroundColor', 'blue'); * } ); * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Filter to rows with 'Webkit' in them, add a background colour and then * // remove the filter, thus highlighting the 'Webkit' rows only. * oTable.fnFilter('Webkit'); * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); * oTable.fnFilter(''); * } ); */ this.$ = function ( sSelector, oOpts ) { return this.api(true).$( sSelector, oOpts ); }; /** * Almost identical to $ in operation, but in this case returns the data for the matched * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes * rather than any descendants, so the data can be obtained for the row/cell. If matching * rows are found, the data returned is the original data array/object that was used to * create the row (or a generated array if from a DOM source). * * This method is often useful in-combination with $ where both functions are given the * same parameters and the array indexes will match identically. * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on * @param {object} [oOpts] Optional parameters for modifying the rows to be included * @param {string} [oOpts.filter=none] Select elements that meet the current filter * criterion ("applied") or all elements (i.e. no filter). * @param {string} [oOpts.order=current] Order of the data in the processed array. * Can be either 'current', whereby the current sorting of the table is used, or * 'original' whereby the original order the data was read into the table is used. * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page * ("current") or not ("all"). If 'current' is given, then order is assumed to be * 'current' and filter is 'applied', regardless of what they might be given as. * @returns {array} Data for the matched elements. If any elements, as a result of the * selector, were not TR, TD or TH elements in the DataTable, they will have a null * entry in the array. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Get the data from the first row in the table * var data = oTable._('tr:first'); * * // Do something useful with the data * alert( "First cell is: "+data[0] ); * } ); * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Filter to 'Webkit' and get all data for * oTable.fnFilter('Webkit'); * var data = oTable._('tr', {"search": "applied"}); * * // Do something with the data * alert( data.length+" rows matched the search" ); * } ); */ this._ = function ( sSelector, oOpts ) { return this.api(true).rows( sSelector, oOpts ).data(); }; /** * Create a DataTables Api instance, with the currently selected tables for * the Api's context. * @param {boolean} [traditional=false] Set the API instance's context to be * only the table referred to by the `DataTable.ext.iApiIndex` option, as was * used in the API presented by DataTables 1.9- (i.e. the traditional mode), * or if all tables captured in the jQuery object should be used. * @return {DataTables.Api} */ this.api = function ( traditional ) { return traditional ? new _Api( _fnSettingsFromNode( this[ _ext.iApiIndex ] ) ) : new _Api( this ); }; /** * Add a single new row or multiple rows of data to the table. Please note * that this is suitable for client-side processing only - if you are using * server-side processing (i.e. "bServerSide": true), then to add data, you * must add it to the data source, i.e. the server-side, through an Ajax call. * @param {array|object} data The data to be added to the table. This can be: * <ul> * <li>1D array of data - add a single row with the data provided</li> * <li>2D array of arrays - add multiple rows in a single call</li> * <li>object - data object when using <i>mData</i></li> * <li>array of objects - multiple data objects when using <i>mData</i></li> * </ul> * @param {bool} [redraw=true] redraw the table or not * @returns {array} An array of integers, representing the list of indexes in * <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to * the table. * @dtopt API * @deprecated Since v1.10 * * @example * // Global var for counter * var giCount = 2; * * $(document).ready(function() { * $('#example').dataTable(); * } ); * * function fnClickAddRow() { * $('#example').dataTable().fnAddData( [ * giCount+".1", * giCount+".2", * giCount+".3", * giCount+".4" ] * ); * * giCount++; * } */ this.fnAddData = function( data, redraw ) { var api = this.api( true ); /* Check if we want to add multiple rows or not */ var rows = Array.isArray(data) && ( Array.isArray(data[0]) || $.isPlainObject(data[0]) ) ? api.rows.add( data ) : api.row.add( data ); if ( redraw === undefined || redraw ) { api.draw(); } return rows.flatten().toArray(); }; /** * This function will make DataTables recalculate the column sizes, based on the data * contained in the table and the sizes applied to the columns (in the DOM, CSS or * through the sWidth parameter). This can be useful when the width of the table's * parent element changes (for example a window resize). * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable( { * "sScrollY": "200px", * "bPaginate": false * } ); * * $(window).on('resize', function () { * oTable.fnAdjustColumnSizing(); * } ); * } ); */ this.fnAdjustColumnSizing = function ( bRedraw ) { var api = this.api( true ).columns.adjust(); var settings = api.settings()[0]; var scroll = settings.oScroll; if ( bRedraw === undefined || bRedraw ) { api.draw( false ); } else if ( scroll.sX !== "" || scroll.sY !== "" ) { /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ _fnScrollDraw( settings ); } }; /** * Quickly and simply clear a table * @param {bool} [bRedraw=true] redraw the table or not * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) * oTable.fnClearTable(); * } ); */ this.fnClearTable = function( bRedraw ) { var api = this.api( true ).clear(); if ( bRedraw === undefined || bRedraw ) { api.draw(); } }; /** * The exact opposite of 'opening' a row, this function will close any rows which * are currently 'open'. * @param {node} nTr the table row to 'close' * @returns {int} 0 on success, or 1 if failed (can't find the row) * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable; * * // 'open' an information row when a row is clicked on * $('#example tbody tr').click( function () { * if ( oTable.fnIsOpen(this) ) { * oTable.fnClose( this ); * } else { * oTable.fnOpen( this, "Temporary row opened", "info_row" ); * } * } ); * * oTable = $('#example').dataTable(); * } ); */ this.fnClose = function( nTr ) { this.api( true ).row( nTr ).child.hide(); }; /** * Remove a row for the table * @param {mixed} target The index of the row from aoData to be deleted, or * the TR element you want to delete * @param {function|null} [callBack] Callback function * @param {bool} [redraw=true] Redraw the table or not * @returns {array} The row that was deleted * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Immediately remove the first row * oTable.fnDeleteRow( 0 ); * } ); */ this.fnDeleteRow = function( target, callback, redraw ) { var api = this.api( true ); var rows = api.rows( target ); var settings = rows.settings()[0]; var data = settings.aoData[ rows[0][0] ]; rows.remove(); if ( callback ) { callback.call( this, settings, data ); } if ( redraw === undefined || redraw ) { api.draw(); } return data; }; /** * Restore the table to it's original state in the DOM by removing all of DataTables * enhancements, alterations to the DOM structure of the table and event listeners. * @param {boolean} [remove=false] Completely remove the table from the DOM * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * // This example is fairly pointless in reality, but shows how fnDestroy can be used * var oTable = $('#example').dataTable(); * oTable.fnDestroy(); * } ); */ this.fnDestroy = function ( remove ) { this.api( true ).destroy( remove ); }; /** * Redraw the table * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) * oTable.fnDraw(); * } ); */ this.fnDraw = function( complete ) { // Note that this isn't an exact match to the old call to _fnDraw - it takes // into account the new data, but can hold position. this.api( true ).draw( complete ); }; /** * Filter the input based on data * @param {string} sInput String to filter the table on * @param {int|null} [iColumn] Column to limit filtering to * @param {bool} [bRegex=false] Treat as regular expression or not * @param {bool} [bSmart=true] Perform smart filtering or not * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Sometime later - filter... * oTable.fnFilter( 'test string' ); * } ); */ this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive ) { var api = this.api( true ); if ( iColumn === null || iColumn === undefined ) { api.search( sInput, bRegex, bSmart, bCaseInsensitive ); } else { api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive ); } api.draw(); }; /** * Get the data for the whole table, an individual row or an individual cell based on the * provided parameters. * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as * a TR node then the data source for the whole row will be returned. If given as a * TD/TH cell node then iCol will be automatically calculated and the data for the * cell returned. If given as an integer, then this is treated as the aoData internal * data index for the row (see fnGetPosition) and the data for that row used. * @param {int} [col] Optional column index that you want the data of. * @returns {array|object|string} If mRow is undefined, then the data for all rows is * returned. If mRow is defined, just data for that row, and is iCol is * defined, only data for the designated cell is returned. * @dtopt API * @deprecated Since v1.10 * * @example * // Row data * $(document).ready(function() { * oTable = $('#example').dataTable(); * * oTable.$('tr').click( function () { * var data = oTable.fnGetData( this ); * // ... do something with the array / object of data for the row * } ); * } ); * * @example * // Individual cell data * $(document).ready(function() { * oTable = $('#example').dataTable(); * * oTable.$('td').click( function () { * var sData = oTable.fnGetData( this ); * alert( 'The cell clicked on had the value of '+sData ); * } ); * } ); */ this.fnGetData = function( src, col ) { var api = this.api( true ); if ( src !== undefined ) { var type = src.nodeName ? src.nodeName.toLowerCase() : ''; return col !== undefined || type == 'td' || type == 'th' ? api.cell( src, col ).data() : api.row( src ).data() || null; } return api.data().toArray(); }; /** * Get an array of the TR nodes that are used in the table's body. Note that you will * typically want to use the '$' API method in preference to this as it is more * flexible. * @param {int} [iRow] Optional row index for the TR element you want * @returns {array|node} If iRow is undefined, returns an array of all TR elements * in the table's body, or iRow is defined, just the TR element requested. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Get the nodes from the table * var nNodes = oTable.fnGetNodes( ); * } ); */ this.fnGetNodes = function( iRow ) { var api = this.api( true ); return iRow !== undefined ? api.row( iRow ).node() : api.rows().nodes().flatten().toArray(); }; /** * Get the array indexes of a particular cell from it's DOM element * and column index including hidden columns * @param {node} node this can either be a TR, TD or TH in the table's body * @returns {int} If nNode is given as a TR, then a single index is returned, or * if given as a cell, an array of [row index, column index (visible), * column index (all)] is given. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * $('#example tbody td').click( function () { * // Get the position of the current data from the node * var aPos = oTable.fnGetPosition( this ); * * // Get the data array for this row * var aData = oTable.fnGetData( aPos[0] ); * * // Update the data array and return the value * aData[ aPos[1] ] = 'clicked'; * this.innerHTML = 'clicked'; * } ); * * // Init DataTables * oTable = $('#example').dataTable(); * } ); */ this.fnGetPosition = function( node ) { var api = this.api( true ); var nodeName = node.nodeName.toUpperCase(); if ( nodeName == 'TR' ) { return api.row( node ).index(); } else if ( nodeName == 'TD' || nodeName == 'TH' ) { var cell = api.cell( node ).index(); return [ cell.row, cell.columnVisible, cell.column ]; } return null; }; /** * Check to see if a row is 'open' or not. * @param {node} nTr the table row to check * @returns {boolean} true if the row is currently open, false otherwise * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable; * * // 'open' an information row when a row is clicked on * $('#example tbody tr').click( function () { * if ( oTable.fnIsOpen(this) ) { * oTable.fnClose( this ); * } else { * oTable.fnOpen( this, "Temporary row opened", "info_row" ); * } * } ); * * oTable = $('#example').dataTable(); * } ); */ this.fnIsOpen = function( nTr ) { return this.api( true ).row( nTr ).child.isShown(); }; /** * This function will place a new row directly after a row which is currently * on display on the page, with the HTML contents that is passed into the * function. This can be used, for example, to ask for confirmation that a * particular record should be deleted. * @param {node} nTr The table row to 'open' * @param {string|node|jQuery} mHtml The HTML to put into the row * @param {string} sClass Class to give the new TD cell * @returns {node} The row opened. Note that if the table row passed in as the * first parameter, is not found in the table, this method will silently * return. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable; * * // 'open' an information row when a row is clicked on * $('#example tbody tr').click( function () { * if ( oTable.fnIsOpen(this) ) { * oTable.fnClose( this ); * } else { * oTable.fnOpen( this, "Temporary row opened", "info_row" ); * } * } ); * * oTable = $('#example').dataTable(); * } ); */ this.fnOpen = function( nTr, mHtml, sClass ) { return this.api( true ) .row( nTr ) .child( mHtml, sClass ) .show() .child()[0]; }; /** * Change the pagination - provides the internal logic for pagination in a simple API * function. With this function you can have a DataTables table go to the next, * previous, first or last pages. * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" * or page number to jump to (integer), note that page 0 is the first page. * @param {bool} [bRedraw=true] Redraw the table or not * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * oTable.fnPageChange( 'next' ); * } ); */ this.fnPageChange = function ( mAction, bRedraw ) { var api = this.api( true ).page( mAction ); if ( bRedraw === undefined || bRedraw ) { api.draw(false); } }; /** * Show a particular column * @param {int} iCol The column whose display should be changed * @param {bool} bShow Show (true) or hide (false) the column * @param {bool} [bRedraw=true] Redraw the table or not * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Hide the second column after initialisation * oTable.fnSetColumnVis( 1, false ); * } ); */ this.fnSetColumnVis = function ( iCol, bShow, bRedraw ) { var api = this.api( true ).column( iCol ).visible( bShow ); if ( bRedraw === undefined || bRedraw ) { api.columns.adjust().draw(); } }; /** * Get the settings for a particular table for external manipulation * @returns {object} DataTables settings object. See * {@link DataTable.models.oSettings} * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * var oSettings = oTable.fnSettings(); * * // Show an example parameter from the settings * alert( oSettings._iDisplayStart ); * } ); */ this.fnSettings = function() { return _fnSettingsFromNode( this[_ext.iApiIndex] ); }; /** * Sort the table by a particular column * @param {int} iCol the data index to sort on. Note that this will not match the * 'display index' if you have hidden data entries * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Sort immediately with columns 0 and 1 * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); * } ); */ this.fnSort = function( aaSort ) { this.api( true ).order( aaSort ).draw(); }; /** * Attach a sort listener to an element for a given column * @param {node} nNode the element to attach the sort listener to * @param {int} iColumn the column that a click on this node will sort on * @param {function} [fnCallback] callback function when sort is run * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Sort on column 1, when 'sorter' is clicked on * oTable.fnSortListener( document.getElementById('sorter'), 1 ); * } ); */ this.fnSortListener = function( nNode, iColumn, fnCallback ) { this.api( true ).order.listener( nNode, iColumn, fnCallback ); }; /** * Update a table cell or row - this method will accept either a single value to * update the cell with, an array of values with one element for each column or * an object in the same format as the original data source. The function is * self-referencing in order to make the multi column updates easier. * @param {object|array|string} mData Data to update the cell/row with * @param {node|int} mRow TR element you want to update or the aoData index * @param {int} [iColumn] The column to update, give as null or undefined to * update a whole row. * @param {bool} [bRedraw=true] Redraw the table or not * @param {bool} [bAction=true] Perform pre-draw actions or not * @returns {int} 0 on success, 1 on error * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row * } ); */ this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction ) { var api = this.api( true ); if ( iColumn === undefined || iColumn === null ) { api.row( mRow ).data( mData ); } else { api.cell( mRow, iColumn ).data( mData ); } if ( bAction === undefined || bAction ) { api.columns.adjust(); } if ( bRedraw === undefined || bRedraw ) { api.draw(); } return 0; }; /** * Provide a common method for plug-ins to check the version of DataTables being used, in order * to ensure compatibility. * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the * formats "X" and "X.Y" are also acceptable. * @returns {boolean} true if this version of DataTables is greater or equal to the required * version, or false if this version of DataTales is not suitable * @method * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * alert( oTable.fnVersionCheck( '1.9.0' ) ); * } ); */ this.fnVersionCheck = _ext.fnVersionCheck; var _that = this; var emptyInit = options === undefined; var len = this.length; if ( emptyInit ) { options = {}; } this.oApi = this.internal = _ext.internal; // Extend with old style plug-in API methods for ( var fn in DataTable.ext.internal ) { if ( fn ) { this[fn] = _fnExternApiFunc(fn); } } this.each(function() { // For each initialisation we want to give it a clean initialisation // object that can be bashed around var o = {}; var oInit = len > 1 ? // optimisation for single table case _fnExtend( o, options, true ) : options; /*global oInit,_that,emptyInit*/ var i=0, iLen, j, jLen, k, kLen; var sId = this.getAttribute( 'id' ); var bInitHandedOff = false; var defaults = DataTable.defaults; var $this = $(this); /* Sanity check */ if ( this.nodeName.toLowerCase() != 'table' ) { _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); return; } /* Backwards compatibility for the defaults */ _fnCompatOpts( defaults ); _fnCompatCols( defaults.column ); /* Convert the camel-case defaults to Hungarian */ _fnCamelToHungarian( defaults, defaults, true ); _fnCamelToHungarian( defaults.column, defaults.column, true ); /* Setting up the initialisation object */ _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true ); /* Check to see if we are re-initialising a table */ var allSettings = DataTable.settings; for ( i=0, iLen=allSettings.length ; i<iLen ; i++ ) { var s = allSettings[i]; /* Base check on table node */ if ( s.nTable == this || (s.nTHead && s.nTHead.parentNode == this) || (s.nTFoot && s.nTFoot.parentNode == this) ) { var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve; var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy; if ( emptyInit || bRetrieve ) { return s.oInstance; } else if ( bDestroy ) { s.oInstance.fnDestroy(); break; } else { _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 ); return; } } /* If the element we are initialising has the same ID as a table which was previously * initialised, but the table nodes don't match (from before) then we destroy the old * instance by simply deleting it. This is under the assumption that the table has been * destroyed by other methods. Anyone using non-id selectors will need to do this manually */ if ( s.sTableId == this.id ) { allSettings.splice( i, 1 ); break; } } /* Ensure the table has an ID - required for accessibility */ if ( sId === null || sId === "" ) { sId = "DataTables_Table_"+(DataTable.ext._unique++); this.id = sId; } /* Create the settings object for this table and set some of the default parameters */ var oSettings = $.extend( true, {}, DataTable.models.oSettings, { "sDestroyWidth": $this[0].style.width, "sInstance": sId, "sTableId": sId } ); oSettings.nTable = this; oSettings.oApi = _that.internal; oSettings.oInit = oInit; allSettings.push( oSettings ); // Need to add the instance after the instance after the settings object has been added // to the settings array, so we can self reference the table instance if more than one oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); // Backwards compatibility, before we apply all the defaults _fnCompatOpts( oInit ); _fnLanguageCompat( oInit.oLanguage ); // If the length menu is given, but the init display length is not, use the length menu if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) { oInit.iDisplayLength = Array.isArray( oInit.aLengthMenu[0] ) ? oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0]; } // Apply the defaults and init options to make a single init object will all // options defined from defaults and instance options. oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); // Map the initialisation options onto the settings object _fnMap( oSettings.oFeatures, oInit, [ "bPaginate", "bLengthChange", "bFilter", "bSort", "bSortMulti", "bInfo", "bProcessing", "bAutoWidth", "bSortClasses", "bServerSide", "bDeferRender" ] ); _fnMap( oSettings, oInit, [ "asStripeClasses", "ajax", "fnServerData", "fnFormatNumber", "sServerMethod", "aaSorting", "aaSortingFixed", "aLengthMenu", "sPaginationType", "sAjaxSource", "sAjaxDataProp", "iStateDuration", "sDom", "bSortCellsTop", "iTabIndex", "fnStateLoadCallback", "fnStateSaveCallback", "renderer", "searchDelay", "rowId", [ "iCookieDuration", "iStateDuration" ], // backwards compat [ "oSearch", "oPreviousSearch" ], [ "aoSearchCols", "aoPreSearchCols" ], [ "iDisplayLength", "_iDisplayLength" ] ] ); _fnMap( oSettings.oScroll, oInit, [ [ "sScrollX", "sX" ], [ "sScrollXInner", "sXInner" ], [ "sScrollY", "sY" ], [ "bScrollCollapse", "bCollapse" ] ] ); _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); /* Callback functions which are array driven */ _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback, 'user' ); _fnCallbackReg( oSettings, 'aoServerParams', oInit.fnServerParams, 'user' ); _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams, 'user' ); _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams, 'user' ); _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded, 'user' ); _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback, 'user' ); _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow, 'user' ); _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback, 'user' ); _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback, 'user' ); _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete, 'user' ); _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback, 'user' ); oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); /* Browser support detection */ _fnBrowserDetect( oSettings ); var oClasses = oSettings.oClasses; $.extend( oClasses, DataTable.ext.classes, oInit.oClasses ); $this.addClass( oClasses.sTable ); if ( oSettings.iInitDisplayStart === undefined ) { /* Display start point, taking into account the save saving */ oSettings.iInitDisplayStart = oInit.iDisplayStart; oSettings._iDisplayStart = oInit.iDisplayStart; } if ( oInit.iDeferLoading !== null ) { oSettings.bDeferLoading = true; var tmp = Array.isArray( oInit.iDeferLoading ); oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading; oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading; } /* Language definitions */ var oLanguage = oSettings.oLanguage; $.extend( true, oLanguage, oInit.oLanguage ); if ( oLanguage.sUrl ) { /* Get the language definitions from a file - because this Ajax call makes the language * get async to the remainder of this function we use bInitHandedOff to indicate that * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor */ $.ajax( { dataType: 'json', url: oLanguage.sUrl, success: function ( json ) { _fnCamelToHungarian( defaults.oLanguage, json ); _fnLanguageCompat( json ); $.extend( true, oLanguage, json, oSettings.oInit.oLanguage ); _fnCallbackFire( oSettings, null, 'i18n', [oSettings]); _fnInitialise( oSettings ); }, error: function () { // Error occurred loading language file, continue on as best we can _fnInitialise( oSettings ); } } ); bInitHandedOff = true; } else { _fnCallbackFire( oSettings, null, 'i18n', [oSettings]); } /* * Stripes */ if ( oInit.asStripeClasses === null ) { oSettings.asStripeClasses =[ oClasses.sStripeOdd, oClasses.sStripeEven ]; } /* Remove row stripe classes if they are already on the table row */ var stripeClasses = oSettings.asStripeClasses; var rowOne = $this.children('tbody').find('tr').eq(0); if ( $.inArray( true, $.map( stripeClasses, function(el, i) { return rowOne.hasClass(el); } ) ) !== -1 ) { $('tbody tr', this).removeClass( stripeClasses.join(' ') ); oSettings.asDestroyStripes = stripeClasses.slice(); } /* * Columns * See if we should load columns automatically or use defined ones */ var anThs = []; var aoColumnsInit; var nThead = this.getElementsByTagName('thead'); if ( nThead.length !== 0 ) { _fnDetectHeader( oSettings.aoHeader, nThead[0] ); anThs = _fnGetUniqueThs( oSettings ); } /* If not given a column array, generate one with nulls */ if ( oInit.aoColumns === null ) { aoColumnsInit = []; for ( i=0, iLen=anThs.length ; i<iLen ; i++ ) { aoColumnsInit.push( null ); } } else { aoColumnsInit = oInit.aoColumns; } /* Add the columns */ for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ ) { _fnAddColumn( oSettings, anThs ? anThs[i] : null ); } /* Apply the column definitions */ _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) { _fnColumnOptions( oSettings, iCol, oDef ); } ); /* HTML5 attribute detection - build an mData object automatically if the * attributes are found */ if ( rowOne.length ) { var a = function ( cell, name ) { return cell.getAttribute( 'data-'+name ) !== null ? name : null; }; $( rowOne[0] ).children('th, td').each( function (i, cell) { var col = oSettings.aoColumns[i]; if (! col) { _fnLog( oSettings, 0, 'Incorrect column count', 18 ); } if ( col.mData === i ) { var sort = a( cell, 'sort' ) || a( cell, 'order' ); var filter = a( cell, 'filter' ) || a( cell, 'search' ); if ( sort !== null || filter !== null ) { col.mData = { _: i+'.display', sort: sort !== null ? i+'.@data-'+sort : undefined, type: sort !== null ? i+'.@data-'+sort : undefined, filter: filter !== null ? i+'.@data-'+filter : undefined }; col._isArrayHost = true; _fnColumnOptions( oSettings, i ); } } } ); } var features = oSettings.oFeatures; var loadedInit = function () { /* * Sorting * @todo For modularisation (1.11) this needs to do into a sort start up handler */ // If aaSorting is not defined, then we use the first indicator in asSorting // in case that has been altered, so the default sort reflects that option if ( oInit.aaSorting === undefined ) { var sorting = oSettings.aaSorting; for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) { sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0]; } } /* Do a first pass on the sorting classes (allows any size changes to be taken into * account, and also will apply sorting disabled classes if disabled */ _fnSortingClasses( oSettings ); if ( features.bSort ) { _fnCallbackReg( oSettings, 'aoDrawCallback', function () { if ( oSettings.bSorted ) { var aSort = _fnSortFlatten( oSettings ); var sortedColumns = {}; $.each( aSort, function (i, val) { sortedColumns[ val.src ] = val.dir; } ); _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort, sortedColumns] ); _fnSortAria( oSettings ); } } ); } _fnCallbackReg( oSettings, 'aoDrawCallback', function () { if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) { _fnSortingClasses( oSettings ); } }, 'sc' ); /* * Final init * Cache the header, body and footer as required, creating them if needed */ // Work around for Webkit bug 83867 - store the caption-side before removing from doc var captions = $this.children('caption').each( function () { this._captionSide = $(this).css('caption-side'); } ); var thead = $this.children('thead'); if ( thead.length === 0 ) { thead = $('<thead/>').appendTo($this); } oSettings.nTHead = thead[0]; var tbody = $this.children('tbody'); if ( tbody.length === 0 ) { tbody = $('<tbody/>').insertAfter(thead); } oSettings.nTBody = tbody[0]; var tfoot = $this.children('tfoot'); if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) { // If we are a scrolling table, and no footer has been given, then we need to create // a tfoot element for the caption element to be appended to tfoot = $('<tfoot/>').appendTo($this); } if ( tfoot.length === 0 || tfoot.children().length === 0 ) { $this.addClass( oClasses.sNoFooter ); } else if ( tfoot.length > 0 ) { oSettings.nTFoot = tfoot[0]; _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot ); } /* Check if there is data passing into the constructor */ if ( oInit.aaData ) { for ( i=0 ; i<oInit.aaData.length ; i++ ) { _fnAddData( oSettings, oInit.aaData[ i ] ); } } else if ( oSettings.bDeferLoading || _fnDataSource( oSettings ) == 'dom' ) { /* Grab the data from the page - only do this when deferred loading or no Ajax * source since there is no point in reading the DOM data if we are then going * to replace it with Ajax data */ _fnAddTr( oSettings, $(oSettings.nTBody).children('tr') ); } /* Copy the data index array */ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); /* Initialisation complete - table can be drawn */ oSettings.bInitialised = true; /* Check if we need to initialise the table (it might not have been handed off to the * language processor) */ if ( bInitHandedOff === false ) { _fnInitialise( oSettings ); } }; /* Must be done after everything which can be overridden by the state saving! */ _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' ); if ( oInit.bStateSave ) { features.bStateSave = true; _fnLoadState( oSettings, oInit, loadedInit ); } else { loadedInit(); } } ); _that = null; return this; }; /* * It is useful to have variables which are scoped locally so only the * DataTables functions can access them and they don't leak into global space. * At the same time these functions are often useful over multiple files in the * core and API, so we list, or at least document, all variables which are used * by DataTables as private variables here. This also ensures that there is no * clashing of variable names and that they can easily referenced for reuse. */ // Defined else where // _selector_run // _selector_opts // _selector_first // _selector_row_indexes var _ext; // DataTable.ext var _Api; // DataTable.Api var _api_register; // DataTable.Api.register var _api_registerPlural; // DataTable.Api.registerPlural var _re_dic = {}; var _re_new_lines = /[\r\n\u2028]/g; var _re_html = /<.*?>/g; // This is not strict ISO8601 - Date.parse() is quite lax, although // implementations differ between browsers. var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/; // Escape regular expression special characters var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); // https://en.wikipedia.org/wiki/Foreign_exchange_market // - \u20BD - Russian ruble. // - \u20a9 - South Korean Won // - \u20BA - Turkish Lira // - \u20B9 - Indian Rupee // - R - Brazil (R$) and South Africa // - fr - Swiss Franc // - kr - Swedish krona, Norwegian krone and Danish krone // - \u2009 is thin space and \u202F is narrow no-break space, both used in many // - Ƀ - Bitcoin // - Ξ - Ethereum // standards as thousands separators. var _re_formatted_numeric = /['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi; var _empty = function ( d ) { return !d || d === true || d === '-' ? true : false; }; var _intVal = function ( s ) { var integer = parseInt( s, 10 ); return !isNaN(integer) && isFinite(s) ? integer : null; }; // Convert from a formatted number with characters other than `.` as the // decimal place, to a Javascript number var _numToDecimal = function ( num, decimalPoint ) { // Cache created regular expressions for speed as this function is called often if ( ! _re_dic[ decimalPoint ] ) { _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); } return typeof num === 'string' && decimalPoint !== '.' ? num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : num; }; var _isNumber = function ( d, decimalPoint, formatted ) { var type = typeof d; var strType = type === 'string'; if ( type === 'number' || type === 'bigint') { return true; } // If empty return immediately so there must be a number if it is a // formatted string (this stops the string "k", or "kr", etc being detected // as a formatted number for currency if ( _empty( d ) ) { return true; } if ( decimalPoint && strType ) { d = _numToDecimal( d, decimalPoint ); } if ( formatted && strType ) { d = d.replace( _re_formatted_numeric, '' ); } return !isNaN( parseFloat(d) ) && isFinite( d ); }; // A string without HTML in it can be considered to be HTML still var _isHtml = function ( d ) { return _empty( d ) || typeof d === 'string'; }; var _htmlNumeric = function ( d, decimalPoint, formatted ) { if ( _empty( d ) ) { return true; } var html = _isHtml( d ); return ! html ? null : _isNumber( _stripHtml( d ), decimalPoint, formatted ) ? true : null; }; var _pluck = function ( a, prop, prop2 ) { var out = []; var i=0, ien=a.length; // Could have the test in the loop for slightly smaller code, but speed // is essential here if ( prop2 !== undefined ) { for ( ; i<ien ; i++ ) { if ( a[i] && a[i][ prop ] ) { out.push( a[i][ prop ][ prop2 ] ); } } } else { for ( ; i<ien ; i++ ) { if ( a[i] ) { out.push( a[i][ prop ] ); } } } return out; }; // Basically the same as _pluck, but rather than looping over `a` we use `order` // as the indexes to pick from `a` var _pluck_order = function ( a, order, prop, prop2 ) { var out = []; var i=0, ien=order.length; // Could have the test in the loop for slightly smaller code, but speed // is essential here if ( prop2 !== undefined ) { for ( ; i<ien ; i++ ) { if ( a[ order[i] ][ prop ] ) { out.push( a[ order[i] ][ prop ][ prop2 ] ); } } } else { for ( ; i<ien ; i++ ) { out.push( a[ order[i] ][ prop ] ); } } return out; }; var _range = function ( len, start ) { var out = []; var end; if ( start === undefined ) { start = 0; end = len; } else { end = start; start = len; } for ( var i=start ; i<end ; i++ ) { out.push( i ); } return out; }; var _removeEmpty = function ( a ) { var out = []; for ( var i=0, ien=a.length ; i<ien ; i++ ) { if ( a[i] ) { // careful - will remove all falsy values! out.push( a[i] ); } } return out; }; var _stripHtml = function ( d ) { return d .replace( _re_html, '' ) // Complete tags .replace(/<script/i, ''); // Safety for incomplete script tag }; /** * 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; }; // Surprisingly this is faster than [].concat.apply // https://jsperf.com/flatten-an-array-loop-vs-reduce/2 var _flatten = function (out, val) { if (Array.isArray(val)) { for (var i=0 ; i<val.length ; i++) { _flatten(out, val[i]); } } else { out.push(val); } return out; } var _includes = function (search, start) { if (start === undefined) { start = 0; } return this.indexOf(search, start) !== -1; }; // Array.isArray polyfill. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray if (! Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; } if (! Array.prototype.includes) { Array.prototype.includes = _includes; } // .trim() polyfill // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim if (!String.prototype.trim) { String.prototype.trim = function () { return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); }; } if (! String.prototype.includes) { String.prototype.includes = _includes; } /** * 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 function that will write to a nested object or array * @param {*} source JSON notation string * @returns Write function */ set: function ( source ) { if ( $.isPlainObject( source ) ) { /* 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 DataTable.util.set( source._ ); } else if ( source === null ) { // Nothing to do when the data source is null return function () {}; } else if ( typeof source === 'function' ) { return function (data, val, meta) { source( data, 'set', val, meta ); }; } else if ( typeof source === 'string' && (source.indexOf('.') !== -1 || source.indexOf('[') !== -1 || source.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++ ) { // Protect against prototype pollution if (a[i] === '__proto__' || a[i] === 'constructor') { throw new Error('Cannot set prototype values'); } // 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 ( Array.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, source ); }; } else { // Array or flat object mapping return function (data, val) { // meta is also passed in, but not used data[source] = val; }; } }, /** * Create a function that will read nested objects from arrays, based on JSON notation * @param {*} source JSON notation string * @returns Value read */ get: function ( source ) { if ( $.isPlainObject( source ) ) { // Build an object of get functions, and wrap them in a single call var o = {}; $.each( source, function (key, val) { if ( val ) { o[key] = DataTable.util.get( val ); } } ); return function (data, type, row, meta) { var t = o[type] || o._; return t !== undefined ? t(data, type, row, meta) : data; }; } else if ( source === 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 source === 'function' ) { return function (data, type, row, meta) { return source( data, type, row, meta ); }; } else if ( typeof source === 'string' && (source.indexOf('.') !== -1 || source.indexOf('[') !== -1 || source.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 ( Array.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] ] === null) { return null; } else if ( data === undefined || 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, source ); }; } else { // Array or flat object mapping return function (data, type) { // row and meta also passed, but not used return data[source]; }; } } }; /** * 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' && ! Array.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 = $('<div/>') .css( { position: 'fixed', top: 0, left: $(window).scrollLeft()*-1, // allow for scrolling height: 1, width: 1, overflow: 'hidden' } ) .append( $('<div/>') .css( { position: 'absolute', top: 1, left: 1, width: 100, overflow: 'scroll' } ) .append( $('<div/>') .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 ); } var origClass = oCol.sClass; $.extend( oCol, oOptions ); _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); // Merge class from previously defined classes with this one, rather than just // overwriting it in the extend above if (origClass !== oCol.sClass) { oCol.sClass = origClass + ' ' + oCol.sClass; } /* 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" ); // Fall back to the aria-label attribute on the table header if no ariaTitle is // provided. if (! oCol.ariaTitle) { oCol.ariaTitle = th.attr("aria-label"); } } /* 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' && ! oCol._isArrayHost ) { 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] ); } /** * Convert 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; } /** * Convert 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 - but it // must not be empty if ( detectedType === 'html' && ! _empty(cache[k]) ) { 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.target !== undefined ? def.target : def.targets !== undefined ? def.targets : def.aTargets; if ( ! Array.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|search' 'sort|order') * @returns {*} Cell data * @memberof DataTable#oApi */ function _fnGetCellData( settings, rowIdx, colIdx, type ) { if (type === 'search') { type = 'filter'; } else if (type === 'order') { type = 'sort'; } 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 ''; } if ( type === 'filter' ) { var fomatters = DataTable.ext.type.search; if ( fomatters[ col.sType ] ) { cellData = fomatters[ col.sType ]( cellData ); } } 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 */ var _fnGetObjectDataFn = DataTable.util.get; /** * 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 */ var _fnSetObjectDataFn = DataTable.util.set; /** * 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 = (cell.innerHTML).trim(); 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]; if (! nTd) { _fnLog( oSettings, 0, 'Incorrect column count', 18 ); } 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 || ((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] ); } } /** * 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 = $('<tr/>').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 ); } /* Deal with the footer - add classes if required */ $(thead).children('tr').children('th, td').addClass( classes.sHeaderTH ); $(tfoot).children('tr').children('th, 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]; if (column) { column.nTf = cells[i].cell; if ( column.sClass ) { $(column.nTf).addClass( column.sClass ); } } else { _fnLog( oSettings, 0, 'Incorrect column count', 18 ); } } } } /** * 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 * @param ajaxComplete true after ajax call to complete rendering * @memberof DataTable#oApi */ function _fnDraw( oSettings, ajaxComplete ) { // Allow for state saving and a custom start position _fnStart( 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 anRows = []; var iRowCount = 0; var asStripeClasses = oSettings.asStripeClasses; var iStripes = asStripeClasses.length; var oLang = oSettings.oLanguage; var bServerSide = _fnDataSource( oSettings ) == 'ssp'; var aiDisplay = oSettings.aiDisplay; var iDisplayStart = oSettings._iDisplayStart; var iDisplayEnd = oSettings.fnDisplayEnd(); oSettings.bDrawing = true; /* 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 && !ajaxComplete) { _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 ] = $( '<tr/>', { 'class': iStripes ? asStripeClasses[0] : '' } ) .append( $('<td />', { '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 = $('<div/>').insertBefore( table ); // Holding element for speed var features = oSettings.oFeatures; // All DataTables are wrapped in a div var insert = $('<div/>', { 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 = $('<div/>')[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; } /** * Set the start position for draw * @param {object} oSettings dataTables settings object */ function _fnStart( oSettings ) { var bServerSide = _fnDataSource( oSettings ) == 'ssp'; var iInitDisplayStart = oSettings.iInitDisplayStart; // 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; } } /** * 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 && Array.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 ) { var status = oSettings.jqXHR ? oSettings.jqXHR.status : null; if ( json === null || (typeof status === 'number' && status == 204 ) ) { json = {}; _fnAjaxDataSrc( oSettings, json, [] ); } var error = json.error || json.sError; if ( error ) { _fnLog( oSettings, 0, error ); } oSettings.json = 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": callback, "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 ) { settings.iDraw++; _fnProcessingDisplay( settings, true ); // Keep track of drawHold state to handle scrolling after the Ajax call var drawHold = settings._drawHold; _fnBuildAjax( settings, _fnAjaxParameters( settings ), function(json) { settings._drawHold = drawHold; _fnAjaxUpdateDraw( settings, json ); settings._drawHold = false; } ); } /** * 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 !== undefined ) { // Protect against out of sequence returns if ( draw*1 < settings.iDraw ) { return; } settings.iDraw = draw * 1; } // No data in returned object, so rather than an array, we show an empty table if ( ! data ) { data = []; } _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(); _fnDraw( settings, true ); if ( ! settings._bInitComplete ) { _fnInitComplete( settings, json ); } _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, write ) { var dataSrc = $.isPlainObject( oSettings.ajax ) && oSettings.ajax.dataSrc !== undefined ? oSettings.ajax.dataSrc : oSettings.sAjaxDataProp; // Compatibility with 1.9-. if ( ! write ) { if ( dataSrc === 'data' ) { // If the default, then we still want to support the old style, and safely ignore // it if possible return json.aaData || json[dataSrc]; } return dataSrc !== "" ? _fnGetObjectDataFn( dataSrc )( json ) : json; } // set _fnSetObjectDataFn( dataSrc )( json, write ); } /** * 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 = '<input type="search" class="'+classes.sFilterInput+'"/>'; var str = language.sSearch; str = str.match(/_INPUT_/) ? str.replace('_INPUT_', input) : str+input; var filter = $('<div/>', { 'id': ! features.f ? tableId+'_filter' : null, 'class': classes.sFilter } ) .append( $('<label/>' ).append( str ) ); var searchFn = function(event) { /* Update all other filter input elements for the new display */ var n = features.f; var val = !this.value ? "" : this.value; // mental IE8 fix :-( if(previousSearch['return'] && event.key !== "Enter") { return; } /* Now do the filter */ if ( val != previousSearch.sSearch ) { _fnFilterComplete( settings, { "sSearch": val, "bRegex": previousSearch.bRegex, "bSmart": previousSearch.bSmart , "bCaseInsensitive": previousSearch.bCaseInsensitive, "return": previousSearch['return'] } ); // Need to redraw, without resorting settings._iDisplayStart = 0; _fnDraw( settings ); } }; var searchDelay = settings.searchDelay !== null ? settings.searchDelay : _fnDataSource( settings ) === 'ssp' ? 400 : 0; var jqFilter = $('input', filter) .val( previousSearch.sSearch ) .attr( 'placeholder', language.sSearchPlaceholder ) .on( 'keyup.DT search.DT input.DT paste.DT cut.DT', searchDelay ? _fnThrottle( searchFn, searchDelay ) : searchFn ) .on( 'mouseup.DT', function(e) { // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn` // checks the value to see if it has changed. In other browsers it won't have. setTimeout( function () { searchFn.call(jqFilter[0], e); }, 10); } ) .on( 'keypress.DT', function(e) { /* Prevent form submission */ if ( e.keyCode == 13 ) { return false; } } ) .attr('aria-controls', tableId); // Update the input elements whenever the table is filtered $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) { if ( settings === s ) { // IE9 throws an 'unknown error' if document.activeElement is used // inside an iframe or frame... try { if ( jqFilter[0] !== document.activeElement ) { jqFilter.val( previousSearch.sSearch ); } } catch ( e ) {} } } ); return filter[0]; } /** * Filter the table using both the global filter and column based filtering * @param {object} oSettings dataTables settings object * @param {object} oSearch search information * @param {int} [iForce] force a research of the master array (1) or not (undefined or 0) * @memberof DataTable#oApi */ function _fnFilterComplete ( oSettings, oInput, iForce ) { var oPrevSearch = oSettings.oPreviousSearch; var aoPrevSearch = oSettings.aoPreSearchCols; var fnSaveFilter = function ( oFilter ) { /* Save the filtering values */ oPrevSearch.sSearch = oFilter.sSearch; oPrevSearch.bRegex = oFilter.bRegex; oPrevSearch.bSmart = oFilter.bSmart; oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive; oPrevSearch['return'] = oFilter['return']; }; var fnRegex = function ( o ) { // Backwards compatibility with the bEscapeRegex option return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex; }; // Resolve any column types that are unknown due to addition or invalidation // @todo As per sort - can this be moved into an event handler? _fnColumnTypes( oSettings ); /* In server-side processing all filtering is done by the server, so no point hanging around here */ if ( _fnDataSource( oSettings ) != 'ssp' ) { /* Global filter */ _fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive ); fnSaveFilter( oInput ); /* Now do the individual column filter */ for ( var i=0 ; i<aoPrevSearch.length ; i++ ) { _fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]), aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive ); } /* Custom filtering */ _fnFilterCustom( oSettings ); } else { fnSaveFilter( oInput ); } /* Tell the draw function we have been filtering */ oSettings.bFiltered = true; _fnCallbackFire( oSettings, null, 'search', [oSettings] ); } /** * Apply custom filtering functions * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnFilterCustom( settings ) { var filters = DataTable.ext.search; var displayRows = settings.aiDisplay; var row, rowIdx; for ( var i=0, ien=filters.length ; i<ien ; i++ ) { var rows = []; // Loop over each row and see if it should be included for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) { rowIdx = displayRows[ j ]; row = settings.aoData[ rowIdx ]; if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) { rows.push( rowIdx ); } } // So the array reference doesn't break set the results into the // existing array displayRows.length = 0; $.merge( displayRows, rows ); } } /** * Filter the table on a per-column basis * @param {object} oSettings dataTables settings object * @param {string} sInput string to filter on * @param {int} iColumn column to filter * @param {bool} bRegex treat search string as a regular expression or not * @param {bool} bSmart use smart filtering or not * @param {bool} bCaseInsensitive Do case insensitive matching or not * @memberof DataTable#oApi */ function _fnFilterColumn ( settings, searchStr, colIdx, regex, smart, caseInsensitive ) { if ( searchStr === '' ) { return; } var data; var out = []; var display = settings.aiDisplay; var rpSearch = _fnFilterCreateSearch( searchStr, regex, smart, caseInsensitive ); for ( var i=0 ; i<display.length ; i++ ) { data = settings.aoData[ display[i] ]._aFilterData[ colIdx ]; if ( rpSearch.test( data ) ) { out.push( display[i] ); } } settings.aiDisplay = out; } /** * Filter the data table based on user input and draw the table * @param {object} settings dataTables settings object * @param {string} input string to filter on * @param {int} force optional - force a research of the master array (1) or not (undefined or 0) * @param {bool} regex treat as a regular expression or not * @param {bool} smart perform smart filtering or not * @param {bool} caseInsensitive Do case insensitive matching or not * @memberof DataTable#oApi */ function _fnFilter( settings, input, force, regex, smart, caseInsensitive ) { var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive ); var prevSearch = settings.oPreviousSearch.sSearch; var displayMaster = settings.aiDisplayMaster; var display, invalidated, i; var filtered = []; // Need to take account of custom filtering functions - always filter if ( DataTable.ext.search.length !== 0 ) { force = true; } // Check if any of the rows were invalidated invalidated = _fnFilterData( settings ); // If the input is blank - we just want the full data set if ( input.length <= 0 ) { settings.aiDisplay = displayMaster.slice(); } else { // New search - start from the master array if ( invalidated || force || regex || prevSearch.length > input.length || input.indexOf(prevSearch) !== 0 || settings.bSorted // On resort, the display master needs to be // re-filtered since indexes will have changed ) { settings.aiDisplay = displayMaster.slice(); } // Search the display array display = settings.aiDisplay; for ( i=0 ; i<display.length ; i++ ) { if ( rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) { filtered.push( display[i] ); } } settings.aiDisplay = filtered; } } /** * Build a regular expression object suitable for searching a table * @param {string} sSearch string to search for * @param {bool} bRegex treat as a regular expression or not * @param {bool} bSmart perform smart filtering or not * @param {bool} bCaseInsensitive Do case insensitive matching or not * @returns {RegExp} constructed object * @memberof DataTable#oApi */ function _fnFilterCreateSearch( search, regex, smart, caseInsensitive ) { search = regex ? search : _fnEscapeRegex( search ); if ( smart ) { /* For smart filtering we want to allow the search to work regardless of * word order. We also want double quoted text to be preserved, so word * order is important - a la google. So this is what we want to * generate: * * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ */ var a = $.map( search.match( /["\u201C][^"\u201D]+["\u201D]|[^ ]+/g ) || [''], function ( word ) { if ( word.charAt(0) === '"' ) { var m = word.match( /^"(.*)"$/ ); word = m ? m[1] : word; } else if ( word.charAt(0) === '\u201C' ) { var m = word.match( /^\u201C(.*)\u201D$/ ); word = m ? m[1] : word; } return word.replace('"', ''); } ); search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$'; } return new RegExp( search, caseInsensitive ? 'i' : '' ); } /** * Escape a string such that it can be used in a regular expression * @param {string} sVal string to escape * @returns {string} escaped string * @memberof DataTable#oApi */ var _fnEscapeRegex = DataTable.util.escapeRegex; var __filter_div = $('<div>')[0]; var __filter_div_textContent = __filter_div.textContent !== undefined; // Update the filtering data for each row if needed (by invalidation or first run) function _fnFilterData ( settings ) { var columns = settings.aoColumns; var column; var i, j, ien, jen, filterData, cellData, row; var wasInvalidated = false; for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { row = settings.aoData[i]; if ( ! row._aFilterData ) { filterData = []; for ( j=0, jen=columns.length ; j<jen ; j++ ) { column = columns[j]; if ( column.bSearchable ) { cellData = _fnGetCellData( settings, i, j, 'filter' ); // Search in DataTables 1.10 is string based. In 1.11 this // should be altered to also allow strict type checking. if ( cellData === null ) { cellData = ''; } if ( typeof cellData !== 'string' && cellData.toString ) { cellData = cellData.toString(); } } else { cellData = ''; } // If it looks like there is an HTML entity in the string, // attempt to decode it so sorting works as expected. Note that // we could use a single line of jQuery to do this, but the DOM // method used here is much faster https://jsperf.com/html-decode if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) { __filter_div.innerHTML = cellData; cellData = __filter_div_textContent ? __filter_div.textContent : __filter_div.innerText; } if ( cellData.replace ) { cellData = cellData.replace(/[\r\n\u2028]/g, ''); } filterData.push( cellData ); } row._aFilterData = filterData; row._sFilterRow = filterData.join(' '); wasInvalidated = true; } } return wasInvalidated; } /** * Convert from the internal Hungarian notation to camelCase for external * interaction * @param {object} obj Object to convert * @returns {object} Inverted object * @memberof DataTable#oApi */ function _fnSearchToCamel ( obj ) { return { search: obj.sSearch, smart: obj.bSmart, regex: obj.bRegex, caseInsensitive: obj.bCaseInsensitive }; } /** * Convert from camelCase notation to the internal Hungarian. We could use the * Hungarian convert function here, but this is cleaner * @param {object} obj Object to convert * @returns {object} Inverted object * @memberof DataTable#oApi */ function _fnSearchToHung ( obj ) { return { sSearch: obj.search, bSmart: obj.smart, bRegex: obj.regex, bCaseInsensitive: obj.caseInsensitive }; } /** * Generate the node required for the info display * @param {object} oSettings dataTables settings object * @returns {node} Information element * @memberof DataTable#oApi */ function _fnFeatureHtmlInfo ( settings ) { var tid = settings.sTableId, nodes = settings.aanFeatures.i, n = $('<div/>', { 'class': settings.oClasses.sInfo, 'id': ! nodes ? tid+'_info' : null } ); if ( ! nodes ) { // Update display on each draw settings.aoDrawCallback.push( { "fn": _fnUpdateInfo, "sName": "information" } ); n .attr( 'role', 'status' ) .attr( 'aria-live', 'polite' ); // Table is described by our info div $(settings.nTable).attr( 'aria-describedby', tid+'_info' ); } return n[0]; } /** * Update the information elements in the display * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnUpdateInfo ( settings ) { /* Show information about the table */ var nodes = settings.aanFeatures.i; if ( nodes.length === 0 ) { return; } var lang = settings.oLanguage, start = settings._iDisplayStart+1, end = settings.fnDisplayEnd(), max = settings.fnRecordsTotal(), total = settings.fnRecordsDisplay(), out = total ? lang.sInfo : lang.sInfoEmpty; if ( total !== max ) { /* Record set after filtering */ out += ' ' + lang.sInfoFiltered; } // Convert the macros out += lang.sInfoPostFix; out = _fnInfoMacros( settings, out ); var callback = lang.fnInfoCallback; if ( callback !== null ) { out = callback.call( settings.oInstance, settings, start, end, max, total, out ); } $(nodes).html( out ); } function _fnInfoMacros ( settings, str ) { // When infinite scrolling, we are always starting at 1. _iDisplayStart is used only // internally var formatter = settings.fnFormatNumber, start = settings._iDisplayStart+1, len = settings._iDisplayLength, vis = settings.fnRecordsDisplay(), all = len === -1; return str. replace(/_START_/g, formatter.call( settings, start ) ). replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ). replace(/_MAX_/g, formatter.call( settings, settings.fnRecordsTotal() ) ). replace(/_TOTAL_/g, formatter.call( settings, vis ) ). replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ). replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) ); } /** * Draw the table for the first time, adding all required features * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnInitialise ( settings ) { var i, iLen, iAjaxStart=settings.iInitDisplayStart; var columns = settings.aoColumns, column; var features = settings.oFeatures; var deferLoading = settings.bDeferLoading; // value modified by the draw /* Ensure that the table data is fully initialised */ if ( ! settings.bInitialised ) { setTimeout( function(){ _fnInitialise( settings ); }, 200 ); return; } /* Show the display HTML options */ _fnAddOptionsHtml( settings ); /* Build and draw the header / footer for the table */ _fnBuildHead( settings ); _fnDrawHead( settings, settings.aoHeader ); _fnDrawHead( settings, settings.aoFooter ); /* Okay to show that something is going on now */ _fnProcessingDisplay( settings, true ); /* Calculate sizes for columns */ if ( features.bAutoWidth ) { _fnCalculateColumnWidths( settings ); } for ( i=0, iLen=columns.length ; i<iLen ; i++ ) { column = columns[i]; if ( column.sWidth ) { column.nTh.style.width = _fnStringToCss( column.sWidth ); } } _fnCallbackFire( settings, null, 'preInit', [settings] ); // If there is default sorting required - let's do it. The sort function // will do the drawing for us. Otherwise we draw the table regardless of the // Ajax source - this allows the table to look initialised for Ajax sourcing // data (show 'loading' message possibly) _fnReDraw( settings ); // Server-side processing init complete is done by _fnAjaxUpdateDraw var dataSrc = _fnDataSource( settings ); if ( dataSrc != 'ssp' || deferLoading ) { // if there is an ajax source load the data if ( dataSrc == 'ajax' ) { _fnBuildAjax( settings, [], function(json) { var aData = _fnAjaxDataSrc( settings, json ); // Got the data - add it to the table for ( i=0 ; i<aData.length ; i++ ) { _fnAddData( settings, aData[i] ); } // Reset the init display for cookie saving. We've already done // a filter, and therefore cleared it before. So we need to make // it appear 'fresh' settings.iInitDisplayStart = iAjaxStart; _fnReDraw( settings ); _fnProcessingDisplay( settings, false ); _fnInitComplete( settings, json ); }, settings ); } else { _fnProcessingDisplay( settings, false ); _fnInitComplete( settings ); } } } /** * Draw the table for the first time, adding all required features * @param {object} oSettings dataTables settings object * @param {object} [json] JSON from the server that completed the table, if using Ajax source * with client-side processing (optional) * @memberof DataTable#oApi */ function _fnInitComplete ( settings, json ) { settings._bInitComplete = true; // When data was added after the initialisation (data or Ajax) we need to // calculate the column sizing if ( json || settings.oInit.aaData ) { _fnAdjustColumnSizing( settings ); } _fnCallbackFire( settings, null, 'plugin-init', [settings, json] ); _fnCallbackFire( settings, 'aoInitComplete', 'init', [settings, json] ); } function _fnLengthChange ( settings, val ) { var len = parseInt( val, 10 ); settings._iDisplayLength = len; _fnLengthOverflow( settings ); // Fire length change event _fnCallbackFire( settings, null, 'length', [settings, len] ); } /** * Generate the node required for user display length changing * @param {object} settings dataTables settings object * @returns {node} Display length feature node * @memberof DataTable#oApi */ function _fnFeatureHtmlLength ( settings ) { var classes = settings.oClasses, tableId = settings.sTableId, menu = settings.aLengthMenu, d2 = Array.isArray( menu[0] ), lengths = d2 ? menu[0] : menu, language = d2 ? menu[1] : menu; var select = $('<select/>', { 'name': tableId+'_length', 'aria-controls': tableId, 'class': classes.sLengthSelect } ); for ( var i=0, ien=lengths.length ; i<ien ; i++ ) { select[0][ i ] = new Option( typeof language[i] === 'number' ? settings.fnFormatNumber( language[i] ) : language[i], lengths[i] ); } var div = $('<div><label/></div>').addClass( classes.sLength ); if ( ! settings.aanFeatures.l ) { div[0].id = tableId+'_length'; } div.children().append( settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML ) ); // Can't use `select` variable as user might provide their own and the // reference is broken by the use of outerHTML $('select', div) .val( settings._iDisplayLength ) .on( 'change.DT', function(e) { _fnLengthChange( settings, $(this).val() ); _fnDraw( settings ); } ); // Update node value whenever anything changes the table's length $(settings.nTable).on( 'length.dt.DT', function (e, s, len) { if ( settings === s ) { $('select', div).val( len ); } } ); return div[0]; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Note that most of the paging logic is done in * DataTable.ext.pager */ /** * Generate the node required for default pagination * @param {object} oSettings dataTables settings object * @returns {node} Pagination feature node * @memberof DataTable#oApi */ function _fnFeatureHtmlPaginate ( settings ) { var type = settings.sPaginationType, plugin = DataTable.ext.pager[ type ], modern = typeof plugin === 'function', redraw = function( settings ) { _fnDraw( settings ); }, node = $('<div/>').addClass( settings.oClasses.sPaging + type )[0], features = settings.aanFeatures; if ( ! modern ) { plugin.fnInit( settings, node, redraw ); } /* Add a draw callback for the pagination on first instance, to update the paging display */ if ( ! features.p ) { node.id = settings.sTableId+'_paginate'; settings.aoDrawCallback.push( { "fn": function( settings ) { if ( modern ) { var start = settings._iDisplayStart, len = settings._iDisplayLength, visRecords = settings.fnRecordsDisplay(), all = len === -1, page = all ? 0 : Math.ceil( start / len ), pages = all ? 1 : Math.ceil( visRecords / len ), buttons = plugin(page, pages), i, ien; for ( i=0, ien=features.p.length ; i<ien ; i++ ) { _fnRenderer( settings, 'pageButton' )( settings, features.p[i], i, buttons, page, pages ); } } else { plugin.fnUpdate( settings, redraw ); } }, "sName": "pagination" } ); } return node; } /** * Alter the display settings to change the page * @param {object} settings DataTables settings object * @param {string|int} action Paging action to take: "first", "previous", * "next" or "last" or page number to jump to (integer) * @param [bool] redraw Automatically draw the update or not * @returns {bool} true page has changed, false - no change * @memberof DataTable#oApi */ function _fnPageChange ( settings, action, redraw ) { var start = settings._iDisplayStart, len = settings._iDisplayLength, records = settings.fnRecordsDisplay(); if ( records === 0 || len === -1 ) { start = 0; } else if ( typeof action === "number" ) { start = action * len; if ( start > records ) { start = 0; } } else if ( action == "first" ) { start = 0; } else if ( action == "previous" ) { start = len >= 0 ? start - len : 0; if ( start < 0 ) { start = 0; } } else if ( action == "next" ) { if ( start + len < records ) { start += len; } } else if ( action == "last" ) { start = Math.floor( (records-1) / len) * len; } else { _fnLog( settings, 0, "Unknown paging action: "+action, 5 ); } var changed = settings._iDisplayStart !== start; settings._iDisplayStart = start; if ( changed ) { _fnCallbackFire( settings, null, 'page', [settings] ); if ( redraw ) { _fnDraw( settings ); } } else { // No change event - paging was called, but no change _fnCallbackFire( settings, null, 'page-nc', [settings] ); } return changed; } /** * Generate the node required for the processing node * @param {object} settings dataTables settings object * @returns {node} Processing element * @memberof DataTable#oApi */ function _fnFeatureHtmlProcessing ( settings ) { return $('<div/>', { 'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null, 'class': settings.oClasses.sProcessing, 'role': 'status' } ) .html( settings.oLanguage.sProcessing ) .append('<div><div></div><div></div><div></div><div></div></div>') .insertBefore( settings.nTable )[0]; } /** * Display or hide the processing indicator * @param {object} settings dataTables settings object * @param {bool} show Show the processing indicator (true) or not (false) * @memberof DataTable#oApi */ function _fnProcessingDisplay ( settings, show ) { if ( settings.oFeatures.bProcessing ) { $(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' ); } _fnCallbackFire( settings, null, 'processing', [settings, show] ); } /** * Add any control elements for the table - specifically scrolling * @param {object} settings dataTables settings object * @returns {node} Node to add to the DOM * @memberof DataTable#oApi */ function _fnFeatureHtmlTable ( settings ) { var table = $(settings.nTable); // Scrolling from here on in var scroll = settings.oScroll; if ( scroll.sX === '' && scroll.sY === '' ) { return settings.nTable; } var scrollX = scroll.sX; var scrollY = scroll.sY; var classes = settings.oClasses; var caption = table.children('caption'); var captionSide = caption.length ? caption[0]._captionSide : null; var headerClone = $( table[0].cloneNode(false) ); var footerClone = $( table[0].cloneNode(false) ); var footer = table.children('tfoot'); var _div = '<div/>'; var size = function ( s ) { return !s ? null : _fnStringToCss( s ); }; if ( ! footer.length ) { footer = null; } /* * The HTML structure that we want to generate in this function is: * div - scroller * div - scroll head * div - scroll head inner * table - scroll head table * thead - thead * div - scroll body * table - table (master table) * thead - thead clone for sizing * tbody - tbody * div - scroll foot * div - scroll foot inner * table - scroll foot table * tfoot - tfoot */ var scroller = $( _div, { 'class': classes.sScrollWrapper } ) .append( $(_div, { 'class': classes.sScrollHead } ) .css( { overflow: 'hidden', position: 'relative', border: 0, width: scrollX ? size(scrollX) : '100%' } ) .append( $(_div, { 'class': classes.sScrollHeadInner } ) .css( { 'box-sizing': 'content-box', width: scroll.sXInner || '100%' } ) .append( headerClone .removeAttr('id') .css( 'margin-left', 0 ) .append( captionSide === 'top' ? caption : null ) .append( table.children('thead') ) ) ) ) .append( $(_div, { 'class': classes.sScrollBody } ) .css( { position: 'relative', overflow: 'auto', width: size( scrollX ) } ) .append( table ) ); if ( footer ) { scroller.append( $(_div, { 'class': classes.sScrollFoot } ) .css( { overflow: 'hidden', border: 0, width: scrollX ? size(scrollX) : '100%' } ) .append( $(_div, { 'class': classes.sScrollFootInner } ) .append( footerClone .removeAttr('id') .css( 'margin-left', 0 ) .append( captionSide === 'bottom' ? caption : null ) .append( table.children('tfoot') ) ) ) ); } var children = scroller.children(); var scrollHead = children[0]; var scrollBody = children[1]; var scrollFoot = footer ? children[2] : null; // When the body is scrolled, then we also want to scroll the headers if ( scrollX ) { $(scrollBody).on( 'scroll.DT', function (e) { var scrollLeft = this.scrollLeft; scrollHead.scrollLeft = scrollLeft; if ( footer ) { scrollFoot.scrollLeft = scrollLeft; } } ); } $(scrollBody).css('max-height', scrollY); if (! scroll.bCollapse) { $(scrollBody).css('height', scrollY); } settings.nScrollHead = scrollHead; settings.nScrollBody = scrollBody; settings.nScrollFoot = scrollFoot; // On redraw - align columns settings.aoDrawCallback.push( { "fn": _fnScrollDraw, "sName": "scrolling" } ); return scroller[0]; } /** * Update the header, footer and body tables for resizing - i.e. column * alignment. * * Welcome to the most horrible function DataTables. The process that this * function follows is basically: * 1. Re-create the table inside the scrolling div * 2. Take live measurements from the DOM * 3. Apply the measurements to align the columns * 4. Clean up * * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnScrollDraw ( settings ) { // Given that this is such a monster function, a lot of variables are use // to try and keep the minimised size as small as possible var scroll = settings.oScroll, scrollX = scroll.sX, scrollXInner = scroll.sXInner, scrollY = scroll.sY, barWidth = scroll.iBarWidth, divHeader = $(settings.nScrollHead), divHeaderStyle = divHeader[0].style, divHeaderInner = divHeader.children('div'), divHeaderInnerStyle = divHeaderInner[0].style, divHeaderTable = divHeaderInner.children('table'), divBodyEl = settings.nScrollBody, divBody = $(divBodyEl), divBodyStyle = divBodyEl.style, divFooter = $(settings.nScrollFoot), divFooterInner = divFooter.children('div'), divFooterTable = divFooterInner.children('table'), header = $(settings.nTHead), table = $(settings.nTable), tableEl = table[0], tableStyle = tableEl.style, footer = settings.nTFoot ? $(settings.nTFoot) : null, browser = settings.oBrowser, ie67 = browser.bScrollOversize, dtHeaderCells = _pluck( settings.aoColumns, 'nTh' ), headerTrgEls, footerTrgEls, headerSrcEls, footerSrcEls, headerCopy, footerCopy, headerWidths=[], footerWidths=[], headerContent=[], footerContent=[], idx, correction, sanityWidth, zeroOut = function(nSizer) { var style = nSizer.style; style.paddingTop = "0"; style.paddingBottom = "0"; style.borderTopWidth = "0"; style.borderBottomWidth = "0"; style.height = 0; }; // If the scrollbar visibility has changed from the last draw, we need to // adjust the column sizes as the table width will have changed to account // for the scrollbar var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight; if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) { settings.scrollBarVis = scrollBarVis; _fnAdjustColumnSizing( settings ); return; // adjust column sizing will call this function again } else { settings.scrollBarVis = scrollBarVis; } /* * 1. Re-create the table inside the scrolling div */ // Remove the old minimised thead and tfoot elements in the inner table table.children('thead, tfoot').remove(); if ( footer ) { footerCopy = footer.clone().prependTo( table ); footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized footerSrcEls = footerCopy.find('tr'); footerCopy.find('[id]').removeAttr('id'); } // Clone the current header and footer elements and then place it into the inner table headerCopy = header.clone().prependTo( table ); headerTrgEls = header.find('tr'); // original header is in its own table headerSrcEls = headerCopy.find('tr'); headerCopy.find('th, td').removeAttr('tabindex'); headerCopy.find('[id]').removeAttr('id'); /* * 2. Take live measurements from the DOM - do not alter the DOM itself! */ // Remove old sizing and apply the calculated column widths // Get the unique column headers in the newly created (cloned) header. We want to apply the // calculated sizes to this header if ( ! scrollX ) { divBodyStyle.width = '100%'; divHeader[0].style.width = '100%'; } $.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) { idx = _fnVisibleToColumnIndex( settings, i ); el.style.width = settings.aoColumns[idx].sWidth; } ); if ( footer ) { _fnApplyToChildren( function(n) { n.style.width = ""; }, footerSrcEls ); } // Size the table as a whole sanityWidth = table.outerWidth(); if ( scrollX === "" ) { // No x scrolling tableStyle.width = "100%"; // IE7 will make the width of the table when 100% include the scrollbar // - which is shouldn't. When there is a scrollbar we need to take this // into account. if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll") ) { tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth); } // Recalculate the sanity width sanityWidth = table.outerWidth(); } else if ( scrollXInner !== "" ) { // legacy x scroll inner has been given - use it tableStyle.width = _fnStringToCss(scrollXInner); // Recalculate the sanity width sanityWidth = table.outerWidth(); } // Hidden header should have zero height, so remove padding and borders. Then // set the width based on the real headers // Apply all styles in one pass _fnApplyToChildren( zeroOut, headerSrcEls ); // Read all widths in next pass _fnApplyToChildren( function(nSizer) { var style = window.getComputedStyle ? window.getComputedStyle(nSizer).width : _fnStringToCss( $(nSizer).width() ); headerContent.push( nSizer.innerHTML ); headerWidths.push( style ); }, headerSrcEls ); // Apply all widths in final pass _fnApplyToChildren( function(nToSize, i) { nToSize.style.width = headerWidths[i]; }, headerTrgEls ); $(headerSrcEls).css('height', 0); /* Same again with the footer if we have one */ if ( footer ) { _fnApplyToChildren( zeroOut, footerSrcEls ); _fnApplyToChildren( function(nSizer) { footerContent.push( nSizer.innerHTML ); footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) ); }, footerSrcEls ); _fnApplyToChildren( function(nToSize, i) { nToSize.style.width = footerWidths[i]; }, footerTrgEls ); $(footerSrcEls).height(0); } /* * 3. Apply the measurements */ // "Hide" the header and footer that we used for the sizing. We need to keep // the content of the cell so that the width applied to the header and body // both match, but we want to hide it completely. We want to also fix their // width to what they currently are _fnApplyToChildren( function(nSizer, i) { nSizer.innerHTML = '<div class="dataTables_sizing">'+headerContent[i]+'</div>'; nSizer.childNodes[0].style.height = "0"; nSizer.childNodes[0].style.overflow = "hidden"; nSizer.style.width = headerWidths[i]; }, headerSrcEls ); if ( footer ) { _fnApplyToChildren( function(nSizer, i) { nSizer.innerHTML = '<div class="dataTables_sizing">'+footerContent[i]+'</div>'; nSizer.childNodes[0].style.height = "0"; nSizer.childNodes[0].style.overflow = "hidden"; nSizer.style.width = footerWidths[i]; }, footerSrcEls ); } // Sanity check that the table is of a sensible width. If not then we are going to get // misalignment - try to prevent this by not allowing the table to shrink below its min width if ( Math.round(table.outerWidth()) < Math.round(sanityWidth) ) { // The min width depends upon if we have a vertical scrollbar visible or not */ correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll")) ? sanityWidth+barWidth : sanityWidth; // IE6/7 are a law unto themselves... if ( ie67 && (divBodyEl.scrollHeight > divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll") ) { tableStyle.width = _fnStringToCss( correction-barWidth ); } // And give the user a warning that we've stopped the table getting too small if ( scrollX === "" || scrollXInner !== "" ) { _fnLog( settings, 1, 'Possible column misalignment', 6 ); } } else { correction = '100%'; } // Apply to the container elements divBodyStyle.width = _fnStringToCss( correction ); divHeaderStyle.width = _fnStringToCss( correction ); if ( footer ) { settings.nScrollFoot.style.width = _fnStringToCss( correction ); } /* * 4. Clean up */ if ( ! scrollY ) { /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting * the scrollbar height from the visible display, rather than adding it on. We need to * set the height in order to sort this. Don't want to do it in any other browsers. */ if ( ie67 ) { divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth ); } } /* Finally set the width's of the header and footer tables */ var iOuterWidth = table.outerWidth(); divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth ); divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth ); // Figure out if there are scrollbar present - if so then we need a the header and footer to // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll"; var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' ); divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px"; if ( footer ) { divFooterTable[0].style.width = _fnStringToCss( iOuterWidth ); divFooterInner[0].style.width = _fnStringToCss( iOuterWidth ); divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px"; } // Correct DOM ordering for colgroup - comes before the thead table.children('colgroup').insertBefore( table.children('thead') ); /* Adjust the position of the header in case we loose the y-scrollbar */ divBody.trigger('scroll'); // If sorting or filtering has occurred, jump the scrolling back to the top // only if we aren't holding the position if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) { divBodyEl.scrollTop = 0; } } /** * Apply a given function to the display child nodes of an element array (typically * TD children of TR rows * @param {function} fn Method to apply to the objects * @param array {nodes} an1 List of elements to look through for display children * @param array {nodes} an2 Another list (identical structure to the first) - optional * @memberof DataTable#oApi */ function _fnApplyToChildren( fn, an1, an2 ) { var index=0, i=0, iLen=an1.length; var nNode1, nNode2; while ( i < iLen ) { nNode1 = an1[i].firstChild; nNode2 = an2 ? an2[i].firstChild : null; while ( nNode1 ) { if ( nNode1.nodeType === 1 ) { if ( an2 ) { fn( nNode1, nNode2, index ); } else { fn( nNode1, index ); } index++; } nNode1 = nNode1.nextSibling; nNode2 = an2 ? nNode2.nextSibling : null; } i++; } } var __re_html_remove = /<.*?>/g; /** * Calculate the width of columns for the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnCalculateColumnWidths ( oSettings ) { var table = oSettings.nTable, columns = oSettings.aoColumns, scroll = oSettings.oScroll, scrollY = scroll.sY, scrollX = scroll.sX, scrollXInner = scroll.sXInner, columnCount = columns.length, visibleColumns = _fnGetColumns( oSettings, 'bVisible' ), headerCells = $('th', oSettings.nTHead), tableWidthAttr = table.getAttribute('width'), // from DOM element tableContainer = table.parentNode, userInputs = false, i, column, columnIdx, width, outerWidth, browser = oSettings.oBrowser, ie67 = browser.bScrollOversize; var styleWidth = table.style.width; if ( styleWidth && styleWidth.indexOf('%') !== -1 ) { tableWidthAttr = styleWidth; } /* Convert any user input sizes into pixel sizes */ var sizes = _fnConvertToWidth(_pluck(columns, 'sWidthOrig'), tableContainer); for ( i=0 ; i<visibleColumns.length ; i++ ) { column = columns[ visibleColumns[i] ]; if ( column.sWidth !== null ) { column.sWidth = sizes[i]; userInputs = true; } } /* If the number of columns in the DOM equals the number that we have to * process in DataTables, then we can use the offsets that are created by * the web- browser. No custom sizes can be set in order for this to happen, * nor scrolling used */ if ( ie67 || ! userInputs && ! scrollX && ! scrollY && columnCount == _fnVisbleColumns( oSettings ) && columnCount == headerCells.length ) { for ( i=0 ; i<columnCount ; i++ ) { var colIdx = _fnVisibleToColumnIndex( oSettings, i ); if ( colIdx !== null ) { columns[ colIdx ].sWidth = _fnStringToCss( headerCells.eq(i).width() ); } } } else { // Otherwise construct a single row, worst case, table with the widest // node in the data, assign any user defined widths, then insert it into // the DOM and allow the browser to do all the hard work of calculating // table widths var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table .css( 'visibility', 'hidden' ) .removeAttr( 'id' ); // Clean up the table body tmpTable.find('tbody tr').remove(); var tr = $('<tr/>').appendTo( tmpTable.find('tbody') ); // Clone the table header and footer - we can't use the header / footer // from the cloned table, since if scrolling is active, the table's // real header and footer are contained in different table tags tmpTable.find('thead, tfoot').remove(); tmpTable .append( $(oSettings.nTHead).clone() ) .append( $(oSettings.nTFoot).clone() ); // Remove any assigned widths from the footer (from scrolling) tmpTable.find('tfoot th, tfoot td').css('width', ''); // Apply custom sizing to the cloned header headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] ); for ( i=0 ; i<visibleColumns.length ; i++ ) { column = columns[ visibleColumns[i] ]; headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ? _fnStringToCss( column.sWidthOrig ) : ''; // For scrollX we need to force the column width otherwise the // browser will collapse it. If this width is smaller than the // width the column requires, then it will have no effect if ( column.sWidthOrig && scrollX ) { $( headerCells[i] ).append( $('<div/>').css( { width: column.sWidthOrig, margin: 0, padding: 0, border: 0, height: 1 } ) ); } } // Find the widest cell for each column and put it into the table if ( oSettings.aoData.length ) { for ( i=0 ; i<visibleColumns.length ; i++ ) { columnIdx = visibleColumns[i]; column = columns[ columnIdx ]; $( _fnGetWidestNode( oSettings, columnIdx ) ) .clone( false ) .append( column.sContentPadding ) .appendTo( tr ); } } // Tidy the temporary table - remove name attributes so there aren't // duplicated in the dom (radio elements for example) $('[name]', tmpTable).removeAttr('name'); // Table has been built, attach to the document so we can work with it. // A holding element is used, positioned at the top of the container // with minimal height, so it has no effect on if the container scrolls // or not. Otherwise it might trigger scrolling when it actually isn't // needed var holder = $('<div/>').css( scrollX || scrollY ? { position: 'absolute', top: 0, left: 0, height: 1, right: 0, overflow: 'hidden' } : {} ) .append( tmpTable ) .appendTo( tableContainer ); // When scrolling (X or Y) we want to set the width of the table as // appropriate. However, when not scrolling leave the table width as it // is. This results in slightly different, but I think correct behaviour if ( scrollX && scrollXInner ) { tmpTable.width( scrollXInner ); } else if ( scrollX ) { tmpTable.css( 'width', 'auto' ); tmpTable.removeAttr('width'); // If there is no width attribute or style, then allow the table to // collapse if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) { tmpTable.width( tableContainer.clientWidth ); } } else if ( scrollY ) { tmpTable.width( tableContainer.clientWidth ); } else if ( tableWidthAttr ) { tmpTable.width( tableWidthAttr ); } // Get the width of each column in the constructed table - we need to // know the inner width (so it can be assigned to the other table's // cells) and the outer width so we can calculate the full width of the // table. This is safe since DataTables requires a unique cell for each // column, but if ever a header can span multiple columns, this will // need to be modified. var total = 0; for ( i=0 ; i<visibleColumns.length ; i++ ) { var cell = $(headerCells[i]); var border = cell.outerWidth() - cell.width(); // Use getBounding... where possible (not IE8-) because it can give // sub-pixel accuracy, which we then want to round up! var bounding = browser.bBounding ? Math.ceil( headerCells[i].getBoundingClientRect().width ) : cell.outerWidth(); // Total is tracked to remove any sub-pixel errors as the outerWidth // of the table might not equal the total given here (IE!). total += bounding; // Width for each column to use columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding - border ); } table.style.width = _fnStringToCss( total ); // Finished with the table - ditch it holder.remove(); } // If there is a width attr, we want to attach an event listener which // allows the table sizing to automatically adjust when the window is // resized. Use the width attr rather than CSS, since we can't know if the // CSS is a relative value or absolute - DOM read is always px. if ( tableWidthAttr ) { table.style.width = _fnStringToCss( tableWidthAttr ); } if ( (tableWidthAttr || scrollX) && ! oSettings._reszEvt ) { var bindResize = function () { $(window).on('resize.DT-'+oSettings.sInstance, _fnThrottle( function () { _fnAdjustColumnSizing( oSettings ); } ) ); }; // IE6/7 will crash if we bind a resize event handler on page load. // To be removed in 1.11 which drops IE6/7 support if ( ie67 ) { setTimeout( bindResize, 1000 ); } else { bindResize(); } oSettings._reszEvt = true; } } /** * Throttle the calls to a function. Arguments and context are maintained for * the throttled function * @param {function} fn Function to be called * @param {int} [freq=200] call frequency in mS * @returns {function} wrapped function * @memberof DataTable#oApi */ var _fnThrottle = DataTable.util.throttle; /** * Convert a set of CSS units width to pixels (e.g. 2em) * @param {string[]} widths widths to be converted * @param {node} parent parent to get the with for (required for relative widths) - optional * @returns {int[]} widths in pixels * @memberof DataTable#oApi */ function _fnConvertToWidth ( widths, parent ) { var els = []; var results = []; // Add the elements in a single loop so we only need to reflow once for (var i=0 ; i<widths.length ; i++) { if (widths[i]) { els.push( $('<div/>') .css( 'width', _fnStringToCss( widths[i] ) ) .appendTo( parent || document.body ) ) } else { els.push(null); } } // Get the sizes (will reflow once) for (var i=0 ; i<widths.length ; i++) { results.push(els[i] ? els[i][0].offsetWidth : null); } // Tidy $(els).remove(); return results; } /** * Get the widest node * @param {object} settings dataTables settings object * @param {int} colIdx column of interest * @returns {node} widest table node * @memberof DataTable#oApi */ function _fnGetWidestNode( settings, colIdx ) { var idx = _fnGetMaxLenString( settings, colIdx ); if ( idx < 0 ) { return null; } var data = settings.aoData[ idx ]; return ! data.nTr ? // Might not have been created when deferred rendering $('<td/>').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] : data.anCells[ colIdx ]; } /** * Get the maximum strlen for each data column * @param {object} settings dataTables settings object * @param {int} colIdx column of interest * @returns {string} max string length for each column * @memberof DataTable#oApi */ function _fnGetMaxLenString( settings, colIdx ) { var s, max=-1, maxIdx = -1; for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { s = _fnGetCellData( settings, i, colIdx, 'display' )+''; s = s.replace( __re_html_remove, '' ); s = s.replace( / /g, ' ' ); if ( s.length > max ) { max = s.length; maxIdx = i; } } return maxIdx; } /** * Append a CSS unit (only if required) to a string * @param {string} value to css-ify * @returns {string} value with css unit * @memberof DataTable#oApi */ function _fnStringToCss( s ) { if ( s === null ) { return '0px'; } if ( typeof s == 'number' ) { return s < 0 ? '0px' : s+'px'; } // Check it has a unit character already return s.match(/\d$/) ? s+'px' : s; } function _fnSortFlatten ( settings ) { var i, iLen, k, kLen, aSort = [], aiOrig = [], aoColumns = settings.aoColumns, aDataSort, iCol, sType, srcCol, fixed = settings.aaSortingFixed, fixedObj = $.isPlainObject( fixed ), nestedSort = [], add = function ( a ) { if ( a.length && ! Array.isArray( a[0] ) ) { // 1D array nestedSort.push( a ); } else { // 2D array $.merge( nestedSort, a ); } }; // Build the sort array, with pre-fix and post-fix options if they have been // specified if ( Array.isArray( fixed ) ) { add( fixed ); } if ( fixedObj && fixed.pre ) { add( fixed.pre ); } add( settings.aaSorting ); if (fixedObj && fixed.post ) { add( fixed.post ); } for ( i=0 ; i<nestedSort.length ; i++ ) { srcCol = nestedSort[i][0]; aDataSort = aoColumns[ srcCol ].aDataSort; for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ ) { iCol = aDataSort[k]; sType = aoColumns[ iCol ].sType || 'string'; if ( nestedSort[i]._idx === undefined ) { nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting ); } aSort.push( { src: srcCol, col: iCol, dir: nestedSort[i][1], index: nestedSort[i]._idx, type: sType, formatter: DataTable.ext.type.order[ sType+"-pre" ] } ); } } return aSort; } /** * Change the order of the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi * @todo This really needs split up! */ function _fnSort ( oSettings ) { var i, ien, iLen, j, jLen, k, kLen, sDataType, nTh, aiOrig = [], oExtSort = DataTable.ext.type.order, aoData = oSettings.aoData, aoColumns = oSettings.aoColumns, aDataSort, data, iCol, sType, oSort, formatters = 0, sortCol, displayMaster = oSettings.aiDisplayMaster, aSort; // Resolve any column types that are unknown due to addition or invalidation // @todo Can this be moved into a 'data-ready' handler which is called when // data is going to be used in the table? _fnColumnTypes( oSettings ); aSort = _fnSortFlatten( oSettings ); for ( i=0, ien=aSort.length ; i<ien ; i++ ) { sortCol = aSort[i]; // Track if we can use the fast sort algorithm if ( sortCol.formatter ) { formatters++; } // Load the data needed for the sort, for each cell _fnSortData( oSettings, sortCol.col ); } /* No sorting required if server-side or no sorting array */ if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 ) { // Create a value - key array of the current row positions such that we can use their // current position during the sort, if values match, in order to perform stable sorting for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) { aiOrig[ displayMaster[i] ] = i; } /* Do the sort - here we want multi-column sorting based on a given data source (column) * and sorting function (from oSort) in a certain direction. It's reasonably complex to * follow on it's own, but this is what we want (example two column sorting): * fnLocalSorting = function(a,b){ * var iTest; * iTest = oSort['string-asc']('data11', 'data12'); * if (iTest !== 0) * return iTest; * iTest = oSort['numeric-desc']('data21', 'data22'); * if (iTest !== 0) * return iTest; * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] ); * } * Basically we have a test for each sorting column, if the data in that column is equal, * test the next column. If all columns match, then we use a numeric sort on the row * positions in the original data array to provide a stable sort. * * Note - I know it seems excessive to have two sorting methods, but the first is around * 15% faster, so the second is only maintained for backwards compatibility with sorting * methods which do not have a pre-sort formatting function. */ if ( formatters === aSort.length ) { // All sort types have formatting functions displayMaster.sort( function ( a, b ) { var x, y, k, test, sort, len=aSort.length, dataA = aoData[a]._aSortData, dataB = aoData[b]._aSortData; for ( k=0 ; k<len ; k++ ) { sort = aSort[k]; x = dataA[ sort.col ]; y = dataB[ sort.col ]; test = x<y ? -1 : x>y ? 1 : 0; if ( test !== 0 ) { return sort.dir === 'asc' ? test : -test; } } x = aiOrig[a]; y = aiOrig[b]; return x<y ? -1 : x>y ? 1 : 0; } ); } else { // Depreciated - remove in 1.11 (providing a plug-in option) // Not all sort types have formatting methods, so we have to call their sorting // methods. displayMaster.sort( function ( a, b ) { var x, y, k, l, test, sort, fn, len=aSort.length, dataA = aoData[a]._aSortData, dataB = aoData[b]._aSortData; for ( k=0 ; k<len ; k++ ) { sort = aSort[k]; x = dataA[ sort.col ]; y = dataB[ sort.col ]; fn = oExtSort[ sort.type+"-"+sort.dir ] || oExtSort[ "string-"+sort.dir ]; test = fn( x, y ); if ( test !== 0 ) { return test; } } x = aiOrig[a]; y = aiOrig[b]; return x<y ? -1 : x>y ? 1 : 0; } ); } } /* Tell the draw function that we have sorted the data */ oSettings.bSorted = true; } function _fnSortAria ( settings ) { var label; var nextSort; var columns = settings.aoColumns; var aSort = _fnSortFlatten( settings ); var oAria = settings.oLanguage.oAria; // ARIA attributes - need to loop all columns, to update all (removing old // attributes as needed) for ( var i=0, iLen=columns.length ; i<iLen ; i++ ) { var col = columns[i]; var asSorting = col.asSorting; var sTitle = col.ariaTitle || col.sTitle.replace( /<.*?>/g, "" ); var th = col.nTh; // IE7 is throwing an error when setting these properties with jQuery's // attr() and removeAttr() methods... th.removeAttribute('aria-sort'); /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */ if ( col.bSortable ) { if ( aSort.length > 0 && aSort[0].col == i ) { th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" ); nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0]; } else { nextSort = asSorting[0]; } label = sTitle + ( nextSort === "asc" ? oAria.sSortAscending : oAria.sSortDescending ); } else { label = sTitle; } th.setAttribute('aria-label', label); } } /** * Function to run on user sort request * @param {object} settings dataTables settings object * @param {node} attachTo node to attach the handler to * @param {int} colIdx column sorting index * @param {boolean} [append=false] Append the requested sort to the existing * sort if true (i.e. multi-column sort) * @param {function} [callback] callback function * @memberof DataTable#oApi */ function _fnSortListener ( settings, colIdx, append, callback ) { var col = settings.aoColumns[ colIdx ]; var sorting = settings.aaSorting; var asSorting = col.asSorting; var nextSortIdx; var next = function ( a, overflow ) { var idx = a._idx; if ( idx === undefined ) { idx = $.inArray( a[1], asSorting ); } return idx+1 < asSorting.length ? idx+1 : overflow ? null : 0; }; // Convert to 2D array if needed if ( typeof sorting[0] === 'number' ) { sorting = settings.aaSorting = [ sorting ]; } // If appending the sort then we are multi-column sorting if ( append && settings.oFeatures.bSortMulti ) { // Are we already doing some kind of sort on this column? var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') ); if ( sortIdx !== -1 ) { // Yes, modify the sort nextSortIdx = next( sorting[sortIdx], true ); if ( nextSortIdx === null && sorting.length === 1 ) { nextSortIdx = 0; // can't remove sorting completely } if ( nextSortIdx === null ) { sorting.splice( sortIdx, 1 ); } else { sorting[sortIdx][1] = asSorting[ nextSortIdx ]; sorting[sortIdx]._idx = nextSortIdx; } } else { // No sort on this column yet sorting.push( [ colIdx, asSorting[0], 0 ] ); sorting[sorting.length-1]._idx = 0; } } else if ( sorting.length && sorting[0][0] == colIdx ) { // Single column - already sorting on this column, modify the sort nextSortIdx = next( sorting[0] ); sorting.length = 1; sorting[0][1] = asSorting[ nextSortIdx ]; sorting[0]._idx = nextSortIdx; } else { // Single column - sort only on this column sorting.length = 0; sorting.push( [ colIdx, asSorting[0] ] ); sorting[0]._idx = 0; } // Run the sort by calling a full redraw _fnReDraw( settings ); // callback used for async user interaction if ( typeof callback == 'function' ) { callback( settings ); } } /** * Attach a sort handler (click) to a node * @param {object} settings dataTables settings object * @param {node} attachTo node to attach the handler to * @param {int} colIdx column sorting index * @param {function} [callback] callback function * @memberof DataTable#oApi */ function _fnSortAttachListener ( settings, attachTo, colIdx, callback ) { var col = settings.aoColumns[ colIdx ]; _fnBindAction( attachTo, {}, function (e) { /* If the column is not sortable - don't to anything */ if ( col.bSortable === false ) { return; } // If processing is enabled use a timeout to allow the processing // display to be shown - otherwise to it synchronously if ( settings.oFeatures.bProcessing ) { _fnProcessingDisplay( settings, true ); setTimeout( function() { _fnSortListener( settings, colIdx, e.shiftKey, callback ); // In server-side processing, the draw callback will remove the // processing display if ( _fnDataSource( settings ) !== 'ssp' ) { _fnProcessingDisplay( settings, false ); } }, 0 ); } else { _fnSortListener( settings, colIdx, e.shiftKey, callback ); } } ); } /** * Set the sorting classes on table's body, Note: it is safe to call this function * when bSort and bSortClasses are false * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnSortingClasses( settings ) { var oldSort = settings.aLastSort; var sortClass = settings.oClasses.sSortColumn; var sort = _fnSortFlatten( settings ); var features = settings.oFeatures; var i, ien, colIdx; if ( features.bSort && features.bSortClasses ) { // Remove old sorting classes for ( i=0, ien=oldSort.length ; i<ien ; i++ ) { colIdx = oldSort[i].src; // Remove column sorting $( _pluck( settings.aoData, 'anCells', colIdx ) ) .removeClass( sortClass + (i<2 ? i+1 : 3) ); } // Add new column sorting for ( i=0, ien=sort.length ; i<ien ; i++ ) { colIdx = sort[i].src; $( _pluck( settings.aoData, 'anCells', colIdx ) ) .addClass( sortClass + (i<2 ? i+1 : 3) ); } } settings.aLastSort = sort; } // Get the data to sort a column, be it from cache, fresh (populating the // cache), or from a sort formatter function _fnSortData( settings, idx ) { // Custom sorting function - provided by the sort data type var column = settings.aoColumns[ idx ]; var customSort = DataTable.ext.order[ column.sSortDataType ]; var customData; if ( customSort ) { customData = customSort.call( settings.oInstance, settings, idx, _fnColumnIndexToVisible( settings, idx ) ); } // Use / populate cache var row, cellData; var formatter = DataTable.ext.type.order[ column.sType+"-pre" ]; for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { row = settings.aoData[i]; if ( ! row._aSortData ) { row._aSortData = []; } if ( ! row._aSortData[idx] || customSort ) { cellData = customSort ? customData[i] : // If there was a custom sort function, use data from there _fnGetCellData( settings, i, idx, 'sort' ); row._aSortData[ idx ] = formatter ? formatter( cellData ) : cellData; } } } /** * Save the state of a table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnSaveState ( settings ) { if (settings._bLoadingState) { return; } /* Store the interesting variables */ var state = { time: +new Date(), start: settings._iDisplayStart, length: settings._iDisplayLength, order: $.extend( true, [], settings.aaSorting ), search: _fnSearchToCamel( settings.oPreviousSearch ), columns: $.map( settings.aoColumns, function ( col, i ) { return { visible: col.bVisible, search: _fnSearchToCamel( settings.aoPreSearchCols[i] ) }; } ) }; settings.oSavedState = state; _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] ); if ( settings.oFeatures.bStateSave && !settings.bDestroying ) { settings.fnStateSaveCallback.call( settings.oInstance, settings, state ); } } /** * Attempt to load a saved table state * @param {object} oSettings dataTables settings object * @param {object} oInit DataTables init object so we can override settings * @param {function} callback Callback to execute when the state has been loaded * @memberof DataTable#oApi */ function _fnLoadState ( settings, oInit, callback ) { if ( ! settings.oFeatures.bStateSave ) { callback(); return; } var loaded = function(state) { _fnImplementState(settings, state, callback); } var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded ); if ( state !== undefined ) { _fnImplementState( settings, state, callback ); } // otherwise, wait for the loaded callback to be executed return true; } function _fnImplementState ( settings, s, callback) { var i, ien; var columns = settings.aoColumns; settings._bLoadingState = true; // When StateRestore was introduced the state could now be implemented at any time // Not just initialisation. To do this an api instance is required in some places var api = settings._bInitComplete ? new DataTable.Api(settings) : null; if ( ! s || ! s.time ) { settings._bLoadingState = false; callback(); return; } // Allow custom and plug-in manipulation functions to alter the saved data set and // cancelling of loading by returning false var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] ); if ( $.inArray( false, abStateLoad ) !== -1 ) { settings._bLoadingState = false; callback(); return; } // Reject old data var duration = settings.iStateDuration; if ( duration > 0 && s.time < +new Date() - (duration*1000) ) { settings._bLoadingState = false; callback(); return; } // Number of columns have changed - all bets are off, no restore of settings if ( s.columns && columns.length !== s.columns.length ) { settings._bLoadingState = false; callback(); return; } // Store the saved state so it might be accessed at any time settings.oLoadedState = $.extend( true, {}, s ); // Page Length if ( s.length !== undefined ) { // If already initialised just set the value directly so that the select element is also updated if (api) { api.page.len(s.length) } else { settings._iDisplayLength = s.length; } } // Restore key features - todo - for 1.11 this needs to be done by // subscribed events if ( s.start !== undefined ) { if(api === null) { settings._iDisplayStart = s.start; settings.iInitDisplayStart = s.start; } else { _fnPageChange(settings, s.start/settings._iDisplayLength); } } // Order if ( s.order !== undefined ) { settings.aaSorting = []; $.each( s.order, function ( i, col ) { settings.aaSorting.push( col[0] >= columns.length ? [ 0, col[1] ] : col ); } ); } // Search if ( s.search !== undefined ) { $.extend( settings.oPreviousSearch, _fnSearchToHung( s.search ) ); } // Columns if ( s.columns ) { for ( i=0, ien=s.columns.length ; i<ien ; i++ ) { var col = s.columns[i]; // Visibility if ( col.visible !== undefined ) { // If the api is defined, the table has been initialised so we need to use it rather than internal settings if (api) { // Don't redraw the columns on every iteration of this loop, we will do this at the end instead api.column(i).visible(col.visible, false); } else { columns[i].bVisible = col.visible; } } // Search if ( col.search !== undefined ) { $.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) ); } } // If the api is defined then we need to adjust the columns once the visibility has been changed if (api) { api.columns.adjust(); } } settings._bLoadingState = false; _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] ); callback(); }; /** * Return the settings object for a particular table * @param {node} table table we are using as a dataTable * @returns {object} Settings object - or null if not found * @memberof DataTable#oApi */ function _fnSettingsFromNode ( table ) { var settings = DataTable.settings; var idx = $.inArray( table, _pluck( settings, 'nTable' ) ); return idx !== -1 ? settings[ idx ] : null; } /** * Log an error message * @param {object} settings dataTables settings object * @param {int} level log error messages, or display them to the user * @param {string} msg error message * @param {int} tn Technical note id to get more information about the error. * @memberof DataTable#oApi */ function _fnLog( settings, level, msg, tn ) { msg = 'DataTables warning: '+ (settings ? 'table id='+settings.sTableId+' - ' : '')+msg; if ( tn ) { msg += '. For more information about this error, please see '+ 'https://datatables.net/tn/'+tn; } if ( ! level ) { // Backwards compatibility pre 1.10 var ext = DataTable.ext; var type = ext.sErrMode || ext.errMode; if ( settings ) { _fnCallbackFire( settings, null, 'error', [ settings, tn, msg ] ); } if ( type == 'alert' ) { alert( msg ); } else if ( type == 'throw' ) { throw new Error(msg); } else if ( typeof type == 'function' ) { type( settings, tn, msg ); } } else if ( window.console && console.log ) { console.log( msg ); } } /** * See if a property is defined on one object, if so assign it to the other object * @param {object} ret target object * @param {object} src source object * @param {string} name property * @param {string} [mappedName] name to map too - optional, name used if not given * @memberof DataTable#oApi */ function _fnMap( ret, src, name, mappedName ) { if ( Array.isArray( name ) ) { $.each( name, function (i, val) { if ( Array.isArray( val ) ) { _fnMap( ret, src, val[0], val[1] ); } else { _fnMap( ret, src, val ); } } ); return; } if ( mappedName === undefined ) { mappedName = name; } if ( src[name] !== undefined ) { ret[mappedName] = src[name]; } } /** * Extend objects - very similar to jQuery.extend, but deep copy objects, and * shallow copy arrays. The reason we need to do this, is that we don't want to * deep copy array init values (such as aaSorting) since the dev wouldn't be * able to override them, but we do want to deep copy arrays. * @param {object} out Object to extend * @param {object} extender Object from which the properties will be applied to * out * @param {boolean} breakRefs If true, then arrays will be sliced to take an * independent copy with the exception of the `data` or `aaData` parameters * if they are present. This is so you can pass in a collection to * DataTables and have that used as your data source without breaking the * references * @returns {object} out Reference, just for convenience - out === the return. * @memberof DataTable#oApi * @todo This doesn't take account of arrays inside the deep copied objects. */ function _fnExtend( out, extender, breakRefs ) { var val; for ( var prop in extender ) { if ( extender.hasOwnProperty(prop) ) { val = extender[prop]; if ( $.isPlainObject( val ) ) { if ( ! $.isPlainObject( out[prop] ) ) { out[prop] = {}; } $.extend( true, out[prop], val ); } else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && Array.isArray(val) ) { out[prop] = val.slice(); } else { out[prop] = val; } } } return out; } /** * Bind an event handers to allow a click or return key to activate the callback. * This is good for accessibility since a return on the keyboard will have the * same effect as a click, if the element has focus. * @param {element} n Element to bind the action to * @param {object} oData Data object to pass to the triggered function * @param {function} fn Callback function for when the event is triggered * @memberof DataTable#oApi */ function _fnBindAction( n, oData, fn ) { $(n) .on( 'click.DT', oData, function (e) { $(n).trigger('blur'); // Remove focus outline for mouse users fn(e); } ) .on( 'keypress.DT', oData, function (e){ if ( e.which === 13 ) { e.preventDefault(); fn(e); } } ) .on( 'selectstart.DT', function () { /* Take the brutal approach to cancelling text selection */ return false; } ); } /** * Register a callback function. Easily allows a callback function to be added to * an array store of callback functions that can then all be called together. * @param {object} oSettings dataTables settings object * @param {string} sStore Name of the array storage for the callbacks in oSettings * @param {function} fn Function to be called back * @param {string} sName Identifying name for the callback (i.e. a label) * @memberof DataTable#oApi */ function _fnCallbackReg( oSettings, sStore, fn, sName ) { if ( fn ) { oSettings[sStore].push( { "fn": fn, "sName": sName } ); } } /** * Fire callback functions and trigger events. Note that the loop over the * callback array store is done backwards! Further note that you do not want to * fire off triggers in time sensitive applications (for example cell creation) * as its slow. * @param {object} settings dataTables settings object * @param {string} callbackArr Name of the array storage for the callbacks in * oSettings * @param {string} eventName Name of the jQuery custom event to trigger. If * null no trigger is fired * @param {array} args Array of arguments to pass to the callback function / * trigger * @memberof DataTable#oApi */ function _fnCallbackFire( settings, callbackArr, eventName, args ) { var ret = []; if ( callbackArr ) { ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) { return val.fn.apply( settings.oInstance, args ); } ); } if ( eventName !== null ) { var e = $.Event( eventName+'.dt' ); var table = $(settings.nTable); table.trigger( e, args ); // If not yet attached to the document, trigger the event // on the body directly to sort of simulate the bubble if (table.parents('body').length === 0) { $('body').trigger( e, args ); } ret.push( e.result ); } return ret; } function _fnLengthOverflow ( settings ) { var start = settings._iDisplayStart, end = settings.fnDisplayEnd(), len = settings._iDisplayLength; /* If we have space to show extra rows (backing up from the end point - then do so */ if ( start >= end ) { start = end - len; } // Keep the start record on the current page start -= (start % len); if ( len === -1 || start < 0 ) { start = 0; } settings._iDisplayStart = start; } function _fnRenderer( settings, type ) { var renderer = settings.renderer; var host = DataTable.ext.renderer[type]; if ( $.isPlainObject( renderer ) && renderer[type] ) { // Specific renderer for this type. If available use it, otherwise use // the default. return host[renderer[type]] || host._; } else if ( typeof renderer === 'string' ) { // Common renderer - if there is one available for this type use it, // otherwise use the default return host[renderer] || host._; } // Use the default return host._; } /** * Detect the data source being used for the table. Used to simplify the code * a little (ajax) and to make it compress a little smaller. * * @param {object} settings dataTables settings object * @returns {string} Data source * @memberof DataTable#oApi */ function _fnDataSource ( settings ) { if ( settings.oFeatures.bServerSide ) { return 'ssp'; } else if ( settings.ajax || settings.sAjaxSource ) { return 'ajax'; } return 'dom'; } /** * Computed structure of the DataTables API, defined by the options passed to * `DataTable.Api.register()` when building the API. * * The structure is built in order to speed creation and extension of the Api * objects since the extensions are effectively pre-parsed. * * The array is an array of objects with the following structure, where this * base array represents the Api prototype base: * * [ * { * name: 'data' -- string - Property name * val: function () {}, -- function - Api method (or undefined if just an object * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result * propExt: [ ... ] -- array - Array of Api object definitions to extend the property * }, * { * name: 'row' * val: {}, * methodExt: [ ... ], * propExt: [ * { * name: 'data' * val: function () {}, * methodExt: [ ... ], * propExt: [ ... ] * }, * ... * ] * } * ] * * @type {Array} * @ignore */ var __apiStruct = []; /** * `Array.prototype` reference. * * @type object * @ignore */ var __arrayProto = Array.prototype; /** * Abstraction for `context` parameter of the `Api` constructor to allow it to * take several different forms for ease of use. * * Each of the input parameter types will be converted to a DataTables settings * object where possible. * * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one * of: * * * `string` - jQuery selector. Any DataTables' matching the given selector * with be found and used. * * `node` - `TABLE` node which has already been formed into a DataTable. * * `jQuery` - A jQuery object of `TABLE` nodes. * * `object` - DataTables settings object * * `DataTables.Api` - API instance * @return {array|null} Matching DataTables settings objects. `null` or * `undefined` is returned if no matching DataTable is found. * @ignore */ var _toSettings = function ( mixed ) { var idx, jq; var settings = DataTable.settings; var tables = $.map( settings, function (el, i) { return el.nTable; } ); if ( ! mixed ) { return []; } else if ( mixed.nTable && mixed.oApi ) { // DataTables settings object return [ mixed ]; } else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) { // Table node idx = $.inArray( mixed, tables ); return idx !== -1 ? [ settings[idx] ] : null; } else if ( mixed && typeof mixed.settings === 'function' ) { return mixed.settings().toArray(); } else if ( typeof mixed === 'string' ) { // jQuery selector jq = $(mixed); } else if ( mixed instanceof $ ) { // jQuery object (also DataTables instance) jq = mixed; } if ( jq ) { return jq.map( function(i) { idx = $.inArray( this, tables ); return idx !== -1 ? settings[idx] : null; } ).toArray(); } }; /** * DataTables API class - used to control and interface with one or more * DataTables enhanced tables. * * The API class is heavily based on jQuery, presenting a chainable interface * that you can use to interact with tables. Each instance of the API class has * a "context" - i.e. the tables that it will operate on. This could be a single * table, all tables on a page or a sub-set thereof. * * Additionally the API is designed to allow you to easily work with the data in * the tables, retrieving and manipulating it as required. This is done by * presenting the API class as an array like interface. The contents of the * array depend upon the actions requested by each method (for example * `rows().nodes()` will return an array of nodes, while `rows().data()` will * return an array of objects or arrays depending upon your table's * configuration). The API object has a number of array like methods (`push`, * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`, * `unique` etc) to assist your working with the data held in a table. * * Most methods (those which return an Api instance) are chainable, which means * the return from a method call also has all of the methods available that the * top level object had. For example, these two calls are equivalent: * * // Not chained * api.row.add( {...} ); * api.draw(); * * // Chained * api.row.add( {...} ).draw(); * * @class DataTable.Api * @param {array|object|string|jQuery} context DataTable identifier. This is * used to define which DataTables enhanced tables this API will operate on. * Can be one of: * * * `string` - jQuery selector. Any DataTables' matching the given selector * with be found and used. * * `node` - `TABLE` node which has already been formed into a DataTable. * * `jQuery` - A jQuery object of `TABLE` nodes. * * `object` - DataTables settings object * @param {array} [data] Data to initialise the Api instance with. * * @example * // Direct initialisation during DataTables construction * var api = $('#example').DataTable(); * * @example * // Initialisation using a DataTables jQuery object * var api = $('#example').dataTable().api(); * * @example * // Initialisation as a constructor * var api = new $.fn.DataTable.Api( 'table.dataTable' ); */ _Api = function ( context, data ) { if ( ! (this instanceof _Api) ) { return new _Api( context, data ); } var settings = []; var ctxSettings = function ( o ) { var a = _toSettings( o ); if ( a ) { settings.push.apply( settings, a ); } }; if ( Array.isArray( context ) ) { for ( var i=0, ien=context.length ; i<ien ; i++ ) { ctxSettings( context[i] ); } } else { ctxSettings( context ); } // Remove duplicates this.context = _unique( settings ); // Initial data if ( data ) { $.merge( this, data ); } // selector this.selector = { rows: null, cols: null, opts: null }; _Api.extend( this, this, __apiStruct ); }; DataTable.Api = _Api; // Don't destroy the existing prototype, just extend it. Required for jQuery 2's // isPlainObject. $.extend( _Api.prototype, { any: function () { return this.count() !== 0; }, concat: __arrayProto.concat, context: [], // array of table settings objects count: function () { return this.flatten().length; }, each: function ( fn ) { for ( var i=0, ien=this.length ; i<ien; i++ ) { fn.call( this, this[i], i, this ); } return this; }, eq: function ( idx ) { var ctx = this.context; return ctx.length > idx ? new _Api( ctx[idx], this[idx] ) : null; }, filter: function ( fn ) { var a = []; if ( __arrayProto.filter ) { a = __arrayProto.filter.call( this, fn, this ); } else { // Compatibility for browsers without EMCA-252-5 (JS 1.6) for ( var i=0, ien=this.length ; i<ien ; i++ ) { if ( fn.call( this, this[i], i, this ) ) { a.push( this[i] ); } } } return new _Api( this.context, a ); }, flatten: function () { var a = []; return new _Api( this.context, a.concat.apply( a, this.toArray() ) ); }, join: __arrayProto.join, indexOf: __arrayProto.indexOf || function (obj, start) { for ( var i=(start || 0), ien=this.length ; i<ien ; i++ ) { if ( this[i] === obj ) { return i; } } return -1; }, iterator: function ( flatten, type, fn, alwaysNew ) { var a = [], ret, i, ien, j, jen, context = this.context, rows, items, item, selector = this.selector; // Argument shifting if ( typeof flatten === 'string' ) { alwaysNew = fn; fn = type; type = flatten; flatten = false; } for ( i=0, ien=context.length ; i<ien ; i++ ) { var apiInst = new _Api( context[i] ); if ( type === 'table' ) { ret = fn.call( apiInst, context[i], i ); if ( ret !== undefined ) { a.push( ret ); } } else if ( type === 'columns' || type === 'rows' ) { // this has same length as context - one entry for each table ret = fn.call( apiInst, context[i], this[i], i ); if ( ret !== undefined ) { a.push( ret ); } } else if ( type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) { // columns and rows share the same structure. // 'this' is an array of column indexes for each context items = this[i]; if ( type === 'column-rows' ) { rows = _selector_row_indexes( context[i], selector.opts ); } for ( j=0, jen=items.length ; j<jen ; j++ ) { item = items[j]; if ( type === 'cell' ) { ret = fn.call( apiInst, context[i], item.row, item.column, i, j ); } else { ret = fn.call( apiInst, context[i], item, i, j, rows ); } if ( ret !== undefined ) { a.push( ret ); } } } } if ( a.length || alwaysNew ) { var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a ); var apiSelector = api.selector; apiSelector.rows = selector.rows; apiSelector.cols = selector.cols; apiSelector.opts = selector.opts; return api; } return this; }, lastIndexOf: __arrayProto.lastIndexOf || function (obj, start) { // Bit cheeky... return this.indexOf.apply( this.toArray.reverse(), arguments ); }, length: 0, map: function ( fn ) { var a = []; if ( __arrayProto.map ) { a = __arrayProto.map.call( this, fn, this ); } else { // Compatibility for browsers without EMCA-252-5 (JS 1.6) for ( var i=0, ien=this.length ; i<ien ; i++ ) { a.push( fn.call( this, this[i], i ) ); } } return new _Api( this.context, a ); }, pluck: function ( prop ) { var fn = DataTable.util.get(prop); return this.map( function ( el ) { return fn(el); } ); }, pop: __arrayProto.pop, push: __arrayProto.push, // Does not return an API instance reduce: __arrayProto.reduce || function ( fn, init ) { return _fnReduce( this, fn, init, 0, this.length, 1 ); }, reduceRight: __arrayProto.reduceRight || function ( fn, init ) { return _fnReduce( this, fn, init, this.length-1, -1, -1 ); }, reverse: __arrayProto.reverse, // Object with rows, columns and opts selector: null, shift: __arrayProto.shift, slice: function () { return new _Api( this.context, this ); }, sort: __arrayProto.sort, // ? name - order? splice: __arrayProto.splice, toArray: function () { return __arrayProto.slice.call( this ); }, to$: function () { return $( this ); }, toJQuery: function () { return $( this ); }, unique: function () { return new _Api( this.context, _unique(this) ); }, unshift: __arrayProto.unshift } ); _Api.extend = function ( scope, obj, ext ) { // Only extend API instances and static properties of the API if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) { return; } var i, ien, struct, methodScoping = function ( scope, fn, struc ) { return function () { var ret = fn.apply( scope, arguments ); // Method extension _Api.extend( ret, ret, struc.methodExt ); return ret; }; }; for ( i=0, ien=ext.length ; i<ien ; i++ ) { struct = ext[i]; // Value obj[ struct.name ] = struct.type === 'function' ? methodScoping( scope, struct.val, struct ) : struct.type === 'object' ? {} : struct.val; obj[ struct.name ].__dt_wrapper = true; // Property extension _Api.extend( scope, obj[ struct.name ], struct.propExt ); } }; // @todo - Is there need for an augment function? // _Api.augment = function ( inst, name ) // { // // Find src object in the structure from the name // var parts = name.split('.'); // _Api.extend( inst, obj ); // }; // [ // { // name: 'data' -- string - Property name // val: function () {}, -- function - Api method (or undefined if just an object // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result // propExt: [ ... ] -- array - Array of Api object definitions to extend the property // }, // { // name: 'row' // val: {}, // methodExt: [ ... ], // propExt: [ // { // name: 'data' // val: function () {}, // methodExt: [ ... ], // propExt: [ ... ] // }, // ... // ] // } // ] _Api.register = _api_register = function ( name, val ) { if ( Array.isArray( name ) ) { for ( var j=0, jen=name.length ; j<jen ; j++ ) { _Api.register( name[j], val ); } return; } var i, ien, heir = name.split('.'), struct = __apiStruct, key, method; var find = function ( src, name ) { for ( var i=0, ien=src.length ; i<ien ; i++ ) { if ( src[i].name === name ) { return src[i]; } } return null; }; for ( i=0, ien=heir.length ; i<ien ; i++ ) { method = heir[i].indexOf('()') !== -1; key = method ? heir[i].replace('()', '') : heir[i]; var src = find( struct, key ); if ( ! src ) { src = { name: key, val: {}, methodExt: [], propExt: [], type: 'object' }; struct.push( src ); } if ( i === ien-1 ) { src.val = val; src.type = typeof val === 'function' ? 'function' : $.isPlainObject( val ) ? 'object' : 'other'; } else { struct = method ? src.methodExt : src.propExt; } } }; _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) { _Api.register( pluralName, val ); _Api.register( singularName, function () { var ret = val.apply( this, arguments ); if ( ret === this ) { // Returned item is the API instance that was passed in, return it return this; } else if ( ret instanceof _Api ) { // New API instance returned, want the value from the first item // in the returned array for the singular result. return ret.length ? Array.isArray( ret[0] ) ? new _Api( ret.context, ret[0] ) : // Array results are 'enhanced' ret[0] : undefined; } // Non-API return - just fire it back return ret; } ); }; /** * Selector for HTML tables. Apply the given selector to the give array of * DataTables settings objects. * * @param {string|integer} [selector] jQuery selector string or integer * @param {array} Array of DataTables settings objects to be filtered * @return {array} * @ignore */ var __table_selector = function ( selector, a ) { if ( Array.isArray(selector) ) { return $.map( selector, function (item) { return __table_selector(item, a); } ); } // Integer is used to pick out a table by index if ( typeof selector === 'number' ) { return [ a[ selector ] ]; } // Perform a jQuery selector on the table nodes var nodes = $.map( a, function (el, i) { return el.nTable; } ); return $(nodes) .filter( selector ) .map( function (i) { // Need to translate back from the table node to the settings var idx = $.inArray( this, nodes ); return a[ idx ]; } ) .toArray(); }; /** * Context selector for the API's context (i.e. the tables the API instance * refers to. * * @name DataTable.Api#tables * @param {string|integer} [selector] Selector to pick which tables the iterator * should operate on. If not given, all tables in the current context are * used. This can be given as a jQuery selector (for example `':gt(0)'`) to * select multiple tables or as an integer to select a single table. * @returns {DataTable.Api} Returns a new API instance if a selector is given. */ _api_register( 'tables()', function ( selector ) { // A new instance is created if there was a selector specified return selector !== undefined && selector !== null ? new _Api( __table_selector( selector, this.context ) ) : this; } ); _api_register( 'table()', function ( selector ) { var tables = this.tables( selector ); var ctx = tables.context; // Truncate to the first matched table return ctx.length ? new _Api( ctx[0] ) : tables; } ); _api_registerPlural( 'tables().nodes()', 'table().node()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTable; }, 1 ); } ); _api_registerPlural( 'tables().body()', 'table().body()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTBody; }, 1 ); } ); _api_registerPlural( 'tables().header()', 'table().header()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTHead; }, 1 ); } ); _api_registerPlural( 'tables().footer()', 'table().footer()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTFoot; }, 1 ); } ); _api_registerPlural( 'tables().containers()', 'table().container()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTableWrapper; }, 1 ); } ); /** * Redraw the tables in the current context. */ _api_register( 'draw()', function ( paging ) { return this.iterator( 'table', function ( settings ) { if ( paging === 'page' ) { _fnDraw( settings ); } else { if ( typeof paging === 'string' ) { paging = paging === 'full-hold' ? false : true; } _fnReDraw( settings, paging===false ); } } ); } ); /** * Get the current page index. * * @return {integer} Current page index (zero based) *//** * Set the current page. * * Note that if you attempt to show a page which does not exist, DataTables will * not throw an error, but rather reset the paging. * * @param {integer|string} action The paging action to take. This can be one of: * * `integer` - The page index to jump to * * `string` - An action to take: * * `first` - Jump to first page. * * `next` - Jump to the next page * * `previous` - Jump to previous page * * `last` - Jump to the last page. * @returns {DataTables.Api} this */ _api_register( 'page()', function ( action ) { if ( action === undefined ) { return this.page.info().page; // not an expensive call } // else, have an action to take on all tables return this.iterator( 'table', function ( settings ) { _fnPageChange( settings, action ); } ); } ); /** * Paging information for the first table in the current context. * * If you require paging information for another table, use the `table()` method * with a suitable selector. * * @return {object} Object with the following properties set: * * `page` - Current page index (zero based - i.e. the first page is `0`) * * `pages` - Total number of pages * * `start` - Display index for the first record shown on the current page * * `end` - Display index for the last record shown on the current page * * `length` - Display length (number of records). Note that generally `start * + length = end`, but this is not always true, for example if there are * only 2 records to show on the final page, with a length of 10. * * `recordsTotal` - Full data set length * * `recordsDisplay` - Data set length once the current filtering criterion * are applied. */ _api_register( 'page.info()', function ( action ) { if ( this.context.length === 0 ) { return undefined; } var settings = this.context[0], start = settings._iDisplayStart, len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1, visRecords = settings.fnRecordsDisplay(), all = len === -1; return { "page": all ? 0 : Math.floor( start / len ), "pages": all ? 1 : Math.ceil( visRecords / len ), "start": start, "end": settings.fnDisplayEnd(), "length": len, "recordsTotal": settings.fnRecordsTotal(), "recordsDisplay": visRecords, "serverSide": _fnDataSource( settings ) === 'ssp' }; } ); /** * Get the current page length. * * @return {integer} Current page length. Note `-1` indicates that all records * are to be shown. *//** * Set the current page length. * * @param {integer} Page length to set. Use `-1` to show all records. * @returns {DataTables.Api} this */ _api_register( 'page.len()', function ( len ) { // Note that we can't call this function 'length()' because `length` // is a Javascript property of functions which defines how many arguments // the function expects. if ( len === undefined ) { return this.context.length !== 0 ? this.context[0]._iDisplayLength : undefined; } // else, set the page length return this.iterator( 'table', function ( settings ) { _fnLengthChange( settings, len ); } ); } ); var __reload = function ( settings, holdPosition, callback ) { // Use the draw event to trigger a callback if ( callback ) { var api = new _Api( settings ); api.one( 'draw', function () { callback( api.ajax.json() ); } ); } if ( _fnDataSource( settings ) == 'ssp' ) { _fnReDraw( settings, holdPosition ); } else { _fnProcessingDisplay( settings, true ); // Cancel an existing request var xhr = settings.jqXHR; if ( xhr && xhr.readyState !== 4 ) { xhr.abort(); } // Trigger xhr _fnBuildAjax( settings, [], function( json ) { _fnClearTable( settings ); var data = _fnAjaxDataSrc( settings, json ); for ( var i=0, ien=data.length ; i<ien ; i++ ) { _fnAddData( settings, data[i] ); } _fnReDraw( settings, holdPosition ); _fnProcessingDisplay( settings, false ); } ); } }; /** * Get the JSON response from the last Ajax request that DataTables made to the * server. Note that this returns the JSON from the first table in the current * context. * * @return {object} JSON received from the server. */ _api_register( 'ajax.json()', function () { var ctx = this.context; if ( ctx.length > 0 ) { return ctx[0].json; } // else return undefined; } ); /** * Get the data submitted in the last Ajax request */ _api_register( 'ajax.params()', function () { var ctx = this.context; if ( ctx.length > 0 ) { return ctx[0].oAjaxData; } // else return undefined; } ); /** * Reload tables from the Ajax data source. Note that this function will * automatically re-draw the table when the remote data has been loaded. * * @param {boolean} [reset=true] Reset (default) or hold the current paging * position. A full re-sort and re-filter is performed when this method is * called, which is why the pagination reset is the default action. * @returns {DataTables.Api} this */ _api_register( 'ajax.reload()', function ( callback, resetPaging ) { return this.iterator( 'table', function (settings) { __reload( settings, resetPaging===false, callback ); } ); } ); /** * Get the current Ajax URL. Note that this returns the URL from the first * table in the current context. * * @return {string} Current Ajax source URL *//** * Set the Ajax URL. Note that this will set the URL for all tables in the * current context. * * @param {string} url URL to set. * @returns {DataTables.Api} this */ _api_register( 'ajax.url()', function ( url ) { var ctx = this.context; if ( url === undefined ) { // get if ( ctx.length === 0 ) { return undefined; } ctx = ctx[0]; return ctx.ajax ? $.isPlainObject( ctx.ajax ) ? ctx.ajax.url : ctx.ajax : ctx.sAjaxSource; } // set return this.iterator( 'table', function ( settings ) { if ( $.isPlainObject( settings.ajax ) ) { settings.ajax.url = url; } else { settings.ajax = url; } // No need to consider sAjaxSource here since DataTables gives priority // to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any // value of `sAjaxSource` redundant. } ); } ); /** * Load data from the newly set Ajax URL. Note that this method is only * available when `ajax.url()` is used to set a URL. Additionally, this method * has the same effect as calling `ajax.reload()` but is provided for * convenience when setting a new URL. Like `ajax.reload()` it will * automatically redraw the table once the remote data has been loaded. * * @returns {DataTables.Api} this */ _api_register( 'ajax.url().load()', function ( callback, resetPaging ) { // Same as a reload, but makes sense to present it for easy access after a // url change return this.iterator( 'table', function ( ctx ) { __reload( ctx, resetPaging===false, callback ); } ); } ); var _selector_run = function ( type, selector, selectFn, settings, opts ) { var out = [], res, a, i, ien, j, jen, selectorType = typeof selector; // Can't just check for isArray here, as an API or jQuery instance might be // given with their array like look if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) { selector = [ selector ]; } for ( i=0, ien=selector.length ; i<ien ; i++ ) { // Only split on simple strings - complex expressions will be jQuery selectors a = selector[i] && selector[i].split && ! selector[i].match(/[\[\(:]/) ? selector[i].split(',') : [ selector[i] ]; for ( j=0, jen=a.length ; j<jen ; j++ ) { res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] ); if ( res && res.length ) { out = out.concat( res ); } } } // selector extensions var ext = _ext.selector[ type ]; if ( ext.length ) { for ( i=0, ien=ext.length ; i<ien ; i++ ) { out = ext[i]( settings, opts, out ); } } return _unique( out ); }; var _selector_opts = function ( opts ) { if ( ! opts ) { opts = {}; } // Backwards compatibility for 1.9- which used the terminology filter rather // than search if ( opts.filter && opts.search === undefined ) { opts.search = opts.filter; } return $.extend( { search: 'none', order: 'current', page: 'all' }, opts ); }; var _selector_first = function ( inst ) { // Reduce the API instance to the first item found for ( var i=0, ien=inst.length ; i<ien ; i++ ) { if ( inst[i].length > 0 ) { // Assign the first element to the first item in the instance // and truncate the instance and context inst[0] = inst[i]; inst[0].length = 1; inst.length = 1; inst.context = [ inst.context[i] ]; return inst; } } // Not found - return an empty instance inst.length = 0; return inst; }; var _selector_row_indexes = function ( settings, opts ) { var i, ien, tmp, a=[], displayFiltered = settings.aiDisplay, displayMaster = settings.aiDisplayMaster; var search = opts.search, // none, applied, removed order = opts.order, // applied, current, index (original - compatibility with 1.9) page = opts.page; // all, current if ( _fnDataSource( settings ) == 'ssp' ) { // In server-side processing mode, most options are irrelevant since // rows not shown don't exist and the index order is the applied order // Removed is a special case - for consistency just return an empty // array return search === 'removed' ? [] : _range( 0, displayMaster.length ); } else if ( page == 'current' ) { // Current page implies that order=current and filter=applied, since it is // fairly senseless otherwise, regardless of what order and search actually // are for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) { a.push( displayFiltered[i] ); } } else if ( order == 'current' || order == 'applied' ) { if ( search == 'none') { a = displayMaster.slice(); } else if ( search == 'applied' ) { a = displayFiltered.slice(); } else if ( search == 'removed' ) { // O(n+m) solution by creating a hash map var displayFilteredMap = {}; for ( var i=0, ien=displayFiltered.length ; i<ien ; i++ ) { displayFilteredMap[displayFiltered[i]] = null; } a = $.map( displayMaster, function (el) { return ! displayFilteredMap.hasOwnProperty(el) ? el : null; } ); } } else if ( order == 'index' || order == 'original' ) { for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { if ( search == 'none' ) { a.push( i ); } else { // applied | removed tmp = $.inArray( i, displayFiltered ); if ((tmp === -1 && search == 'removed') || (tmp >= 0 && search == 'applied') ) { a.push( i ); } } } } return a; }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Rows * * {} - no selector - use all available rows * {integer} - row aoData index * {node} - TR node * {string} - jQuery selector to apply to the TR elements * {array} - jQuery array of nodes, or simply an array of TR nodes * */ var __row_selector = function ( settings, selector, opts ) { var rows; var run = function ( sel ) { var selInt = _intVal( sel ); var i, ien; var aoData = settings.aoData; // Short cut - selector is a number and no options provided (default is // all records, so no need to check if the index is in there, since it // must be - dev error if the index doesn't exist). if ( selInt !== null && ! opts ) { return [ selInt ]; } if ( ! rows ) { rows = _selector_row_indexes( settings, opts ); } if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) { // Selector - integer return [ selInt ]; } else if ( sel === null || sel === undefined || sel === '' ) { // Selector - none return rows; } // Selector - function if ( typeof sel === 'function' ) { return $.map( rows, function (idx) { var row = aoData[ idx ]; return sel( idx, row._aData, row.nTr ) ? idx : null; } ); } // Selector - node if ( sel.nodeName ) { var rowIdx = sel._DT_RowIndex; // Property added by DT for fast lookup var cellIdx = sel._DT_CellIndex; if ( rowIdx !== undefined ) { // Make sure that the row is actually still present in the table return aoData[ rowIdx ] && aoData[ rowIdx ].nTr === sel ? [ rowIdx ] : []; } else if ( cellIdx ) { return aoData[ cellIdx.row ] && aoData[ cellIdx.row ].nTr === sel.parentNode ? [ cellIdx.row ] : []; } else { var host = $(sel).closest('*[data-dt-row]'); return host.length ? [ host.data('dt-row') ] : []; } } // ID selector. Want to always be able to select rows by id, regardless // of if the tr element has been created or not, so can't rely upon // jQuery here - hence a custom implementation. This does not match // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything, // but to select it using a CSS selector engine (like Sizzle or // querySelect) it would need to need to be escaped for some characters. // DataTables simplifies this for row selectors since you can select // only a row. A # indicates an id any anything that follows is the id - // unescaped. if ( typeof sel === 'string' && sel.charAt(0) === '#' ) { // get row index from id var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ]; if ( rowObj !== undefined ) { return [ rowObj.idx ]; } // need to fall through to jQuery in case there is DOM id that // matches } // Get nodes in the order from the `rows` array with null values removed var nodes = _removeEmpty( _pluck_order( settings.aoData, rows, 'nTr' ) ); // Selector - jQuery selector string, array of nodes or jQuery object/ // As jQuery's .filter() allows jQuery objects to be passed in filter, // it also allows arrays, so this will cope with all three options return $(nodes) .filter( sel ) .map( function () { return this._DT_RowIndex; } ) .toArray(); }; return _selector_run( 'row', selector, run, settings, opts ); }; _api_register( 'rows()', function ( selector, opts ) { // argument shifting if ( selector === undefined ) { selector = ''; } else if ( $.isPlainObject( selector ) ) { opts = selector; selector = ''; } opts = _selector_opts( opts ); var inst = this.iterator( 'table', function ( settings ) { return __row_selector( settings, selector, opts ); }, 1 ); // Want argument shifting here and in __row_selector? inst.selector.rows = selector; inst.selector.opts = opts; return inst; } ); _api_register( 'rows().nodes()', function () { return this.iterator( 'row', function ( settings, row ) { return settings.aoData[ row ].nTr || undefined; }, 1 ); } ); _api_register( 'rows().data()', function () { return this.iterator( true, 'rows', function ( settings, rows ) { return _pluck_order( settings.aoData, rows, '_aData' ); }, 1 ); } ); _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) { return this.iterator( 'row', function ( settings, row ) { var r = settings.aoData[ row ]; return type === 'search' ? r._aFilterData : r._aSortData; }, 1 ); } ); _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) { return this.iterator( 'row', function ( settings, row ) { _fnInvalidate( settings, row, src ); } ); } ); _api_registerPlural( 'rows().indexes()', 'row().index()', function () { return this.iterator( 'row', function ( settings, row ) { return row; }, 1 ); } ); _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) { var a = []; var context = this.context; // `iterator` will drop undefined values, but in this case we want them for ( var i=0, ien=context.length ; i<ien ; i++ ) { for ( var j=0, jen=this[i].length ; j<jen ; j++ ) { var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData ); a.push( (hash === true ? '#' : '' )+ id ); } } return new _Api( context, a ); } ); _api_registerPlural( 'rows().remove()', 'row().remove()', function () { var that = this; this.iterator( 'row', function ( settings, row, thatIdx ) { var data = settings.aoData; var rowData = data[ row ]; var i, ien, j, jen; var loopRow, loopCells; data.splice( row, 1 ); // Update the cached indexes for ( i=0, ien=data.length ; i<ien ; i++ ) { loopRow = data[i]; loopCells = loopRow.anCells; // Rows if ( loopRow.nTr !== null ) { loopRow.nTr._DT_RowIndex = i; } // Cells if ( loopCells !== null ) { for ( j=0, jen=loopCells.length ; j<jen ; j++ ) { loopCells[j]._DT_CellIndex.row = i; } } } // Delete from the display arrays _fnDeleteIndex( settings.aiDisplayMaster, row ); _fnDeleteIndex( settings.aiDisplay, row ); _fnDeleteIndex( that[ thatIdx ], row, false ); // maintain local indexes // For server-side processing tables - subtract the deleted row from the count if ( settings._iRecordsDisplay > 0 ) { settings._iRecordsDisplay--; } // Check for an 'overflow' they case for displaying the table _fnLengthOverflow( settings ); // Remove the row's ID reference if there is one var id = settings.rowIdFn( rowData._aData ); if ( id !== undefined ) { delete settings.aIds[ id ]; } } ); this.iterator( 'table', function ( settings ) { for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { settings.aoData[i].idx = i; } } ); return this; } ); _api_register( 'rows.add()', function ( rows ) { var newRows = this.iterator( 'table', function ( settings ) { var row, i, ien; var out = []; for ( i=0, ien=rows.length ; i<ien ; i++ ) { row = rows[i]; if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { out.push( _fnAddTr( settings, row )[0] ); } else { out.push( _fnAddData( settings, row ) ); } } return out; }, 1 ); // Return an Api.rows() extended instance, so rows().nodes() etc can be used var modRows = this.rows( -1 ); modRows.pop(); $.merge( modRows, newRows ); return modRows; } ); /** * */ _api_register( 'row()', function ( selector, opts ) { return _selector_first( this.rows( selector, opts ) ); } ); _api_register( 'row().data()', function ( data ) { var ctx = this.context; if ( data === undefined ) { // Get return ctx.length && this.length ? ctx[0].aoData[ this[0] ]._aData : undefined; } // Set var row = ctx[0].aoData[ this[0] ]; row._aData = data; // If the DOM has an id, and the data source is an array if ( Array.isArray( data ) && row.nTr && row.nTr.id ) { _fnSetObjectDataFn( ctx[0].rowId )( data, row.nTr.id ); } // Automatically invalidate _fnInvalidate( ctx[0], this[0], 'data' ); return this; } ); _api_register( 'row().node()', function () { var ctx = this.context; return ctx.length && this.length ? ctx[0].aoData[ this[0] ].nTr || null : null; } ); _api_register( 'row.add()', function ( row ) { // Allow a jQuery object to be passed in - only a single row is added from // it though - the first element in the set if ( row instanceof $ && row.length ) { row = row[0]; } var rows = this.iterator( 'table', function ( settings ) { if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { return _fnAddTr( settings, row )[0]; } return _fnAddData( settings, row ); } ); // Return an Api.rows() extended instance, with the newly added row selected return this.row( rows[0] ); } ); $(document).on('plugin-init.dt', function (e, context) { var api = new _Api( context ); var namespace = 'on-plugin-init'; var stateSaveParamsEvent = 'stateSaveParams.' + namespace; var destroyEvent = 'destroy. ' + namespace; api.on( stateSaveParamsEvent, function ( e, settings, d ) { // This could be more compact with the API, but it is a lot faster as a simple // internal loop var idFn = settings.rowIdFn; var data = settings.aoData; var ids = []; for (var i=0 ; i<data.length ; i++) { if (data[i]._detailsShow) { ids.push( '#' + idFn(data[i]._aData) ); } } d.childRows = ids; }); api.on( destroyEvent, function () { api.off(stateSaveParamsEvent + ' ' + destroyEvent); }); var loaded = api.state.loaded(); if ( loaded && loaded.childRows ) { api .rows( $.map(loaded.childRows, function (id){ return id.replace(/:/g, '\\:') }) ) .every( function () { _fnCallbackFire( context, null, 'requestChild', [ this ] ) }); } }); var __details_add = function ( ctx, row, data, klass ) { // Convert to array of TR elements var rows = []; var addRow = function ( r, k ) { // Recursion to allow for arrays of jQuery objects if ( Array.isArray( r ) || r instanceof $ ) { for ( var i=0, ien=r.length ; i<ien ; i++ ) { addRow( r[i], k ); } return; } // If we get a TR element, then just add it directly - up to the dev // to add the correct number of columns etc if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) { rows.push( r ); } else { // Otherwise create a row with a wrapper var created = $('<tr><td></td></tr>').addClass( k ); $('td', created) .addClass( k ) .html( r ) [0].colSpan = _fnVisbleColumns( ctx ); rows.push( created[0] ); } }; addRow( data, klass ); if ( row._details ) { row._details.detach(); } row._details = $(rows); // If the children were already shown, that state should be retained if ( row._detailsShow ) { row._details.insertAfter( row.nTr ); } }; // Make state saving of child row details async to allow them to be batch processed var __details_state = DataTable.util.throttle( function (ctx) { _fnSaveState( ctx[0] ) }, 500 ); var __details_remove = function ( api, idx ) { var ctx = api.context; if ( ctx.length ) { var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ]; if ( row && row._details ) { row._details.remove(); row._detailsShow = undefined; row._details = undefined; $( row.nTr ).removeClass( 'dt-hasChild' ); __details_state( ctx ); } } }; var __details_display = function ( api, show ) { var ctx = api.context; if ( ctx.length && api.length ) { var row = ctx[0].aoData[ api[0] ]; if ( row._details ) { row._detailsShow = show; if ( show ) { row._details.insertAfter( row.nTr ); $( row.nTr ).addClass( 'dt-hasChild' ); } else { row._details.detach(); $( row.nTr ).removeClass( 'dt-hasChild' ); } _fnCallbackFire( ctx[0], null, 'childRow', [ show, api.row( api[0] ) ] ) __details_events( ctx[0] ); __details_state( ctx ); } } }; var __details_events = function ( settings ) { var api = new _Api( settings ); var namespace = '.dt.DT_details'; var drawEvent = 'draw'+namespace; var colvisEvent = 'column-sizing'+namespace; var destroyEvent = 'destroy'+namespace; var data = settings.aoData; api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent ); if ( _pluck( data, '_details' ).length > 0 ) { // On each draw, insert the required elements into the document api.on( drawEvent, function ( e, ctx ) { if ( settings !== ctx ) { return; } api.rows( {page:'current'} ).eq(0).each( function (idx) { // Internal data grab var row = data[ idx ]; if ( row._detailsShow ) { row._details.insertAfter( row.nTr ); } } ); } ); // Column visibility change - update the colspan api.on( colvisEvent, function ( e, ctx, idx, vis ) { if ( settings !== ctx ) { return; } // Update the colspan for the details rows (note, only if it already has // a colspan) var row, visible = _fnVisbleColumns( ctx ); for ( var i=0, ien=data.length ; i<ien ; i++ ) { row = data[i]; if ( row._details ) { row._details.each(function () { var el = $(this).children('td'); if (el.length == 1) { el.attr('colspan', visible); } }); } } } ); // Table destroyed - nuke any child rows api.on( destroyEvent, function ( e, ctx ) { if ( settings !== ctx ) { return; } for ( var i=0, ien=data.length ; i<ien ; i++ ) { if ( data[i]._details ) { __details_remove( api, i ); } } } ); } }; // Strings for the method names to help minification var _emp = ''; var _child_obj = _emp+'row().child'; var _child_mth = _child_obj+'()'; // data can be: // tr // string // jQuery or array of any of the above _api_register( _child_mth, function ( data, klass ) { var ctx = this.context; if ( data === undefined ) { // get return ctx.length && this.length ? ctx[0].aoData[ this[0] ]._details : undefined; } else if ( data === true ) { // show this.child.show(); } else if ( data === false ) { // remove __details_remove( this ); } else if ( ctx.length && this.length ) { // set __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass ); } return this; } ); _api_register( [ _child_obj+'.show()', _child_mth+'.show()' // only when `child()` was called with parameters (without ], function ( show ) { // it returns an object and this method is not executed) __details_display( this, true ); return this; } ); _api_register( [ _child_obj+'.hide()', _child_mth+'.hide()' // only when `child()` was called with parameters (without ], function () { // it returns an object and this method is not executed) __details_display( this, false ); return this; } ); _api_register( [ _child_obj+'.remove()', _child_mth+'.remove()' // only when `child()` was called with parameters (without ], function () { // it returns an object and this method is not executed) __details_remove( this ); return this; } ); _api_register( _child_obj+'.isShown()', function () { var ctx = this.context; if ( ctx.length && this.length ) { // _detailsShown as false or undefined will fall through to return false return ctx[0].aoData[ this[0] ]._detailsShow || false; } return false; } ); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Columns * * {integer} - column index (>=0 count from left, <0 count from right) * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right) * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right) * "{string}:name" - column name * "{string}" - jQuery selector on column header nodes * */ // can be an array of these items, comma separated list, or an array of comma // separated lists var __re_column_selector = /^([^:]+):(name|visIdx|visible)$/; // r1 and r2 are redundant - but it means that the parameters match for the // iterator callback in columns().data() var __columnData = function ( settings, column, r1, r2, rows ) { var a = []; for ( var row=0, ien=rows.length ; row<ien ; row++ ) { a.push( _fnGetCellData( settings, rows[row], column ) ); } return a; }; var __column_selector = function ( settings, selector, opts ) { var columns = settings.aoColumns, names = _pluck( columns, 'sName' ), nodes = _pluck( columns, 'nTh' ); var run = function ( s ) { var selInt = _intVal( s ); // Selector - all if ( s === '' ) { return _range( columns.length ); } // Selector - index if ( selInt !== null ) { return [ selInt >= 0 ? selInt : // Count from left columns.length + selInt // Count from right (+ because its a negative value) ]; } // Selector = function if ( typeof s === 'function' ) { var rows = _selector_row_indexes( settings, opts ); return $.map( columns, function (col, idx) { return s( idx, __columnData( settings, idx, 0, 0, rows ), nodes[ idx ] ) ? idx : null; } ); } // jQuery or string selector var match = typeof s === 'string' ? s.match( __re_column_selector ) : ''; if ( match ) { switch( match[2] ) { case 'visIdx': case 'visible': var idx = parseInt( match[1], 10 ); // Visible index given, convert to column index if ( idx < 0 ) { // Counting from the right var visColumns = $.map( columns, function (col,i) { return col.bVisible ? i : null; } ); return [ visColumns[ visColumns.length + idx ] ]; } // Counting from the left return [ _fnVisibleToColumnIndex( settings, idx ) ]; case 'name': // match by name. `names` is column index complete and in order return $.map( names, function (name, i) { return name === match[1] ? i : null; } ); default: return []; } } // Cell in the table body if ( s.nodeName && s._DT_CellIndex ) { return [ s._DT_CellIndex.column ]; } // jQuery selector on the TH elements for the columns var jqResult = $( nodes ) .filter( s ) .map( function () { return $.inArray( this, nodes ); // `nodes` is column index complete and in order } ) .toArray(); if ( jqResult.length || ! s.nodeName ) { return jqResult; } // Otherwise a node which might have a `dt-column` data attribute, or be // a child or such an element var host = $(s).closest('*[data-dt-column]'); return host.length ? [ host.data('dt-column') ] : []; }; return _selector_run( 'column', selector, run, settings, opts ); }; var __setColumnVis = function ( settings, column, vis ) { var cols = settings.aoColumns, col = cols[ column ], data = settings.aoData, row, cells, i, ien, tr; // Get if ( vis === undefined ) { return col.bVisible; } // Set // No change if ( col.bVisible === vis ) { return; } if ( vis ) { // Insert column // Need to decide if we should use appendChild or insertBefore var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 ); for ( i=0, ien=data.length ; i<ien ; i++ ) { tr = data[i].nTr; cells = data[i].anCells; if ( tr ) { // insertBefore can act like appendChild if 2nd arg is null tr.insertBefore( cells[ column ], cells[ insertBefore ] || null ); } } } else { // Remove column $( _pluck( settings.aoData, 'anCells', column ) ).detach(); } // Common actions col.bVisible = vis; }; _api_register( 'columns()', function ( selector, opts ) { // argument shifting if ( selector === undefined ) { selector = ''; } else if ( $.isPlainObject( selector ) ) { opts = selector; selector = ''; } opts = _selector_opts( opts ); var inst = this.iterator( 'table', function ( settings ) { return __column_selector( settings, selector, opts ); }, 1 ); // Want argument shifting here and in _row_selector? inst.selector.cols = selector; inst.selector.opts = opts; return inst; } ); _api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].nTh; }, 1 ); } ); _api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].nTf; }, 1 ); } ); _api_registerPlural( 'columns().data()', 'column().data()', function () { return this.iterator( 'column-rows', __columnData, 1 ); } ); _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].mData; }, 1 ); } ); _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) { return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { return _pluck_order( settings.aoData, rows, type === 'search' ? '_aFilterData' : '_aSortData', column ); }, 1 ); } ); _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () { return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { return _pluck_order( settings.aoData, rows, 'anCells', column ) ; }, 1 ); } ); _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) { var that = this; var ret = this.iterator( 'column', function ( settings, column ) { if ( vis === undefined ) { return settings.aoColumns[ column ].bVisible; } // else __setColumnVis( settings, column, vis ); } ); // Group the column visibility changes if ( vis !== undefined ) { this.iterator( 'table', function ( settings ) { // Redraw the header after changes _fnDrawHead( settings, settings.aoHeader ); _fnDrawHead( settings, settings.aoFooter ); // Update colspan for no records display. Child rows and extensions will use their own // listeners to do this - only need to update the empty table item here if ( ! settings.aiDisplay.length ) { $(settings.nTBody).find('td[colspan]').attr('colspan', _fnVisbleColumns(settings)); } _fnSaveState( settings ); // Second loop once the first is done for events that.iterator( 'column', function ( settings, column ) { _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] ); } ); if ( calc === undefined || calc ) { that.columns.adjust(); } }); } return ret; } ); _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) { return this.iterator( 'column', function ( settings, column ) { return type === 'visible' ? _fnColumnIndexToVisible( settings, column ) : column; }, 1 ); } ); _api_register( 'columns.adjust()', function () { return this.iterator( 'table', function ( settings ) { _fnAdjustColumnSizing( settings ); }, 1 ); } ); _api_register( 'column.index()', function ( type, idx ) { if ( this.context.length !== 0 ) { var ctx = this.context[0]; if ( type === 'fromVisible' || type === 'toData' ) { return _fnVisibleToColumnIndex( ctx, idx ); } else if ( type === 'fromData' || type === 'toVisible' ) { return _fnColumnIndexToVisible( ctx, idx ); } } } ); _api_register( 'column()', function ( selector, opts ) { return _selector_first( this.columns( selector, opts ) ); } ); var __cell_selector = function ( settings, selector, opts ) { var data = settings.aoData; var rows = _selector_row_indexes( settings, opts ); var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) ); var allCells = $(_flatten( [], cells )); var row; var columns = settings.aoColumns.length; var a, i, ien, j, o, host; var run = function ( s ) { var fnSelector = typeof s === 'function'; if ( s === null || s === undefined || fnSelector ) { // All cells and function selectors a = []; for ( i=0, ien=rows.length ; i<ien ; i++ ) { row = rows[i]; for ( j=0 ; j<columns ; j++ ) { o = { row: row, column: j }; if ( fnSelector ) { // Selector - function host = data[ row ]; if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) { a.push( o ); } } else { // Selector - all a.push( o ); } } } return a; } // Selector - index if ( $.isPlainObject( s ) ) { // Valid cell index and its in the array of selectable rows return s.column !== undefined && s.row !== undefined && $.inArray( s.row, rows ) !== -1 ? [s] : []; } // Selector - jQuery filtered cells var jqResult = allCells .filter( s ) .map( function (i, el) { return { // use a new object, in case someone changes the values row: el._DT_CellIndex.row, column: el._DT_CellIndex.column }; } ) .toArray(); if ( jqResult.length || ! s.nodeName ) { return jqResult; } // Otherwise the selector is a node, and there is one last option - the // element might be a child of an element which has dt-row and dt-column // data attributes host = $(s).closest('*[data-dt-row]'); return host.length ? [ { row: host.data('dt-row'), column: host.data('dt-column') } ] : []; }; return _selector_run( 'cell', selector, run, settings, opts ); }; _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) { // Argument shifting if ( $.isPlainObject( rowSelector ) ) { // Indexes if ( rowSelector.row === undefined ) { // Selector options in first parameter opts = rowSelector; rowSelector = null; } else { // Cell index objects in first parameter opts = columnSelector; columnSelector = null; } } if ( $.isPlainObject( columnSelector ) ) { opts = columnSelector; columnSelector = null; } // Cell selector if ( columnSelector === null || columnSelector === undefined ) { return this.iterator( 'table', function ( settings ) { return __cell_selector( settings, rowSelector, _selector_opts( opts ) ); } ); } // The default built in options need to apply to row and columns var internalOpts = opts ? { page: opts.page, order: opts.order, search: opts.search } : {}; // Row + column selector var columns = this.columns( columnSelector, internalOpts ); var rows = this.rows( rowSelector, internalOpts ); var i, ien, j, jen; var cellsNoOpts = this.iterator( 'table', function ( settings, idx ) { var a = []; for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) { for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) { a.push( { row: rows[idx][i], column: columns[idx][j] } ); } } return a; }, 1 ); // There is currently only one extension which uses a cell selector extension // It is a _major_ performance drag to run this if it isn't needed, so this is // an extension specific check at the moment var cells = opts && opts.selected ? this.cells( cellsNoOpts, opts ) : cellsNoOpts; $.extend( cells.selector, { cols: columnSelector, rows: rowSelector, opts: opts } ); return cells; } ); _api_registerPlural( 'cells().nodes()', 'cell().node()', function () { return this.iterator( 'cell', function ( settings, row, column ) { var data = settings.aoData[ row ]; return data && data.anCells ? data.anCells[ column ] : undefined; }, 1 ); } ); _api_register( 'cells().data()', function () { return this.iterator( 'cell', function ( settings, row, column ) { return _fnGetCellData( settings, row, column ); }, 1 ); } ); _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) { type = type === 'search' ? '_aFilterData' : '_aSortData'; return this.iterator( 'cell', function ( settings, row, column ) { return settings.aoData[ row ][ type ][ column ]; }, 1 ); } ); _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) { return this.iterator( 'cell', function ( settings, row, column ) { return _fnGetCellData( settings, row, column, type ); }, 1 ); } ); _api_registerPlural( 'cells().indexes()', 'cell().index()', function () { return this.iterator( 'cell', function ( settings, row, column ) { return { row: row, column: column, columnVisible: _fnColumnIndexToVisible( settings, column ) }; }, 1 ); } ); _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) { return this.iterator( 'cell', function ( settings, row, column ) { _fnInvalidate( settings, row, src, column ); } ); } ); _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) { return _selector_first( this.cells( rowSelector, columnSelector, opts ) ); } ); _api_register( 'cell().data()', function ( data ) { var ctx = this.context; var cell = this[0]; if ( data === undefined ) { // Get return ctx.length && cell.length ? _fnGetCellData( ctx[0], cell[0].row, cell[0].column ) : undefined; } // Set _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data ); _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column ); return this; } ); /** * Get current ordering (sorting) that has been applied to the table. * * @returns {array} 2D array containing the sorting information for the first * table in the current context. Each element in the parent array represents * a column being sorted upon (i.e. multi-sorting with two columns would have * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is * the column index that the sorting condition applies to, the second is the * direction of the sort (`desc` or `asc`) and, optionally, the third is the * index of the sorting order from the `column.sorting` initialisation array. *//** * Set the ordering for the table. * * @param {integer} order Column index to sort upon. * @param {string} direction Direction of the sort to be applied (`asc` or `desc`) * @returns {DataTables.Api} this *//** * Set the ordering for the table. * * @param {array} order 1D array of sorting information to be applied. * @param {array} [...] Optional additional sorting conditions * @returns {DataTables.Api} this *//** * Set the ordering for the table. * * @param {array} order 2D array of sorting information to be applied. * @returns {DataTables.Api} this */ _api_register( 'order()', function ( order, dir ) { var ctx = this.context; if ( order === undefined ) { // get return ctx.length !== 0 ? ctx[0].aaSorting : undefined; } // set if ( typeof order === 'number' ) { // Simple column / direction passed in order = [ [ order, dir ] ]; } else if ( order.length && ! Array.isArray( order[0] ) ) { // Arguments passed in (list of 1D arrays) order = Array.prototype.slice.call( arguments ); } // otherwise a 2D array was passed in return this.iterator( 'table', function ( settings ) { settings.aaSorting = order.slice(); } ); } ); /** * Attach a sort listener to an element for a given column * * @param {node|jQuery|string} node Identifier for the element(s) to attach the * listener to. This can take the form of a single DOM node, a jQuery * collection of nodes or a jQuery selector which will identify the node(s). * @param {integer} column the column that a click on this node will sort on * @param {function} [callback] callback function when sort is run * @returns {DataTables.Api} this */ _api_register( 'order.listener()', function ( node, column, callback ) { return this.iterator( 'table', function ( settings ) { _fnSortAttachListener( settings, node, column, callback ); } ); } ); _api_register( 'order.fixed()', function ( set ) { if ( ! set ) { var ctx = this.context; var fixed = ctx.length ? ctx[0].aaSortingFixed : undefined; return Array.isArray( fixed ) ? { pre: fixed } : fixed; } return this.iterator( 'table', function ( settings ) { settings.aaSortingFixed = $.extend( true, {}, set ); } ); } ); // Order by the selected column(s) _api_register( [ 'columns().order()', 'column().order()' ], function ( dir ) { var that = this; return this.iterator( 'table', function ( settings, i ) { var sort = []; $.each( that[i], function (j, col) { sort.push( [ col, dir ] ); } ); settings.aaSorting = sort; } ); } ); _api_register( 'search()', function ( input, regex, smart, caseInsen ) { var ctx = this.context; if ( input === undefined ) { // get return ctx.length !== 0 ? ctx[0].oPreviousSearch.sSearch : undefined; } // set return this.iterator( 'table', function ( settings ) { if ( ! settings.oFeatures.bFilter ) { return; } _fnFilterComplete( settings, $.extend( {}, settings.oPreviousSearch, { "sSearch": input+"", "bRegex": regex === null ? false : regex, "bSmart": smart === null ? true : smart, "bCaseInsensitive": caseInsen === null ? true : caseInsen } ), 1 ); } ); } ); _api_registerPlural( 'columns().search()', 'column().search()', function ( input, regex, smart, caseInsen ) { return this.iterator( 'column', function ( settings, column ) { var preSearch = settings.aoPreSearchCols; if ( input === undefined ) { // get return preSearch[ column ].sSearch; } // set if ( ! settings.oFeatures.bFilter ) { return; } $.extend( preSearch[ column ], { "sSearch": input+"", "bRegex": regex === null ? false : regex, "bSmart": smart === null ? true : smart, "bCaseInsensitive": caseInsen === null ? true : caseInsen } ); _fnFilterComplete( settings, settings.oPreviousSearch, 1 ); } ); } ); /* * State API methods */ _api_register( 'state()', function () { return this.context.length ? this.context[0].oSavedState : null; } ); _api_register( 'state.clear()', function () { return this.iterator( 'table', function ( settings ) { // Save an empty object settings.fnStateSaveCallback.call( settings.oInstance, settings, {} ); } ); } ); _api_register( 'state.loaded()', function () { return this.context.length ? this.context[0].oLoadedState : null; } ); _api_register( 'state.save()', function () { return this.iterator( 'table', function ( settings ) { _fnSaveState( settings ); } ); } ); /** * Set the jQuery or window object to be used by DataTables * * @param {*} module Library / container object * @param {string} [type] Library or container type `lib`, `win` or `datetime`. * If not provided, automatic detection is attempted. */ DataTable.use = function (module, type) { if (type === 'lib' || module.fn) { $ = module; } else if (type == 'win' || module.document) { window = module; document = module.document; } else if (type === 'datetime' || module.type === 'DateTime') { DataTable.DateTime = module; } } /** * CommonJS factory function pass through. This will check if the arguments * given are a window object or a jQuery object. If so they are set * accordingly. * @param {*} root Window * @param {*} jq jQUery * @returns {boolean} Indicator */ DataTable.factory = function (root, jq) { var is = false; // Test if the first parameter is a window object if (root && root.document) { window = root; document = root.document; } // Test if the second parameter is a jQuery object if (jq && jq.fn && jq.fn.jquery) { $ = jq; is = true; } return is; } /** * Provide a common method for plug-ins to check the version of DataTables being * used, in order to ensure compatibility. * * @param {string} version Version string to check for, in the format "X.Y.Z". * 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 * @static * @dtopt API-Static * * @example * alert( $.fn.dataTable.versionCheck( '1.9.0' ) ); */ DataTable.versionCheck = DataTable.fnVersionCheck = function( version ) { var aThis = DataTable.version.split('.'); var aThat = version.split('.'); var iThis, iThat; for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) { iThis = parseInt( aThis[i], 10 ) || 0; iThat = parseInt( aThat[i], 10 ) || 0; // Parts are the same, keep comparing if (iThis === iThat) { continue; } // Parts are different, return immediately return iThis > iThat; } return true; }; /** * Check if a `<table>` node is a DataTable table already or not. * * @param {node|jquery|string} table Table node, jQuery object or jQuery * selector for the table to test. Note that if more than more than one * table is passed on, only the first will be checked * @returns {boolean} true the table given is a DataTable, or false otherwise * @static * @dtopt API-Static * * @example * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) { * $('#example').dataTable(); * } */ DataTable.isDataTable = DataTable.fnIsDataTable = function ( table ) { var t = $(table).get(0); var is = false; if ( table instanceof DataTable.Api ) { return true; } $.each( DataTable.settings, function (i, o) { var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null; var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null; if ( o.nTable === t || head === t || foot === t ) { is = true; } } ); return is; }; /** * Get all DataTable tables that have been initialised - optionally you can * select to get only currently visible tables. * * @param {boolean} [visible=false] Flag to indicate if you want all (default) * or visible tables only. * @returns {array} Array of `table` nodes (not DataTable instances) which are * DataTables * @static * @dtopt API-Static * * @example * $.each( $.fn.dataTable.tables(true), function () { * $(table).DataTable().columns.adjust(); * } ); */ DataTable.tables = DataTable.fnTables = function ( visible ) { var api = false; if ( $.isPlainObject( visible ) ) { api = visible.api; visible = visible.visible; } var a = $.map( DataTable.settings, function (o) { if ( !visible || (visible && $(o.nTable).is(':visible')) ) { return o.nTable; } } ); return api ? new _Api( a ) : a; }; /** * Convert from camel case parameters to Hungarian notation. This is made public * for the extensions to provide the same ability as DataTables core to accept * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase * parameters. * * @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. */ DataTable.camelToHungarian = _fnCamelToHungarian; /** * */ _api_register( '$()', function ( selector, opts ) { var rows = this.rows( opts ).nodes(), // Get all rows jqRows = $(rows); return $( [].concat( jqRows.filter( selector ).toArray(), jqRows.find( selector ).toArray() ) ); } ); // jQuery functions to operate on the tables $.each( [ 'on', 'one', 'off' ], function (i, key) { _api_register( key+'()', function ( /* event, handler */ ) { var args = Array.prototype.slice.call(arguments); // Add the `dt` namespace automatically if it isn't already present args[0] = $.map( args[0].split( /\s/ ), function ( e ) { return ! e.match(/\.dt\b/) ? e+'.dt' : e; } ).join( ' ' ); var inst = $( this.tables().nodes() ); inst[key].apply( inst, args ); return this; } ); } ); _api_register( 'clear()', function () { return this.iterator( 'table', function ( settings ) { _fnClearTable( settings ); } ); } ); _api_register( 'settings()', function () { return new _Api( this.context, this.context ); } ); _api_register( 'init()', function () { var ctx = this.context; return ctx.length ? ctx[0].oInit : null; } ); _api_register( 'data()', function () { return this.iterator( 'table', function ( settings ) { return _pluck( settings.aoData, '_aData' ); } ).flatten(); } ); _api_register( 'destroy()', function ( remove ) { remove = remove || false; return this.iterator( 'table', function ( settings ) { var classes = settings.oClasses; var table = settings.nTable; var tbody = settings.nTBody; var thead = settings.nTHead; var tfoot = settings.nTFoot; var jqTable = $(table); var jqTbody = $(tbody); var jqWrapper = $(settings.nTableWrapper); var rows = $.map( settings.aoData, function (r) { return r.nTr; } ); var i, ien; // Flag to note that the table is currently being destroyed - no action // should be taken settings.bDestroying = true; // Fire off the destroy callbacks for plug-ins etc _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] ); // If not being removed from the document, make all columns visible if ( ! remove ) { new _Api( settings ).columns().visible( true ); } // Blitz all `DT` namespaced events (these are internal events, the // lowercase, `dt` events are user subscribed and they are responsible // for removing them jqWrapper.off('.DT').find(':not(tbody *)').off('.DT'); $(window).off('.DT-'+settings.sInstance); // When scrolling we had to break the table up - restore it if ( table != thead.parentNode ) { jqTable.children('thead').detach(); jqTable.append( thead ); } if ( tfoot && table != tfoot.parentNode ) { jqTable.children('tfoot').detach(); jqTable.append( tfoot ); } settings.aaSorting = []; settings.aaSortingFixed = []; _fnSortingClasses( settings ); $( rows ).removeClass( settings.asStripeClasses.join(' ') ); $('th, td', thead).removeClass( classes.sSortable+' '+ classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone ); // Add the TR elements back into the table in their original order jqTbody.children().detach(); jqTbody.append( rows ); var orig = settings.nTableWrapper.parentNode; // Remove the DataTables generated nodes, events and classes var removedMethod = remove ? 'remove' : 'detach'; jqTable[ removedMethod ](); jqWrapper[ removedMethod ](); // If we need to reattach the table to the document if ( ! remove && orig ) { // insertBefore acts like appendChild if !arg[1] orig.insertBefore( table, settings.nTableReinsertBefore ); // Restore the width of the original table - was read from the style property, // so we can restore directly to that jqTable .css( 'width', settings.sDestroyWidth ) .removeClass( classes.sTable ); // If the were originally stripe classes - then we add them back here. // Note this is not fool proof (for example if not all rows had stripe // classes - but it's a good effort without getting carried away ien = settings.asDestroyStripes.length; if ( ien ) { jqTbody.children().each( function (i) { $(this).addClass( settings.asDestroyStripes[i % ien] ); } ); } } /* Remove the settings object from the settings array */ var idx = $.inArray( settings, DataTable.settings ); if ( idx !== -1 ) { DataTable.settings.splice( idx, 1 ); } } ); } ); // Add the `every()` method for rows, columns and cells in a compact form $.each( [ 'column', 'row', 'cell' ], function ( i, type ) { _api_register( type+'s().every()', function ( fn ) { var opts = this.selector.opts; var api = this; return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) { // Rows and columns: // arg1 - index // arg2 - table counter // arg3 - loop counter // arg4 - undefined // Cells: // arg1 - row index // arg2 - column index // arg3 - table counter // arg4 - loop counter fn.call( api[ type ]( arg1, type==='cell' ? arg2 : opts, type==='cell' ? opts : undefined ), arg1, arg2, arg3, arg4 ); } ); } ); } ); // i18n method for extensions to be able to use the language object from the // DataTable _api_register( 'i18n()', function ( token, def, plural ) { var ctx = this.context[0]; var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage ); if ( resolved === undefined ) { resolved = def; } if ( plural !== undefined && $.isPlainObject( resolved ) ) { resolved = resolved[ plural ] !== undefined ? resolved[ plural ] : resolved._; } return typeof resolved === 'string' ? resolved.replace( '%d', plural ) // nb: plural might be undefined, : resolved; } ); /** * Version string for plug-ins to check compatibility. Allowed format is * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used * only for non-release builds. See https://semver.org/ for more information. * @member * @type string * @default Version number */ DataTable.version = "1.13.8"; /** * Private data store, containing all of the settings objects that are * created for the tables on a given page. * * Note that the `DataTable.settings` object is aliased to * `jQuery.fn.dataTableExt` through which it may be accessed and * manipulated, or `jQuery.fn.dataTable.settings`. * @member * @type array * @default [] * @private */ DataTable.settings = []; /** * Object models container, for the various models that DataTables has * available to it. These models define the objects that are used to hold * the active state and configuration of the table. * @namespace */ DataTable.models = {}; /** * Template object for the way in which DataTables holds information about * search information for the global filter and individual column filters. * @namespace */ DataTable.models.oSearch = { /** * Flag to indicate if the filtering should be case insensitive or not * @type boolean * @default true */ "bCaseInsensitive": true, /** * Applied search term * @type string * @default <i>Empty string</i> */ "sSearch": "", /** * Flag to indicate if the search term should be interpreted as a * regular expression (true) or not (false) and therefore and special * regex characters escaped. * @type boolean * @default false */ "bRegex": false, /** * Flag to indicate if DataTables is to use its smart filtering or not. * @type boolean * @default true */ "bSmart": true, /** * Flag to indicate if DataTables should only trigger a search when * the return key is pressed. * @type boolean * @default false */ "return": false }; /** * Template object for the way in which DataTables holds information about * each individual row. This is the object format used for the settings * aoData array. * @namespace */ DataTable.models.oRow = { /** * TR element for the row * @type node * @default null */ "nTr": null, /** * Array of TD elements for each row. This is null until the row has been * created. * @type array nodes * @default [] */ "anCells": null, /** * Data object from the original data source for the row. This is either * an array if using the traditional form of DataTables, or an object if * using mData options. The exact type will depend on the passed in * data from the data source, or will be an array if using DOM a data * source. * @type array|object * @default [] */ "_aData": [], /** * Sorting data cache - this array is ostensibly the same length as the * number of columns (although each index is generated only as it is * needed), and holds the data that is used for sorting each column in the * row. We do this cache generation at the start of the sort in order that * the formatting of the sort data need be done only once for each cell * per sort. This array should not be read from or written to by anything * other than the master sorting methods. * @type array * @default null * @private */ "_aSortData": null, /** * Per cell filtering data cache. As per the sort data cache, used to * increase the performance of the filtering in DataTables * @type array * @default null * @private */ "_aFilterData": null, /** * Filtering data cache. This is the same as the cell filtering cache, but * in this case a string rather than an array. This is easily computed with * a join on `_aFilterData`, but is provided as a cache so the join isn't * needed on every search (memory traded for performance) * @type array * @default null * @private */ "_sFilterRow": null, /** * Cache of the class name that DataTables has applied to the row, so we * can quickly look at this variable rather than needing to do a DOM check * on className for the nTr property. * @type string * @default <i>Empty string</i> * @private */ "_sRowStripe": "", /** * Denote if the original data source was from the DOM, or the data source * object. This is used for invalidating data, so DataTables can * automatically read data from the original source, unless uninstructed * otherwise. * @type string * @default null * @private */ "src": null, /** * Index in the aoData array. This saves an indexOf lookup when we have the * object, but want to know the index * @type integer * @default -1 * @private */ "idx": -1 }; /** * Template object for the column information object in DataTables. This object * is held in the settings aoColumns array and contains all the information that * DataTables needs about each individual column. * * Note that this object is related to {@link DataTable.defaults.column} * but this one is the internal data store for DataTables's cache of columns. * It should NOT be manipulated outside of DataTables. Any configuration should * be done through the initialisation options. * @namespace */ DataTable.models.oColumn = { /** * Column index. This could be worked out on-the-fly with $.inArray, but it * is faster to just hold it as a variable * @type integer * @default null */ "idx": null, /** * A list of the columns that sorting should occur on when this column * is sorted. That this property is an array allows multi-column sorting * to be defined for a column (for example first name / last name columns * would benefit from this). The values are integers pointing to the * columns to be sorted on (typically it will be a single integer pointing * at itself, but that doesn't need to be the case). * @type array */ "aDataSort": null, /** * Define the sorting directions that are applied to the column, in sequence * as the column is repeatedly sorted upon - i.e. the first value is used * as the sorting direction when the column if first sorted (clicked on). * Sort it again (click again) and it will move on to the next index. * Repeat until loop. * @type array */ "asSorting": null, /** * Flag to indicate if the column is searchable, and thus should be included * in the filtering or not. * @type boolean */ "bSearchable": null, /** * Flag to indicate if the column is sortable or not. * @type boolean */ "bSortable": null, /** * Flag to indicate if the column is currently visible in the table or not * @type boolean */ "bVisible": null, /** * Store for manual type assignment using the `column.type` option. This * is held in store so we can manipulate the column's `sType` property. * @type string * @default null * @private */ "_sManualType": null, /** * Flag to indicate if HTML5 data attributes should be used as the data * source for filtering or sorting. True is either are. * @type boolean * @default false * @private */ "_bAttrSrc": false, /** * Developer definable function that is called whenever a cell is created (Ajax source, * etc) or processed for input (DOM source). This can be used as a compliment to mRender * allowing you to modify the DOM element (add background colour for example) when the * element is available. * @type function * @param {element} nTd The TD node that has been created * @param {*} sData The Data for the cell * @param {array|object} oData The data for the whole row * @param {int} iRow The row index for the aoData data store * @default null */ "fnCreatedCell": null, /** * Function to get data from a cell in a column. You should <b>never</b> * access data directly through _aData internally in DataTables - always use * the method attached to this property. It allows mData to function as * required. This function is automatically assigned by the column * initialisation method * @type function * @param {array|object} oData The data array/object for the array * (i.e. aoData[]._aData) * @param {string} sSpecific The specific data type you want to get - * 'display', 'type' 'filter' 'sort' * @returns {*} The data for the cell from the given row's data * @default null */ "fnGetData": null, /** * Function to set data for a cell in the column. You should <b>never</b> * set the data directly to _aData internally in DataTables - always use * this method. It allows mData to function as required. This function * is automatically assigned by the column initialisation method * @type function * @param {array|object} oData The data array/object for the array * (i.e. aoData[]._aData) * @param {*} sValue Value to set * @default null */ "fnSetData": null, /** * Property to read the value for the cells in the column from the data * source array / object. If null, then the default content is used, if a * function is given then the return from the function is used. * @type function|int|string|null * @default null */ "mData": null, /** * Partner property to mData which is used (only when defined) to get * the data - i.e. it is basically the same as mData, but without the * 'set' option, and also the data fed to it is the result from mData. * This is the rendering method to match the data method of mData. * @type function|int|string|null * @default null */ "mRender": null, /** * Unique header TH/TD element for this column - this is what the sorting * listener is attached to (if sorting is enabled.) * @type node * @default null */ "nTh": null, /** * Unique footer TH/TD element for this column (if there is one). Not used * in DataTables as such, but can be used for plug-ins to reference the * footer for each column. * @type node * @default null */ "nTf": null, /** * The class to apply to all TD elements in the table's TBODY for the column * @type string * @default null */ "sClass": null, /** * When DataTables calculates the column widths to assign to each column, * it finds the longest string in each column and then constructs a * temporary table and reads the widths from that. The problem with this * is that "mmm" is much wider then "iiii", but the latter is a longer * string - thus the calculation can go wrong (doing it properly and putting * it into an DOM object and measuring that is horribly(!) slow). Thus as * a "work around" we provide this option. It will append its value to the * text that is found to be the longest string for the column - i.e. padding. * @type string */ "sContentPadding": null, /** * Allows a default value to be given for a column's data, and will be used * whenever a null data source is encountered (this can be because mData * is set to null, or because the data source itself is null). * @type string * @default null */ "sDefaultContent": null, /** * Name for the column, allowing reference to the column by name as well as * by index (needs a lookup to work by name). * @type string */ "sName": null, /** * Custom sorting data type - defines which of the available plug-ins in * afnSortData the custom sorting will use - if any is defined. * @type string * @default std */ "sSortDataType": 'std', /** * Class to be applied to the header element when sorting on this column * @type string * @default null */ "sSortingClass": null, /** * Class to be applied to the header element when sorting on this column - * when jQuery UI theming is used. * @type string * @default null */ "sSortingClassJUI": null, /** * Title of the column - what is seen in the TH element (nTh). * @type string */ "sTitle": null, /** * Column sorting and filtering type * @type string * @default null */ "sType": null, /** * Width of the column * @type string * @default null */ "sWidth": null, /** * Width of the column when it was first "encountered" * @type string * @default null */ "sWidthOrig": null }; /* * Developer note: The properties of the object below are given in Hungarian * notation, that was used as the interface for DataTables prior to v1.10, however * from v1.10 onwards the primary interface is camel case. In order to avoid * breaking backwards compatibility utterly with this change, the Hungarian * version is still, internally the primary interface, but is is not documented * - hence the @name tags in each doc comment. This allows a Javascript function * to create a map from Hungarian notation to camel case (going the other direction * would require each property to be listed, which would add around 3K to the size * of DataTables, while this method is about a 0.5K hit). * * Ultimately this does pave the way for Hungarian notation to be dropped * completely, but that is a massive amount of work and will break current * installs (therefore is on-hold until v2). */ /** * Initialisation options that can be given to DataTables at initialisation * time. * @namespace */ DataTable.defaults = { /** * An array of data to use for the table, passed in at initialisation which * will be used in preference to any data which is already in the DOM. This is * particularly useful for constructing tables purely in Javascript, for * example with a custom Ajax call. * @type array * @default null * * @dtopt Option * @name DataTable.defaults.data * * @example * // Using a 2D array data source * $(document).ready( function () { * $('#example').dataTable( { * "data": [ * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'], * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'], * ], * "columns": [ * { "title": "Engine" }, * { "title": "Browser" }, * { "title": "Platform" }, * { "title": "Version" }, * { "title": "Grade" } * ] * } ); * } ); * * @example * // Using an array of objects as a data source (`data`) * $(document).ready( function () { * $('#example').dataTable( { * "data": [ * { * "engine": "Trident", * "browser": "Internet Explorer 4.0", * "platform": "Win 95+", * "version": 4, * "grade": "X" * }, * { * "engine": "Trident", * "browser": "Internet Explorer 5.0", * "platform": "Win 95+", * "version": 5, * "grade": "C" * } * ], * "columns": [ * { "title": "Engine", "data": "engine" }, * { "title": "Browser", "data": "browser" }, * { "title": "Platform", "data": "platform" }, * { "title": "Version", "data": "version" }, * { "title": "Grade", "data": "grade" } * ] * } ); * } ); */ "aaData": null, /** * If ordering is enabled, then DataTables will perform a first pass sort on * initialisation. You can define which column(s) the sort is performed * upon, and the sorting direction, with this variable. The `sorting` array * should contain an array for each column to be sorted initially containing * the column's index and a direction string ('asc' or 'desc'). * @type array * @default [[0,'asc']] * * @dtopt Option * @name DataTable.defaults.order * * @example * // Sort by 3rd column first, and then 4th column * $(document).ready( function() { * $('#example').dataTable( { * "order": [[2,'asc'], [3,'desc']] * } ); * } ); * * // No initial sorting * $(document).ready( function() { * $('#example').dataTable( { * "order": [] * } ); * } ); */ "aaSorting": [[0,'asc']], /** * This parameter is basically identical to the `sorting` parameter, but * cannot be overridden by user interaction with the table. What this means * is that you could have a column (visible or hidden) which the sorting * will always be forced on first - any sorting after that (from the user) * will then be performed as required. This can be useful for grouping rows * together. * @type array * @default null * * @dtopt Option * @name DataTable.defaults.orderFixed * * @example * $(document).ready( function() { * $('#example').dataTable( { * "orderFixed": [[0,'asc']] * } ); * } ) */ "aaSortingFixed": [], /** * DataTables can be instructed to load data to display in the table from a * Ajax source. This option defines how that Ajax call is made and where to. * * The `ajax` property has three different modes of operation, depending on * how it is defined. These are: * * * `string` - Set the URL from where the data should be loaded from. * * `object` - Define properties for `jQuery.ajax`. * * `function` - Custom data get function * * `string` * -------- * * As a string, the `ajax` property simply defines the URL from which * DataTables will load data. * * `object` * -------- * * As an object, the parameters in the object are passed to * [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) allowing fine control * of the Ajax request. DataTables has a number of default parameters which * you can override using this option. Please refer to the jQuery * documentation for a full description of the options available, although * the following parameters provide additional options in DataTables or * require special consideration: * * * `data` - As with jQuery, `data` can be provided as an object, but it * can also be used as a function to manipulate the data DataTables sends * to the server. The function takes a single parameter, an object of * parameters with the values that DataTables has readied for sending. An * object may be returned which will be merged into the DataTables * defaults, or you can add the items to the object that was passed in and * not return anything from the function. This supersedes `fnServerParams` * from DataTables 1.9-. * * * `dataSrc` - By default DataTables will look for the property `data` (or * `aaData` for compatibility with DataTables 1.9-) when obtaining data * from an Ajax source or for server-side processing - this parameter * allows that property to be changed. You can use Javascript dotted * object notation to get a data source for multiple levels of nesting, or * it my be used as a function. As a function it takes a single parameter, * the JSON returned from the server, which can be manipulated as * required, with the returned value being that used by DataTables as the * data source for the table. This supersedes `sAjaxDataProp` from * DataTables 1.9-. * * * `success` - Should not be overridden it is used internally in * DataTables. To manipulate / transform the data returned by the server * use `ajax.dataSrc`, or use `ajax` as a function (see below). * * `function` * ---------- * * As a function, making the Ajax call is left up to yourself allowing * complete control of the Ajax request. Indeed, if desired, a method other * than Ajax could be used to obtain the required data, such as Web storage * or an AIR database. * * The function is given four parameters and no return is required. The * parameters are: * * 1. _object_ - Data to send to the server * 2. _function_ - Callback function that must be executed when the required * data has been obtained. That data should be passed into the callback * as the only parameter * 3. _object_ - DataTables settings object for the table * * Note that this supersedes `fnServerData` from DataTables 1.9-. * * @type string|object|function * @default null * * @dtopt Option * @name DataTable.defaults.ajax * @since 1.10.0 * * @example * // Get JSON data from a file via Ajax. * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default). * $('#example').dataTable( { * "ajax": "data.json" * } ); * * @example * // Get JSON data from a file via Ajax, using `dataSrc` to change * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`) * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "dataSrc": "tableData" * } * } ); * * @example * // Get JSON data from a file via Ajax, using `dataSrc` to read data * // from a plain array rather than an array in an object * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "dataSrc": "" * } * } ); * * @example * // Manipulate the data returned from the server - add a link to data * // (note this can, should, be done using `render` for the column - this * // is just a simple example of how the data can be manipulated). * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "dataSrc": function ( json ) { * for ( var i=0, ien=json.length ; i<ien ; i++ ) { * json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>'; * } * return json; * } * } * } ); * * @example * // Add data to the request * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "data": function ( d ) { * return { * "extra_search": $('#extra').val() * }; * } * } * } ); * * @example * // Send request as POST * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "type": "POST" * } * } ); * * @example * // Get the data from localStorage (could interface with a form for * // adding, editing and removing rows). * $('#example').dataTable( { * "ajax": function (data, callback, settings) { * callback( * JSON.parse( localStorage.getItem('dataTablesData') ) * ); * } * } ); */ "ajax": null, /** * This parameter allows you to readily specify the entries in the length drop * down menu that DataTables shows when pagination is enabled. It can be * either a 1D array of options which will be used for both the displayed * option and the value, or a 2D array which will use the array in the first * position as the value, and the array in the second position as the * displayed options (useful for language strings such as 'All'). * * Note that the `pageLength` property will be automatically set to the * first value given in this array, unless `pageLength` is also provided. * @type array * @default [ 10, 25, 50, 100 ] * * @dtopt Option * @name DataTable.defaults.lengthMenu * * @example * $(document).ready( function() { * $('#example').dataTable( { * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]] * } ); * } ); */ "aLengthMenu": [ 10, 25, 50, 100 ], /** * The `columns` option in the initialisation parameter allows you to define * details about the way individual columns behave. For a full list of * column options that can be set, please see * {@link DataTable.defaults.column}. Note that if you use `columns` to * define your columns, you must have an entry in the array for every single * column that you have in your table (these can be null if you don't which * to specify any options). * @member * * @name DataTable.defaults.column */ "aoColumns": null, /** * Very similar to `columns`, `columnDefs` allows you to target a specific * column, multiple columns, or all columns, using the `targets` property of * each object in the array. This allows great flexibility when creating * tables, as the `columnDefs` arrays can be of any length, targeting the * columns you specifically want. `columnDefs` may use any of the column * options available: {@link DataTable.defaults.column}, but it _must_ * have `targets` defined in each object in the array. Values in the `targets` * array may be: * <ul> * <li>a string - class name will be matched on the TH for the column</li> * <li>0 or a positive integer - column index counting from the left</li> * <li>a negative integer - column index counting from the right</li> * <li>the string "_all" - all columns (i.e. assign a default)</li> * </ul> * @member * * @name DataTable.defaults.columnDefs */ "aoColumnDefs": null, /** * Basically the same as `search`, this parameter defines the individual column * filtering state at initialisation time. The array must be of the same size * as the number of columns, and each element be an object with the parameters * `search` and `escapeRegex` (the latter is optional). 'null' is also * accepted and the default will be used. * @type array * @default [] * * @dtopt Option * @name DataTable.defaults.searchCols * * @example * $(document).ready( function() { * $('#example').dataTable( { * "searchCols": [ * null, * { "search": "My filter" }, * null, * { "search": "^[0-9]", "escapeRegex": false } * ] * } ); * } ) */ "aoSearchCols": [], /** * An array of CSS classes that should be applied to displayed rows. This * array may be of any length, and DataTables will apply each class * sequentially, looping when required. * @type array * @default null <i>Will take the values determined by the `oClasses.stripe*` * options</i> * * @dtopt Option * @name DataTable.defaults.stripeClasses * * @example * $(document).ready( function() { * $('#example').dataTable( { * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ] * } ); * } ) */ "asStripeClasses": null, /** * Enable or disable automatic column width calculation. This can be disabled * as an optimisation (it takes some time to calculate the widths) if the * tables widths are passed in using `columns`. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.autoWidth * * @example * $(document).ready( function () { * $('#example').dataTable( { * "autoWidth": false * } ); * } ); */ "bAutoWidth": true, /** * Deferred rendering can provide DataTables with a huge speed boost when you * are using an Ajax or JS data source for the table. This option, when set to * true, will cause DataTables to defer the creation of the table elements for * each row until they are needed for a draw - saving a significant amount of * time. * @type boolean * @default false * * @dtopt Features * @name DataTable.defaults.deferRender * * @example * $(document).ready( function() { * $('#example').dataTable( { * "ajax": "sources/arrays.txt", * "deferRender": true * } ); * } ); */ "bDeferRender": false, /** * Replace a DataTable which matches the given selector and replace it with * one which has the properties of the new initialisation object passed. If no * table matches the selector, then the new DataTable will be constructed as * per normal. * @type boolean * @default false * * @dtopt Options * @name DataTable.defaults.destroy * * @example * $(document).ready( function() { * $('#example').dataTable( { * "srollY": "200px", * "paginate": false * } ); * * // Some time later.... * $('#example').dataTable( { * "filter": false, * "destroy": true * } ); * } ); */ "bDestroy": false, /** * Enable or disable filtering of data. Filtering in DataTables is "smart" in * that it allows the end user to input multiple words (space separated) and * will match a row containing those words, even if not in the order that was * specified (this allow matching across multiple columns). Note that if you * wish to use filtering in DataTables this must remain 'true' - to remove the * default filtering input box and retain filtering abilities, please use * {@link DataTable.defaults.dom}. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.searching * * @example * $(document).ready( function () { * $('#example').dataTable( { * "searching": false * } ); * } ); */ "bFilter": true, /** * Enable or disable the table information display. This shows information * about the data that is currently visible on the page, including information * about filtered data if that action is being performed. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.info * * @example * $(document).ready( function () { * $('#example').dataTable( { * "info": false * } ); * } ); */ "bInfo": true, /** * Allows the end user to select the size of a formatted page from a select * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`). * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.lengthChange * * @example * $(document).ready( function () { * $('#example').dataTable( { * "lengthChange": false * } ); * } ); */ "bLengthChange": true, /** * Enable or disable pagination. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.paging * * @example * $(document).ready( function () { * $('#example').dataTable( { * "paging": false * } ); * } ); */ "bPaginate": true, /** * Enable or disable the display of a 'processing' indicator when the table is * being processed (e.g. a sort). This is particularly useful for tables with * large amounts of data where it can take a noticeable amount of time to sort * the entries. * @type boolean * @default false * * @dtopt Features * @name DataTable.defaults.processing * * @example * $(document).ready( function () { * $('#example').dataTable( { * "processing": true * } ); * } ); */ "bProcessing": false, /** * Retrieve the DataTables object for the given selector. Note that if the * table has already been initialised, this parameter will cause DataTables * to simply return the object that has already been set up - it will not take * account of any changes you might have made to the initialisation object * passed to DataTables (setting this parameter to true is an acknowledgement * that you understand this). `destroy` can be used to reinitialise a table if * you need. * @type boolean * @default false * * @dtopt Options * @name DataTable.defaults.retrieve * * @example * $(document).ready( function() { * initTable(); * tableActions(); * } ); * * function initTable () * { * return $('#example').dataTable( { * "scrollY": "200px", * "paginate": false, * "retrieve": true * } ); * } * * function tableActions () * { * var table = initTable(); * // perform API operations with oTable * } */ "bRetrieve": false, /** * When vertical (y) scrolling is enabled, DataTables will force the height of * the table's viewport to the given height at all times (useful for layout). * However, this can look odd when filtering data down to a small data set, * and the footer is left "floating" further down. This parameter (when * enabled) will cause DataTables to collapse the table's viewport down when * the result set will fit within the given Y height. * @type boolean * @default false * * @dtopt Options * @name DataTable.defaults.scrollCollapse * * @example * $(document).ready( function() { * $('#example').dataTable( { * "scrollY": "200", * "scrollCollapse": true * } ); * } ); */ "bScrollCollapse": false, /** * Configure DataTables to use server-side processing. Note that the * `ajax` parameter must also be given in order to give DataTables a * source to obtain the required data for each draw. * @type boolean * @default false * * @dtopt Features * @dtopt Server-side * @name DataTable.defaults.serverSide * * @example * $(document).ready( function () { * $('#example').dataTable( { * "serverSide": true, * "ajax": "xhr.php" * } ); * } ); */ "bServerSide": false, /** * Enable or disable sorting of columns. Sorting of individual columns can be * disabled by the `sortable` option for each column. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.ordering * * @example * $(document).ready( function () { * $('#example').dataTable( { * "ordering": false * } ); * } ); */ "bSort": true, /** * Enable or display DataTables' ability to sort multiple columns at the * same time (activated by shift-click by the user). * @type boolean * @default true * * @dtopt Options * @name DataTable.defaults.orderMulti * * @example * // Disable multiple column sorting ability * $(document).ready( function () { * $('#example').dataTable( { * "orderMulti": false * } ); * } ); */ "bSortMulti": true, /** * Allows control over whether DataTables should use the top (true) unique * cell that is found for a single column, or the bottom (false - default). * This is useful when using complex headers. * @type boolean * @default false * * @dtopt Options * @name DataTable.defaults.orderCellsTop * * @example * $(document).ready( function() { * $('#example').dataTable( { * "orderCellsTop": true * } ); * } ); */ "bSortCellsTop": false, /** * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and * `sorting\_3` to the columns which are currently being sorted on. This is * presented as a feature switch as it can increase processing time (while * classes are removed and added) so for large data sets you might want to * turn this off. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.orderClasses * * @example * $(document).ready( function () { * $('#example').dataTable( { * "orderClasses": false * } ); * } ); */ "bSortClasses": true, /** * Enable or disable state saving. When enabled HTML5 `localStorage` will be * used to save table display information such as pagination information, * display length, filtering and sorting. As such when the end user reloads * the page the display display will match what thy had previously set up. * * Due to the use of `localStorage` the default state saving is not supported * in IE6 or 7. If state saving is required in those browsers, use * `stateSaveCallback` to provide a storage solution such as cookies. * @type boolean * @default false * * @dtopt Features * @name DataTable.defaults.stateSave * * @example * $(document).ready( function () { * $('#example').dataTable( { * "stateSave": true * } ); * } ); */ "bStateSave": false, /** * This function is called when a TR element is created (and all TD child * elements have been inserted), or registered if using a DOM source, allowing * manipulation of the TR element (adding classes etc). * @type function * @param {node} row "TR" element for the current row * @param {array} data Raw data array for this row * @param {int} dataIndex The index of this row in the internal aoData array * * @dtopt Callbacks * @name DataTable.defaults.createdRow * * @example * $(document).ready( function() { * $('#example').dataTable( { * "createdRow": function( row, data, dataIndex ) { * // Bold the grade for all 'A' grade browsers * if ( data[4] == "A" ) * { * $('td:eq(4)', row).html( '<b>A</b>' ); * } * } * } ); * } ); */ "fnCreatedRow": null, /** * This function is called on every 'draw' event, and allows you to * dynamically modify any aspect you want about the created DOM. * @type function * @param {object} settings DataTables settings object * * @dtopt Callbacks * @name DataTable.defaults.drawCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "drawCallback": function( settings ) { * alert( 'DataTables has redrawn the table' ); * } * } ); * } ); */ "fnDrawCallback": null, /** * Identical to fnHeaderCallback() but for the table footer this function * allows you to modify the table footer on every 'draw' event. * @type function * @param {node} foot "TR" element for the footer * @param {array} data Full table data (as derived from the original HTML) * @param {int} start Index for the current display starting point in the * display array * @param {int} end Index for the current display ending point in the * display array * @param {array int} display Index array to translate the visual position * to the full data array * * @dtopt Callbacks * @name DataTable.defaults.footerCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "footerCallback": function( tfoot, data, start, end, display ) { * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start; * } * } ); * } ) */ "fnFooterCallback": null, /** * When rendering large numbers in the information element for the table * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers * to have a comma separator for the 'thousands' units (e.g. 1 million is * rendered as "1,000,000") to help readability for the end user. This * function will override the default method DataTables uses. * @type function * @member * @param {int} toFormat number to be formatted * @returns {string} formatted string for DataTables to show the number * * @dtopt Callbacks * @name DataTable.defaults.formatNumber * * @example * // Format a number using a single quote for the separator (note that * // this can also be done with the language.thousands option) * $(document).ready( function() { * $('#example').dataTable( { * "formatNumber": function ( toFormat ) { * return toFormat.toString().replace( * /\B(?=(\d{3})+(?!\d))/g, "'" * ); * }; * } ); * } ); */ "fnFormatNumber": function ( toFormat ) { return toFormat.toString().replace( /\B(?=(\d{3})+(?!\d))/g, this.oLanguage.sThousands ); }, /** * This function is called on every 'draw' event, and allows you to * dynamically modify the header row. This can be used to calculate and * display useful information about the table. * @type function * @param {node} head "TR" element for the header * @param {array} data Full table data (as derived from the original HTML) * @param {int} start Index for the current display starting point in the * display array * @param {int} end Index for the current display ending point in the * display array * @param {array int} display Index array to translate the visual position * to the full data array * * @dtopt Callbacks * @name DataTable.defaults.headerCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "fheaderCallback": function( head, data, start, end, display ) { * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records"; * } * } ); * } ) */ "fnHeaderCallback": null, /** * The information element can be used to convey information about the current * state of the table. Although the internationalisation options presented by * DataTables are quite capable of dealing with most customisations, there may * be times where you wish to customise the string further. This callback * allows you to do exactly that. * @type function * @param {object} oSettings DataTables settings object * @param {int} start Starting position in data for the draw * @param {int} end End position in data for the draw * @param {int} max Total number of rows in the table (regardless of * filtering) * @param {int} total Total number of rows in the data set, after filtering * @param {string} pre The string that DataTables has formatted using it's * own rules * @returns {string} The string to be displayed in the information element. * * @dtopt Callbacks * @name DataTable.defaults.infoCallback * * @example * $('#example').dataTable( { * "infoCallback": function( settings, start, end, max, total, pre ) { * return start +" to "+ end; * } * } ); */ "fnInfoCallback": null, /** * Called when the table has been initialised. Normally DataTables will * initialise sequentially and there will be no need for this function, * however, this does not hold true when using external language information * since that is obtained using an async XHR call. * @type function * @param {object} settings DataTables settings object * @param {object} json The JSON object request from the server - only * present if client-side Ajax sourced data is used * * @dtopt Callbacks * @name DataTable.defaults.initComplete * * @example * $(document).ready( function() { * $('#example').dataTable( { * "initComplete": function(settings, json) { * alert( 'DataTables has finished its initialisation.' ); * } * } ); * } ) */ "fnInitComplete": null, /** * Called at the very start of each table draw and can be used to cancel the * draw by returning false, any other return (including undefined) results in * the full draw occurring). * @type function * @param {object} settings DataTables settings object * @returns {boolean} False will cancel the draw, anything else (including no * return) will allow it to complete. * * @dtopt Callbacks * @name DataTable.defaults.preDrawCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "preDrawCallback": function( settings ) { * if ( $('#test').val() == 1 ) { * return false; * } * } * } ); * } ); */ "fnPreDrawCallback": null, /** * This function allows you to 'post process' each row after it have been * generated for each table draw, but before it is rendered on screen. This * function might be used for setting the row class name etc. * @type function * @param {node} row "TR" element for the current row * @param {array} data Raw data array for this row * @param {int} displayIndex The display index for the current table draw * @param {int} displayIndexFull The index of the data in the full list of * rows (after filtering) * * @dtopt Callbacks * @name DataTable.defaults.rowCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "rowCallback": function( row, data, displayIndex, displayIndexFull ) { * // Bold the grade for all 'A' grade browsers * if ( data[4] == "A" ) { * $('td:eq(4)', row).html( '<b>A</b>' ); * } * } * } ); * } ); */ "fnRowCallback": null, /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * This parameter allows you to override the default function which obtains * the data from the server so something more suitable for your application. * For example you could use POST data, or pull information from a Gears or * AIR database. * @type function * @member * @param {string} source HTTP source to obtain the data from (`ajax`) * @param {array} data A key/value pair object containing the data to send * to the server * @param {function} callback to be called on completion of the data get * process that will draw the data on the page. * @param {object} settings DataTables settings object * * @dtopt Callbacks * @dtopt Server-side * @name DataTable.defaults.serverData * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "fnServerData": null, /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * It is often useful to send extra data to the server when making an Ajax * request - for example custom filtering information, and this callback * function makes it trivial to send extra information to the server. The * passed in parameter is the data set that has been constructed by * DataTables, and you can add to this or modify it as you require. * @type function * @param {array} data Data array (array of objects which are name/value * pairs) that has been constructed by DataTables and will be sent to the * server. In the case of Ajax sourced data with server-side processing * this will be an empty array, for server-side processing there will be a * significant number of parameters! * @returns {undefined} Ensure that you modify the data array passed in, * as this is passed by reference. * * @dtopt Callbacks * @dtopt Server-side * @name DataTable.defaults.serverParams * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "fnServerParams": null, /** * Load the table state. With this function you can define from where, and how, the * state of a table is loaded. By default DataTables will load from `localStorage` * but you might wish to use a server-side database or cookies. * @type function * @member * @param {object} settings DataTables settings object * @param {object} callback Callback that can be executed when done. It * should be passed the loaded state object. * @return {object} The DataTables state object to be loaded * * @dtopt Callbacks * @name DataTable.defaults.stateLoadCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateLoadCallback": function (settings, callback) { * $.ajax( { * "url": "/state_load", * "dataType": "json", * "success": function (json) { * callback( json ); * } * } ); * } * } ); * } ); */ "fnStateLoadCallback": function ( settings ) { try { return JSON.parse( (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( 'DataTables_'+settings.sInstance+'_'+location.pathname ) ); } catch (e) { return {}; } }, /** * Callback which allows modification of the saved state prior to loading that state. * This callback is called when the table is loading state from the stored data, but * prior to the settings object being modified by the saved state. Note that for * plug-in authors, you should use the `stateLoadParams` event to load parameters for * a plug-in. * @type function * @param {object} settings DataTables settings object * @param {object} data The state object that is to be loaded * * @dtopt Callbacks * @name DataTable.defaults.stateLoadParams * * @example * // Remove a saved filter, so filtering is never loaded * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateLoadParams": function (settings, data) { * data.oSearch.sSearch = ""; * } * } ); * } ); * * @example * // Disallow state loading by returning false * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateLoadParams": function (settings, data) { * return false; * } * } ); * } ); */ "fnStateLoadParams": null, /** * Callback that is called when the state has been loaded from the state saving method * and the DataTables settings object has been modified as a result of the loaded state. * @type function * @param {object} settings DataTables settings object * @param {object} data The state object that was loaded * * @dtopt Callbacks * @name DataTable.defaults.stateLoaded * * @example * // Show an alert with the filtering value that was saved * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateLoaded": function (settings, data) { * alert( 'Saved filter was: '+data.oSearch.sSearch ); * } * } ); * } ); */ "fnStateLoaded": null, /** * Save the table state. This function allows you to define where and how the state * information for the table is stored By default DataTables will use `localStorage` * but you might wish to use a server-side database or cookies. * @type function * @member * @param {object} settings DataTables settings object * @param {object} data The state object to be saved * * @dtopt Callbacks * @name DataTable.defaults.stateSaveCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateSaveCallback": function (settings, data) { * // Send an Ajax request to the server with the state object * $.ajax( { * "url": "/state_save", * "data": data, * "dataType": "json", * "method": "POST" * "success": function () {} * } ); * } * } ); * } ); */ "fnStateSaveCallback": function ( settings, data ) { try { (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( 'DataTables_'+settings.sInstance+'_'+location.pathname, JSON.stringify( data ) ); } catch (e) {} }, /** * Callback which allows modification of the state to be saved. Called when the table * has changed state a new state save is required. This method allows modification of * the state saving object prior to actually doing the save, including addition or * other state properties or modification. Note that for plug-in authors, you should * use the `stateSaveParams` event to save parameters for a plug-in. * @type function * @param {object} settings DataTables settings object * @param {object} data The state object to be saved * * @dtopt Callbacks * @name DataTable.defaults.stateSaveParams * * @example * // Remove a saved filter, so filtering is never saved * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateSaveParams": function (settings, data) { * data.oSearch.sSearch = ""; * } * } ); * } ); */ "fnStateSaveParams": null, /** * Duration for which the saved state information is considered valid. After this period * has elapsed the state will be returned to the default. * Value is given in seconds. * @type int * @default 7200 <i>(2 hours)</i> * * @dtopt Options * @name DataTable.defaults.stateDuration * * @example * $(document).ready( function() { * $('#example').dataTable( { * "stateDuration": 60*60*24; // 1 day * } ); * } ) */ "iStateDuration": 7200, /** * When enabled DataTables will not make a request to the server for the first * page draw - rather it will use the data already on the page (no sorting etc * will be applied to it), thus saving on an XHR at load time. `deferLoading` * is used to indicate that deferred loading is required, but it is also used * to tell DataTables how many records there are in the full table (allowing * the information element and pagination to be displayed correctly). In the case * where a filtering is applied to the table on initial load, this can be * indicated by giving the parameter as an array, where the first element is * the number of records available after filtering and the second element is the * number of records without filtering (allowing the table information element * to be shown correctly). * @type int | array * @default null * * @dtopt Options * @name DataTable.defaults.deferLoading * * @example * // 57 records available in the table, no filtering applied * $(document).ready( function() { * $('#example').dataTable( { * "serverSide": true, * "ajax": "scripts/server_processing.php", * "deferLoading": 57 * } ); * } ); * * @example * // 57 records after filtering, 100 without filtering (an initial filter applied) * $(document).ready( function() { * $('#example').dataTable( { * "serverSide": true, * "ajax": "scripts/server_processing.php", * "deferLoading": [ 57, 100 ], * "search": { * "search": "my_filter" * } * } ); * } ); */ "iDeferLoading": null, /** * Number of rows to display on a single page when using pagination. If * feature enabled (`lengthChange`) then the end user will be able to override * this to a custom setting using a pop-up menu. * @type int * @default 10 * * @dtopt Options * @name DataTable.defaults.pageLength * * @example * $(document).ready( function() { * $('#example').dataTable( { * "pageLength": 50 * } ); * } ) */ "iDisplayLength": 10, /** * Define the starting point for data display when using DataTables with * pagination. Note that this parameter is the number of records, rather than * the page number, so if you have 10 records per page and want to start on * the third page, it should be "20". * @type int * @default 0 * * @dtopt Options * @name DataTable.defaults.displayStart * * @example * $(document).ready( function() { * $('#example').dataTable( { * "displayStart": 20 * } ); * } ) */ "iDisplayStart": 0, /** * By default DataTables allows keyboard navigation of the table (sorting, paging, * and filtering) by adding a `tabindex` attribute to the required elements. This * allows you to tab through the controls and press the enter key to activate them. * The tabindex is default 0, meaning that the tab follows the flow of the document. * You can overrule this using this parameter if you wish. Use a value of -1 to * disable built-in keyboard navigation. * @type int * @default 0 * * @dtopt Options * @name DataTable.defaults.tabIndex * * @example * $(document).ready( function() { * $('#example').dataTable( { * "tabIndex": 1 * } ); * } ); */ "iTabIndex": 0, /** * Classes that DataTables assigns to the various components and features * that it adds to the HTML table. This allows classes to be configured * during initialisation in addition to through the static * {@link DataTable.ext.oStdClasses} object). * @namespace * @name DataTable.defaults.classes */ "oClasses": {}, /** * All strings that DataTables uses in the user interface that it creates * are defined in this object, allowing you to modified them individually or * completely replace them all as required. * @namespace * @name DataTable.defaults.language */ "oLanguage": { /** * Strings that are used for WAI-ARIA labels and controls only (these are not * actually visible on the page, but will be read by screenreaders, and thus * must be internationalised as well). * @namespace * @name DataTable.defaults.language.aria */ "oAria": { /** * ARIA label that is added to the table headers when the column may be * sorted ascending by activing the column (click or return when focused). * Note that the column header is prefixed to this string. * @type string * @default : activate to sort column ascending * * @dtopt Language * @name DataTable.defaults.language.aria.sortAscending * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "aria": { * "sortAscending": " - click/return to sort ascending" * } * } * } ); * } ); */ "sSortAscending": ": activate to sort column ascending", /** * ARIA label that is added to the table headers when the column may be * sorted descending by activing the column (click or return when focused). * Note that the column header is prefixed to this string. * @type string * @default : activate to sort column ascending * * @dtopt Language * @name DataTable.defaults.language.aria.sortDescending * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "aria": { * "sortDescending": " - click/return to sort descending" * } * } * } ); * } ); */ "sSortDescending": ": activate to sort column descending" }, /** * Pagination string used by DataTables for the built-in pagination * control types. * @namespace * @name DataTable.defaults.language.paginate */ "oPaginate": { /** * Text to use when using the 'full_numbers' type of pagination for the * button to take the user to the first page. * @type string * @default First * * @dtopt Language * @name DataTable.defaults.language.paginate.first * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "paginate": { * "first": "First page" * } * } * } ); * } ); */ "sFirst": "First", /** * Text to use when using the 'full_numbers' type of pagination for the * button to take the user to the last page. * @type string * @default Last * * @dtopt Language * @name DataTable.defaults.language.paginate.last * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "paginate": { * "last": "Last page" * } * } * } ); * } ); */ "sLast": "Last", /** * Text to use for the 'next' pagination button (to take the user to the * next page). * @type string * @default Next * * @dtopt Language * @name DataTable.defaults.language.paginate.next * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "paginate": { * "next": "Next page" * } * } * } ); * } ); */ "sNext": "Next", /** * Text to use for the 'previous' pagination button (to take the user to * the previous page). * @type string * @default Previous * * @dtopt Language * @name DataTable.defaults.language.paginate.previous * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "paginate": { * "previous": "Previous page" * } * } * } ); * } ); */ "sPrevious": "Previous" }, /** * This string is shown in preference to `zeroRecords` when the table is * empty of data (regardless of filtering). Note that this is an optional * parameter - if it is not given, the value of `zeroRecords` will be used * instead (either the default or given value). * @type string * @default No data available in table * * @dtopt Language * @name DataTable.defaults.language.emptyTable * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "emptyTable": "No data available in table" * } * } ); * } ); */ "sEmptyTable": "No data available in table", /** * This string gives information to the end user about the information * that is current on display on the page. The following tokens can be * used in the string and will be dynamically replaced as the table * display updates. This tokens can be placed anywhere in the string, or * removed as needed by the language requires: * * * `\_START\_` - Display index of the first record on the current page * * `\_END\_` - Display index of the last record on the current page * * `\_TOTAL\_` - Number of records in the table after filtering * * `\_MAX\_` - Number of records in the table without filtering * * `\_PAGE\_` - Current page number * * `\_PAGES\_` - Total number of pages of data in the table * * @type string * @default Showing _START_ to _END_ of _TOTAL_ entries * * @dtopt Language * @name DataTable.defaults.language.info * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "info": "Showing page _PAGE_ of _PAGES_" * } * } ); * } ); */ "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries", /** * Display information string for when the table is empty. Typically the * format of this string should match `info`. * @type string * @default Showing 0 to 0 of 0 entries * * @dtopt Language * @name DataTable.defaults.language.infoEmpty * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "infoEmpty": "No entries to show" * } * } ); * } ); */ "sInfoEmpty": "Showing 0 to 0 of 0 entries", /** * When a user filters the information in a table, this string is appended * to the information (`info`) to give an idea of how strong the filtering * is. The variable _MAX_ is dynamically updated. * @type string * @default (filtered from _MAX_ total entries) * * @dtopt Language * @name DataTable.defaults.language.infoFiltered * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "infoFiltered": " - filtering from _MAX_ records" * } * } ); * } ); */ "sInfoFiltered": "(filtered from _MAX_ total entries)", /** * If can be useful to append extra information to the info string at times, * and this variable does exactly that. This information will be appended to * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are * being used) at all times. * @type string * @default <i>Empty string</i> * * @dtopt Language * @name DataTable.defaults.language.infoPostFix * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "infoPostFix": "All records shown are derived from real information." * } * } ); * } ); */ "sInfoPostFix": "", /** * This decimal place operator is a little different from the other * language options since DataTables doesn't output floating point * numbers, so it won't ever use this for display of a number. Rather, * what this parameter does is modify the sort methods of the table so * that numbers which are in a format which has a character other than * a period (`.`) as a decimal place will be sorted numerically. * * Note that numbers with different decimal places cannot be shown in * the same table and still be sortable, the table must be consistent. * However, multiple different tables on the page can use different * decimal place characters. * @type string * @default * * @dtopt Language * @name DataTable.defaults.language.decimal * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "decimal": "," * "thousands": "." * } * } ); * } ); */ "sDecimal": "", /** * DataTables has a build in number formatter (`formatNumber`) which is * used to format large numbers that are used in the table information. * By default a comma is used, but this can be trivially changed to any * character you wish with this parameter. * @type string * @default , * * @dtopt Language * @name DataTable.defaults.language.thousands * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "thousands": "'" * } * } ); * } ); */ "sThousands": ",", /** * Detail the action that will be taken when the drop down menu for the * pagination length option is changed. The '_MENU_' variable is replaced * with a default select list of 10, 25, 50 and 100, and can be replaced * with a custom select box if required. * @type string * @default Show _MENU_ entries * * @dtopt Language * @name DataTable.defaults.language.lengthMenu * * @example * // Language change only * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "lengthMenu": "Display _MENU_ records" * } * } ); * } ); * * @example * // Language and options change * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "lengthMenu": 'Display <select>'+ * '<option value="10">10</option>'+ * '<option value="20">20</option>'+ * '<option value="30">30</option>'+ * '<option value="40">40</option>'+ * '<option value="50">50</option>'+ * '<option value="-1">All</option>'+ * '</select> records' * } * } ); * } ); */ "sLengthMenu": "Show _MENU_ entries", /** * When using Ajax sourced data and during the first draw when DataTables is * gathering the data, this message is shown in an empty row in the table to * indicate to the end user the the data is being loaded. Note that this * parameter is not used when loading data by server-side processing, just * Ajax sourced data with client-side processing. * @type string * @default Loading... * * @dtopt Language * @name DataTable.defaults.language.loadingRecords * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "loadingRecords": "Please wait - loading..." * } * } ); * } ); */ "sLoadingRecords": "Loading...", /** * Text which is displayed when the table is processing a user action * (usually a sort command or similar). * @type string * * @dtopt Language * @name DataTable.defaults.language.processing * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "processing": "DataTables is currently busy" * } * } ); * } ); */ "sProcessing": "", /** * Details the actions that will be taken when the user types into the * filtering input text box. The variable "_INPUT_", if used in the string, * is replaced with the HTML text box for the filtering input allowing * control over where it appears in the string. If "_INPUT_" is not given * then the input box is appended to the string automatically. * @type string * @default Search: * * @dtopt Language * @name DataTable.defaults.language.search * * @example * // Input text box will be appended at the end automatically * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "search": "Filter records:" * } * } ); * } ); * * @example * // Specify where the filter should appear * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "search": "Apply filter _INPUT_ to table" * } * } ); * } ); */ "sSearch": "Search:", /** * Assign a `placeholder` attribute to the search `input` element * @type string * @default * * @dtopt Language * @name DataTable.defaults.language.searchPlaceholder */ "sSearchPlaceholder": "", /** * All of the language information can be stored in a file on the * server-side, which DataTables will look up if this parameter is passed. * It must store the URL of the language file, which is in a JSON format, * and the object has the same properties as the oLanguage object in the * initialiser object (i.e. the above parameters). Please refer to one of * the example language files to see how this works in action. * @type string * @default <i>Empty string - i.e. disabled</i> * * @dtopt Language * @name DataTable.defaults.language.url * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "url": "https://www.sprymedia.co.uk/dataTables/lang.txt" * } * } ); * } ); */ "sUrl": "", /** * Text shown inside the table records when the is no information to be * displayed after filtering. `emptyTable` is shown when there is simply no * information in the table at all (regardless of filtering). * @type string * @default No matching records found * * @dtopt Language * @name DataTable.defaults.language.zeroRecords * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "zeroRecords": "No records to display" * } * } ); * } ); */ "sZeroRecords": "No matching records found" }, /** * This parameter allows you to have define the global filtering state at * initialisation time. As an object the `search` parameter must be * defined, but all other parameters are optional. When `regex` is true, * the search string will be treated as a regular expression, when false * (default) it will be treated as a straight string. When `smart` * DataTables will use it's smart filtering methods (to word match at * any point in the data), when false this will not be done. * @namespace * @extends DataTable.models.oSearch * * @dtopt Options * @name DataTable.defaults.search * * @example * $(document).ready( function() { * $('#example').dataTable( { * "search": {"search": "Initial search"} * } ); * } ) */ "oSearch": $.extend( {}, DataTable.models.oSearch ), /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * By default DataTables will look for the property `data` (or `aaData` for * compatibility with DataTables 1.9-) when obtaining data from an Ajax * source or for server-side processing - this parameter allows that * property to be changed. You can use Javascript dotted object notation to * get a data source for multiple levels of nesting. * @type string * @default data * * @dtopt Options * @dtopt Server-side * @name DataTable.defaults.ajaxDataProp * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "sAjaxDataProp": "data", /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * You can instruct DataTables to load data from an external * source using this parameter (use aData if you want to pass data in you * already have). Simply provide a url a JSON object can be obtained from. * @type string * @default null * * @dtopt Options * @dtopt Server-side * @name DataTable.defaults.ajaxSource * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "sAjaxSource": null, /** * This initialisation variable allows you to specify exactly where in the * DOM you want DataTables to inject the various controls it adds to the page * (for example you might want the pagination controls at the top of the * table). DIV elements (with or without a custom class) can also be added to * aid styling. The follow syntax is used: * <ul> * <li>The following options are allowed: * <ul> * <li>'l' - Length changing</li> * <li>'f' - Filtering input</li> * <li>'t' - The table!</li> * <li>'i' - Information</li> * <li>'p' - Pagination</li> * <li>'r' - pRocessing</li> * </ul> * </li> * <li>The following constants are allowed: * <ul> * <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li> * <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li> * </ul> * </li> * <li>The following syntax is expected: * <ul> * <li>'<' and '>' - div elements</li> * <li>'<"class" and '>' - div with a class</li> * <li>'<"#id" and '>' - div with an ID</li> * </ul> * </li> * <li>Examples: * <ul> * <li>'<"wrapper"flipt>'</li> * <li>'<lf<t>ip>'</li> * </ul> * </li> * </ul> * @type string * @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b> * <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i> * * @dtopt Options * @name DataTable.defaults.dom * * @example * $(document).ready( function() { * $('#example').dataTable( { * "dom": '<"top"i>rt<"bottom"flp><"clear">' * } ); * } ); */ "sDom": "lfrtip", /** * Search delay option. This will throttle full table searches that use the * DataTables provided search input element (it does not effect calls to * `dt-api search()`, providing a delay before the search is made. * @type integer * @default 0 * * @dtopt Options * @name DataTable.defaults.searchDelay * * @example * $(document).ready( function() { * $('#example').dataTable( { * "searchDelay": 200 * } ); * } ) */ "searchDelay": null, /** * DataTables features six different built-in options for the buttons to * display for pagination control: * * * `numbers` - Page number buttons only * * `simple` - 'Previous' and 'Next' buttons only * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers * * Further methods can be added using {@link DataTable.ext.oPagination}. * @type string * @default simple_numbers * * @dtopt Options * @name DataTable.defaults.pagingType * * @example * $(document).ready( function() { * $('#example').dataTable( { * "pagingType": "full_numbers" * } ); * } ) */ "sPaginationType": "simple_numbers", /** * Enable horizontal scrolling. When a table is too wide to fit into a * certain layout, or you have a large number of columns in the table, you * can enable x-scrolling to show the table in a viewport, which can be * scrolled. This property can be `true` which will allow the table to * scroll horizontally when needed, or any CSS unit, or a number (in which * case it will be treated as a pixel measurement). Setting as simply `true` * is recommended. * @type boolean|string * @default <i>blank string - i.e. disabled</i> * * @dtopt Features * @name DataTable.defaults.scrollX * * @example * $(document).ready( function() { * $('#example').dataTable( { * "scrollX": true, * "scrollCollapse": true * } ); * } ); */ "sScrollX": "", /** * This property can be used to force a DataTable to use more width than it * might otherwise do when x-scrolling is enabled. For example if you have a * table which requires to be well spaced, this parameter is useful for * "over-sizing" the table, and thus forcing scrolling. This property can by * any CSS unit, or a number (in which case it will be treated as a pixel * measurement). * @type string * @default <i>blank string - i.e. disabled</i> * * @dtopt Options * @name DataTable.defaults.scrollXInner * * @example * $(document).ready( function() { * $('#example').dataTable( { * "scrollX": "100%", * "scrollXInner": "110%" * } ); * } ); */ "sScrollXInner": "", /** * Enable vertical scrolling. Vertical scrolling will constrain the DataTable * to the given height, and enable scrolling for any data which overflows the * current viewport. This can be used as an alternative to paging to display * a lot of data in a small area (although paging and scrolling can both be * enabled at the same time). This property can be any CSS unit, or a number * (in which case it will be treated as a pixel measurement). * @type string * @default <i>blank string - i.e. disabled</i> * * @dtopt Features * @name DataTable.defaults.scrollY * * @example * $(document).ready( function() { * $('#example').dataTable( { * "scrollY": "200px", * "paginate": false * } ); * } ); */ "sScrollY": "", /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * Set the HTTP method that is used to make the Ajax call for server-side * processing or Ajax sourced data. * @type string * @default GET * * @dtopt Options * @dtopt Server-side * @name DataTable.defaults.serverMethod * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "sServerMethod": "GET", /** * DataTables makes use of renderers when displaying HTML elements for * a table. These renderers can be added or modified by plug-ins to * generate suitable mark-up for a site. For example the Bootstrap * integration plug-in for DataTables uses a paging button renderer to * display pagination buttons in the mark-up required by Bootstrap. * * For further information about the renderers available see * DataTable.ext.renderer * @type string|object * @default null * * @name DataTable.defaults.renderer * */ "renderer": null, /** * Set the data property name that DataTables should use to get a row's id * to set as the `id` property in the node. * @type string * @default DT_RowId * * @name DataTable.defaults.rowId */ "rowId": "DT_RowId" }; _fnHungarianMap( DataTable.defaults ); /* * Developer note - See note in model.defaults.js about the use of Hungarian * notation and camel case. */ /** * Column options that can be given to DataTables at initialisation time. * @namespace */ DataTable.defaults.column = { /** * Define which column(s) an order will occur on for this column. This * allows a column's ordering to take multiple columns into account when * doing a sort or use the data from a different column. For example first * name / last name columns make sense to do a multi-column sort over the * two columns. * @type array|int * @default null <i>Takes the value of the column index automatically</i> * * @name DataTable.defaults.column.orderData * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "orderData": [ 0, 1 ], "targets": [ 0 ] }, * { "orderData": [ 1, 0 ], "targets": [ 1 ] }, * { "orderData": 2, "targets": [ 2 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "orderData": [ 0, 1 ] }, * { "orderData": [ 1, 0 ] }, * { "orderData": 2 }, * null, * null * ] * } ); * } ); */ "aDataSort": null, "iDataSort": -1, /** * You can control the default ordering direction, and even alter the * behaviour of the sort handler (i.e. only allow ascending ordering etc) * using this parameter. * @type array * @default [ 'asc', 'desc' ] * * @name DataTable.defaults.column.orderSequence * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "orderSequence": [ "asc" ], "targets": [ 1 ] }, * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] }, * { "orderSequence": [ "desc" ], "targets": [ 3 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * null, * { "orderSequence": [ "asc" ] }, * { "orderSequence": [ "desc", "asc", "asc" ] }, * { "orderSequence": [ "desc" ] }, * null * ] * } ); * } ); */ "asSorting": [ 'asc', 'desc' ], /** * Enable or disable filtering on the data in this column. * @type boolean * @default true * * @name DataTable.defaults.column.searchable * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "searchable": false, "targets": [ 0 ] } * ] } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "searchable": false }, * null, * null, * null, * null * ] } ); * } ); */ "bSearchable": true, /** * Enable or disable ordering on this column. * @type boolean * @default true * * @name DataTable.defaults.column.orderable * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "orderable": false, "targets": [ 0 ] } * ] } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "orderable": false }, * null, * null, * null, * null * ] } ); * } ); */ "bSortable": true, /** * Enable or disable the display of this column. * @type boolean * @default true * * @name DataTable.defaults.column.visible * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "visible": false, "targets": [ 0 ] } * ] } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "visible": false }, * null, * null, * null, * null * ] } ); * } ); */ "bVisible": true, /** * Developer definable function that is called whenever a cell is created (Ajax source, * etc) or processed for input (DOM source). This can be used as a compliment to mRender * allowing you to modify the DOM element (add background colour for example) when the * element is available. * @type function * @param {element} td The TD node that has been created * @param {*} cellData The Data for the cell * @param {array|object} rowData The data for the whole row * @param {int} row The row index for the aoData data store * @param {int} col The column index for aoColumns * * @name DataTable.defaults.column.createdCell * @dtopt Columns * * @example * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [3], * "createdCell": function (td, cellData, rowData, row, col) { * if ( cellData == "1.7" ) { * $(td).css('color', 'blue') * } * } * } ] * }); * } ); */ "fnCreatedCell": null, /** * This parameter has been replaced by `data` in DataTables to ensure naming * consistency. `dataProp` can still be used, as there is backwards * compatibility in DataTables for this option, but it is strongly * recommended that you use `data` in preference to `dataProp`. * @name DataTable.defaults.column.dataProp */ /** * This property can be used to read data from any data source property, * including deeply nested objects / properties. `data` can be given in a * number of different ways which effect its behaviour: * * * `integer` - treated as an array index for the data source. This is the * default that DataTables uses (incrementally increased for each column). * * `string` - read an object property from the data source. There are * three 'special' options that can be used in the string to alter how * DataTables reads the data from the source object: * * `.` - Dotted Javascript notation. Just as you use a `.` in * Javascript to read from nested objects, so to can the options * specified in `data`. For example: `browser.version` or * `browser.name`. If your object parameter name contains a period, use * `\\` to escape it - i.e. `first\\.name`. * * `[]` - Array notation. DataTables can automatically combine data * from and array source, joining the data with the characters provided * between the two brackets. For example: `name[, ]` would provide a * comma-space separated list from the source array. If no characters * are provided between the brackets, the original array source is * returned. * * `()` - Function notation. Adding `()` to the end of a parameter will * execute a function of the name given. For example: `browser()` for a * simple function on the data source, `browser.version()` for a * function in a nested property or even `browser().version` to get an * object property if the function called returns an object. Note that * function notation is recommended for use in `render` rather than * `data` as it is much simpler to use as a renderer. * * `null` - use the original data source for the row rather than plucking * data directly from it. This action has effects on two other * initialisation options: * * `defaultContent` - When null is given as the `data` option and * `defaultContent` is specified for the column, the value defined by * `defaultContent` will be used for the cell. * * `render` - When null is used for the `data` option and the `render` * option is specified for the column, the whole data source for the * row is used for the renderer. * * `function` - the function given will be executed whenever DataTables * needs to set or get the data for a cell in the column. The function * takes three parameters: * * Parameters: * * `{array|object}` The data source for the row * * `{string}` The type call data requested - this will be 'set' when * setting data or 'filter', 'display', 'type', 'sort' or undefined * when gathering data. Note that when `undefined` is given for the * type DataTables expects to get the raw data for the object back< * * `{*}` Data to set when the second parameter is 'set'. * * Return: * * The return value from the function is not required when 'set' is * the type of call, but otherwise the return is what will be used * for the data requested. * * Note that `data` is a getter and setter option. If you just require * formatting of data for output, you will likely want to use `render` which * is simply a getter and thus simpler to use. * * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The * name change reflects the flexibility of this property and is consistent * with the naming of mRender. If 'mDataProp' is given, then it will still * be used by DataTables, as it automatically maps the old name to the new * if required. * * @type string|int|function|null * @default null <i>Use automatically calculated column index</i> * * @name DataTable.defaults.column.data * @dtopt Columns * * @example * // Read table data from objects * // JSON structure for each row: * // { * // "engine": {value}, * // "browser": {value}, * // "platform": {value}, * // "version": {value}, * // "grade": {value} * // } * $(document).ready( function() { * $('#example').dataTable( { * "ajaxSource": "sources/objects.txt", * "columns": [ * { "data": "engine" }, * { "data": "browser" }, * { "data": "platform" }, * { "data": "version" }, * { "data": "grade" } * ] * } ); * } ); * * @example * // Read information from deeply nested objects * // JSON structure for each row: * // { * // "engine": {value}, * // "browser": {value}, * // "platform": { * // "inner": {value} * // }, * // "details": [ * // {value}, {value} * // ] * // } * $(document).ready( function() { * $('#example').dataTable( { * "ajaxSource": "sources/deep.txt", * "columns": [ * { "data": "engine" }, * { "data": "browser" }, * { "data": "platform.inner" }, * { "data": "details.0" }, * { "data": "details.1" } * ] * } ); * } ); * * @example * // Using `data` as a function to provide different information for * // sorting, filtering and display. In this case, currency (price) * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": function ( source, type, val ) { * if (type === 'set') { * source.price = val; * // Store the computed display and filter values for efficiency * source.price_display = val=="" ? "" : "$"+numberFormat(val); * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val; * return; * } * else if (type === 'display') { * return source.price_display; * } * else if (type === 'filter') { * return source.price_filter; * } * // 'sort', 'type' and undefined all just use the integer * return source.price; * } * } ] * } ); * } ); * * @example * // Using default content * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": null, * "defaultContent": "Click to edit" * } ] * } ); * } ); * * @example * // Using array notation - outputting a list from an array * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": "name[, ]" * } ] * } ); * } ); * */ "mData": null, /** * This property is the rendering partner to `data` and it is suggested that * when you want to manipulate data for display (including filtering, * sorting etc) without altering the underlying data for the table, use this * property. `render` can be considered to be the the read only companion to * `data` which is read / write (then as such more complex). Like `data` * this option can be given in a number of different ways to effect its * behaviour: * * * `integer` - treated as an array index for the data source. This is the * default that DataTables uses (incrementally increased for each column). * * `string` - read an object property from the data source. There are * three 'special' options that can be used in the string to alter how * DataTables reads the data from the source object: * * `.` - Dotted Javascript notation. Just as you use a `.` in * Javascript to read from nested objects, so to can the options * specified in `data`. For example: `browser.version` or * `browser.name`. If your object parameter name contains a period, use * `\\` to escape it - i.e. `first\\.name`. * * `[]` - Array notation. DataTables can automatically combine data * from and array source, joining the data with the characters provided * between the two brackets. For example: `name[, ]` would provide a * comma-space separated list from the source array. If no characters * are provided between the brackets, the original array source is * returned. * * `()` - Function notation. Adding `()` to the end of a parameter will * execute a function of the name given. For example: `browser()` for a * simple function on the data source, `browser.version()` for a * function in a nested property or even `browser().version` to get an * object property if the function called returns an object. * * `object` - use different data for the different data types requested by * DataTables ('filter', 'display', 'type' or 'sort'). The property names * of the object is the data type the property refers to and the value can * defined using an integer, string or function using the same rules as * `render` normally does. Note that an `_` option _must_ be specified. * This is the default value to use if you haven't specified a value for * the data type requested by DataTables. * * `function` - the function given will be executed whenever DataTables * needs to set or get the data for a cell in the column. The function * takes three parameters: * * Parameters: * * {array|object} The data source for the row (based on `data`) * * {string} The type call data requested - this will be 'filter', * 'display', 'type' or 'sort'. * * {array|object} The full data source for the row (not based on * `data`) * * Return: * * The return value from the function is what will be used for the * data requested. * * @type string|int|function|object|null * @default null Use the data source value. * * @name DataTable.defaults.column.render * @dtopt Columns * * @example * // Create a comma separated list from an array of objects * $(document).ready( function() { * $('#example').dataTable( { * "ajaxSource": "sources/deep.txt", * "columns": [ * { "data": "engine" }, * { "data": "browser" }, * { * "data": "platform", * "render": "[, ].name" * } * ] * } ); * } ); * * @example * // Execute a function to obtain data * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": null, // Use the full data source object for the renderer's source * "render": "browserName()" * } ] * } ); * } ); * * @example * // As an object, extracting different data for the different types * // This would be used with a data source such as: * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" } * // Here the `phone` integer is used for sorting and type detection, while `phone_filter` * // (which has both forms) is used for filtering for if a user inputs either format, while * // the formatted phone number is the one that is shown in the table. * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": null, // Use the full data source object for the renderer's source * "render": { * "_": "phone", * "filter": "phone_filter", * "display": "phone_display" * } * } ] * } ); * } ); * * @example * // Use as a function to create a link from the data source * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": "download_link", * "render": function ( data, type, full ) { * return '<a href="'+data+'">Download</a>'; * } * } ] * } ); * } ); */ "mRender": null, /** * Change the cell type created for the column - either TD cells or TH cells. This * can be useful as TH cells have semantic meaning in the table body, allowing them * to act as a header for a row (you may wish to add scope='row' to the TH elements). * @type string * @default td * * @name DataTable.defaults.column.cellType * @dtopt Columns * * @example * // Make the first column use TH cells * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "cellType": "th" * } ] * } ); * } ); */ "sCellType": "td", /** * Class to give to each cell in this column. * @type string * @default <i>Empty string</i> * * @name DataTable.defaults.column.class * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "class": "my_class", "targets": [ 0 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "class": "my_class" }, * null, * null, * null, * null * ] * } ); * } ); */ "sClass": "", /** * When DataTables calculates the column widths to assign to each column, * it finds the longest string in each column and then constructs a * temporary table and reads the widths from that. The problem with this * is that "mmm" is much wider then "iiii", but the latter is a longer * string - thus the calculation can go wrong (doing it properly and putting * it into an DOM object and measuring that is horribly(!) slow). Thus as * a "work around" we provide this option. It will append its value to the * text that is found to be the longest string for the column - i.e. padding. * Generally you shouldn't need this! * @type string * @default <i>Empty string<i> * * @name DataTable.defaults.column.contentPadding * @dtopt Columns * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * null, * null, * null, * { * "contentPadding": "mmm" * } * ] * } ); * } ); */ "sContentPadding": "", /** * Allows a default value to be given for a column's data, and will be used * whenever a null data source is encountered (this can be because `data` * is set to null, or because the data source itself is null). * @type string * @default null * * @name DataTable.defaults.column.defaultContent * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { * "data": null, * "defaultContent": "Edit", * "targets": [ -1 ] * } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * null, * null, * null, * { * "data": null, * "defaultContent": "Edit" * } * ] * } ); * } ); */ "sDefaultContent": null, /** * This parameter is only used in DataTables' server-side processing. It can * be exceptionally useful to know what columns are being displayed on the * client side, and to map these to database fields. When defined, the names * also allow DataTables to reorder information from the server if it comes * back in an unexpected order (i.e. if you switch your columns around on the * client-side, your server-side code does not also need updating). * @type string * @default <i>Empty string</i> * * @name DataTable.defaults.column.name * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "name": "engine", "targets": [ 0 ] }, * { "name": "browser", "targets": [ 1 ] }, * { "name": "platform", "targets": [ 2 ] }, * { "name": "version", "targets": [ 3 ] }, * { "name": "grade", "targets": [ 4 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "name": "engine" }, * { "name": "browser" }, * { "name": "platform" }, * { "name": "version" }, * { "name": "grade" } * ] * } ); * } ); */ "sName": "", /** * Defines a data source type for the ordering which can be used to read * real-time information from the table (updating the internally cached * version) prior to ordering. This allows ordering to occur on user * editable elements such as form inputs. * @type string * @default std * * @name DataTable.defaults.column.orderDataType * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "orderDataType": "dom-text", "targets": [ 2, 3 ] }, * { "type": "numeric", "targets": [ 3 ] }, * { "orderDataType": "dom-select", "targets": [ 4 ] }, * { "orderDataType": "dom-checkbox", "targets": [ 5 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * null, * null, * { "orderDataType": "dom-text" }, * { "orderDataType": "dom-text", "type": "numeric" }, * { "orderDataType": "dom-select" }, * { "orderDataType": "dom-checkbox" } * ] * } ); * } ); */ "sSortDataType": "std", /** * The title of this column. * @type string * @default null <i>Derived from the 'TH' value for this column in the * original HTML table.</i> * * @name DataTable.defaults.column.title * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "title": "My column title", "targets": [ 0 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "title": "My column title" }, * null, * null, * null, * null * ] * } ); * } ); */ "sTitle": null, /** * The type allows you to specify how the data for this column will be * ordered. Four types (string, numeric, date and html (which will strip * HTML tags before ordering)) are currently available. Note that only date * formats understood by Javascript's Date() object will be accepted as type * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', * 'numeric', 'date' or 'html' (by default). Further types can be adding * through plug-ins. * @type string * @default null <i>Auto-detected from raw data</i> * * @name DataTable.defaults.column.type * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "type": "html", "targets": [ 0 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "type": "html" }, * null, * null, * null, * null * ] * } ); * } ); */ "sType": null, /** * Defining the width of the column, this parameter may take any CSS value * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not * been given a specific width through this interface ensuring that the table * remains readable. * @type string * @default null <i>Automatic</i> * * @name DataTable.defaults.column.width * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "width": "20%", "targets": [ 0 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "width": "20%" }, * null, * null, * null, * null * ] * } ); * } ); */ "sWidth": null }; _fnHungarianMap( DataTable.defaults.column ); /** * DataTables settings object - this holds all the information needed for a * given table, including configuration, data and current application of the * table options. DataTables does not have a single instance for each DataTable * with the settings attached to that instance, but rather instances of the * DataTable "class" are created on-the-fly as needed (typically by a * $().dataTable() call) and the settings object is then applied to that * instance. * * Note that this object is related to {@link DataTable.defaults} but this * one is the internal data store for DataTables's cache of columns. It should * NOT be manipulated outside of DataTables. Any configuration should be done * through the initialisation options. * @namespace * @todo Really should attach the settings object to individual instances so we * don't need to create new instances on each $().dataTable() call (if the * table already exists). It would also save passing oSettings around and * into every single function. However, this is a very significant * architecture change for DataTables and will almost certainly break * backwards compatibility with older installations. This is something that * will be done in 2.0. */ DataTable.models.oSettings = { /** * Primary features of DataTables and their enablement state. * @namespace */ "oFeatures": { /** * Flag to say if DataTables should automatically try to calculate the * optimum table and columns widths (true) or not (false). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bAutoWidth": null, /** * Delay the creation of TR and TD elements until they are actually * needed by a driven page draw. This can give a significant speed * increase for Ajax source and Javascript source data, but makes no * difference at all for DOM and server-side processing tables. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bDeferRender": null, /** * Enable filtering on the table or not. Note that if this is disabled * then there is no filtering at all on the table, including fnFilter. * To just remove the filtering input use sDom and remove the 'f' option. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bFilter": null, /** * Table information element (the 'Showing x of y records' div) enable * flag. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bInfo": null, /** * Present a user control allowing the end user to change the page size * when pagination is enabled. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bLengthChange": null, /** * Pagination enabled or not. Note that if this is disabled then length * changing must also be disabled. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bPaginate": null, /** * Processing indicator enable flag whenever DataTables is enacting a * user request - typically an Ajax request for server-side processing. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bProcessing": null, /** * Server-side processing enabled flag - when enabled DataTables will * get all data from the server for every draw - there is no filtering, * sorting or paging done on the client-side. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bServerSide": null, /** * Sorting enablement flag. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bSort": null, /** * Multi-column sorting * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bSortMulti": null, /** * Apply a class to the columns which are being sorted to provide a * visual highlight or not. This can slow things down when enabled since * there is a lot of DOM interaction. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bSortClasses": null, /** * State saving enablement flag. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bStateSave": null }, /** * Scrolling settings for a table. * @namespace */ "oScroll": { /** * When the table is shorter in height than sScrollY, collapse the * table container down to the height of the table (when true). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bCollapse": null, /** * Width of the scrollbar for the web-browser's platform. Calculated * during table initialisation. * @type int * @default 0 */ "iBarWidth": 0, /** * Viewport width for horizontal scrolling. Horizontal scrolling is * disabled if an empty string. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string */ "sX": null, /** * Width to expand the table to when using x-scrolling. Typically you * should not need to use this. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string * @deprecated */ "sXInner": null, /** * Viewport height for vertical scrolling. Vertical scrolling is disabled * if an empty string. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string */ "sY": null }, /** * Language information for the table. * @namespace * @extends DataTable.defaults.oLanguage */ "oLanguage": { /** * Information callback function. See * {@link DataTable.defaults.fnInfoCallback} * @type function * @default null */ "fnInfoCallback": null }, /** * Browser support parameters * @namespace */ "oBrowser": { /** * Indicate if the browser incorrectly calculates width:100% inside a * scrolling element (IE6/7) * @type boolean * @default false */ "bScrollOversize": false, /** * Determine if the vertical scrollbar is on the right or left of the * scrolling container - needed for rtl language layout, although not * all browsers move the scrollbar (Safari). * @type boolean * @default false */ "bScrollbarLeft": false, /** * Flag for if `getBoundingClientRect` is fully supported or not * @type boolean * @default false */ "bBounding": false, /** * Browser scrollbar width * @type integer * @default 0 */ "barWidth": 0 }, "ajax": null, /** * Array referencing the nodes which are used for the features. The * parameters of this object match what is allowed by sDom - i.e. * <ul> * <li>'l' - Length changing</li> * <li>'f' - Filtering input</li> * <li>'t' - The table!</li> * <li>'i' - Information</li> * <li>'p' - Pagination</li> * <li>'r' - pRocessing</li> * </ul> * @type array * @default [] */ "aanFeatures": [], /** * Store data information - see {@link DataTable.models.oRow} for detailed * information. * @type array * @default [] */ "aoData": [], /** * Array of indexes which are in the current display (after filtering etc) * @type array * @default [] */ "aiDisplay": [], /** * Array of indexes for display - no filtering * @type array * @default [] */ "aiDisplayMaster": [], /** * Map of row ids to data indexes * @type object * @default {} */ "aIds": {}, /** * Store information about each column that is in use * @type array * @default [] */ "aoColumns": [], /** * Store information about the table's header * @type array * @default [] */ "aoHeader": [], /** * Store information about the table's footer * @type array * @default [] */ "aoFooter": [], /** * Store the applied global search information in case we want to force a * research or compare the old search to a new one. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @namespace * @extends DataTable.models.oSearch */ "oPreviousSearch": {}, /** * Store the applied search for each column - see * {@link DataTable.models.oSearch} for the format that is used for the * filtering information for each column. * @type array * @default [] */ "aoPreSearchCols": [], /** * Sorting that is applied to the table. Note that the inner arrays are * used in the following manner: * <ul> * <li>Index 0 - column number</li> * <li>Index 1 - current sorting direction</li> * </ul> * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type array * @todo These inner arrays should really be objects */ "aaSorting": null, /** * Sorting that is always applied to the table (i.e. prefixed in front of * aaSorting). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type array * @default [] */ "aaSortingFixed": [], /** * Classes to use for the striping of a table. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type array * @default [] */ "asStripeClasses": null, /** * If restoring a table - we should restore its striping classes as well * @type array * @default [] */ "asDestroyStripes": [], /** * If restoring a table - we should restore its width * @type int * @default 0 */ "sDestroyWidth": 0, /** * Callback functions array for every time a row is inserted (i.e. on a draw). * @type array * @default [] */ "aoRowCallback": [], /** * Callback functions for the header on each draw. * @type array * @default [] */ "aoHeaderCallback": [], /** * Callback function for the footer on each draw. * @type array * @default [] */ "aoFooterCallback": [], /** * Array of callback functions for draw callback functions * @type array * @default [] */ "aoDrawCallback": [], /** * Array of callback functions for row created function * @type array * @default [] */ "aoRowCreatedCallback": [], /** * Callback functions for just before the table is redrawn. A return of * false will be used to cancel the draw. * @type array * @default [] */ "aoPreDrawCallback": [], /** * Callback functions for when the table has been initialised. * @type array * @default [] */ "aoInitComplete": [], /** * Callbacks for modifying the settings to be stored for state saving, prior to * saving state. * @type array * @default [] */ "aoStateSaveParams": [], /** * Callbacks for modifying the settings that have been stored for state saving * prior to using the stored values to restore the state. * @type array * @default [] */ "aoStateLoadParams": [], /** * Callbacks for operating on the settings object once the saved state has been * loaded * @type array * @default [] */ "aoStateLoaded": [], /** * Cache the table ID for quick access * @type string * @default <i>Empty string</i> */ "sTableId": "", /** * The TABLE node for the main table * @type node * @default null */ "nTable": null, /** * Permanent ref to the thead element * @type node * @default null */ "nTHead": null, /** * Permanent ref to the tfoot element - if it exists * @type node * @default null */ "nTFoot": null, /** * Permanent ref to the tbody element * @type node * @default null */ "nTBody": null, /** * Cache the wrapper node (contains all DataTables controlled elements) * @type node * @default null */ "nTableWrapper": null, /** * Indicate if when using server-side processing the loading of data * should be deferred until the second draw. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean * @default false */ "bDeferLoading": false, /** * Indicate if all required information has been read in * @type boolean * @default false */ "bInitialised": false, /** * Information about open rows. Each object in the array has the parameters * 'nTr' and 'nParent' * @type array * @default [] */ "aoOpenRows": [], /** * Dictate the positioning of DataTables' control elements - see * {@link DataTable.model.oInit.sDom}. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string * @default null */ "sDom": null, /** * Search delay (in mS) * @type integer * @default null */ "searchDelay": null, /** * Which type of pagination should be used. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string * @default two_button */ "sPaginationType": "two_button", /** * The state duration (for `stateSave`) in seconds. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type int * @default 0 */ "iStateDuration": 0, /** * Array of callback functions for state saving. Each array element is an * object with the following parameters: * <ul> * <li>function:fn - function to call. Takes two parameters, oSettings * and the JSON string to save that has been thus far created. Returns * a JSON string to be inserted into a json object * (i.e. '"param": [ 0, 1, 2]')</li> * <li>string:sName - name of callback</li> * </ul> * @type array * @default [] */ "aoStateSave": [], /** * Array of callback functions for state loading. Each array element is an * object with the following parameters: * <ul> * <li>function:fn - function to call. Takes two parameters, oSettings * and the object stored. May return false to cancel state loading</li> * <li>string:sName - name of callback</li> * </ul> * @type array * @default [] */ "aoStateLoad": [], /** * State that was saved. Useful for back reference * @type object * @default null */ "oSavedState": null, /** * State that was loaded. Useful for back reference * @type object * @default null */ "oLoadedState": null, /** * Source url for AJAX data for the table. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string * @default null */ "sAjaxSource": null, /** * Property from a given object from which to read the table data from. This * can be an empty string (when not server-side processing), in which case * it is assumed an an array is given directly. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string */ "sAjaxDataProp": null, /** * The last jQuery XHR object that was used for server-side data gathering. * This can be used for working with the XHR information in one of the * callbacks * @type object * @default null */ "jqXHR": null, /** * JSON returned from the server in the last Ajax request * @type object * @default undefined */ "json": undefined, /** * Data submitted as part of the last Ajax request * @type object * @default undefined */ "oAjaxData": undefined, /** * Function to get the server-side data. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type function */ "fnServerData": null, /** * Functions which are called prior to sending an Ajax request so extra * parameters can easily be sent to the server * @type array * @default [] */ "aoServerParams": [], /** * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if * required). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string */ "sServerMethod": null, /** * Format numbers for display. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type function */ "fnFormatNumber": null, /** * List of options that can be used for the user selectable length menu. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type array * @default [] */ "aLengthMenu": null, /** * Counter for the draws that the table does. Also used as a tracker for * server-side processing * @type int * @default 0 */ "iDraw": 0, /** * Indicate if a redraw is being done - useful for Ajax * @type boolean * @default false */ "bDrawing": false, /** * Draw index (iDraw) of the last error when parsing the returned data * @type int * @default -1 */ "iDrawError": -1, /** * Paging display length * @type int * @default 10 */ "_iDisplayLength": 10, /** * Paging start point - aiDisplay index * @type int * @default 0 */ "_iDisplayStart": 0, /** * Server-side processing - number of records in the result set * (i.e. before filtering), Use fnRecordsTotal rather than * this property to get the value of the number of records, regardless of * the server-side processing setting. * @type int * @default 0 * @private */ "_iRecordsTotal": 0, /** * Server-side processing - number of records in the current display set * (i.e. after filtering). Use fnRecordsDisplay rather than * this property to get the value of the number of records, regardless of * the server-side processing setting. * @type boolean * @default 0 * @private */ "_iRecordsDisplay": 0, /** * The classes to use for the table * @type object * @default {} */ "oClasses": {}, /** * Flag attached to the settings object so you can check in the draw * callback if filtering has been done in the draw. Deprecated in favour of * events. * @type boolean * @default false * @deprecated */ "bFiltered": false, /** * Flag attached to the settings object so you can check in the draw * callback if sorting has been done in the draw. Deprecated in favour of * events. * @type boolean * @default false * @deprecated */ "bSorted": false, /** * Indicate that if multiple rows are in the header and there is more than * one unique cell per column, if the top one (true) or bottom one (false) * should be used for sorting / title by DataTables. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bSortCellsTop": null, /** * Initialisation object that is used for the table * @type object * @default null */ "oInit": null, /** * Destroy callback functions - for plug-ins to attach themselves to the * destroy so they can clean up markup and events. * @type array * @default [] */ "aoDestroyCallback": [], /** * Get the number of records in the current record set, before filtering * @type function */ "fnRecordsTotal": function () { return _fnDataSource( this ) == 'ssp' ? this._iRecordsTotal * 1 : this.aiDisplayMaster.length; }, /** * Get the number of records in the current record set, after filtering * @type function */ "fnRecordsDisplay": function () { return _fnDataSource( this ) == 'ssp' ? this._iRecordsDisplay * 1 : this.aiDisplay.length; }, /** * Get the display end point - aiDisplay index * @type function */ "fnDisplayEnd": function () { var len = this._iDisplayLength, start = this._iDisplayStart, calc = start + len, records = this.aiDisplay.length, features = this.oFeatures, paginate = features.bPaginate; if ( features.bServerSide ) { return paginate === false || len === -1 ? start + records : Math.min( start+len, this._iRecordsDisplay ); } else { return ! paginate || calc>records || len===-1 ? records : calc; } }, /** * The DataTables object for this table * @type object * @default null */ "oInstance": null, /** * Unique identifier for each instance of the DataTables object. If there * is an ID on the table node, then it takes that value, otherwise an * incrementing internal counter is used. * @type string * @default null */ "sInstance": null, /** * tabindex attribute value that is added to DataTables control elements, allowing * keyboard navigation of the table and its controls. */ "iTabIndex": 0, /** * DIV container for the footer scrolling table if scrolling */ "nScrollHead": null, /** * DIV container for the footer scrolling table if scrolling */ "nScrollFoot": null, /** * Last applied sort * @type array * @default [] */ "aLastSort": [], /** * Stored plug-in instances * @type object * @default {} */ "oPlugins": {}, /** * Function used to get a row's id from the row's data * @type function * @default null */ "rowIdFn": null, /** * Data location where to store a row's id * @type string * @default null */ "rowId": null }; /** * Extension object for DataTables that is used to provide all extension * options. * * Note that the `DataTable.ext` object is available through * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is * also aliased to `jQuery.fn.dataTableExt` for historic reasons. * @namespace * @extends DataTable.models.ext */ /** * DataTables extensions * * This namespace acts as a collection area for plug-ins that can be used to * extend DataTables capabilities. Indeed many of the build in methods * use this method to provide their own capabilities (sorting methods for * example). * * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy * reasons * * @namespace */ DataTable.ext = _ext = { /** * Buttons. For use with the Buttons extension for DataTables. This is * defined here so other extensions can define buttons regardless of load * order. It is _not_ used by DataTables core. * * @type object * @default {} */ buttons: {}, /** * Element class names * * @type object * @default {} */ classes: {}, /** * DataTables build type (expanded by the download builder) * * @type string */ build:"dt/dt-1.13.8/af-2.6.0/b-2.4.2/cr-1.7.0/fc-4.3.0/fh-3.4.0/kt-2.11.0/r-2.5.0/rr-1.4.1/sc-2.3.0/sl-1.7.0", /** * Error reporting. * * How should DataTables report an error. Can take the value 'alert', * 'throw', 'none' or a function. * * @type string|function * @default alert */ errMode: "alert", /** * Feature plug-ins. * * This is an array of objects which describe the feature plug-ins that are * available to DataTables. These feature plug-ins are then available for * use through the `dom` initialisation option. * * Each feature plug-in is described by an object which must have the * following properties: * * * `fnInit` - function that is used to initialise the plug-in, * * `cFeature` - a character so the feature can be enabled by the `dom` * instillation option. This is case sensitive. * * The `fnInit` function has the following input parameters: * * 1. `{object}` DataTables settings object: see * {@link DataTable.models.oSettings} * * And the following return is expected: * * * {node|null} The element which contains your feature. Note that the * return may also be void if your plug-in does not require to inject any * DOM elements into DataTables control (`dom`) - for example this might * be useful when developing a plug-in which allows table control via * keyboard entry * * @type array * * @example * $.fn.dataTable.ext.features.push( { * "fnInit": function( oSettings ) { * return new TableTools( { "oDTSettings": oSettings } ); * }, * "cFeature": "T" * } ); */ feature: [], /** * Row searching. * * This method of searching is complimentary to the default type based * searching, and a lot more comprehensive as it allows you complete control * over the searching logic. Each element in this array is a function * (parameters described below) that is called for every row in the table, * and your logic decides if it should be included in the searching data set * or not. * * Searching functions have the following input parameters: * * 1. `{object}` DataTables settings object: see * {@link DataTable.models.oSettings} * 2. `{array|object}` Data for the row to be processed (same as the * original format that was passed in as the data source, or an array * from a DOM data source * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which * can be useful to retrieve the `TR` element if you need DOM interaction. * * And the following return is expected: * * * {boolean} Include the row in the searched result set (true) or not * (false) * * Note that as with the main search ability in DataTables, technically this * is "filtering", since it is subtractive. However, for consistency in * naming we call it searching here. * * @type array * @default [] * * @example * // The following example shows custom search being applied to the * // fourth column (i.e. the data[3] index) based on two input values * // from the end-user, matching the data in a certain range. * $.fn.dataTable.ext.search.push( * function( settings, data, dataIndex ) { * var min = document.getElementById('min').value * 1; * var max = document.getElementById('max').value * 1; * var version = data[3] == "-" ? 0 : data[3]*1; * * if ( min == "" && max == "" ) { * return true; * } * else if ( min == "" && version < max ) { * return true; * } * else if ( min < version && "" == max ) { * return true; * } * else if ( min < version && version < max ) { * return true; * } * return false; * } * ); */ search: [], /** * Selector extensions * * The `selector` option can be used to extend the options available for the * selector modifier options (`selector-modifier` object data type) that * each of the three built in selector types offer (row, column and cell + * their plural counterparts). For example the Select extension uses this * mechanism to provide an option to select only rows, columns and cells * that have been marked as selected by the end user (`{selected: true}`), * which can be used in conjunction with the existing built in selector * options. * * Each property is an array to which functions can be pushed. The functions * take three attributes: * * * Settings object for the host table * * Options object (`selector-modifier` object type) * * Array of selected item indexes * * The return is an array of the resulting item indexes after the custom * selector has been applied. * * @type object */ selector: { cell: [], column: [], row: [] }, /** * Internal functions, exposed for used in plug-ins. * * Please note that you should not need to use the internal methods for * anything other than a plug-in (and even then, try to avoid if possible). * The internal function may change between releases. * * @type object * @default {} */ internal: {}, /** * Legacy configuration options. Enable and disable legacy options that * are available in DataTables. * * @type object */ legacy: { /** * Enable / disable DataTables 1.9 compatible server-side processing * requests * * @type boolean * @default null */ ajax: null }, /** * Pagination plug-in methods. * * Each entry in this object is a function and defines which buttons should * be shown by the pagination rendering method that is used for the table: * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the * buttons are displayed in the document, while the functions here tell it * what buttons to display. This is done by returning an array of button * descriptions (what each button will do). * * Pagination types (the four built in options and any additional plug-in * options defined here) can be used through the `paginationType` * initialisation parameter. * * The functions defined take two parameters: * * 1. `{int} page` The current page index * 2. `{int} pages` The number of pages in the table * * Each function is expected to return an array where each element of the * array can be one of: * * * `first` - Jump to first page when activated * * `last` - Jump to last page when activated * * `previous` - Show previous page when activated * * `next` - Show next page when activated * * `{int}` - Show page of the index given * * `{array}` - A nested array containing the above elements to add a * containing 'DIV' element (might be useful for styling). * * Note that DataTables v1.9- used this object slightly differently whereby * an object with two functions would be defined for each plug-in. That * ability is still supported by DataTables 1.10+ to provide backwards * compatibility, but this option of use is now decremented and no longer * documented in DataTables 1.10+. * * @type object * @default {} * * @example * // Show previous, next and current page buttons only * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { * return [ 'previous', page, 'next' ]; * }; */ pager: {}, renderer: { pageButton: {}, header: {} }, /** * Ordering plug-ins - custom data source * * The extension options for ordering of data available here is complimentary * to the default type based ordering that DataTables typically uses. It * allows much greater control over the the data that is being used to * order a column, but is necessarily therefore more complex. * * This type of ordering is useful if you want to do ordering based on data * live from the DOM (for example the contents of an 'input' element) rather * than just the static string that DataTables knows of. * * The way these plug-ins work is that you create an array of the values you * wish to be ordering for the column in question and then return that * array. The data in the array much be in the index order of the rows in * the table (not the currently ordering order!). Which order data gathering * function is run here depends on the `dt-init columns.orderDataType` * parameter that is used for the column (if any). * * The functions defined take two parameters: * * 1. `{object}` DataTables settings object: see * {@link DataTable.models.oSettings} * 2. `{int}` Target column index * * Each function is expected to return an array: * * * `{array}` Data for the column to be ordering upon * * @type array * * @example * // Ordering using `input` node values * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) * { * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { * return $('input', td).val(); * } ); * } */ order: {}, /** * Type based plug-ins. * * Each column in DataTables has a type assigned to it, either by automatic * detection or by direct assignment using the `type` option for the column. * The type of a column will effect how it is ordering and search (plug-ins * can also make use of the column type if required). * * @namespace */ type: { /** * Type detection functions. * * The functions defined in this object are used to automatically detect * a column's type, making initialisation of DataTables super easy, even * when complex data is in the table. * * The functions defined take two parameters: * * 1. `{*}` Data from the column cell to be analysed * 2. `{settings}` DataTables settings object. This can be used to * perform context specific type detection - for example detection * based on language settings such as using a comma for a decimal * place. Generally speaking the options from the settings will not * be required * * Each function is expected to return: * * * `{string|null}` Data type detected, or null if unknown (and thus * pass it on to the other type detection functions. * * @type array * * @example * // Currency type detection plug-in: * $.fn.dataTable.ext.type.detect.push( * function ( data, settings ) { * // Check the numeric part * if ( ! data.substring(1).match(/[0-9]/) ) { * return null; * } * * // Check prefixed by currency * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { * return 'currency'; * } * return null; * } * ); */ detect: [], /** * Type based search formatting. * * The type based searching functions can be used to pre-format the * data to be search on. For example, it can be used to strip HTML * tags or to de-format telephone numbers for numeric only searching. * * Note that is a search is not defined for a column of a given type, * no search formatting will be performed. * * Pre-processing of searching data plug-ins - When you assign the sType * for a column (or have it automatically detected for you by DataTables * or a type detection plug-in), you will typically be using this for * custom sorting, but it can also be used to provide custom searching * by allowing you to pre-processing the data and returning the data in * the format that should be searched upon. This is done by adding * functions this object with a parameter name which matches the sType * for that target column. This is the corollary of <i>afnSortData</i> * for searching data. * * The functions defined take a single parameter: * * 1. `{*}` Data from the column cell to be prepared for searching * * Each function is expected to return: * * * `{string|null}` Formatted string that will be used for the searching. * * @type object * @default {} * * @example * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); * } */ search: {}, /** * Type based ordering. * * The column type tells DataTables what ordering to apply to the table * when a column is sorted upon. The order for each type that is defined, * is defined by the functions available in this object. * * Each ordering option can be described by three properties added to * this object: * * * `{type}-pre` - Pre-formatting function * * `{type}-asc` - Ascending order function * * `{type}-desc` - Descending order function * * All three can be used together, only `{type}-pre` or only * `{type}-asc` and `{type}-desc` together. It is generally recommended * that only `{type}-pre` is used, as this provides the optimal * implementation in terms of speed, although the others are provided * for compatibility with existing Javascript sort functions. * * `{type}-pre`: Functions defined take a single parameter: * * 1. `{*}` Data from the column cell to be prepared for ordering * * And return: * * * `{*}` Data to be sorted upon * * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort * functions, taking two parameters: * * 1. `{*}` Data to compare to the second parameter * 2. `{*}` Data to compare to the first parameter * * And returning: * * * `{*}` Ordering match: <0 if first parameter should be sorted lower * than the second parameter, ===0 if the two parameters are equal and * >0 if the first parameter should be sorted height than the second * parameter. * * @type object * @default {} * * @example * // Numeric ordering of formatted numbers with a pre-formatter * $.extend( $.fn.dataTable.ext.type.order, { * "string-pre": function(x) { * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); * return parseFloat( a ); * } * } ); * * @example * // Case-sensitive string ordering, with no pre-formatting method * $.extend( $.fn.dataTable.ext.order, { * "string-case-asc": function(x,y) { * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); * }, * "string-case-desc": function(x,y) { * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); * } * } ); */ order: {} }, /** * Unique DataTables instance counter * * @type int * @private */ _unique: 0, // // Depreciated // The following properties are retained for backwards compatibility only. // The should not be used in new projects and will be removed in a future // version // /** * Version check function. * @type function * @depreciated Since 1.10 */ fnVersionCheck: DataTable.fnVersionCheck, /** * Index for what 'this' index API functions should use * @type int * @deprecated Since v1.10 */ iApiIndex: 0, /** * jQuery UI class container * @type object * @deprecated Since v1.10 */ oJUIClasses: {}, /** * Software version * @type string * @deprecated Since v1.10 */ sVersion: DataTable.version }; // // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts // $.extend( _ext, { afnFiltering: _ext.search, aTypes: _ext.type.detect, ofnSearch: _ext.type.search, oSort: _ext.type.order, afnSortData: _ext.order, aoFeatures: _ext.feature, oApi: _ext.internal, oStdClasses: _ext.classes, oPagination: _ext.pager } ); $.extend( DataTable.ext.classes, { "sTable": "dataTable", "sNoFooter": "no-footer", /* Paging buttons */ "sPageButton": "paginate_button", "sPageButtonActive": "current", "sPageButtonDisabled": "disabled", /* Striping classes */ "sStripeOdd": "odd", "sStripeEven": "even", /* Empty row */ "sRowEmpty": "dataTables_empty", /* Features */ "sWrapper": "dataTables_wrapper", "sFilter": "dataTables_filter", "sInfo": "dataTables_info", "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */ "sLength": "dataTables_length", "sProcessing": "dataTables_processing", /* Sorting */ "sSortAsc": "sorting_asc", "sSortDesc": "sorting_desc", "sSortable": "sorting", /* Sortable in both directions */ "sSortableAsc": "sorting_desc_disabled", "sSortableDesc": "sorting_asc_disabled", "sSortableNone": "sorting_disabled", "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */ /* Filtering */ "sFilterInput": "", /* Page length */ "sLengthSelect": "", /* Scrolling */ "sScrollWrapper": "dataTables_scroll", "sScrollHead": "dataTables_scrollHead", "sScrollHeadInner": "dataTables_scrollHeadInner", "sScrollBody": "dataTables_scrollBody", "sScrollFoot": "dataTables_scrollFoot", "sScrollFootInner": "dataTables_scrollFootInner", /* Misc */ "sHeaderTH": "", "sFooterTH": "", // Deprecated "sSortJUIAsc": "", "sSortJUIDesc": "", "sSortJUI": "", "sSortJUIAscAllowed": "", "sSortJUIDescAllowed": "", "sSortJUIWrapper": "", "sSortIcon": "", "sJUIHeader": "", "sJUIFooter": "" } ); var extPagination = DataTable.ext.pager; function _numbers ( page, pages ) { var numbers = [], buttons = extPagination.numbers_length, half = Math.floor( buttons / 2 ), i = 1; if ( pages <= buttons ) { numbers = _range( 0, pages ); } else if ( page <= half ) { numbers = _range( 0, buttons-2 ); numbers.push( 'ellipsis' ); numbers.push( pages-1 ); } else if ( page >= pages - 1 - half ) { numbers = _range( pages-(buttons-2), pages ); numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6 numbers.splice( 0, 0, 0 ); } else { numbers = _range( page-half+2, page+half-1 ); numbers.push( 'ellipsis' ); numbers.push( pages-1 ); numbers.splice( 0, 0, 'ellipsis' ); numbers.splice( 0, 0, 0 ); } numbers.DT_el = 'span'; return numbers; } $.extend( extPagination, { simple: function ( page, pages ) { return [ 'previous', 'next' ]; }, full: function ( page, pages ) { return [ 'first', 'previous', 'next', 'last' ]; }, numbers: function ( page, pages ) { return [ _numbers(page, pages) ]; }, simple_numbers: function ( page, pages ) { return [ 'previous', _numbers(page, pages), 'next' ]; }, full_numbers: function ( page, pages ) { return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ]; }, first_last_numbers: function (page, pages) { return ['first', _numbers(page, pages), 'last']; }, // For testing and plug-ins to use _numbers: _numbers, // Number of number buttons (including ellipsis) to show. _Must be odd!_ numbers_length: 7 } ); $.extend( true, DataTable.ext.renderer, { pageButton: { _: function ( settings, host, idx, buttons, page, pages ) { var classes = settings.oClasses; var lang = settings.oLanguage.oPaginate; var aria = settings.oLanguage.oAria.paginate || {}; var btnDisplay, btnClass; var attach = function( container, buttons ) { var i, ien, node, button; var disabledClass = classes.sPageButtonDisabled; var clickHandler = function ( e ) { _fnPageChange( settings, e.data.action, true ); }; for ( i=0, ien=buttons.length ; i<ien ; i++ ) { button = buttons[i]; if ( Array.isArray( button ) ) { var inner = $( '<'+(button.DT_el || 'div')+'/>' ) .appendTo( container ); attach( inner, button ); } else { var disabled = false; btnDisplay = null; btnClass = button; switch ( button ) { case 'ellipsis': container.append('<span class="ellipsis">…</span>'); break; case 'first': btnDisplay = lang.sFirst; if ( page === 0 ) { disabled = true; } break; case 'previous': btnDisplay = lang.sPrevious; if ( page === 0 ) { disabled = true; } break; case 'next': btnDisplay = lang.sNext; if ( pages === 0 || page === pages-1 ) { disabled = true; } break; case 'last': btnDisplay = lang.sLast; if ( pages === 0 || page === pages-1 ) { disabled = true; } break; default: btnDisplay = settings.fnFormatNumber( button + 1 ); btnClass = page === button ? classes.sPageButtonActive : ''; break; } if ( btnDisplay !== null ) { var tag = settings.oInit.pagingTag || 'a'; if (disabled) { btnClass += ' ' + disabledClass; } node = $('<'+tag+'>', { 'class': classes.sPageButton+' '+btnClass, 'aria-controls': settings.sTableId, 'aria-disabled': disabled ? 'true' : null, 'aria-label': aria[ button ], 'role': 'link', 'aria-current': btnClass === classes.sPageButtonActive ? 'page' : null, 'data-dt-idx': button, 'tabindex': disabled ? -1 : settings.iTabIndex, 'id': idx === 0 && typeof button === 'string' ? settings.sTableId +'_'+ button : null } ) .html( btnDisplay ) .appendTo( container ); _fnBindAction( node, {action: button}, clickHandler ); } } } }; // IE9 throws an 'unknown error' if document.activeElement is used // inside an iframe or frame. Try / catch the error. Not good for // accessibility, but neither are frames. var activeEl; try { // Because this approach is destroying and recreating the paging // elements, focus is lost on the select button which is bad for // accessibility. So we want to restore focus once the draw has // completed activeEl = $(host).find(document.activeElement).data('dt-idx'); } catch (e) {} attach( $(host).empty(), buttons ); if ( activeEl !== undefined ) { $(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); } } } } ); // Built in type detection. See model.ext.aTypes for information about // what is required from this methods. $.extend( DataTable.ext.type.detect, [ // Plain numbers - first since V8 detects some plain numbers as dates // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). function ( d, settings ) { var decimal = settings.oLanguage.sDecimal; return _isNumber( d, decimal ) ? 'num'+decimal : null; }, // Dates (only those recognised by the browser's Date.parse) function ( d, settings ) { // V8 tries _very_ hard to make a string passed into `Date.parse()` // valid, so we need to use a regex to restrict date formats. Use a // plug-in for anything other than ISO8601 style strings if ( d && !(d instanceof Date) && ! _re_date.test(d) ) { return null; } var parsed = Date.parse(d); return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; }, // Formatted numbers function ( d, settings ) { var decimal = settings.oLanguage.sDecimal; return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null; }, // HTML numeric function ( d, settings ) { var decimal = settings.oLanguage.sDecimal; return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null; }, // HTML numeric, formatted function ( d, settings ) { var decimal = settings.oLanguage.sDecimal; return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null; }, // HTML (this is strict checking - there must be html) function ( d, settings ) { return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ? 'html' : null; } ] ); // Filter formatting functions. See model.ext.ofnSearch for information about // what is required from these methods. // // Note that additional search methods are added for the html numbers and // html formatted numbers by `_addNumericSort()` when we know what the decimal // place is $.extend( DataTable.ext.type.search, { html: function ( data ) { return _empty(data) ? data : typeof data === 'string' ? data .replace( _re_new_lines, " " ) .replace( _re_html, "" ) : ''; }, string: function ( data ) { return _empty(data) ? data : typeof data === 'string' ? data.replace( _re_new_lines, " " ) : data; } } ); var __numericReplace = function ( d, decimalPlace, re1, re2 ) { if ( d !== 0 && (!d || d === '-') ) { return -Infinity; } var type = typeof d; if (type === 'number' || type === 'bigint') { return d; } // If a decimal place other than `.` is used, it needs to be given to the // function so we can detect it and replace with a `.` which is the only // decimal place Javascript recognises - it is not locale aware. if ( decimalPlace ) { d = _numToDecimal( d, decimalPlace ); } if ( d.replace ) { if ( re1 ) { d = d.replace( re1, '' ); } if ( re2 ) { d = d.replace( re2, '' ); } } return d * 1; }; // Add the numeric 'deformatting' functions for sorting and search. This is done // in a function to provide an easy ability for the language options to add // additional methods if a non-period decimal place is used. function _addNumericSort ( decimalPlace ) { $.each( { // Plain numbers "num": function ( d ) { return __numericReplace( d, decimalPlace ); }, // Formatted numbers "num-fmt": function ( d ) { return __numericReplace( d, decimalPlace, _re_formatted_numeric ); }, // HTML numeric "html-num": function ( d ) { return __numericReplace( d, decimalPlace, _re_html ); }, // HTML numeric, formatted "html-num-fmt": function ( d ) { return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); } }, function ( key, fn ) { // Add the ordering method _ext.type.order[ key+decimalPlace+'-pre' ] = fn; // For HTML types add a search formatter that will strip the HTML if ( key.match(/^html\-/) ) { _ext.type.search[ key+decimalPlace ] = _ext.type.search.html; } } ); } // Default sort methods $.extend( _ext.type.order, { // Dates "date-pre": function ( d ) { var ts = Date.parse( d ); return isNaN(ts) ? -Infinity : ts; }, // html "html-pre": function ( a ) { return _empty(a) ? '' : a.replace ? a.replace( /<.*?>/g, "" ).toLowerCase() : a+''; }, // string "string-pre": function ( a ) { // This is a little complex, but faster than always calling toString, // https://jsperf.com/tostring-v-check return _empty(a) ? '' : typeof a === 'string' ? a.toLowerCase() : ! a.toString ? '' : a.toString(); }, // string-asc and -desc are retained only for compatibility with the old // sort methods "string-asc": function ( x, y ) { return ((x < y) ? -1 : ((x > y) ? 1 : 0)); }, "string-desc": function ( x, y ) { return ((x < y) ? 1 : ((x > y) ? -1 : 0)); } } ); // Numeric sorting types - order doesn't matter here _addNumericSort( '' ); $.extend( true, DataTable.ext.renderer, { header: { _: function ( settings, cell, column, classes ) { // No additional mark-up required // Attach a sort listener to update on sort - note that using the // `DT` namespace will allow the event to be removed automatically // on destroy, while the `dt` namespaced event is the one we are // listening for $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { if ( settings !== ctx ) { // need to check this this is the host return; // table, not a nested one } var colIdx = column.idx; cell .removeClass( classes.sSortAsc +' '+ classes.sSortDesc ) .addClass( columns[ colIdx ] == 'asc' ? classes.sSortAsc : columns[ colIdx ] == 'desc' ? classes.sSortDesc : column.sSortingClass ); } ); }, jqueryui: function ( settings, cell, column, classes ) { $('<div/>') .addClass( classes.sSortJUIWrapper ) .append( cell.contents() ) .append( $('<span/>') .addClass( classes.sSortIcon+' '+column.sSortingClassJUI ) ) .appendTo( cell ); // Attach a sort listener to update on sort $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { if ( settings !== ctx ) { return; } var colIdx = column.idx; cell .removeClass( classes.sSortAsc +" "+classes.sSortDesc ) .addClass( columns[ colIdx ] == 'asc' ? classes.sSortAsc : columns[ colIdx ] == 'desc' ? classes.sSortDesc : column.sSortingClass ); cell .find( 'span.'+classes.sSortIcon ) .removeClass( classes.sSortJUIAsc +" "+ classes.sSortJUIDesc +" "+ classes.sSortJUI +" "+ classes.sSortJUIAscAllowed +" "+ classes.sSortJUIDescAllowed ) .addClass( columns[ colIdx ] == 'asc' ? classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ? classes.sSortJUIDesc : column.sSortingClassJUI ); } ); } } } ); /* * Public helper functions. These aren't used internally by DataTables, or * called by any of the options passed into DataTables, but they can be used * externally by developers working with DataTables. They are helper functions * to make working with DataTables a little bit easier. */ var __htmlEscapeEntities = function ( d ) { if (Array.isArray(d)) { d = d.join(','); } return typeof d === 'string' ? d .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') : d; }; // Common logic for moment, luxon or a date action function __mld( dt, momentFn, luxonFn, dateFn, arg1 ) { if (window.moment) { return dt[momentFn]( arg1 ); } else if (window.luxon) { return dt[luxonFn]( arg1 ); } return dateFn ? dt[dateFn]( arg1 ) : dt; } var __mlWarning = false; function __mldObj (d, format, locale) { var dt; if (window.moment) { dt = window.moment.utc( d, format, locale, true ); if (! dt.isValid()) { return null; } } else if (window.luxon) { dt = format && typeof d === 'string' ? window.luxon.DateTime.fromFormat( d, format ) : window.luxon.DateTime.fromISO( d ); if (! dt.isValid) { return null; } dt.setLocale(locale); } else if (! format) { // No format given, must be ISO dt = new Date(d); } else { if (! __mlWarning) { alert('DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17'); } __mlWarning = true; } return dt; } // Wrapper for date, datetime and time which all operate the same way with the exception of // the output string for auto locale support function __mlHelper (localeString) { return function ( from, to, locale, def ) { // Luxon and Moment support // Argument shifting if ( arguments.length === 0 ) { locale = 'en'; to = null; // means toLocaleString from = null; // means iso8601 } else if ( arguments.length === 1 ) { locale = 'en'; to = from; from = null; } else if ( arguments.length === 2 ) { locale = to; to = from; from = null; } var typeName = 'datetime-' + to; // Add type detection and sorting specific to this date format - we need to be able to identify // date type columns as such, rather than as numbers in extensions. Hence the need for this. if (! DataTable.ext.type.order[typeName]) { // The renderer will give the value to type detect as the type! DataTable.ext.type.detect.unshift(function (d) { return d === typeName ? typeName : false; }); // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a // `valueOf` which gives milliseconds epoch DataTable.ext.type.order[typeName + '-asc'] = function (a, b) { var x = a.valueOf(); var y = b.valueOf(); return x === y ? 0 : x < y ? -1 : 1; } DataTable.ext.type.order[typeName + '-desc'] = function (a, b) { var x = a.valueOf(); var y = b.valueOf(); return x === y ? 0 : x > y ? -1 : 1; } } return function ( d, type ) { // Allow for a default value if (d === null || d === undefined) { if (def === '--now') { // We treat everything as UTC further down, so no changes are // made, as such need to get the local date / time as if it were // UTC var local = new Date(); d = new Date( Date.UTC( local.getFullYear(), local.getMonth(), local.getDate(), local.getHours(), local.getMinutes(), local.getSeconds() ) ); } else { d = ''; } } if (type === 'type') { // Typing uses the type name for fast matching return typeName; } if (d === '') { return type !== 'sort' ? '' : __mldObj('0000-01-01 00:00:00', null, locale); } // Shortcut. If `from` and `to` are the same, we are using the renderer to // format for ordering, not display - its already in the display format. if ( to !== null && from === to && type !== 'sort' && type !== 'type' && ! (d instanceof Date) ) { return d; } var dt = __mldObj(d, from, locale); if (dt === null) { return d; } if (type === 'sort') { return dt; } var formatted = to === null ? __mld(dt, 'toDate', 'toJSDate', '')[localeString]() : __mld(dt, 'format', 'toFormat', 'toISOString', to); // XSS protection return type === 'display' ? __htmlEscapeEntities( formatted ) : formatted; }; } } // Based on locale, determine standard number formatting // Fallback for legacy browsers is US English var __thousands = ','; var __decimal = '.'; if (window.Intl !== undefined) { try { var num = new Intl.NumberFormat().formatToParts(100000.1); for (var i=0 ; i<num.length ; i++) { if (num[i].type === 'group') { __thousands = num[i].value; } else if (num[i].type === 'decimal') { __decimal = num[i].value; } } } catch (e) { // noop } } // Formatted date time detection - use by declaring the formats you are going to use DataTable.datetime = function ( format, locale ) { var typeName = 'datetime-detect-' + format; if (! locale) { locale = 'en'; } if (! DataTable.ext.type.order[typeName]) { DataTable.ext.type.detect.unshift(function (d) { var dt = __mldObj(d, format, locale); return d === '' || dt ? typeName : false; }); DataTable.ext.type.order[typeName + '-pre'] = function (d) { return __mldObj(d, format, locale) || 0; } } } /** * Helpers for `columns.render`. * * The options defined here can be used with the `columns.render` initialisation * option to provide a display renderer. The following functions are defined: * * * `number` - Will format numeric data (defined by `columns.data`) for * display, retaining the original unformatted data for sorting and filtering. * It takes 5 parameters: * * `string` - Thousands grouping separator * * `string` - Decimal point indicator * * `integer` - Number of decimal points to show * * `string` (optional) - Prefix. * * `string` (optional) - Postfix (/suffix). * * `text` - Escape HTML to help prevent XSS attacks. It has no optional * parameters. * * @example * // Column definition using the number renderer * { * data: "salary", * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) * } * * @namespace */ DataTable.render = { date: __mlHelper('toLocaleDateString'), datetime: __mlHelper('toLocaleString'), time: __mlHelper('toLocaleTimeString'), number: function ( thousands, decimal, precision, prefix, postfix ) { // Auto locale detection if (thousands === null || thousands === undefined) { thousands = __thousands; } if (decimal === null || decimal === undefined) { decimal = __decimal; } return { display: function ( d ) { if ( typeof d !== 'number' && typeof d !== 'string' ) { return d; } if (d === '' || d === null) { return d; } var negative = d < 0 ? '-' : ''; var flo = parseFloat( d ); // If NaN then there isn't much formatting that we can do - just // return immediately, escaping any HTML (this was supposed to // be a number after all) if ( isNaN( flo ) ) { return __htmlEscapeEntities( d ); } flo = flo.toFixed( precision ); d = Math.abs( flo ); var intPart = parseInt( d, 10 ); var floatPart = precision ? decimal+(d - intPart).toFixed( precision ).substring( 2 ): ''; // If zero, then can't have a negative prefix if (intPart === 0 && parseFloat(floatPart) === 0) { negative = ''; } return negative + (prefix||'') + intPart.toString().replace( /\B(?=(\d{3})+(?!\d))/g, thousands ) + floatPart + (postfix||''); } }; }, text: function () { return { display: __htmlEscapeEntities, filter: __htmlEscapeEntities }; } }; /* * This is really a good bit rubbish this method of exposing the internal methods * publicly... - To be fixed in 2.0 using methods on the prototype */ /** * Create a wrapper function for exporting an internal functions to an external API. * @param {string} fn API function name * @returns {function} wrapped function * @memberof DataTable#internal */ function _fnExternApiFunc (fn) { return function() { var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat( Array.prototype.slice.call(arguments) ); return DataTable.ext.internal[fn].apply( this, args ); }; } /** * Reference to internal functions for use by plug-in developers. Note that * these methods are references to internal functions and are considered to be * private. If you use these methods, be aware that they are liable to change * between versions. * @namespace */ $.extend( DataTable.ext.internal, { _fnExternApiFunc: _fnExternApiFunc, _fnBuildAjax: _fnBuildAjax, _fnAjaxUpdate: _fnAjaxUpdate, _fnAjaxParameters: _fnAjaxParameters, _fnAjaxUpdateDraw: _fnAjaxUpdateDraw, _fnAjaxDataSrc: _fnAjaxDataSrc, _fnAddColumn: _fnAddColumn, _fnColumnOptions: _fnColumnOptions, _fnAdjustColumnSizing: _fnAdjustColumnSizing, _fnVisibleToColumnIndex: _fnVisibleToColumnIndex, _fnColumnIndexToVisible: _fnColumnIndexToVisible, _fnVisbleColumns: _fnVisbleColumns, _fnGetColumns: _fnGetColumns, _fnColumnTypes: _fnColumnTypes, _fnApplyColumnDefs: _fnApplyColumnDefs, _fnHungarianMap: _fnHungarianMap, _fnCamelToHungarian: _fnCamelToHungarian, _fnLanguageCompat: _fnLanguageCompat, _fnBrowserDetect: _fnBrowserDetect, _fnAddData: _fnAddData, _fnAddTr: _fnAddTr, _fnNodeToDataIndex: _fnNodeToDataIndex, _fnNodeToColumnIndex: _fnNodeToColumnIndex, _fnGetCellData: _fnGetCellData, _fnSetCellData: _fnSetCellData, _fnSplitObjNotation: _fnSplitObjNotation, _fnGetObjectDataFn: _fnGetObjectDataFn, _fnSetObjectDataFn: _fnSetObjectDataFn, _fnGetDataMaster: _fnGetDataMaster, _fnClearTable: _fnClearTable, _fnDeleteIndex: _fnDeleteIndex, _fnInvalidate: _fnInvalidate, _fnGetRowElements: _fnGetRowElements, _fnCreateTr: _fnCreateTr, _fnBuildHead: _fnBuildHead, _fnDrawHead: _fnDrawHead, _fnDraw: _fnDraw, _fnReDraw: _fnReDraw, _fnAddOptionsHtml: _fnAddOptionsHtml, _fnDetectHeader: _fnDetectHeader, _fnGetUniqueThs: _fnGetUniqueThs, _fnFeatureHtmlFilter: _fnFeatureHtmlFilter, _fnFilterComplete: _fnFilterComplete, _fnFilterCustom: _fnFilterCustom, _fnFilterColumn: _fnFilterColumn, _fnFilter: _fnFilter, _fnFilterCreateSearch: _fnFilterCreateSearch, _fnEscapeRegex: _fnEscapeRegex, _fnFilterData: _fnFilterData, _fnFeatureHtmlInfo: _fnFeatureHtmlInfo, _fnUpdateInfo: _fnUpdateInfo, _fnInfoMacros: _fnInfoMacros, _fnInitialise: _fnInitialise, _fnInitComplete: _fnInitComplete, _fnLengthChange: _fnLengthChange, _fnFeatureHtmlLength: _fnFeatureHtmlLength, _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate, _fnPageChange: _fnPageChange, _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing, _fnProcessingDisplay: _fnProcessingDisplay, _fnFeatureHtmlTable: _fnFeatureHtmlTable, _fnScrollDraw: _fnScrollDraw, _fnApplyToChildren: _fnApplyToChildren, _fnCalculateColumnWidths: _fnCalculateColumnWidths, _fnThrottle: _fnThrottle, _fnConvertToWidth: _fnConvertToWidth, _fnGetWidestNode: _fnGetWidestNode, _fnGetMaxLenString: _fnGetMaxLenString, _fnStringToCss: _fnStringToCss, _fnSortFlatten: _fnSortFlatten, _fnSort: _fnSort, _fnSortAria: _fnSortAria, _fnSortListener: _fnSortListener, _fnSortAttachListener: _fnSortAttachListener, _fnSortingClasses: _fnSortingClasses, _fnSortData: _fnSortData, _fnSaveState: _fnSaveState, _fnLoadState: _fnLoadState, _fnImplementState: _fnImplementState, _fnSettingsFromNode: _fnSettingsFromNode, _fnLog: _fnLog, _fnMap: _fnMap, _fnBindAction: _fnBindAction, _fnCallbackReg: _fnCallbackReg, _fnCallbackFire: _fnCallbackFire, _fnLengthOverflow: _fnLengthOverflow, _fnRenderer: _fnRenderer, _fnDataSource: _fnDataSource, _fnRowAttributes: _fnRowAttributes, _fnExtend: _fnExtend, _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant // in 1.10, so this dead-end function is // added to prevent errors } ); // jQuery access $.fn.dataTable = DataTable; // Provide access to the host jQuery object (circular reference) DataTable.$ = $; // Legacy aliases $.fn.dataTableSettings = DataTable.settings; $.fn.dataTableExt = DataTable.ext; // With a capital `D` we return a DataTables API instance rather than a // jQuery object $.fn.DataTable = function ( opts ) { return $(this).dataTable( opts ).api(); }; // All properties that are available to $.fn.dataTable should also be // available on $.fn.DataTable $.each( DataTable, function ( prop, val ) { $.fn.DataTable[ prop ] = val; } ); return DataTable; })); /*! DataTables styling integration * ©2018 SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; return DataTable; })); /*! AutoFill 2.6.0 * ©2008-2023 SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; /** * @summary AutoFill * @description Add Excel like click and drag auto-fill options to DataTables * @version 2.6.0 * @author SpryMedia Ltd (www.sprymedia.co.uk) * @copyright SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * 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 */ var _instance = 0; /** * AutoFill provides Excel like auto-fill features for a DataTable * * @class AutoFill * @constructor * @param {object} oTD DataTables settings object * @param {object} oConfig Configuration object for AutoFill */ var AutoFill = function( dt, opts ) { if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) { throw( "Warning: AutoFill requires DataTables 1.10.8 or greater"); } // User and defaults configuration object this.c = $.extend( true, {}, DataTable.defaults.autoFill, AutoFill.defaults, opts ); /** * @namespace Settings object which contains customisable information for AutoFill instance */ this.s = { /** @type {DataTable.Api} DataTables' API instance */ dt: new DataTable.Api( dt ), /** @type {String} Unique namespace for events attached to the document */ namespace: '.autoFill'+(_instance++), /** @type {Object} Cached dimension information for use in the mouse move event handler */ scroll: {}, /** @type {integer} Interval object used for smooth scrolling */ scrollInterval: null, handle: { height: 0, width: 0 }, /** * Enabled setting * @type {Boolean} */ enabled: false }; /** * @namespace Common and useful DOM elements for the class instance */ this.dom = { closeButton: $('<div class="dtaf-popover-close">×</div>'), /** @type {jQuery} AutoFill handle */ handle: $('<div class="dt-autofill-handle"/>'), /** * @type {Object} Selected cells outline - Need to use 4 elements, * otherwise the mouse over if you back into the selected rectangle * will be over that element, rather than the cells! */ select: { top: $('<div class="dt-autofill-select top"/>'), right: $('<div class="dt-autofill-select right"/>'), bottom: $('<div class="dt-autofill-select bottom"/>'), left: $('<div class="dt-autofill-select left"/>') }, /** @type {jQuery} Fill type chooser background */ background: $('<div class="dt-autofill-background"/>'), /** @type {jQuery} Fill type chooser */ list: $('<div class="dt-autofill-list">'+this.s.dt.i18n('autoFill.info', '')+'</div>') .attr('aria-modal', true) .attr('role', 'dialog') .append('<div class="dt-autofill-list-items"></div>'), /** @type {jQuery} DataTables scrolling container */ dtScroll: null, /** @type {jQuery} Offset parent element */ offsetParent: null }; /* Constructor logic */ this._constructor(); }; $.extend( AutoFill.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public methods (exposed via the DataTables API below) */ enabled: function () { return this.s.enabled; }, enable: function ( flag ) { var that = this; if ( flag === false ) { return this.disable(); } this.s.enabled = true; this._focusListener(); this.dom.handle.on( 'mousedown touchstart', function (e) { that._mousedown( e ); return false; } ); $(window).on('resize', function() { var handle = $('div.dt-autofill-handle'); if(handle.length > 0 && that.dom.attachedTo !== undefined) { that._attach(that.dom.attachedTo) } }) let orientationReset = function() { that.s.handle = { height: false, width: false }; $(that.dom.handle).css({ 'height': '', 'width': '' }) if(that.dom.attachedTo !== undefined) { that._attach(that.dom.attachedTo) } } $(window) .on('orientationchange', function() { setTimeout(function() { orientationReset(); setTimeout(orientationReset, 150); }, 50); }); return this; }, disable: function () { this.s.enabled = false; this._focusListenerRemove(); return this; }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constructor */ /** * Initialise the RowReorder instance * * @private */ _constructor: function () { var that = this; var dt = this.s.dt; var dtScroll = $('div.dataTables_scrollBody', this.s.dt.table().container()); // Make the instance accessible to the API dt.settings()[0].autoFill = this; if ( dtScroll.length ) { this.dom.dtScroll = dtScroll; // Need to scroll container to be the offset parent if ( dtScroll.css('position') === 'static' ) { dtScroll.css( 'position', 'relative' ); } } if ( this.c.enable !== false ) { this.enable(); } dt.on( 'destroy.autoFill', function () { that._focusListenerRemove(); } ); }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods */ /** * Display the AutoFill drag handle by appending it to a table cell. This * is the opposite of the _detach method. * * @param {node} node TD/TH cell to insert the handle into * @private */ _attach: function ( node ) { var dt = this.s.dt; var idx = dt.cell( node ).index(); var handle = this.dom.handle; var handleDim = this.s.handle; if ( ! idx || dt.columns( this.c.columns ).indexes().indexOf( idx.column ) === -1 ) { this._detach(); return; } if ( ! this.dom.offsetParent ) { // We attach to the table's offset parent this.dom.offsetParent = $( dt.table().node() ).offsetParent(); } if ( ! handleDim.height || ! handleDim.width ) { // Append to document so we can get its size. Not expecting it to // change during the life time of the page handle.appendTo( 'body' ); handleDim.height = handle.outerHeight(); handleDim.width = handle.outerWidth(); } // Might need to go through multiple offset parents var offset = this._getPosition( node, this.dom.offsetParent ); this.dom.attachedTo = node; handle .css( { top: offset.top + node.offsetHeight - handleDim.height, left: offset.left + node.offsetWidth - handleDim.width } ) .appendTo( this.dom.offsetParent ); }, /** * Determine can the fill type should be. This can be automatic, or ask the * end user. * * @param {array} cells Information about the selected cells from the key * up function * @private */ _actionSelector: function ( cells ) { var that = this; var dt = this.s.dt; var actions = AutoFill.actions; var available = []; // "Ask" each plug-in if it wants to handle this data $.each( actions, function ( key, action ) { if ( action.available( dt, cells ) ) { available.push( key ); } } ); if ( available.length === 1 && this.c.alwaysAsk === false ) { // Only one action available - enact it immediately var result = actions[ available[0] ].execute( dt, cells ); this._update( result, cells ); } else if ( available.length > 1 || this.c.alwaysAsk ) { // Multiple actions available - ask the end user what they want to do var list = this.dom.list.children('div.dt-autofill-list-items').empty(); // Add a cancel option available.push( 'cancel' ); $.each( available, function ( i, name ) { list.append( $('<button/>') .html(actions[ name ].option( dt, cells )) .append( $('<span class="dt-autofill-button"/>').html(dt.i18n('autoFill.button', '>'))) .on( 'click', function () { var result = actions[ name ].execute( dt, cells, $(this).closest('li') ); that._update( result, cells ); that.dom.background.remove(); that.dom.list.remove(); } ) ); } ); this.dom.background.appendTo( 'body' ); this.dom.background.one('click', function() { that.dom.background.remove(); that.dom.list.remove(); }) this.dom.list.appendTo( 'body' ); if (this.c.closeButton) { this.dom.list.prepend(this.dom.closeButton).addClass(AutoFill.classes.closeable) this.dom.closeButton.on('click', function() { return that.dom.background.click() }); } this.dom.list.css( 'margin-top', this.dom.list.outerHeight()/2 * -1 ); } }, /** * Remove the AutoFill handle from the document * * @private */ _detach: function () { this.dom.attachedTo = null; this.dom.handle.detach(); }, /** * Draw the selection outline by calculating the range between the start * and end cells, then placing the highlighting elements to draw a rectangle * * @param {node} target End cell * @param {object} e Originating event * @private */ _drawSelection: function ( target, e ) { // Calculate boundary for start cell to this one var dt = this.s.dt; var start = this.s.start; var startCell = $(this.dom.start); var end = { row: this.c.vertical ? dt.rows( { page: 'current' } ).nodes().indexOf( target.parentNode ) : start.row, column: this.c.horizontal ? $(target).index() : start.column }; var colIndx = dt.column.index( 'toData', end.column ); var endRow = dt.row( ':eq('+end.row+')', { page: 'current' } ); // Workaround for M581 var endCell = $( dt.cell( endRow.index(), colIndx ).node() ); // Be sure that is a DataTables controlled cell if ( ! dt.cell( endCell ).any() ) { return; } // if target is not in the columns available - do nothing if ( dt.columns( this.c.columns ).indexes().indexOf( colIndx ) === -1 || end.row === -1) { return; } this.s.end = end; var top, bottom, left, right, height, width; top = start.row < end.row ? startCell : endCell; bottom = start.row < end.row ? endCell : startCell; left = start.column < end.column ? startCell : endCell; right = start.column < end.column ? endCell : startCell; top = this._getPosition( top.get(0) ).top; left = this._getPosition( left.get(0) ).left; height = this._getPosition( bottom.get(0) ).top + bottom.outerHeight() - top; width = this._getPosition( right.get(0) ).left + right.outerWidth() - left; var select = this.dom.select; select.top.css( { top: top, left: left, width: width } ); select.left.css( { top: top, left: left, height: height } ); select.bottom.css( { top: top + height, left: left, width: width } ); select.right.css( { top: top, left: left + width, height: height } ); }, /** * Use the Editor API to perform an update based on the new data for the * cells * * @param {array} cells Information about the selected cells from the key * up function * @private */ _editor: function ( cells ) { var dt = this.s.dt; var editor = this.c.editor; if ( ! editor ) { return; } // Build the object structure for Editor's multi-row editing var idValues = {}; var nodes = []; var fields = editor.fields(); for ( var i=0, ien=cells.length ; i<ien ; i++ ) { for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { var cell = cells[i][j]; // Determine the field name for the cell being edited var col = dt.settings()[0].aoColumns[ cell.index.column ]; var fieldName = col.editField; if ( fieldName === undefined ) { var dataSrc = col.mData; // dataSrc is the `field.data` property, but we need to set // using the field name, so we need to translate from the // data to the name for ( var k=0, ken=fields.length ; k<ken ; k++ ) { var field = editor.field( fields[k] ); if ( field.dataSrc() === dataSrc ) { fieldName = field.name(); break; } } } if ( ! fieldName ) { throw 'Could not automatically determine field data. '+ 'Please see https://datatables.net/tn/11'; } if ( ! idValues[ fieldName ] ) { idValues[ fieldName ] = {}; } var id = dt.row( cell.index.row ).id(); idValues[ fieldName ][ id ] = cell.set; // Keep a list of cells so we can activate the bubble editing // with them nodes.push( cell.index ); } } // Perform the edit using bubble editing as it allows us to specify // the cells to be edited, rather than using full rows editor .bubble( nodes, false ) .multiSet( idValues ) .submit(); }, /** * Emit an event on the DataTable for listeners * * @param {string} name Event name * @param {array} args Event arguments * @private */ _emitEvent: function ( name, args ) { this.s.dt.iterator( 'table', function ( ctx, i ) { $(ctx.nTable).triggerHandler( name+'.dt', args ); } ); }, /** * Attach suitable listeners (based on the configuration) that will attach * and detach the AutoFill handle in the document. * * @private */ _focusListener: function () { var that = this; var dt = this.s.dt; var namespace = this.s.namespace; var focus = this.c.focus !== null ? this.c.focus : dt.init().keys || dt.settings()[0].keytable ? 'focus' : 'hover'; // All event listeners attached here are removed in the `destroy` // callback in the constructor if ( focus === 'focus' ) { dt .on( 'key-focus.autoFill', function ( e, dt, cell ) { that._attach( cell.node() ); } ) .on( 'key-blur.autoFill', function ( e, dt, cell ) { that._detach(); } ); } else if ( focus === 'click' ) { $(dt.table().body()).on( 'click'+namespace, 'td, th', function (e) { that._attach( this ); } ); $(document.body).on( 'click'+namespace, function (e) { if ( ! $(e.target).parents().filter( dt.table().body() ).length ) { that._detach(); } } ); } else { $(dt.table().body()) .on( 'mouseenter'+namespace+' touchstart'+namespace, 'td, th', function (e) { that._attach( this ); } ) .on( 'mouseleave'+namespace+'touchend'+namespace, function (e) { if ( $(e.relatedTarget).hasClass('dt-autofill-handle') ) { return; } that._detach(); } ); } }, _focusListenerRemove: function () { var dt = this.s.dt; dt.off( '.autoFill' ); $(dt.table().body()).off( this.s.namespace ); $(document.body).off( this.s.namespace ); }, /** * Get the position of a node, relative to another, including any scrolling * offsets. * @param {Node} node Node to get the position of * @param {jQuery} targetParent Node to use as the parent * @return {object} Offset calculation * @private */ _getPosition: function ( node, targetParent ) { var currNode = node, currOffsetParent, top = 0, left = 0; if ( ! targetParent ) { targetParent = $( $( this.s.dt.table().node() )[0].offsetParent ); } do { // Don't use jQuery().position() the behaviour changes between 1.x and 3.x for // tables var positionTop = currNode.offsetTop; var positionLeft = currNode.offsetLeft; // jQuery doesn't give a `table` as the offset parent oddly, so use DOM directly currOffsetParent = $( currNode.offsetParent ); top += positionTop + parseInt( currOffsetParent.css('border-top-width') || 0 ) * 1; left += positionLeft + parseInt( currOffsetParent.css('border-left-width') || 0 ) * 1; // Emergency fall back. Shouldn't happen, but just in case! if ( currNode.nodeName.toLowerCase() === 'body' ) { break; } currNode = currOffsetParent.get(0); // for next loop } while ( currOffsetParent.get(0) !== targetParent.get(0) ) return { top: top, left: left }; }, /** * Start mouse drag - selects the start cell * * @param {object} e Mouse down event * @private */ _mousedown: function ( e ) { var that = this; var dt = this.s.dt; this.dom.start = this.dom.attachedTo; this.s.start = { row: dt.rows( { page: 'current' } ).nodes().indexOf( $(this.dom.start).parent()[0] ), column: $(this.dom.start).index() }; $(document.body) .on( 'mousemove.autoFill touchmove.autoFill', function (e) { that._mousemove( e ); // If it is a touch event then when the touch ends we need to remove the handle if(e.type === 'touchmove') { $(document.body).one('touchend.autoFill', function() { that._detach(); }) } } ) .on( 'mouseup.autoFill touchend.autoFill', function (e) { that._mouseup( e ); } ); var select = this.dom.select; var offsetParent = $( dt.table().node() ).offsetParent(); select.top.appendTo( offsetParent ); select.left.appendTo( offsetParent ); select.right.appendTo( offsetParent ); select.bottom.appendTo( offsetParent ); this._drawSelection( this.dom.start, e ); this.dom.handle.css( 'display', 'none' ); // Cache scrolling information so mouse move doesn't need to read. // This assumes that the window and DT scroller will not change size // during an AutoFill drag, which I think is a fair assumption var scrollWrapper = this.dom.dtScroll; this.s.scroll = { windowHeight: $(window).height(), windowWidth: $(window).width(), dtTop: scrollWrapper ? scrollWrapper.offset().top : null, dtLeft: scrollWrapper ? scrollWrapper.offset().left : null, dtHeight: scrollWrapper ? scrollWrapper.outerHeight() : null, dtWidth: scrollWrapper ? scrollWrapper.outerWidth() : null }; }, /** * Mouse drag - selects the end cell and update the selection display for * the end user * * @param {object} e Mouse move event * @private */ _mousemove: function ( e ) { var target = e.touches && e.touches.length ? document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY) : e.target; var name = target.nodeName.toLowerCase(); if ( name !== 'td' && name !== 'th' ) { return; } this._drawSelection( target, e ); this._shiftScroll( e ); }, /** * End mouse drag - perform the update actions * * @param {object} e Mouse up event * @private */ _mouseup: function ( e ) { $(document.body).off( '.autoFill' ); var that = this; var dt = this.s.dt; var select = this.dom.select; select.top.remove(); select.left.remove(); select.right.remove(); select.bottom.remove(); this.dom.handle.css( 'display', 'block' ); // Display complete - now do something useful with the selection! var start = this.s.start; var end = this.s.end; // Haven't selected multiple cells, so nothing to do if ( start.row === end.row && start.column === end.column ) { return; } var startDt = dt.cell( ':eq('+start.row+')', start.column+':visible', {page:'current'} ); // If Editor is active inside this cell (inline editing) we need to wait for Editor to // submit and then we can loop back and trigger the fill. if ( $('div.DTE', startDt.node()).length ) { var editor = dt.editor(); editor .on( 'submitSuccess.dtaf close.dtaf', function () { editor.off( '.dtaf'); setTimeout( function () { that._mouseup( e ); }, 100 ); } ) .on( 'submitComplete.dtaf preSubmitCancelled.dtaf close.dtaf', function () { editor.off( '.dtaf'); } ); // Make the current input submit editor.submit(); return; } // Build a matrix representation of the selected rows var rows = this._range( start.row, end.row ); var columns = this._range( start.column, end.column ); var selected = []; var dtSettings = dt.settings()[0]; var dtColumns = dtSettings.aoColumns; var enabledColumns = dt.columns( this.c.columns ).indexes(); // Can't use Array.prototype.map as IE8 doesn't support it // Can't use $.map as jQuery flattens 2D arrays // Need to use a good old fashioned for loop for ( var rowIdx=0 ; rowIdx<rows.length ; rowIdx++ ) { selected.push( $.map( columns, function (column) { var row = dt.row( ':eq('+rows[rowIdx]+')', {page:'current'} ); // Workaround for M581 var cell = dt.cell( row.index(), column+':visible' ); var data = cell.data(); var cellIndex = cell.index(); var editField = dtColumns[ cellIndex.column ].editField; if ( editField !== undefined ) { data = dtSettings.oApi._fnGetObjectDataFn( editField )( dt.row( cellIndex.row ).data() ); } if ( enabledColumns.indexOf(cellIndex.column) === -1 ) { return; } return { cell: cell, data: data, label: cell.data(), index: cellIndex }; } ) ); } this._actionSelector( selected ); // Stop shiftScroll clearInterval( this.s.scrollInterval ); this.s.scrollInterval = null; }, /** * Create an array with a range of numbers defined by the start and end * parameters passed in (inclusive!). * * @param {integer} start Start * @param {integer} end End * @private */ _range: function ( start, end ) { var out = []; var i; if ( start <= end ) { for ( i=start ; i<=end ; i++ ) { out.push( i ); } } else { for ( i=start ; i>=end ; i-- ) { out.push( i ); } } return out; }, /** * Move the window and DataTables scrolling during a drag to scroll new * content into view. This is done by proximity to the edge of the scrolling * container of the mouse - for example near the top edge of the window * should scroll up. This is a little complicated as there are two elements * that can be scrolled - the window and the DataTables scrolling view port * (if scrollX and / or scrollY is enabled). * * @param {object} e Mouse move event object * @private */ _shiftScroll: function ( e ) { var that = this; var dt = this.s.dt; var scroll = this.s.scroll; var runInterval = false; var scrollSpeed = 5; var buffer = 65; // Different values if using a touchscreen var pageX = !e.type.includes('touch') ? e.pageX - window.scrollX :e.touches[0].clientX; var pageY = !e.type.includes('touch') ? e.pageY - window.scrollY :e.touches[0].clientY; var windowY = pageY, windowX = pageX, windowVert, windowHoriz, dtVert, dtHoriz; // Window calculations - based on the mouse position in the window, // regardless of scrolling if ( windowY < buffer ) { windowVert = scrollSpeed * -1; } else if ( windowY > scroll.windowHeight - buffer ) { windowVert = scrollSpeed; } if ( windowX < buffer ) { windowHoriz = scrollSpeed * -1; } else if ( windowX > scroll.windowWidth - buffer ) { windowHoriz = scrollSpeed; } // DataTables scrolling calculations - based on the table's position in // the document and the mouse position on the page if ( scroll.dtTop !== null && pageY < scroll.dtTop + buffer ) { dtVert = scrollSpeed * -1; } else if ( scroll.dtTop !== null && pageY > scroll.dtTop + scroll.dtHeight - buffer ) { dtVert = scrollSpeed; } if ( scroll.dtLeft !== null && pageX < scroll.dtLeft + buffer ) { dtHoriz = scrollSpeed * -1; } else if ( scroll.dtLeft !== null && pageX > scroll.dtLeft + scroll.dtWidth - buffer ) { dtHoriz = scrollSpeed; } // This is where it gets interesting. We want to continue scrolling // without requiring a mouse move, so we need an interval to be // triggered. The interval should continue until it is no longer needed, // but it must also use the latest scroll commands (for example consider // that the mouse might move from scrolling up to scrolling left, all // with the same interval running. We use the `scroll` object to "pass" // this information to the interval. Can't use local variables as they // wouldn't be the ones that are used by an already existing interval! if ( windowVert || windowHoriz || dtVert || dtHoriz ) { scroll.windowVert = windowVert; scroll.windowHoriz = windowHoriz; scroll.dtVert = dtVert; scroll.dtHoriz = dtHoriz; runInterval = true; } else if ( this.s.scrollInterval ) { // Don't need to scroll - remove any existing timer clearInterval( this.s.scrollInterval ); this.s.scrollInterval = null; } // If we need to run the interval to scroll and there is no existing // interval (if there is an existing one, it will continue to run) if ( ! this.s.scrollInterval && runInterval ) { this.s.scrollInterval = setInterval( function () { // Don't need to worry about setting scroll <0 or beyond the // scroll bound as the browser will just reject that. window.scrollTo(window.scrollX + (scroll.windowHoriz ? scroll.windowHoriz : 0), window.scrollY + (scroll.windowVert ? scroll.windowVert : 0)) // DataTables scrolling if ( scroll.dtVert || scroll.dtHoriz ) { var scroller = that.dom.dtScroll[0]; if ( scroll.dtVert ) { scroller.scrollTop += scroll.dtVert; } if ( scroll.dtHoriz ) { scroller.scrollLeft += scroll.dtHoriz; } } }, 20 ); } }, /** * Update the DataTable after the user has selected what they want to do * * @param {false|undefined} result Return from the `execute` method - can * be false internally to do nothing. This is not documented for plug-ins * and is used only by the cancel option. * @param {array} cells Information about the selected cells from the key * up function, argumented with the set values * @private */ _update: function ( result, cells ) { // Do nothing on `false` return from an execute function if ( result === false ) { return; } var dt = this.s.dt; var cell; var columns = dt.columns( this.c.columns ).indexes(); // Potentially allow modifications to the cells matrix this._emitEvent( 'preAutoFill', [ dt, cells ] ); this._editor( cells ); // Automatic updates are not performed if `update` is null and the // `editor` parameter is passed in - the reason being that Editor will // update the data once submitted var update = this.c.update !== null ? this.c.update : this.c.editor ? false : true; if ( update ) { for ( var i=0, ien=cells.length ; i<ien ; i++ ) { for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { cell = cells[i][j]; if ( columns.indexOf(cell.index.column) !== -1 ) { cell.cell.data( cell.set ); } } } dt.draw(false); } this._emitEvent( 'autoFill', [ dt, cells ] ); } } ); /** * AutoFill actions. The options here determine how AutoFill will fill the data * in the table when the user has selected a range of cells. Please see the * documentation on the DataTables site for full details on how to create plug- * ins. * * @type {Object} */ AutoFill.actions = { increment: { available: function ( dt, cells ) { var d = cells[0][0].label; // is numeric test based on jQuery's old `isNumeric` function return !isNaN( d - parseFloat( d ) ); }, option: function ( dt, cells ) { return dt.i18n( 'autoFill.increment', 'Increment / decrement each cell by: <input type="number" value="1">' ); }, execute: function ( dt, cells, node ) { var value = cells[0][0].data * 1; var increment = $('input', node).val() * 1; for ( var i=0, ien=cells.length ; i<ien ; i++ ) { for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { cells[i][j].set = value; value += increment; } } } }, fill: { available: function ( dt, cells ) { return true; }, option: function ( dt, cells ) { return dt.i18n('autoFill.fill', 'Fill all cells with <i>%d</i>', cells[0][0].label ); }, execute: function ( dt, cells, node ) { var value = cells[0][0].data; for ( var i=0, ien=cells.length ; i<ien ; i++ ) { for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { cells[i][j].set = value; } } } }, fillHorizontal: { available: function ( dt, cells ) { return cells.length > 1 && cells[0].length > 1; }, option: function ( dt, cells ) { return dt.i18n('autoFill.fillHorizontal', 'Fill cells horizontally' ); }, execute: function ( dt, cells, node ) { for ( var i=0, ien=cells.length ; i<ien ; i++ ) { for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { cells[i][j].set = cells[i][0].data; } } } }, fillVertical: { available: function ( dt, cells ) { return cells.length > 1 && cells[0].length > 1; }, option: function ( dt, cells ) { return dt.i18n('autoFill.fillVertical', 'Fill cells vertically' ); }, execute: function ( dt, cells, node ) { for ( var i=0, ien=cells.length ; i<ien ; i++ ) { for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) { cells[i][j].set = cells[0][j].data; } } } }, // Special type that does not make itself available, but is added // automatically by AutoFill if a multi-choice list is shown. This allows // sensible code reuse cancel: { available: function () { return false; }, option: function ( dt ) { return dt.i18n('autoFill.cancel', 'Cancel' ); }, execute: function () { return false; } } }; /** * AutoFill version * * @static * @type String */ AutoFill.version = '2.6.0'; /** * AutoFill defaults * * @namespace */ AutoFill.defaults = { /** @type {Boolean} Ask user what they want to do, even for a single option */ alwaysAsk: false, closeButton: true, /** @type {string|null} What will trigger a focus */ focus: null, // focus, click, hover /** @type {column-selector} Columns to provide auto fill for */ columns: '', // all /** @type {Boolean} Enable AutoFill on load */ enable: true, /** @type {boolean|null} Update the cells after a drag */ update: null, // false is editor given, true otherwise /** @type {DataTable.Editor} Editor instance for automatic submission */ editor: null, /** @type {boolean} Enable vertical fill */ vertical: true, /** @type {boolean} Enable horizontal fill */ horizontal: true }; /** * Classes used by AutoFill that are configurable * * @namespace */ AutoFill.classes = { /** @type {String} Class used by the selection button */ btn: 'btn', closeable: 'dtaf-popover-closeable' }; /* * API */ var Api = $.fn.dataTable.Api; // Doesn't do anything - Not documented Api.register( 'autoFill()', function () { return this; } ); Api.register( 'autoFill().enabled()', function () { var ctx = this.context[0]; return ctx.autoFill ? ctx.autoFill.enabled() : false; } ); Api.register( 'autoFill().enable()', function ( flag ) { return this.iterator( 'table', function ( ctx ) { if ( ctx.autoFill ) { ctx.autoFill.enable( flag ); } } ); } ); Api.register( 'autoFill().disable()', function () { return this.iterator( 'table', function ( ctx ) { if ( ctx.autoFill ) { ctx.autoFill.disable(); } } ); } ); // Attach a listener to the document which listens for DataTables initialisation // events so we can automatically initialise $(document).on( 'preInit.dt.autofill', function (e, settings, json) { if ( e.namespace !== 'dt' ) { return; } var init = settings.oInit.autoFill; var defaults = DataTable.defaults.autoFill; if ( init || defaults ) { var opts = $.extend( {}, init, defaults ); if ( init !== false ) { new AutoFill( settings, opts ); } } } ); // Alias for access DataTable.AutoFill = AutoFill; $.fn.DataTable.AutoFill = AutoFill; return DataTable; })); /*! DataTables styling wrapper for AutoFill * ©2018 SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net-dt', 'datatables.net-autofill'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net-dt')(root, $); } if ( ! $.fn.dataTable.AutoFill ) { require('datatables.net-autofill')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; return DataTable; })); /*! Buttons for DataTables 2.4.2 * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; // Used for namespacing events added to the document by each instance, so they // can be removed on destroy var _instCounter = 0; // Button namespacing counter for namespacing events on individual buttons var _buttonCounter = 0; var _dtButtons = DataTable.ext.buttons; // Custom entity decoder for data export var _entityDecoder = null; // Allow for jQuery slim function _fadeIn(el, duration, fn) { if ($.fn.animate) { el.stop().fadeIn(duration, fn); } else { el.css('display', 'block'); if (fn) { fn.call(el); } } } function _fadeOut(el, duration, fn) { if ($.fn.animate) { el.stop().fadeOut(duration, fn); } else { el.css('display', 'none'); if (fn) { fn.call(el); } } } /** * [Buttons description] * @param {[type]} * @param {[type]} */ var Buttons = function (dt, config) { // If not created with a `new` keyword then we return a wrapper function that // will take the settings object for a DT. This allows easy use of new instances // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`. if (!(this instanceof Buttons)) { return function (settings) { return new Buttons(settings, dt).container(); }; } // If there is no config set it to an empty object if (typeof config === 'undefined') { config = {}; } // Allow a boolean true for defaults if (config === true) { config = {}; } // For easy configuration of buttons an array can be given if (Array.isArray(config)) { config = { buttons: config }; } this.c = $.extend(true, {}, Buttons.defaults, config); // Don't want a deep copy for the buttons if (config.buttons) { this.c.buttons = config.buttons; } this.s = { dt: new DataTable.Api(dt), buttons: [], listenKeys: '', namespace: 'dtb' + _instCounter++ }; this.dom = { container: $('<' + this.c.dom.container.tag + '/>').addClass(this.c.dom.container.className) }; this._constructor(); }; $.extend(Buttons.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public methods */ /** * Get the action of a button * @param {int|string} Button index * @return {function} */ /** * Set the action of a button * @param {node} node Button element * @param {function} action Function to set * @return {Buttons} Self for chaining */ action: function (node, action) { var button = this._nodeToButton(node); if (action === undefined) { return button.conf.action; } button.conf.action = action; return this; }, /** * Add an active class to the button to make to look active or get current * active state. * @param {node} node Button element * @param {boolean} [flag] Enable / disable flag * @return {Buttons} Self for chaining or boolean for getter */ active: function (node, flag) { var button = this._nodeToButton(node); var klass = this.c.dom.button.active; var jqNode = $(button.node); if ( button.inCollection && this.c.dom.collection.button && this.c.dom.collection.button.active !== undefined ) { klass = this.c.dom.collection.button.active; } if (flag === undefined) { return jqNode.hasClass(klass); } jqNode.toggleClass(klass, flag === undefined ? true : flag); return this; }, /** * Add a new button * @param {object} config Button configuration object, base string name or function * @param {int|string} [idx] Button index for where to insert the button * @param {boolean} [draw=true] Trigger a draw. Set a false when adding * lots of buttons, until the last button. * @return {Buttons} Self for chaining */ add: function (config, idx, draw) { var buttons = this.s.buttons; if (typeof idx === 'string') { var split = idx.split('-'); var base = this.s; for (var i = 0, ien = split.length - 1; i < ien; i++) { base = base.buttons[split[i] * 1]; } buttons = base.buttons; idx = split[split.length - 1] * 1; } this._expandButton( buttons, config, config !== undefined ? config.split : undefined, (config === undefined || config.split === undefined || config.split.length === 0) && base !== undefined, false, idx ); if (draw === undefined || draw === true) { this._draw(); } return this; }, /** * Clear buttons from a collection and then insert new buttons */ collectionRebuild: function (node, newButtons) { var button = this._nodeToButton(node); if (newButtons !== undefined) { var i; // Need to reverse the array for (i = button.buttons.length - 1; i >= 0; i--) { this.remove(button.buttons[i].node); } // If the collection has prefix and / or postfix buttons we need to add them in if (button.conf.prefixButtons) { newButtons.unshift.apply(newButtons, button.conf.prefixButtons); } if (button.conf.postfixButtons) { newButtons.push.apply(newButtons, button.conf.postfixButtons); } for (i = 0; i < newButtons.length; i++) { var newBtn = newButtons[i]; this._expandButton( button.buttons, newBtn, newBtn !== undefined && newBtn.config !== undefined && newBtn.config.split !== undefined, true, newBtn.parentConf !== undefined && newBtn.parentConf.split !== undefined, null, newBtn.parentConf ); } } this._draw(button.collection, button.buttons); }, /** * Get the container node for the buttons * @return {jQuery} Buttons node */ container: function () { return this.dom.container; }, /** * Disable a button * @param {node} node Button node * @return {Buttons} Self for chaining */ disable: function (node) { var button = this._nodeToButton(node); $(button.node).addClass(this.c.dom.button.disabled).prop('disabled', true); return this; }, /** * Destroy the instance, cleaning up event handlers and removing DOM * elements * @return {Buttons} Self for chaining */ destroy: function () { // Key event listener $('body').off('keyup.' + this.s.namespace); // Individual button destroy (so they can remove their own events if // needed). Take a copy as the array is modified by `remove` var buttons = this.s.buttons.slice(); var i, ien; for (i = 0, ien = buttons.length; i < ien; i++) { this.remove(buttons[i].node); } // Container this.dom.container.remove(); // Remove from the settings object collection var buttonInsts = this.s.dt.settings()[0]; for (i = 0, ien = buttonInsts.length; i < ien; i++) { if (buttonInsts.inst === this) { buttonInsts.splice(i, 1); break; } } return this; }, /** * Enable / disable a button * @param {node} node Button node * @param {boolean} [flag=true] Enable / disable flag * @return {Buttons} Self for chaining */ enable: function (node, flag) { if (flag === false) { return this.disable(node); } var button = this._nodeToButton(node); $(button.node).removeClass(this.c.dom.button.disabled).prop('disabled', false); return this; }, /** * Get a button's index * * This is internally recursive * @param {element} node Button to get the index of * @return {string} Button index */ index: function (node, nested, buttons) { if (!nested) { nested = ''; buttons = this.s.buttons; } for (var i = 0, ien = buttons.length; i < ien; i++) { var inner = buttons[i].buttons; if (buttons[i].node === node) { return nested + i; } if (inner && inner.length) { var match = this.index(node, i + '-', inner); if (match !== null) { return match; } } } return null; }, /** * Get the instance name for the button set selector * @return {string} Instance name */ name: function () { return this.c.name; }, /** * Get a button's node of the buttons container if no button is given * @param {node} [node] Button node * @return {jQuery} Button element, or container */ node: function (node) { if (!node) { return this.dom.container; } var button = this._nodeToButton(node); return $(button.node); }, /** * Set / get a processing class on the selected button * @param {element} node Triggering button node * @param {boolean} flag true to add, false to remove, undefined to get * @return {boolean|Buttons} Getter value or this if a setter. */ processing: function (node, flag) { var dt = this.s.dt; var button = this._nodeToButton(node); if (flag === undefined) { return $(button.node).hasClass('processing'); } $(button.node).toggleClass('processing', flag); $(dt.table().node()).triggerHandler('buttons-processing.dt', [ flag, dt.button(node), dt, $(node), button.conf ]); return this; }, /** * Remove a button. * @param {node} node Button node * @return {Buttons} Self for chaining */ remove: function (node) { var button = this._nodeToButton(node); var host = this._nodeToHost(node); var dt = this.s.dt; // Remove any child buttons first if (button.buttons.length) { for (var i = button.buttons.length - 1; i >= 0; i--) { this.remove(button.buttons[i].node); } } button.conf.destroying = true; // Allow the button to remove event handlers, etc if (button.conf.destroy) { button.conf.destroy.call(dt.button(node), dt, $(node), button.conf); } this._removeKey(button.conf); $(button.node).remove(); var idx = $.inArray(button, host); host.splice(idx, 1); return this; }, /** * Get the text for a button * @param {int|string} node Button index * @return {string} Button text */ /** * Set the text for a button * @param {int|string|function} node Button index * @param {string} label Text * @return {Buttons} Self for chaining */ text: function (node, label) { var button = this._nodeToButton(node); var textNode = button.textNode; var dt = this.s.dt; var jqNode = $(button.node); var text = function (opt) { return typeof opt === 'function' ? opt(dt, jqNode, button.conf) : opt; }; if (label === undefined) { return text(button.conf.text); } button.conf.text = label; textNode.html(text(label)); return this; }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constructor */ /** * Buttons constructor * @private */ _constructor: function () { var that = this; var dt = this.s.dt; var dtSettings = dt.settings()[0]; var buttons = this.c.buttons; if (!dtSettings._buttons) { dtSettings._buttons = []; } dtSettings._buttons.push({ inst: this, name: this.c.name }); for (var i = 0, ien = buttons.length; i < ien; i++) { this.add(buttons[i]); } dt.on('destroy', function (e, settings) { if (settings === dtSettings) { that.destroy(); } }); // Global key event binding to listen for button keys $('body').on('keyup.' + this.s.namespace, function (e) { if (!document.activeElement || document.activeElement === document.body) { // SUse a string of characters for fast lookup of if we need to // handle this var character = String.fromCharCode(e.keyCode).toLowerCase(); if (that.s.listenKeys.toLowerCase().indexOf(character) !== -1) { that._keypress(character, e); } } }); }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods */ /** * Add a new button to the key press listener * @param {object} conf Resolved button configuration object * @private */ _addKey: function (conf) { if (conf.key) { this.s.listenKeys += $.isPlainObject(conf.key) ? conf.key.key : conf.key; } }, /** * Insert the buttons into the container. Call without parameters! * @param {node} [container] Recursive only - Insert point * @param {array} [buttons] Recursive only - Buttons array * @private */ _draw: function (container, buttons) { if (!container) { container = this.dom.container; buttons = this.s.buttons; } container.children().detach(); for (var i = 0, ien = buttons.length; i < ien; i++) { container.append(buttons[i].inserter); container.append(' '); if (buttons[i].buttons && buttons[i].buttons.length) { this._draw(buttons[i].collection, buttons[i].buttons); } } }, /** * Create buttons from an array of buttons * @param {array} attachTo Buttons array to attach to * @param {object} button Button definition * @param {boolean} inCollection true if the button is in a collection * @private */ _expandButton: function ( attachTo, button, split, inCollection, inSplit, attachPoint, parentConf ) { var dt = this.s.dt; var isSplit = false; var domCollection = this.c.dom.collection; var buttons = !Array.isArray(button) ? [button] : button; if (button === undefined) { buttons = !Array.isArray(split) ? [split] : split; } for (var i = 0, ien = buttons.length; i < ien; i++) { var conf = this._resolveExtends(buttons[i]); if (!conf) { continue; } isSplit = conf.config && conf.config.split ? true : false; // If the configuration is an array, then expand the buttons at this // point if (Array.isArray(conf)) { this._expandButton( attachTo, conf, built !== undefined && built.conf !== undefined ? built.conf.split : undefined, inCollection, parentConf !== undefined && parentConf.split !== undefined, attachPoint, parentConf ); continue; } var built = this._buildButton( conf, inCollection, conf.split !== undefined || (conf.config !== undefined && conf.config.split !== undefined), inSplit ); if (!built) { continue; } if (attachPoint !== undefined && attachPoint !== null) { attachTo.splice(attachPoint, 0, built); attachPoint++; } else { attachTo.push(built); } // Create the dropdown for a collection if (built.conf.buttons) { built.collection = $('<' + domCollection.container.content.tag + '/>'); built.conf._collection = built.collection; $(built.node).append(domCollection.action.dropHtml); this._expandButton( built.buttons, built.conf.buttons, built.conf.split, !isSplit, isSplit, attachPoint, built.conf ); } // And the split collection if (built.conf.split) { built.collection = $('<' + domCollection.container.tag + '/>'); built.conf._collection = built.collection; for (var j = 0; j < built.conf.split.length; j++) { var item = built.conf.split[j]; if (typeof item === 'object') { item.parent = parentConf; if (item.collectionLayout === undefined) { item.collectionLayout = built.conf.collectionLayout; } if (item.dropup === undefined) { item.dropup = built.conf.dropup; } if (item.fade === undefined) { item.fade = built.conf.fade; } } } this._expandButton( built.buttons, built.conf.buttons, built.conf.split, !isSplit, isSplit, attachPoint, built.conf ); } built.conf.parent = parentConf; // init call is made here, rather than buildButton as it needs to // be selectable, and for that it needs to be in the buttons array if (conf.init) { conf.init.call(dt.button(built.node), dt, $(built.node), conf); } } }, /** * Create an individual button * @param {object} config Resolved button configuration * @param {boolean} inCollection `true` if a collection button * @return {object} Completed button description object * @private */ _buildButton: function (config, inCollection, isSplit, inSplit) { var configDom = this.c.dom; var textNode; var dt = this.s.dt; var text = function (opt) { return typeof opt === 'function' ? opt(dt, button, config) : opt; }; // Create an object that describes the button which can be in `dom.button`, or // `dom.collection.button` or `dom.split.button` or `dom.collection.split.button`! // Each should extend from `dom.button`. var dom = $.extend(true, {}, configDom.button); if (inCollection && isSplit && configDom.collection.split) { $.extend(true, dom, configDom.collection.split.action); } else if (inSplit || inCollection) { $.extend(true, dom, configDom.collection.button); } else if (isSplit) { $.extend(true, dom, configDom.split.button); } // Spacers don't do much other than insert an element into the DOM if (config.spacer) { var spacer = $('<' + dom.spacer.tag + '/>') .addClass('dt-button-spacer ' + config.style + ' ' + dom.spacer.className) .html(text(config.text)); return { conf: config, node: spacer, inserter: spacer, buttons: [], inCollection: inCollection, isSplit: isSplit, collection: null, textNode: spacer }; } // Make sure that the button is available based on whatever requirements // it has. For example, PDF button require pdfmake if (config.available && !config.available(dt, config) && !config.hasOwnProperty('html')) { return false; } var button; if (!config.hasOwnProperty('html')) { var action = function (e, dt, button, config) { config.action.call(dt.button(button), e, dt, button, config); $(dt.table().node()).triggerHandler('buttons-action.dt', [ dt.button(button), dt, button, config ]); }; var tag = config.tag || dom.tag; var clickBlurs = config.clickBlurs === undefined ? true : config.clickBlurs; button = $('<' + tag + '/>') .addClass(dom.className) .attr('tabindex', this.s.dt.settings()[0].iTabIndex) .attr('aria-controls', this.s.dt.table().node().id) .on('click.dtb', function (e) { e.preventDefault(); if (!button.hasClass(dom.disabled) && config.action) { action(e, dt, button, config); } if (clickBlurs) { button.trigger('blur'); } }) .on('keypress.dtb', function (e) { if (e.keyCode === 13) { e.preventDefault(); if (!button.hasClass(dom.disabled) && config.action) { action(e, dt, button, config); } } }); // Make `a` tags act like a link if (tag.toLowerCase() === 'a') { button.attr('href', '#'); } // Button tags should have `type=button` so they don't have any default behaviour if (tag.toLowerCase() === 'button') { button.attr('type', 'button'); } if (dom.liner.tag) { var liner = $('<' + dom.liner.tag + '/>') .html(text(config.text)) .addClass(dom.liner.className); if (dom.liner.tag.toLowerCase() === 'a') { liner.attr('href', '#'); } button.append(liner); textNode = liner; } else { button.html(text(config.text)); textNode = button; } if (config.enabled === false) { button.addClass(dom.disabled); } if (config.className) { button.addClass(config.className); } if (config.titleAttr) { button.attr('title', text(config.titleAttr)); } if (config.attr) { button.attr(config.attr); } if (!config.namespace) { config.namespace = '.dt-button-' + _buttonCounter++; } if (config.config !== undefined && config.config.split) { config.split = config.config.split; } } else { button = $(config.html); } var buttonContainer = this.c.dom.buttonContainer; var inserter; if (buttonContainer && buttonContainer.tag) { inserter = $('<' + buttonContainer.tag + '/>') .addClass(buttonContainer.className) .append(button); } else { inserter = button; } this._addKey(config); // Style integration callback for DOM manipulation // Note that this is _not_ documented. It is currently // for style integration only if (this.c.buttonCreated) { inserter = this.c.buttonCreated(config, inserter); } var splitDiv; if (isSplit) { var dropdownConf = inCollection ? $.extend(true, this.c.dom.split, this.c.dom.collection.split) : this.c.dom.split; var wrapperConf = dropdownConf.wrapper; splitDiv = $('<' + wrapperConf.tag + '/>') .addClass(wrapperConf.className) .append(button); var dropButtonConfig = $.extend(config, { align: dropdownConf.dropdown.align, attr: { 'aria-haspopup': 'dialog', 'aria-expanded': false }, className: dropdownConf.dropdown.className, closeButton: false, splitAlignClass: dropdownConf.dropdown.splitAlignClass, text: dropdownConf.dropdown.text }); this._addKey(dropButtonConfig); var splitAction = function (e, dt, button, config) { _dtButtons.split.action.call(dt.button(splitDiv), e, dt, button, config); $(dt.table().node()).triggerHandler('buttons-action.dt', [ dt.button(button), dt, button, config ]); button.attr('aria-expanded', true); }; var dropButton = $( '<button class="' + dropdownConf.dropdown.className + ' dt-button"></button>' ) .html(dropdownConf.dropdown.dropHtml) .on('click.dtb', function (e) { e.preventDefault(); e.stopPropagation(); if (!dropButton.hasClass(dom.disabled)) { splitAction(e, dt, dropButton, dropButtonConfig); } if (clickBlurs) { dropButton.trigger('blur'); } }) .on('keypress.dtb', function (e) { if (e.keyCode === 13) { e.preventDefault(); if (!dropButton.hasClass(dom.disabled)) { splitAction(e, dt, dropButton, dropButtonConfig); } } }); if (config.split.length === 0) { dropButton.addClass('dtb-hide-drop'); } splitDiv.append(dropButton).attr(dropButtonConfig.attr); } return { conf: config, node: isSplit ? splitDiv.get(0) : button.get(0), inserter: isSplit ? splitDiv : inserter, buttons: [], inCollection: inCollection, isSplit: isSplit, inSplit: inSplit, collection: null, textNode: textNode }; }, /** * Get the button object from a node (recursive) * @param {node} node Button node * @param {array} [buttons] Button array, uses base if not defined * @return {object} Button object * @private */ _nodeToButton: function (node, buttons) { if (!buttons) { buttons = this.s.buttons; } for (var i = 0, ien = buttons.length; i < ien; i++) { if (buttons[i].node === node) { return buttons[i]; } if (buttons[i].buttons.length) { var ret = this._nodeToButton(node, buttons[i].buttons); if (ret) { return ret; } } } }, /** * Get container array for a button from a button node (recursive) * @param {node} node Button node * @param {array} [buttons] Button array, uses base if not defined * @return {array} Button's host array * @private */ _nodeToHost: function (node, buttons) { if (!buttons) { buttons = this.s.buttons; } for (var i = 0, ien = buttons.length; i < ien; i++) { if (buttons[i].node === node) { return buttons; } if (buttons[i].buttons.length) { var ret = this._nodeToHost(node, buttons[i].buttons); if (ret) { return ret; } } } }, /** * Handle a key press - determine if any button's key configured matches * what was typed and trigger the action if so. * @param {string} character The character pressed * @param {object} e Key event that triggered this call * @private */ _keypress: function (character, e) { // Check if this button press already activated on another instance of Buttons if (e._buttonsHandled) { return; } var run = function (conf, node) { if (!conf.key) { return; } if (conf.key === character) { e._buttonsHandled = true; $(node).click(); } else if ($.isPlainObject(conf.key)) { if (conf.key.key !== character) { return; } if (conf.key.shiftKey && !e.shiftKey) { return; } if (conf.key.altKey && !e.altKey) { return; } if (conf.key.ctrlKey && !e.ctrlKey) { return; } if (conf.key.metaKey && !e.metaKey) { return; } // Made it this far - it is good e._buttonsHandled = true; $(node).click(); } }; var recurse = function (a) { for (var i = 0, ien = a.length; i < ien; i++) { run(a[i].conf, a[i].node); if (a[i].buttons.length) { recurse(a[i].buttons); } } }; recurse(this.s.buttons); }, /** * Remove a key from the key listener for this instance (to be used when a * button is removed) * @param {object} conf Button configuration * @private */ _removeKey: function (conf) { if (conf.key) { var character = $.isPlainObject(conf.key) ? conf.key.key : conf.key; // Remove only one character, as multiple buttons could have the // same listening key var a = this.s.listenKeys.split(''); var idx = $.inArray(character, a); a.splice(idx, 1); this.s.listenKeys = a.join(''); } }, /** * Resolve a button configuration * @param {string|function|object} conf Button config to resolve * @return {object} Button configuration * @private */ _resolveExtends: function (conf) { var that = this; var dt = this.s.dt; var i, ien; var toConfObject = function (base) { var loop = 0; // Loop until we have resolved to a button configuration, or an // array of button configurations (which will be iterated // separately) while (!$.isPlainObject(base) && !Array.isArray(base)) { if (base === undefined) { return; } if (typeof base === 'function') { base = base.call(that, dt, conf); if (!base) { return false; } } else if (typeof base === 'string') { if (!_dtButtons[base]) { return { html: base }; } base = _dtButtons[base]; } loop++; if (loop > 30) { // Protect against misconfiguration killing the browser throw 'Buttons: Too many iterations'; } } return Array.isArray(base) ? base : $.extend({}, base); }; conf = toConfObject(conf); while (conf && conf.extend) { // Use `toConfObject` in case the button definition being extended // is itself a string or a function if (!_dtButtons[conf.extend]) { throw 'Cannot extend unknown button type: ' + conf.extend; } var objArray = toConfObject(_dtButtons[conf.extend]); if (Array.isArray(objArray)) { return objArray; } else if (!objArray) { // This is a little brutal as it might be possible to have a // valid button without the extend, but if there is no extend // then the host button would be acting in an undefined state return false; } // Stash the current class name var originalClassName = objArray.className; if (conf.config !== undefined && objArray.config !== undefined) { conf.config = $.extend({}, objArray.config, conf.config); } conf = $.extend({}, objArray, conf); // The extend will have overwritten the original class name if the // `conf` object also assigned a class, but we want to concatenate // them so they are list that is combined from all extended buttons if (originalClassName && conf.className !== originalClassName) { conf.className = originalClassName + ' ' + conf.className; } // Although we want the `conf` object to overwrite almost all of // the properties of the object being extended, the `extend` // property should come from the object being extended conf.extend = objArray.extend; } // Buttons to be added to a collection -gives the ability to define // if buttons should be added to the start or end of a collection var postfixButtons = conf.postfixButtons; if (postfixButtons) { if (!conf.buttons) { conf.buttons = []; } for (i = 0, ien = postfixButtons.length; i < ien; i++) { conf.buttons.push(postfixButtons[i]); } } var prefixButtons = conf.prefixButtons; if (prefixButtons) { if (!conf.buttons) { conf.buttons = []; } for (i = 0, ien = prefixButtons.length; i < ien; i++) { conf.buttons.splice(i, 0, prefixButtons[i]); } } return conf; }, /** * Display (and replace if there is an existing one) a popover attached to a button * @param {string|node} content Content to show * @param {DataTable.Api} hostButton DT API instance of the button * @param {object} inOpts Options (see object below for all options) */ _popover: function (content, hostButton, inOpts, e) { var dt = hostButton; var c = this.c; var closed = false; var options = $.extend( { align: 'button-left', // button-right, dt-container, split-left, split-right autoClose: false, background: true, backgroundClassName: 'dt-button-background', closeButton: true, containerClassName: c.dom.collection.container.className, contentClassName: c.dom.collection.container.content.className, collectionLayout: '', collectionTitle: '', dropup: false, fade: 400, popoverTitle: '', rightAlignClassName: 'dt-button-right', tag: c.dom.collection.container.tag }, inOpts ); var containerSelector = options.tag + '.' + options.containerClassName.replace(/ /g, '.'); var hostNode = hostButton.node(); var close = function () { closed = true; _fadeOut($(containerSelector), options.fade, function () { $(this).detach(); }); $(dt.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes()).attr( 'aria-expanded', 'false' ); $('div.dt-button-background').off('click.dtb-collection'); Buttons.background(false, options.backgroundClassName, options.fade, hostNode); $(window).off('resize.resize.dtb-collection'); $('body').off('.dtb-collection'); dt.off('buttons-action.b-internal'); dt.off('destroy'); }; if (content === false) { close(); return; } var existingExpanded = $( dt.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes() ); if (existingExpanded.length) { // Reuse the current position if the button that was triggered is inside an existing collection if (hostNode.closest(containerSelector).length) { hostNode = existingExpanded.eq(0); } close(); } // Try to be smart about the layout var cnt = $('.dt-button', content).length; var mod = ''; if (cnt === 3) { mod = 'dtb-b3'; } else if (cnt === 2) { mod = 'dtb-b2'; } else if (cnt === 1) { mod = 'dtb-b1'; } var display = $('<' + options.tag + '/>') .addClass(options.containerClassName) .addClass(options.collectionLayout) .addClass(options.splitAlignClass) .addClass(mod) .css('display', 'none') .attr({ 'aria-modal': true, role: 'dialog' }); content = $(content) .addClass(options.contentClassName) .attr('role', 'menu') .appendTo(display); hostNode.attr('aria-expanded', 'true'); if (hostNode.parents('body')[0] !== document.body) { hostNode = document.body.lastChild; } if (options.popoverTitle) { display.prepend( '<div class="dt-button-collection-title">' + options.popoverTitle + '</div>' ); } else if (options.collectionTitle) { display.prepend( '<div class="dt-button-collection-title">' + options.collectionTitle + '</div>' ); } if (options.closeButton) { display .prepend('<div class="dtb-popover-close">×</div>') .addClass('dtb-collection-closeable'); } _fadeIn(display.insertAfter(hostNode), options.fade); var tableContainer = $(hostButton.table().container()); var position = display.css('position'); if (options.span === 'container' || options.align === 'dt-container') { hostNode = hostNode.parent(); display.css('width', tableContainer.width()); } // Align the popover relative to the DataTables container // Useful for wide popovers such as SearchPanes if (position === 'absolute') { // Align relative to the host button var offsetParent = $(hostNode[0].offsetParent); var buttonPosition = hostNode.position(); var buttonOffset = hostNode.offset(); var tableSizes = offsetParent.offset(); var containerPosition = offsetParent.position(); var computed = window.getComputedStyle(offsetParent[0]); tableSizes.height = offsetParent.outerHeight(); tableSizes.width = offsetParent.width() + parseFloat(computed.paddingLeft); tableSizes.right = tableSizes.left + tableSizes.width; tableSizes.bottom = tableSizes.top + tableSizes.height; // Set the initial position so we can read height / width var top = buttonPosition.top + hostNode.outerHeight(); var left = buttonPosition.left; display.css({ top: top, left: left }); // Get the popover position computed = window.getComputedStyle(display[0]); var popoverSizes = display.offset(); popoverSizes.height = display.outerHeight(); popoverSizes.width = display.outerWidth(); popoverSizes.right = popoverSizes.left + popoverSizes.width; popoverSizes.bottom = popoverSizes.top + popoverSizes.height; popoverSizes.marginTop = parseFloat(computed.marginTop); popoverSizes.marginBottom = parseFloat(computed.marginBottom); // First position per the class requirements - pop up and right align if (options.dropup) { top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom; } if (options.align === 'button-right' || display.hasClass(options.rightAlignClassName)) { left = buttonPosition.left - popoverSizes.width + hostNode.outerWidth(); } // Container alignment - make sure it doesn't overflow the table container if (options.align === 'dt-container' || options.align === 'container') { if (left < buttonPosition.left) { left = -buttonPosition.left; } if (left + popoverSizes.width > tableSizes.width) { left = tableSizes.width - popoverSizes.width; } } // Window adjustment if (containerPosition.left + left + popoverSizes.width > $(window).width()) { // Overflowing the document to the right left = $(window).width() - popoverSizes.width - containerPosition.left; } if (buttonOffset.left + left < 0) { // Off to the left of the document left = -buttonOffset.left; } if ( containerPosition.top + top + popoverSizes.height > $(window).height() + $(window).scrollTop() ) { // Pop up if otherwise we'd need the user to scroll down top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom; } if (containerPosition.top + top < $(window).scrollTop()) { // Correction for when the top is beyond the top of the page top = buttonPosition.top + hostNode.outerHeight(); } // Calculations all done - now set it display.css({ top: top, left: left }); } else { // Fix position - centre on screen var position = function () { var half = $(window).height() / 2; var top = display.height() / 2; if (top > half) { top = half; } display.css('marginTop', top * -1); }; position(); $(window).on('resize.dtb-collection', function () { position(); }); } if (options.background) { Buttons.background( true, options.backgroundClassName, options.fade, options.backgroundHost || hostNode ); } // This is bonkers, but if we don't have a click listener on the // background element, iOS Safari will ignore the body click // listener below. An empty function here is all that is // required to make it work... $('div.dt-button-background').on('click.dtb-collection', function () {}); if (options.autoClose) { setTimeout(function () { dt.on('buttons-action.b-internal', function (e, btn, dt, node) { if (node[0] === hostNode[0]) { return; } close(); }); }, 0); } $(display).trigger('buttons-popover.dt'); dt.on('destroy', close); setTimeout(function () { closed = false; $('body') .on('click.dtb-collection', function (e) { if (closed) { return; } // andSelf is deprecated in jQ1.8, but we want 1.7 compat var back = $.fn.addBack ? 'addBack' : 'andSelf'; var parent = $(e.target).parent()[0]; if ( (!$(e.target).parents()[back]().filter(content).length && !$(parent).hasClass('dt-buttons')) || $(e.target).hasClass('dt-button-background') ) { close(); } }) .on('keyup.dtb-collection', function (e) { if (e.keyCode === 27) { close(); } }) .on('keydown.dtb-collection', function (e) { // Focus trap for tab key var elements = $('a, button', content); var active = document.activeElement; if (e.keyCode !== 9) { // tab return; } if (elements.index(active) === -1) { // If current focus is not inside the popover elements.first().focus(); e.preventDefault(); } else if (e.shiftKey) { // Reverse tabbing order when shift key is pressed if (active === elements[0]) { elements.last().focus(); e.preventDefault(); } } else { if (active === elements.last()[0]) { elements.first().focus(); e.preventDefault(); } } }); }, 0); } }); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Statics */ /** * Show / hide a background layer behind a collection * @param {boolean} Flag to indicate if the background should be shown or * hidden * @param {string} Class to assign to the background * @static */ Buttons.background = function (show, className, fade, insertPoint) { if (fade === undefined) { fade = 400; } if (!insertPoint) { insertPoint = document.body; } if (show) { _fadeIn( $('<div/>').addClass(className).css('display', 'none').insertAfter(insertPoint), fade ); } else { _fadeOut($('div.' + className), fade, function () { $(this).removeClass(className).remove(); }); } }; /** * Instance selector - select Buttons instances based on an instance selector * value from the buttons assigned to a DataTable. This is only useful if * multiple instances are attached to a DataTable. * @param {string|int|array} Instance selector - see `instance-selector` * documentation on the DataTables site * @param {array} Button instance array that was attached to the DataTables * settings object * @return {array} Buttons instances * @static */ Buttons.instanceSelector = function (group, buttons) { if (group === undefined || group === null) { return $.map(buttons, function (v) { return v.inst; }); } var ret = []; var names = $.map(buttons, function (v) { return v.name; }); // Flatten the group selector into an array of single options var process = function (input) { if (Array.isArray(input)) { for (var i = 0, ien = input.length; i < ien; i++) { process(input[i]); } return; } if (typeof input === 'string') { if (input.indexOf(',') !== -1) { // String selector, list of names process(input.split(',')); } else { // String selector individual name var idx = $.inArray(input.trim(), names); if (idx !== -1) { ret.push(buttons[idx].inst); } } } else if (typeof input === 'number') { // Index selector ret.push(buttons[input].inst); } else if (typeof input === 'object') { // Actual instance selector ret.push(input); } }; process(group); return ret; }; /** * Button selector - select one or more buttons from a selector input so some * operation can be performed on them. * @param {array} Button instances array that the selector should operate on * @param {string|int|node|jQuery|array} Button selector - see * `button-selector` documentation on the DataTables site * @return {array} Array of objects containing `inst` and `idx` properties of * the selected buttons so you know which instance each button belongs to. * @static */ Buttons.buttonSelector = function (insts, selector) { var ret = []; var nodeBuilder = function (a, buttons, baseIdx) { var button; var idx; for (var i = 0, ien = buttons.length; i < ien; i++) { button = buttons[i]; if (button) { idx = baseIdx !== undefined ? baseIdx + i : i + ''; a.push({ node: button.node, name: button.conf.name, idx: idx }); if (button.buttons) { nodeBuilder(a, button.buttons, idx + '-'); } } } }; var run = function (selector, inst) { var i, ien; var buttons = []; nodeBuilder(buttons, inst.s.buttons); var nodes = $.map(buttons, function (v) { return v.node; }); if (Array.isArray(selector) || selector instanceof $) { for (i = 0, ien = selector.length; i < ien; i++) { run(selector[i], inst); } return; } if (selector === null || selector === undefined || selector === '*') { // Select all for (i = 0, ien = buttons.length; i < ien; i++) { ret.push({ inst: inst, node: buttons[i].node }); } } else if (typeof selector === 'number') { // Main button index selector if (inst.s.buttons[selector]) { ret.push({ inst: inst, node: inst.s.buttons[selector].node }); } } else if (typeof selector === 'string') { if (selector.indexOf(',') !== -1) { // Split var a = selector.split(','); for (i = 0, ien = a.length; i < ien; i++) { run(a[i].trim(), inst); } } else if (selector.match(/^\d+(\-\d+)*$/)) { // Sub-button index selector var indexes = $.map(buttons, function (v) { return v.idx; }); ret.push({ inst: inst, node: buttons[$.inArray(selector, indexes)].node }); } else if (selector.indexOf(':name') !== -1) { // Button name selector var name = selector.replace(':name', ''); for (i = 0, ien = buttons.length; i < ien; i++) { if (buttons[i].name === name) { ret.push({ inst: inst, node: buttons[i].node }); } } } else { // jQuery selector on the nodes $(nodes) .filter(selector) .each(function () { ret.push({ inst: inst, node: this }); }); } } else if (typeof selector === 'object' && selector.nodeName) { // Node selector var idx = $.inArray(selector, nodes); if (idx !== -1) { ret.push({ inst: inst, node: nodes[idx] }); } } }; for (var i = 0, ien = insts.length; i < ien; i++) { var inst = insts[i]; run(selector, inst); } return ret; }; /** * Default function used for formatting output data. * @param {*} str Data to strip */ Buttons.stripData = function (str, config) { if (typeof str !== 'string') { return str; } // Always remove script tags str = str.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // Always remove comments str = str.replace(/<!\-\-.*?\-\->/g, ''); if (!config || config.stripHtml) { str = str.replace(/<[^>]*>/g, ''); } if (!config || config.trim) { str = str.replace(/^\s+|\s+$/g, ''); } if (!config || config.stripNewlines) { str = str.replace(/\n/g, ' '); } if (!config || config.decodeEntities) { if (_entityDecoder) { str = _entityDecoder(str); } else { _exportTextarea.innerHTML = str; str = _exportTextarea.value; } } return str; }; /** * Provide a custom entity decoding function - e.g. a regex one, which can be * much faster than the built in DOM option, but also larger code size. * @param {function} fn */ Buttons.entityDecoder = function (fn) { _entityDecoder = fn; } /** * Buttons defaults. For full documentation, please refer to the docs/option * directory or the DataTables site. * @type {Object} * @static */ Buttons.defaults = { buttons: ['copy', 'excel', 'csv', 'pdf', 'print'], name: 'main', tabIndex: 0, dom: { container: { tag: 'div', className: 'dt-buttons' }, collection: { action: { // action button dropHtml: '<span class="dt-button-down-arrow">▼</span>' }, container: { // The element used for the dropdown className: 'dt-button-collection', content: { className: '', tag: 'div' }, tag: 'div' } // optionally // , button: IButton - buttons inside the collection container // , split: ISplit - splits inside the collection container }, button: { tag: 'button', className: 'dt-button', active: 'dt-button-active', // class name disabled: 'disabled', // class name spacer: { className: 'dt-button-spacer', tag: 'span' }, liner: { tag: 'span', className: '' } }, split: { action: { // action button className: 'dt-button-split-drop-button dt-button', tag: 'button' }, dropdown: { // button to trigger the dropdown align: 'split-right', className: 'dt-button-split-drop', dropHtml: '<span class="dt-button-down-arrow">▼</span>', splitAlignClass: 'dt-button-split-left', tag: 'button' }, wrapper: { // wrap around both className: 'dt-button-split', tag: 'div' } } } }; /** * Version information * @type {string} * @static */ Buttons.version = '2.4.2'; $.extend(_dtButtons, { collection: { text: function (dt) { return dt.i18n('buttons.collection', 'Collection'); }, className: 'buttons-collection', closeButton: false, init: function (dt, button, config) { button.attr('aria-expanded', false); }, action: function (e, dt, button, config) { if (config._collection.parents('body').length) { this.popover(false, config); } else { this.popover(config._collection, config); } // When activated using a key - auto focus on the // first item in the popover if (e.type === 'keypress') { $('a, button', config._collection).eq(0).focus(); } }, attr: { 'aria-haspopup': 'dialog' } // Also the popover options, defined in Buttons.popover }, split: { text: function (dt) { return dt.i18n('buttons.split', 'Split'); }, className: 'buttons-split', closeButton: false, init: function (dt, button, config) { return button.attr('aria-expanded', false); }, action: function (e, dt, button, config) { this.popover(config._collection, config); }, attr: { 'aria-haspopup': 'dialog' } // Also the popover options, defined in Buttons.popover }, copy: function (dt, conf) { if (_dtButtons.copyHtml5) { return 'copyHtml5'; } }, csv: function (dt, conf) { if (_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt, conf)) { return 'csvHtml5'; } }, excel: function (dt, conf) { if (_dtButtons.excelHtml5 && _dtButtons.excelHtml5.available(dt, conf)) { return 'excelHtml5'; } }, pdf: function (dt, conf) { if (_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt, conf)) { return 'pdfHtml5'; } }, pageLength: function (dt) { var lengthMenu = dt.settings()[0].aLengthMenu; var vals = []; var lang = []; var text = function (dt) { return dt.i18n( 'buttons.pageLength', { '-1': 'Show all rows', _: 'Show %d rows' }, dt.page.len() ); }; // Support for DataTables 1.x 2D array if (Array.isArray(lengthMenu[0])) { vals = lengthMenu[0]; lang = lengthMenu[1]; } else { for (var i = 0; i < lengthMenu.length; i++) { var option = lengthMenu[i]; // Support for DataTables 2 object in the array if ($.isPlainObject(option)) { vals.push(option.value); lang.push(option.label); } else { vals.push(option); lang.push(option); } } } return { extend: 'collection', text: text, className: 'buttons-page-length', autoClose: true, buttons: $.map(vals, function (val, i) { return { text: lang[i], className: 'button-page-length', action: function (e, dt) { dt.page.len(val).draw(); }, init: function (dt, node, conf) { var that = this; var fn = function () { that.active(dt.page.len() === val); }; dt.on('length.dt' + conf.namespace, fn); fn(); }, destroy: function (dt, node, conf) { dt.off('length.dt' + conf.namespace); } }; }), init: function (dt, node, conf) { var that = this; dt.on('length.dt' + conf.namespace, function () { that.text(conf.text); }); }, destroy: function (dt, node, conf) { dt.off('length.dt' + conf.namespace); } }; }, spacer: { style: 'empty', spacer: true, text: function (dt) { return dt.i18n('buttons.spacer', ''); } } }); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * DataTables API * * For complete documentation, please refer to the docs/api directory or the * DataTables site */ // Buttons group and individual button selector DataTable.Api.register('buttons()', function (group, selector) { // Argument shifting if (selector === undefined) { selector = group; group = undefined; } this.selector.buttonGroup = group; var res = this.iterator( true, 'table', function (ctx) { if (ctx._buttons) { return Buttons.buttonSelector( Buttons.instanceSelector(group, ctx._buttons), selector ); } }, true ); res._groupSelector = group; return res; }); // Individual button selector DataTable.Api.register('button()', function (group, selector) { // just run buttons() and truncate var buttons = this.buttons(group, selector); if (buttons.length > 1) { buttons.splice(1, buttons.length); } return buttons; }); // Active buttons DataTable.Api.registerPlural('buttons().active()', 'button().active()', function (flag) { if (flag === undefined) { return this.map(function (set) { return set.inst.active(set.node); }); } return this.each(function (set) { set.inst.active(set.node, flag); }); }); // Get / set button action DataTable.Api.registerPlural('buttons().action()', 'button().action()', function (action) { if (action === undefined) { return this.map(function (set) { return set.inst.action(set.node); }); } return this.each(function (set) { set.inst.action(set.node, action); }); }); // Collection control DataTable.Api.registerPlural( 'buttons().collectionRebuild()', 'button().collectionRebuild()', function (buttons) { return this.each(function (set) { for (var i = 0; i < buttons.length; i++) { if (typeof buttons[i] === 'object') { buttons[i].parentConf = set; } } set.inst.collectionRebuild(set.node, buttons); }); } ); // Enable / disable buttons DataTable.Api.register(['buttons().enable()', 'button().enable()'], function (flag) { return this.each(function (set) { set.inst.enable(set.node, flag); }); }); // Disable buttons DataTable.Api.register(['buttons().disable()', 'button().disable()'], function () { return this.each(function (set) { set.inst.disable(set.node); }); }); // Button index DataTable.Api.register('button().index()', function () { var idx = null; this.each(function (set) { var res = set.inst.index(set.node); if (res !== null) { idx = res; } }); return idx; }); // Get button nodes DataTable.Api.registerPlural('buttons().nodes()', 'button().node()', function () { var jq = $(); // jQuery will automatically reduce duplicates to a single entry $( this.each(function (set) { jq = jq.add(set.inst.node(set.node)); }) ); return jq; }); // Get / set button processing state DataTable.Api.registerPlural('buttons().processing()', 'button().processing()', function (flag) { if (flag === undefined) { return this.map(function (set) { return set.inst.processing(set.node); }); } return this.each(function (set) { set.inst.processing(set.node, flag); }); }); // Get / set button text (i.e. the button labels) DataTable.Api.registerPlural('buttons().text()', 'button().text()', function (label) { if (label === undefined) { return this.map(function (set) { return set.inst.text(set.node); }); } return this.each(function (set) { set.inst.text(set.node, label); }); }); // Trigger a button's action DataTable.Api.registerPlural('buttons().trigger()', 'button().trigger()', function () { return this.each(function (set) { set.inst.node(set.node).trigger('click'); }); }); // Button resolver to the popover DataTable.Api.register('button().popover()', function (content, options) { return this.map(function (set) { return set.inst._popover(content, this.button(this[0].node), options); }); }); // Get the container elements DataTable.Api.register('buttons().containers()', function () { var jq = $(); var groupSelector = this._groupSelector; // We need to use the group selector directly, since if there are no buttons // the result set will be empty this.iterator(true, 'table', function (ctx) { if (ctx._buttons) { var insts = Buttons.instanceSelector(groupSelector, ctx._buttons); for (var i = 0, ien = insts.length; i < ien; i++) { jq = jq.add(insts[i].container()); } } }); return jq; }); DataTable.Api.register('buttons().container()', function () { // API level of nesting is `buttons()` so we can zip into the containers method return this.containers().eq(0); }); // Add a new button DataTable.Api.register('button().add()', function (idx, conf, draw) { var ctx = this.context; // Don't use `this` as it could be empty - select the instances directly if (ctx.length) { var inst = Buttons.instanceSelector(this._groupSelector, ctx[0]._buttons); if (inst.length) { inst[0].add(conf, idx, draw); } } return this.button(this._groupSelector, idx); }); // Destroy the button sets selected DataTable.Api.register('buttons().destroy()', function () { this.pluck('inst') .unique() .each(function (inst) { inst.destroy(); }); return this; }); // Remove a button DataTable.Api.registerPlural('buttons().remove()', 'buttons().remove()', function () { this.each(function (set) { set.inst.remove(set.node); }); return this; }); // Information box that can be used by buttons var _infoTimer; DataTable.Api.register('buttons.info()', function (title, message, time) { var that = this; if (title === false) { this.off('destroy.btn-info'); _fadeOut($('#datatables_buttons_info'), 400, function () { $(this).remove(); }); clearTimeout(_infoTimer); _infoTimer = null; return this; } if (_infoTimer) { clearTimeout(_infoTimer); } if ($('#datatables_buttons_info').length) { $('#datatables_buttons_info').remove(); } title = title ? '<h2>' + title + '</h2>' : ''; _fadeIn( $('<div id="datatables_buttons_info" class="dt-button-info"/>') .html(title) .append($('<div/>')[typeof message === 'string' ? 'html' : 'append'](message)) .css('display', 'none') .appendTo('body') ); if (time !== undefined && time !== 0) { _infoTimer = setTimeout(function () { that.buttons.info(false); }, time); } this.on('destroy.btn-info', function () { that.buttons.info(false); }); return this; }); // Get data from the table for export - this is common to a number of plug-in // buttons so it is included in the Buttons core library DataTable.Api.register('buttons.exportData()', function (options) { if (this.context.length) { return _exportData(new DataTable.Api(this.context[0]), options); } }); // Get information about the export that is common to many of the export data // types (DRY) DataTable.Api.register('buttons.exportInfo()', function (conf) { if (!conf) { conf = {}; } return { filename: _filename(conf), title: _title(conf), messageTop: _message(this, conf.message || conf.messageTop, 'top'), messageBottom: _message(this, conf.messageBottom, 'bottom') }; }); /** * Get the file name for an exported file. * * @param {object} config Button configuration * @param {boolean} incExtension Include the file name extension */ var _filename = function (config) { // Backwards compatibility var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined && config.title !== null && config.title !== '' ? config.title : config.filename; if (typeof filename === 'function') { filename = filename(); } if (filename === undefined || filename === null) { return null; } if (filename.indexOf('*') !== -1) { filename = filename.replace('*', $('head > title').text()).trim(); } // Strip characters which the OS will object to filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, ''); var extension = _stringOrFunction(config.extension); if (!extension) { extension = ''; } return filename + extension; }; /** * Simply utility method to allow parameters to be given as a function * * @param {undefined|string|function} option Option * @return {null|string} Resolved value */ var _stringOrFunction = function (option) { if (option === null || option === undefined) { return null; } else if (typeof option === 'function') { return option(); } return option; }; /** * Get the title for an exported file. * * @param {object} config Button configuration */ var _title = function (config) { var title = _stringOrFunction(config.title); return title === null ? null : title.indexOf('*') !== -1 ? title.replace('*', $('head > title').text() || 'Exported data') : title; }; var _message = function (dt, option, position) { var message = _stringOrFunction(option); if (message === null) { return null; } var caption = $('caption', dt.table().container()).eq(0); if (message === '*') { var side = caption.css('caption-side'); if (side !== position) { return null; } return caption.length ? caption.text() : ''; } return message; }; var _exportTextarea = $('<textarea/>')[0]; var _exportData = function (dt, inOpts) { var config = $.extend( true, {}, { rows: null, columns: '', modifier: { search: 'applied', order: 'applied' }, orthogonal: 'display', stripHtml: true, stripNewlines: true, decodeEntities: true, trim: true, format: { header: function (d) { return Buttons.stripData(d, config); }, footer: function (d) { return Buttons.stripData(d, config); }, body: function (d) { return Buttons.stripData(d, config); } }, customizeData: null }, inOpts ); var header = dt .columns(config.columns) .indexes() .map(function (idx) { var el = dt.column(idx).header(); return config.format.header(el.innerHTML, idx, el); }) .toArray(); var footer = dt.table().footer() ? dt .columns(config.columns) .indexes() .map(function (idx) { var el = dt.column(idx).footer(); return config.format.footer(el ? el.innerHTML : '', idx, el); }) .toArray() : null; // If Select is available on this table, and any rows are selected, limit the export // to the selected rows. If no rows are selected, all rows will be exported. Specify // a `selected` modifier to control directly. var modifier = $.extend({}, config.modifier); if (dt.select && typeof dt.select.info === 'function' && modifier.selected === undefined) { if (dt.rows(config.rows, $.extend({ selected: true }, modifier)).any()) { $.extend(modifier, { selected: true }); } } var rowIndexes = dt.rows(config.rows, modifier).indexes().toArray(); var selectedCells = dt.cells(rowIndexes, config.columns); var cells = selectedCells.render(config.orthogonal).toArray(); var cellNodes = selectedCells.nodes().toArray(); var columns = header.length; var rows = columns > 0 ? cells.length / columns : 0; var body = []; var cellCounter = 0; for (var i = 0, ien = rows; i < ien; i++) { var row = [columns]; for (var j = 0; j < columns; j++) { row[j] = config.format.body(cells[cellCounter], i, j, cellNodes[cellCounter]); cellCounter++; } body[i] = row; } var data = { header: header, footer: footer, body: body }; if (config.customizeData) { config.customizeData(data); } return data; }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * DataTables interface */ // Attach to DataTables objects for global access $.fn.dataTable.Buttons = Buttons; $.fn.DataTable.Buttons = Buttons; // DataTables creation - check if the buttons have been defined for this table, // they will have been if the `B` option was used in `dom`, otherwise we should // create the buttons instance here so they can be inserted into the document // using the API. Listen for `init` for compatibility with pre 1.10.10, but to // be removed in future. $(document).on('init.dt plugin-init.dt', function (e, settings) { if (e.namespace !== 'dt') { return; } var opts = settings.oInit.buttons || DataTable.defaults.buttons; if (opts && !settings._buttons) { new Buttons(settings, opts).container(); } }); function _init(settings, options) { var api = new DataTable.Api(settings); var opts = options ? options : api.init().buttons || DataTable.defaults.buttons; return new Buttons(api, opts).container(); } // DataTables `dom` feature option DataTable.ext.feature.push({ fnInit: _init, cFeature: 'B' }); // DataTables 2 layout feature if (DataTable.ext.features) { DataTable.ext.features.register('buttons', _init); } return DataTable; })); /*! DataTables styling wrapper for Buttons * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net-dt', 'datatables.net-buttons'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net-dt')(root, $); } if ( ! $.fn.dataTable.Buttons ) { require('datatables.net-buttons')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; return DataTable; })); /*! ColReorder 1.7.0 * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; /** * @summary ColReorder * @description Provide the ability to reorder columns in a DataTable * @version 1.7.0 * @author SpryMedia Ltd * @contact datatables.net * @copyright SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * 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 */ /** * Switch the key value pairing of an index array to be value key (i.e. the old value is now the * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ]. * @method fnInvertKeyValues * @param array aIn Array to switch around * @returns array */ function fnInvertKeyValues( aIn ) { var aRet=[]; for ( var i=0, iLen=aIn.length ; i<iLen ; i++ ) { aRet[ aIn[i] ] = i; } return aRet; } /** * Modify an array by switching the position of two elements * @method fnArraySwitch * @param array aArray Array to consider, will be modified by reference (i.e. no return) * @param int iFrom From point * @param int iTo Insert point * @returns void */ function fnArraySwitch( aArray, iFrom, iTo ) { var mStore = aArray.splice( iFrom, 1 )[0]; aArray.splice( iTo, 0, mStore ); } /** * Switch the positions of nodes in a parent node (note this is specifically designed for * table rows). Note this function considers all element nodes under the parent! * @method fnDomSwitch * @param string sTag Tag to consider * @param int iFrom Element to move * @param int Point to element the element to (before this point), can be null for append * @returns void */ function fnDomSwitch( nParent, iFrom, iTo ) { var anTags = []; for ( var i=0, iLen=nParent.childNodes.length ; i<iLen ; i++ ) { if ( nParent.childNodes[i].nodeType == 1 ) { anTags.push( nParent.childNodes[i] ); } } var nStore = anTags[ iFrom ]; if ( iTo !== null ) { nParent.insertBefore( nStore, anTags[iTo] ); } else { nParent.appendChild( nStore ); } } /** * Plug-in for DataTables which will reorder the internal column structure by taking the column * from one position (iFrom) and insert it into a given point (iTo). * @method $.fn.dataTableExt.oApi.fnColReorder * @param object oSettings DataTables settings object - automatically added by DataTables! * @param int iFrom Take the column to be repositioned from this point * @param int iTo and insert it into this point * @param bool drop Indicate if the reorder is the final one (i.e. a drop) * not a live reorder * @param bool invalidateRows speeds up processing if false passed * @returns void */ $.fn.dataTableExt.oApi.fnColReorder = function ( oSettings, iFrom, iTo, drop, invalidateRows ) { var i, iLen, j, jLen, jen, iCols=oSettings.aoColumns.length, nTrs, oCol; var attrMap = function ( obj, prop, mapping ) { if ( ! obj[ prop ] || typeof obj[ prop ] === 'function' ) { return; } var a = obj[ prop ].split('.'); var num = a.shift(); if ( isNaN( num*1 ) ) { return; } obj[ prop ] = mapping[ num*1 ]+'.'+a.join('.'); }; /* Sanity check in the input */ if ( iFrom == iTo ) { /* Pointless reorder */ return; } if ( iFrom < 0 || iFrom >= iCols ) { this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom ); return; } if ( iTo < 0 || iTo >= iCols ) { this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo ); return; } /* * Calculate the new column array index, so we have a mapping between the old and new */ var aiMapping = []; for ( i=0, iLen=iCols ; i<iLen ; i++ ) { aiMapping[i] = i; } fnArraySwitch( aiMapping, iFrom, iTo ); var aiInvertMapping = fnInvertKeyValues( aiMapping ); /* * Convert all internal indexing to the new column order indexes */ /* Sorting */ for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ ) { oSettings.aaSorting[i][0] = aiInvertMapping[ oSettings.aaSorting[i][0] ]; } /* Fixed sorting */ if ( oSettings.aaSortingFixed !== null ) { for ( i=0, iLen=oSettings.aaSortingFixed.length ; i<iLen ; i++ ) { oSettings.aaSortingFixed[i][0] = aiInvertMapping[ oSettings.aaSortingFixed[i][0] ]; } } /* Data column sorting (the column which the sort for a given column should take place on) */ for ( i=0, iLen=iCols ; i<iLen ; i++ ) { oCol = oSettings.aoColumns[i]; for ( j=0, jLen=oCol.aDataSort.length ; j<jLen ; j++ ) { oCol.aDataSort[j] = aiInvertMapping[ oCol.aDataSort[j] ]; } // Update the column indexes oCol.idx = aiInvertMapping[ oCol.idx ]; } // Update 1.10 optimised sort class removal variable $.each( oSettings.aLastSort, function (i, val) { oSettings.aLastSort[i].src = aiInvertMapping[ val.src ]; } ); /* Update the Get and Set functions for each column */ for ( i=0, iLen=iCols ; i<iLen ; i++ ) { oCol = oSettings.aoColumns[i]; if ( typeof oCol.mData == 'number' ) { oCol.mData = aiInvertMapping[ oCol.mData ]; } else if ( $.isPlainObject( oCol.mData ) ) { // HTML5 data sourced attrMap( oCol.mData, '_', aiInvertMapping ); attrMap( oCol.mData, 'filter', aiInvertMapping ); attrMap( oCol.mData, 'sort', aiInvertMapping ); attrMap( oCol.mData, 'type', aiInvertMapping ); } } /* * Move the DOM elements */ if ( oSettings.aoColumns[iFrom].bVisible ) { /* Calculate the current visible index and the point to insert the node before. The insert * before needs to take into account that there might not be an element to insert before, * in which case it will be null, and an appendChild should be used */ var iVisibleIndex = this.oApi._fnColumnIndexToVisible( oSettings, iFrom ); var iInsertBeforeIndex = null; i = iTo < iFrom ? iTo : iTo + 1; while ( iInsertBeforeIndex === null && i < iCols ) { iInsertBeforeIndex = this.oApi._fnColumnIndexToVisible( oSettings, i ); i++; } /* Header */ nTrs = oSettings.nTHead.getElementsByTagName('tr'); for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) { fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex ); } /* Footer */ if ( oSettings.nTFoot !== null ) { nTrs = oSettings.nTFoot.getElementsByTagName('tr'); for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) { fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex ); } } /* Body */ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) { if ( oSettings.aoData[i].nTr !== null ) { fnDomSwitch( oSettings.aoData[i].nTr, iVisibleIndex, iInsertBeforeIndex ); } } } /* * Move the internal array elements */ /* Columns */ fnArraySwitch( oSettings.aoColumns, iFrom, iTo ); // regenerate the get / set functions for ( i=0, iLen=iCols ; i<iLen ; i++ ) { oSettings.oApi._fnColumnOptions( oSettings, i, {} ); } /* Search columns */ fnArraySwitch( oSettings.aoPreSearchCols, iFrom, iTo ); /* Array array - internal data anodes cache */ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ ) { var data = oSettings.aoData[i]; var cells = data.anCells; if ( cells ) { fnArraySwitch( cells, iFrom, iTo ); // Longer term, should this be moved into the DataTables' invalidate // methods? for ( j=0, jen=cells.length ; j<jen ; j++ ) { if ( cells[j] && cells[j]._DT_CellIndex ) { cells[j]._DT_CellIndex.column = j; } } } // Swap around array sourced data (object based is left as is) if ( Array.isArray( data._aData ) ) { fnArraySwitch( data._aData, iFrom, iTo ); } } /* Reposition the header elements in the header layout array */ for ( i=0, iLen=oSettings.aoHeader.length ; i<iLen ; i++ ) { fnArraySwitch( oSettings.aoHeader[i], iFrom, iTo ); } if ( oSettings.aoFooter !== null ) { for ( i=0, iLen=oSettings.aoFooter.length ; i<iLen ; i++ ) { fnArraySwitch( oSettings.aoFooter[i], iFrom, iTo ); } } if ( invalidateRows || invalidateRows === undefined ) { // Always read from the data object rather than reading back from the DOM // since it could have been changed by a renderer $.fn.dataTable.Api( oSettings ).rows().invalidate('data'); } /* * Update DataTables' event handlers */ /* Sort listener */ for ( i=0, iLen=iCols ; i<iLen ; i++ ) { $(oSettings.aoColumns[i].nTh).off('.DT'); this.oApi._fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i ); } /* Fire an event so other plug-ins can update */ $(oSettings.oInstance).trigger( 'column-reorder.dt', [ oSettings, { from: iFrom, to: iTo, mapping: aiInvertMapping, drop: drop, // Old style parameters for compatibility iFrom: iFrom, iTo: iTo, aiInvertMapping: aiInvertMapping } ] ); }; /** * ColReorder provides column visibility control for DataTables * @class ColReorder * @constructor * @param {object} dt DataTables settings object * @param {object} opts ColReorder options */ var ColReorder = function( dt, opts ) { var settings = new $.fn.dataTable.Api( dt ).settings()[0]; // Ensure that we can't initialise on the same table twice if ( settings._colReorder ) { return settings._colReorder; } // Allow the options to be a boolean for defaults if ( opts === true ) { opts = {}; } // Convert from camelCase to Hungarian, just as DataTables does var camelToHungarian = $.fn.dataTable.camelToHungarian; if ( camelToHungarian ) { camelToHungarian( ColReorder.defaults, ColReorder.defaults, true ); camelToHungarian( ColReorder.defaults, opts || {} ); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public class variables * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * @namespace Settings object which contains customisable information for ColReorder instance */ this.s = { /** * DataTables settings object * @property dt * @type Object * @default null */ "dt": null, /** * Enable flag * @property dt * @type Object * @default null */ "enable": null, /** * Initialisation object used for this instance * @property init * @type object * @default {} */ "init": $.extend( true, {}, ColReorder.defaults, opts ), /** * Number of columns to fix (not allow to be reordered) * @property fixed * @type int * @default 0 */ "fixed": 0, /** * Number of columns to fix counting from right (not allow to be reordered) * @property fixedRight * @type int * @default 0 */ "fixedRight": 0, /** * Callback function for once the reorder has been done * @property reorderCallback * @type function * @default null */ "reorderCallback": null, /** * @namespace Information used for the mouse drag */ "mouse": { "startX": -1, "startY": -1, "offsetX": -1, "offsetY": -1, "target": -1, "targetIndex": -1, "fromIndex": -1 }, /** * Information which is used for positioning the insert cusor and knowing where to do the * insert. Array of objects with the properties: * x: x-axis position * to: insert point * @property aoTargets * @type array * @default [] */ "aoTargets": [] }; /** * @namespace Common and useful DOM elements for the class instance */ this.dom = { /** * Dragging element (the one the mouse is moving) * @property drag * @type element * @default null */ "drag": null, /** * The insert cursor * @property pointer * @type element * @default null */ "pointer": null }; /* Constructor logic */ this.s.enable = this.s.init.bEnable; this.s.dt = settings; this.s.dt._colReorder = this; this._fnConstruct(); return this; }; $.extend( ColReorder.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public methods * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Enable / disable end user interaction */ fnEnable: function ( flag ) { if ( flag === false ) { return this.fnDisable(); } this.s.enable = true; }, /** * Disable end user interaction */ fnDisable: function () { this.s.enable = false; }, /** * Reset the column ordering to the original ordering that was detected on * start up. * @return {this} Returns `this` for chaining. * * @example * // DataTables initialisation with ColReorder * var table = $('#example').dataTable( { * "sDom": 'Rlfrtip' * } ); * * // Add click event to a button to reset the ordering * $('#resetOrdering').click( function (e) { * e.preventDefault(); * $.fn.dataTable.ColReorder( table ).fnReset(); * } ); */ "fnReset": function () { this._fnOrderColumns( this.fnOrder() ); return this; }, /** * `Deprecated` - Get the current order of the columns, as an array. * @return {array} Array of column identifiers * @deprecated `fnOrder` should be used in preference to this method. * `fnOrder` acts as a getter/setter. */ "fnGetCurrentOrder": function () { return this.fnOrder(); }, /** * Get the current order of the columns, as an array. Note that the values * given in the array are unique identifiers for each column. Currently * these are the original ordering of the columns that was detected on * start up, but this could potentially change in future. * @return {array} Array of column identifiers * * @example * // Get column ordering for the table * var order = $.fn.dataTable.ColReorder( dataTable ).fnOrder(); *//** * Set the order of the columns, from the positions identified in the * ordering array given. Note that ColReorder takes a brute force approach * to reordering, so it is possible multiple reordering events will occur * before the final order is settled upon. * @param {array} [set] Array of column identifiers in the new order. Note * that every column must be included, uniquely, in this array. * @return {this} Returns `this` for chaining. * * @example * // Swap the first and second columns * $.fn.dataTable.ColReorder( dataTable ).fnOrder( [1, 0, 2, 3, 4] ); * * @example * // Move the first column to the end for the table `#example` * var curr = $.fn.dataTable.ColReorder( '#example' ).fnOrder(); * var first = curr.shift(); * curr.push( first ); * $.fn.dataTable.ColReorder( '#example' ).fnOrder( curr ); * * @example * // Reverse the table's order * $.fn.dataTable.ColReorder( '#example' ).fnOrder( * $.fn.dataTable.ColReorder( '#example' ).fnOrder().reverse() * ); */ "fnOrder": function ( set, original ) { var a = [], i, ien, j, jen; var columns = this.s.dt.aoColumns; if ( set === undefined ){ for ( i=0, ien=columns.length ; i<ien ; i++ ) { a.push( columns[i]._ColReorder_iOrigCol ); } return a; } // The order given is based on the original indexes, rather than the // existing ones, so we need to translate from the original to current // before then doing the order if ( original ) { var order = this.fnOrder(); for ( i=0, ien=set.length ; i<ien ; i++ ) { a.push( $.inArray( set[i], order ) ); } set = a; } this._fnOrderColumns( fnInvertKeyValues( set ) ); return this; }, /** * Convert from the original column index, to the original * * @param {int|array} idx Index(es) to convert * @param {string} dir Transpose direction - `fromOriginal` / `toCurrent` * or `'toOriginal` / `fromCurrent` * @return {int|array} Converted values */ fnTranspose: function ( idx, dir ) { if ( ! dir ) { dir = 'toCurrent'; } var order = this.fnOrder(); var columns = this.s.dt.aoColumns; if ( dir === 'toCurrent' ) { // Given an original index, want the current return ! Array.isArray( idx ) ? $.inArray( idx, order ) : $.map( idx, function ( index ) { return $.inArray( index, order ); } ); } else { // Given a current index, want the original return ! Array.isArray( idx ) ? columns[idx]._ColReorder_iOrigCol : $.map( idx, function ( index ) { return columns[index]._ColReorder_iOrigCol; } ); } }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods (they are of course public in JS, but recommended as private) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Constructor logic * @method _fnConstruct * @returns void * @private */ "_fnConstruct": function () { var that = this; var iLen = this.s.dt.aoColumns.length; var table = this.s.dt.nTable; var i; /* Columns discounted from reordering - counting left to right */ if ( this.s.init.iFixedColumns ) { this.s.fixed = this.s.init.iFixedColumns; } if ( this.s.init.iFixedColumnsLeft ) { this.s.fixed = this.s.init.iFixedColumnsLeft; } /* Columns discounted from reordering - counting right to left */ this.s.fixedRight = this.s.init.iFixedColumnsRight ? this.s.init.iFixedColumnsRight : 0; /* Drop callback initialisation option */ if ( this.s.init.fnReorderCallback ) { this.s.reorderCallback = this.s.init.fnReorderCallback; } /* Add event handlers for the drag and drop, and also mark the original column order */ for ( i = 0; i < iLen; i++ ) { if ( i > this.s.fixed-1 && i < iLen - this.s.fixedRight ) { this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh ); } /* Mark the original column order for later reference */ this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i; } /* State saving */ this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) { that._fnStateSave.call( that, oData ); }, "ColReorder_State" ); this.s.dt.oApi._fnCallbackReg(this.s.dt, 'aoStateLoadParams', function(oS, oData) { that.s.dt._colReorder.fnOrder(oData.ColReorder, true); }) /* An initial column order has been specified */ var aiOrder = null; if ( this.s.init.aiOrder ) { aiOrder = this.s.init.aiOrder.slice(); } /* State loading, overrides the column order given */ if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' && this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length ) { aiOrder = this.s.dt.oLoadedState.ColReorder; } /* If we have an order to apply - do so */ if ( aiOrder ) { /* We might be called during or after the DataTables initialisation. If before, then we need * to wait until the draw is done, if after, then do what we need to do right away */ if ( !that.s.dt._bInitComplete ) { var bDone = false; $(table).on( 'draw.dt.colReorder', function () { if ( !that.s.dt._bInitComplete && !bDone ) { bDone = true; var resort = fnInvertKeyValues( aiOrder ); that._fnOrderColumns.call( that, resort ); } } ); } else { var resort = fnInvertKeyValues( aiOrder ); that._fnOrderColumns.call( that, resort ); } } else { this._fnSetColumnIndexes(); } // Destroy clean up $(table).on( 'destroy.dt.colReorder', function () { // Restore table to original order from when it was loaded that.fnReset(); $(table).off( 'destroy.dt.colReorder draw.dt.colReorder' ); $.each( that.s.dt.aoColumns, function (i, column) { $(column.nTh).off('.ColReorder'); $(column.nTh).removeAttr('data-column-index'); } ); that.s.dt._colReorder = null; that.s = null; } ); }, /** * Set the column order from an array * @method _fnOrderColumns * @param array a An array of integers which dictate the column order that should be applied * @returns void * @private */ "_fnOrderColumns": function ( a ) { var changed = false; if ( a.length != this.s.dt.aoColumns.length ) { this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "ColReorder - array reorder does not "+ "match known number of columns. Skipping." ); return; } for ( var i=0, iLen=a.length ; i<iLen ; i++ ) { var currIndex = $.inArray( i, a ); if ( i != currIndex ) { /* Reorder our switching array */ fnArraySwitch( a, currIndex, i ); /* Do the column reorder in the table */ this.s.dt.oInstance.fnColReorder( currIndex, i, true, false ); changed = true; } } this._fnSetColumnIndexes(); // Has anything actually changed? If not, then nothing else to do if ( ! changed ) { return; } $.fn.dataTable.Api( this.s.dt ).rows().invalidate('data'); /* When scrolling we need to recalculate the column sizes to allow for the shift */ if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) { this.s.dt.oInstance.fnAdjustColumnSizing( false ); } /* Save the state */ this.s.dt.oInstance.oApi._fnSaveState( this.s.dt ); if ( this.s.reorderCallback !== null ) { this.s.reorderCallback.call( this ); } }, /** * Because we change the indexes of columns in the table, relative to their starting point * we need to reorder the state columns to what they are at the starting point so we can * then rearrange them again on state load! * @method _fnStateSave * @param object oState DataTables state * @returns string JSON encoded cookie string for DataTables * @private */ "_fnStateSave": function ( oState ) { if(this.s === null) { return; } var i, iLen, aCopy, iOrigColumn; var oSettings = this.s.dt; var columns = oSettings.aoColumns; oState.ColReorder = []; /* Sorting */ if ( oState.aaSorting ) { // 1.10.0- for ( i=0 ; i<oState.aaSorting.length ; i++ ) { oState.aaSorting[i][0] = columns[ oState.aaSorting[i][0] ]._ColReorder_iOrigCol; } var aSearchCopy = $.extend( true, [], oState.aoSearchCols ); for ( i=0, iLen=columns.length ; i<iLen ; i++ ) { iOrigColumn = columns[i]._ColReorder_iOrigCol; /* Column filter */ oState.aoSearchCols[ iOrigColumn ] = aSearchCopy[i]; /* Visibility */ oState.abVisCols[ iOrigColumn ] = columns[i].bVisible; /* Column reordering */ oState.ColReorder.push( iOrigColumn ); } } else if ( oState.order ) { // 1.10.1+ for ( i=0 ; i<oState.order.length ; i++ ) { oState.order[i][0] = columns[ oState.order[i][0] ]._ColReorder_iOrigCol; } var stateColumnsCopy = $.extend( true, [], oState.columns ); for ( i=0, iLen=columns.length ; i<iLen ; i++ ) { iOrigColumn = columns[i]._ColReorder_iOrigCol; /* Columns */ oState.columns[ iOrigColumn ] = stateColumnsCopy[i]; /* Column reordering */ oState.ColReorder.push( iOrigColumn ); } } }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Mouse drop and drag */ /** * Add a mouse down listener to a particluar TH element * @method _fnMouseListener * @param int i Column index * @param element nTh TH element clicked on * @returns void * @private */ "_fnMouseListener": function ( i, nTh ) { var that = this; $(nTh) .on( 'mousedown.ColReorder', function (e) { if ( that.s.enable && e.which === 1 ) { that._fnMouseDown.call( that, e, nTh ); } } ) .on( 'touchstart.ColReorder', function (e) { if ( that.s.enable ) { that._fnMouseDown.call( that, e, nTh ); } } ); }, /** * Mouse down on a TH element in the table header * @method _fnMouseDown * @param event e Mouse event * @param element nTh TH element to be dragged * @returns void * @private */ "_fnMouseDown": function ( e, nTh ) { var that = this; /* Store information about the mouse position */ var target = $(e.target).closest('th, td'); var offset = target.offset(); var idx = parseInt( $(nTh).attr('data-column-index'), 10 ); if ( idx === undefined ) { return; } this.s.mouse.startX = this._fnCursorPosition( e, 'pageX' ); this.s.mouse.startY = this._fnCursorPosition( e, 'pageY' ); this.s.mouse.offsetX = this._fnCursorPosition( e, 'pageX' ) - offset.left; this.s.mouse.offsetY = this._fnCursorPosition( e, 'pageY' ) - offset.top; this.s.mouse.target = this.s.dt.aoColumns[ idx ].nTh;//target[0]; this.s.mouse.targetIndex = idx; this.s.mouse.fromIndex = idx; this._fnRegions(); /* Add event handlers to the document */ $(document) .on( 'mousemove.ColReorder touchmove.ColReorder', function (e) { that._fnMouseMove.call( that, e ); } ) .on( 'mouseup.ColReorder touchend.ColReorder', function (e) { that._fnMouseUp.call( that, e ); } ); }, /** * Deal with a mouse move event while dragging a node * @method _fnMouseMove * @param event e Mouse event * @returns void * @private */ "_fnMouseMove": function ( e ) { var that = this; if ( this.dom.drag === null ) { /* Only create the drag element if the mouse has moved a specific distance from the start * point - this allows the user to make small mouse movements when sorting and not have a * possibly confusing drag element showing up */ if ( Math.pow( Math.pow(this._fnCursorPosition( e, 'pageX') - this.s.mouse.startX, 2) + Math.pow(this._fnCursorPosition( e, 'pageY') - this.s.mouse.startY, 2), 0.5 ) < 5 ) { return; } this._fnCreateDragNode(); } /* Position the element - we respect where in the element the click occured */ this.dom.drag.css( { left: this._fnCursorPosition( e, 'pageX' ) - this.s.mouse.offsetX, top: this._fnCursorPosition( e, 'pageY' ) - this.s.mouse.offsetY } ); /* Based on the current mouse position, calculate where the insert should go */ var target; var lastToIndex = this.s.mouse.toIndex; var cursorXPosiotion = this._fnCursorPosition(e, 'pageX'); var targetsPrev = function (i) { while (i >= 0) { i--; if (i <= 0) { return null; } if (that.s.aoTargets[i+1].x !== that.s.aoTargets[i].x) { return that.s.aoTargets[i]; } } }; var firstNotHidden = function () { for (var i=0 ; i<that.s.aoTargets.length-1 ; i++) { if (that.s.aoTargets[i].x !== that.s.aoTargets[i+1].x) { return that.s.aoTargets[i]; } } }; var lastNotHidden = function () { for (var i=that.s.aoTargets.length-1 ; i>0 ; i--) { if (that.s.aoTargets[i].x !== that.s.aoTargets[i-1].x) { return that.s.aoTargets[i]; } } }; for (var i = 1; i < this.s.aoTargets.length; i++) { var prevTarget = targetsPrev(i); if (! prevTarget) { prevTarget = firstNotHidden(); } var prevTargetMiddle = prevTarget.x + (this.s.aoTargets[i].x - prevTarget.x) / 2; if (this._fnIsLtr()) { if (cursorXPosiotion < prevTargetMiddle ) { target = prevTarget; break; } } else { if (cursorXPosiotion > prevTargetMiddle) { target = prevTarget; break; } } } if (target) { this.dom.pointer.css('left', target.x); this.s.mouse.toIndex = target.to; } else { // The insert element wasn't positioned in the array (less than // operator), so we put it at the end this.dom.pointer.css( 'left', lastNotHidden().x ); this.s.mouse.toIndex = lastNotHidden().to; } // Perform reordering if realtime updating is on and the column has moved if ( this.s.init.bRealtime && lastToIndex !== this.s.mouse.toIndex ) { this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex ); this.s.mouse.fromIndex = this.s.mouse.toIndex; // Not great for performance, but required to keep everything in alignment if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) { this.s.dt.oInstance.fnAdjustColumnSizing( false ); } this._fnRegions(); } }, /** * Finish off the mouse drag and insert the column where needed * @method _fnMouseUp * @param event e Mouse event * @returns void * @private */ "_fnMouseUp": function ( e ) { var that = this; $(document).off( '.ColReorder' ); if ( this.dom.drag !== null ) { /* Remove the guide elements */ this.dom.drag.remove(); this.dom.pointer.remove(); this.dom.drag = null; this.dom.pointer = null; /* Actually do the reorder */ this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex, true ); this._fnSetColumnIndexes(); /* When scrolling we need to recalculate the column sizes to allow for the shift */ if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" ) { this.s.dt.oInstance.fnAdjustColumnSizing( false ); } /* Save the state */ this.s.dt.oInstance.oApi._fnSaveState( this.s.dt ); if ( this.s.reorderCallback !== null ) { this.s.reorderCallback.call( this ); } } }, /** * Calculate a cached array with the points of the column inserts, and the * 'to' points * @method _fnRegions * @returns void * @private */ "_fnRegions": function () { var aoColumns = this.s.dt.aoColumns; var isLTR = this._fnIsLtr(); this.s.aoTargets.splice(0, this.s.aoTargets.length); var lastBound = $(this.s.dt.nTable).offset().left; var aoColumnBounds = []; $.each(aoColumns, function (i, column) { if (column.bVisible && column.nTh.style.display !== 'none') { var nth = $(column.nTh); var bound = nth.offset().left; if (isLTR) { bound += nth.outerWidth(); } aoColumnBounds.push({ index: i, bound: bound }); lastBound = bound; } else { aoColumnBounds.push({ index: i, bound: lastBound }); } }); var firstColumn = aoColumnBounds[0]; var firstColumnWidth = $(aoColumns[firstColumn.index].nTh).outerWidth(); this.s.aoTargets.push({ to: 0, x: firstColumn.bound - firstColumnWidth }); for (var i = 0; i < aoColumnBounds.length; i++) { var columnBound = aoColumnBounds[i]; var iToPoint = columnBound.index; /* For the column / header in question, we want it's position to remain the same if the * position is just to it's immediate left or right, so we only increment the counter for * other columns */ if (columnBound.index < this.s.mouse.fromIndex) { iToPoint++; } this.s.aoTargets.push({ to: iToPoint, x: columnBound.bound }); } /* Disallow columns for being reordered by drag and drop, counting right to left */ if ( this.s.fixedRight !== 0 ) { this.s.aoTargets.splice( this.s.aoTargets.length - this.s.fixedRight ); } /* Disallow columns for being reordered by drag and drop, counting left to right */ if ( this.s.fixed !== 0 ) { this.s.aoTargets.splice( 0, this.s.fixed ); } }, /** * Copy the TH element that is being drags so the user has the idea that they are actually * moving it around the page. * @method _fnCreateDragNode * @returns void * @private */ "_fnCreateDragNode": function () { var scrolling = this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== ""; var origCell = this.s.dt.aoColumns[ this.s.mouse.targetIndex ].nTh; var origTr = origCell.parentNode; var origThead = origTr.parentNode; var origTable = origThead.parentNode; var cloneCell = $(origCell).clone(); // This is a slightly odd combination of jQuery and DOM, but it is the // fastest and least resource intensive way I could think of cloning // the table with just a single header cell in it. this.dom.drag = $(origTable.cloneNode(false)) .addClass( 'DTCR_clonedTable' ) .append( $(origThead.cloneNode(false)).append( $(origTr.cloneNode(false)).append( cloneCell[0] ) ) ) .css( { position: 'absolute', top: 0, left: 0, width: $(origCell).outerWidth(), height: $(origCell).outerHeight() } ) .appendTo( 'body' ); this.dom.pointer = $('<div></div>') .addClass( 'DTCR_pointer' ) .css( { position: 'absolute', top: scrolling ? $($(this.s.dt.nScrollBody).parent()).offset().top : $(this.s.dt.nTable).offset().top, height : scrolling ? $($(this.s.dt.nScrollBody).parent()).height() : $(this.s.dt.nTable).height() } ) .appendTo( 'body' ); }, /** * Add a data attribute to the column headers, so we know the index of * the row to be reordered. This allows fast detection of the index, and * for this plug-in to work with FixedHeader which clones the nodes. * @private */ "_fnSetColumnIndexes": function () { $.each( this.s.dt.aoColumns, function (i, column) { $(column.nTh).attr('data-column-index', i); } ); }, /** * Get cursor position regardless of mouse or touch input * @param {Event} e jQuery Event * @param {string} prop Property to get * @return {number} Value */ _fnCursorPosition: function ( e, prop ) { if ( e.type.indexOf('touch') !== -1 ) { return e.originalEvent.touches[0][ prop ]; } return e[ prop ]; }, _fnIsLtr: function () { return $(this.s.dt.nTable).css('direction') !== "rtl"; } } ); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Static parameters * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * ColReorder default settings for initialisation * @namespace * @static */ ColReorder.defaults = { /** * Predefined ordering for the columns that will be applied automatically * on initialisation. If not specified then the order that the columns are * found to be in the HTML is the order used. * @type array * @default null * @static */ aiOrder: null, /** * ColReorder enable on initialisation * @type boolean * @default true * @static */ bEnable: true, /** * Redraw the table's column ordering as the end user draws the column * (`true`) or wait until the mouse is released (`false` - default). Note * that this will perform a redraw on each reordering, which involves an * Ajax request each time if you are using server-side processing in * DataTables. * @type boolean * @default false * @static */ bRealtime: true, /** * Indicate how many columns should be fixed in position (counting from the * left). This will typically be 1 if used, but can be as high as you like. * @type int * @default 0 * @static */ iFixedColumnsLeft: 0, /** * As `iFixedColumnsRight` but counting from the right. * @type int * @default 0 * @static */ iFixedColumnsRight: 0, /** * Callback function that is fired when columns are reordered. The `column- * reorder` event is preferred over this callback * @type function():void * @default null * @static */ fnReorderCallback: null }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constants * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * ColReorder version * @constant version * @type String * @default As code */ ColReorder.version = "1.7.0"; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * DataTables interfaces * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // Expose $.fn.dataTable.ColReorder = ColReorder; $.fn.DataTable.ColReorder = ColReorder; // Register a new feature with DataTables if ( typeof $.fn.dataTable == "function" && typeof $.fn.dataTableExt.fnVersionCheck == "function" && $.fn.dataTableExt.fnVersionCheck('1.10.8') ) { $.fn.dataTableExt.aoFeatures.push( { "fnInit": function( settings ) { var table = settings.oInstance; if ( ! settings._colReorder ) { var dtInit = settings.oInit; var opts = dtInit.colReorder || dtInit.oColReorder || {}; new ColReorder( settings, opts ); } else { table.oApi._fnLog( settings, 1, "ColReorder attempted to initialise twice. Ignoring second" ); } return null; /* No node for DataTables to insert */ }, "cFeature": "R", "sFeature": "ColReorder" } ); } else { alert( "Warning: ColReorder requires DataTables 1.10.8 or greater - www.datatables.net/download"); } // Attach a listener to the document which listens for DataTables initialisation // events so we can automatically initialise $(document).on( 'preInit.dt.colReorder', function (e, settings) { if ( e.namespace !== 'dt' ) { return; } var init = settings.oInit.colReorder; var defaults = DataTable.defaults.colReorder; if ( init || defaults ) { var opts = $.extend( {}, init, defaults ); if ( init !== false ) { new ColReorder( settings, opts ); } } } ); // API augmentation $.fn.dataTable.Api.register( 'colReorder.reset()', function () { return this.iterator( 'table', function ( ctx ) { ctx._colReorder.fnReset(); } ); } ); $.fn.dataTable.Api.register( 'colReorder.order()', function ( set, original ) { if ( set ) { return this.iterator( 'table', function ( ctx ) { ctx._colReorder.fnOrder( set, original ); } ); } return this.context.length ? this.context[0]._colReorder.fnOrder() : null; } ); $.fn.dataTable.Api.register( 'colReorder.transpose()', function ( idx, dir ) { return this.context.length && this.context[0]._colReorder ? this.context[0]._colReorder.fnTranspose( idx, dir ) : idx; } ); $.fn.dataTable.Api.register( 'colReorder.move()', function( from, to, drop, invalidateRows ) { if (this.context.length) { this.context[0]._colReorder.s.dt.oInstance.fnColReorder( from, to, drop, invalidateRows ); this.context[0]._colReorder._fnSetColumnIndexes(); } return this; } ); $.fn.dataTable.Api.register( 'colReorder.enable()', function( flag ) { return this.iterator( 'table', function ( ctx ) { if ( ctx._colReorder ) { ctx._colReorder.fnEnable( flag ); } } ); } ); $.fn.dataTable.Api.register( 'colReorder.disable()', function() { return this.iterator( 'table', function ( ctx ) { if ( ctx._colReorder ) { ctx._colReorder.fnDisable(); } } ); } ); return DataTable; })); /*! FixedColumns 4.3.0 * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; (function () { 'use strict'; var $$1; var dataTable; function setJQuery(jq) { $$1 = jq; dataTable = $$1.fn.dataTable; } var FixedColumns = /** @class */ (function () { function FixedColumns(settings, opts) { var _this = this; // Check that the required version of DataTables is included if (!dataTable || !dataTable.versionCheck || !dataTable.versionCheck('1.10.0')) { throw new Error('FixedColumns requires DataTables 1.10 or newer'); } var table = new dataTable.Api(settings); this.classes = $$1.extend(true, {}, FixedColumns.classes); // Get options from user this.c = $$1.extend(true, {}, FixedColumns.defaults, opts); // Backwards compatibility for deprecated leftColumns if ((!opts || opts.left === undefined) && this.c.leftColumns !== undefined) { this.c.left = this.c.leftColumns; } // Backwards compatibility for deprecated rightColumns if ((!opts || opts.right === undefined) && this.c.rightColumns !== undefined) { this.c.right = this.c.rightColumns; } this.s = { barWidth: 0, dt: table, rtl: $$1('body').css('direction') === 'rtl' }; // Common CSS for all blockers var blockerCSS = { 'bottom': '0px', 'display': 'block', 'position': 'absolute', 'width': this.s.barWidth + 1 + 'px' }; this.dom = { leftBottomBlocker: $$1('<div>') .css(blockerCSS) .css('left', 0) .addClass(this.classes.leftBottomBlocker), leftTopBlocker: $$1('<div>') .css(blockerCSS) .css({ left: 0, top: 0 }) .addClass(this.classes.leftTopBlocker), rightBottomBlocker: $$1('<div>') .css(blockerCSS) .css('right', 0) .addClass(this.classes.rightBottomBlocker), rightTopBlocker: $$1('<div>') .css(blockerCSS) .css({ right: 0, top: 0 }) .addClass(this.classes.rightTopBlocker) }; if (this.s.dt.settings()[0]._bInitComplete) { // Fixed Columns Initialisation this._addStyles(); this._setKeyTableListener(); } else { table.one('init.dt.dtfc', function () { // Fixed Columns Initialisation _this._addStyles(); _this._setKeyTableListener(); }); } table.on('column-sizing.dt.dtfc', function () { return _this._addStyles(); }); // Make class available through dt object table.settings()[0]._fixedColumns = this; table.on('destroy', function () { return _this._destroy(); }); return this; } FixedColumns.prototype.left = function (newVal) { // If the value is to change if (newVal !== undefined) { if (newVal >= 0 && newVal <= this.s.dt.columns().count()) { // Set the new values and redraw the columns this.c.left = newVal; this._addStyles(); } return this; } return this.c.left; }; FixedColumns.prototype.right = function (newVal) { // If the value is to change if (newVal !== undefined) { if (newVal >= 0 && newVal <= this.s.dt.columns().count()) { // Set the new values and redraw the columns this.c.right = newVal; this._addStyles(); } return this; } return this.c.right; }; /** * Iterates over the columns, fixing the appropriate ones to the left and right */ FixedColumns.prototype._addStyles = function () { // Set the bar width if vertical scrolling is enabled if (this.s.dt.settings()[0].oScroll.sY) { var scroll_1 = $$1(this.s.dt.table().node()).closest('div.dataTables_scrollBody')[0]; var barWidth = this.s.dt.settings()[0].oBrowser.barWidth; if (scroll_1.offsetWidth - scroll_1.clientWidth >= barWidth) { this.s.barWidth = barWidth; } else { this.s.barWidth = 0; } this.dom.rightTopBlocker.css('width', this.s.barWidth + 1); this.dom.leftTopBlocker.css('width', this.s.barWidth + 1); this.dom.rightBottomBlocker.css('width', this.s.barWidth + 1); this.dom.leftBottomBlocker.css('width', this.s.barWidth + 1); } var parentDiv = null; // Get the header and it's height var header = this.s.dt.column(0).header(); var headerHeight = null; if (header !== null) { header = $$1(header); headerHeight = header.outerHeight() + 1; parentDiv = $$1(header.closest('div.dataTables_scroll')).css('position', 'relative'); } // Get the footer and it's height var footer = this.s.dt.column(0).footer(); var footerHeight = null; if (footer !== null) { footer = $$1(footer); footerHeight = footer.outerHeight(); // Only attempt to retrieve the parentDiv if it has not been retrieved already if (parentDiv === null) { parentDiv = $$1(footer.closest('div.dataTables_scroll')).css('position', 'relative'); } } // Get the number of columns in the table - this is used often so better to only make 1 api call var numCols = this.s.dt.columns().data().toArray().length; // Tracker for the number of pixels should be left to the left of the table var distLeft = 0; // Sometimes the headers have slightly different widths so need to track them individually var headLeft = 0; // Get all of the row elements in the table var rows = $$1(this.s.dt.table().node()).children('tbody').children('tr'); var invisibles = 0; // When working from right to left we need to know how many are invisible before a point, // without including those that are invisible after var prevInvisible = new Map(); // Iterate over all of the columns for (var i = 0; i < numCols; i++) { var column = this.s.dt.column(i); // Set the map for the previous column if (i > 0) { prevInvisible.set(i - 1, invisibles); } if (!column.visible()) { invisibles++; continue; } // Get the columns header and footer element var colHeader = $$1(column.header()); var colFooter = $$1(column.footer()); // If i is less than the value of left then this column should be fixed left if (i - invisibles < this.c.left) { $$1(this.s.dt.table().node()).addClass(this.classes.tableFixedLeft); parentDiv.addClass(this.classes.tableFixedLeft); // Add the width of the previous node - only if we are on atleast the second column if (i - invisibles > 0) { var prevIdx = i; // Simply using the number of hidden columns doesn't work here, // if the first is hidden then this would be thrown off while (prevIdx + 1 < numCols) { var prevCol = this.s.dt.column(prevIdx - 1, { page: 'current' }); if (prevCol.visible()) { distLeft += $$1(prevCol.nodes()[0]).outerWidth(); headLeft += prevCol.header() ? $$1(prevCol.header()).outerWidth() : prevCol.footer() ? $$1(prevCol.header()).outerWidth() : 0; break; } prevIdx--; } } // Iterate over all of the rows, fixing the cell to the left for (var _i = 0, rows_1 = rows; _i < rows_1.length; _i++) { var row = rows_1[_i]; $$1($$1(row).children()[i - invisibles]) .css(this._getCellCSS(false, distLeft, 'left')) .addClass(this.classes.fixedLeft); } // Add the css for the header and the footer colHeader .css(this._getCellCSS(true, headLeft, 'left')) .addClass(this.classes.fixedLeft); colFooter .css(this._getCellCSS(true, headLeft, 'left')) .addClass(this.classes.fixedLeft); } else { // Iteriate through all of the rows, making sure they aren't currently trying to fix left for (var _a = 0, rows_2 = rows; _a < rows_2.length; _a++) { var row = rows_2[_a]; var cell = $$1($$1(row).children()[i - invisibles]); // If the cell is trying to fix to the left, remove the class and the css if (cell.hasClass(this.classes.fixedLeft)) { cell .css(this._clearCellCSS('left')) .removeClass(this.classes.fixedLeft); } } // Make sure the header for this column isn't fixed left if (colHeader.hasClass(this.classes.fixedLeft)) { colHeader .css(this._clearCellCSS('left')) .removeClass(this.classes.fixedLeft); } // Make sure the footer for this column isn't fixed left if (colFooter.hasClass(this.classes.fixedLeft)) { colFooter .css(this._clearCellCSS('left')) .removeClass(this.classes.fixedLeft); } } } var distRight = 0; var headRight = 0; // Counter for the number of invisible columns so far var rightInvisibles = 0; for (var i = numCols - 1; i >= 0; i--) { var column = this.s.dt.column(i); // If a column is invisible just skip it if (!column.visible()) { rightInvisibles++; continue; } // Get the columns header and footer element var colHeader = $$1(column.header()); var colFooter = $$1(column.footer()); // Get the number of visible columns that came before this one var prev = prevInvisible.get(i); if (prev === undefined) { // If it wasn't set then it was the last column so just use the final value prev = invisibles; } if (i + rightInvisibles >= numCols - this.c.right) { $$1(this.s.dt.table().node()).addClass(this.classes.tableFixedRight); parentDiv.addClass(this.classes.tableFixedRight); // Add the widht of the previous node, only if we are on atleast the second column if (i + 1 + rightInvisibles < numCols) { var prevIdx = i; // Simply using the number of hidden columns doesn't work here, // if the first is hidden then this would be thrown off while (prevIdx + 1 < numCols) { var prevCol = this.s.dt.column(prevIdx + 1, { page: 'current' }); if (prevCol.visible()) { distRight += $$1(prevCol.nodes()[0]).outerWidth(); headRight += prevCol.header() ? $$1(prevCol.header()).outerWidth() : prevCol.footer() ? $$1(prevCol.header()).outerWidth() : 0; break; } prevIdx++; } } // Iterate over all of the rows, fixing the cell to the right for (var _b = 0, rows_3 = rows; _b < rows_3.length; _b++) { var row = rows_3[_b]; $$1($$1(row).children()[i - prev]) .css(this._getCellCSS(false, distRight, 'right')) .addClass(this.classes.fixedRight); } // Add the css for the header and the footer colHeader .css(this._getCellCSS(true, headRight, 'right')) .addClass(this.classes.fixedRight); colFooter .css(this._getCellCSS(true, headRight, 'right')) .addClass(this.classes.fixedRight); } else { // Iteriate through all of the rows, making sure they aren't currently trying to fix right for (var _c = 0, rows_4 = rows; _c < rows_4.length; _c++) { var row = rows_4[_c]; var cell = $$1($$1(row).children()[i - prev]); // If the cell is trying to fix to the right, remove the class and the css if (cell.hasClass(this.classes.fixedRight)) { cell .css(this._clearCellCSS('right')) .removeClass(this.classes.fixedRight); } } // Make sure the header for this column isn't fixed right if (colHeader.hasClass(this.classes.fixedRight)) { colHeader .css(this._clearCellCSS('right')) .removeClass(this.classes.fixedRight); } // Make sure the footer for this column isn't fixed right if (colFooter.hasClass(this.classes.fixedRight)) { colFooter .css(this._clearCellCSS('right')) .removeClass(this.classes.fixedRight); } } } // If there is a header with the index class and reading rtl then add right top blocker if (header) { if (!this.s.rtl) { this.dom.rightTopBlocker.outerHeight(headerHeight); parentDiv.append(this.dom.rightTopBlocker); } else { this.dom.leftTopBlocker.outerHeight(headerHeight); parentDiv.append(this.dom.leftTopBlocker); } } // If there is a footer with the index class and reading rtl then add right bottom blocker if (footer) { if (!this.s.rtl) { this.dom.rightBottomBlocker.outerHeight(footerHeight); parentDiv.append(this.dom.rightBottomBlocker); } else { this.dom.leftBottomBlocker.outerHeight(footerHeight); parentDiv.append(this.dom.leftBottomBlocker); } } }; /** * Clean up */ FixedColumns.prototype._destroy = function () { this.s.dt.off('.dtfc'); this.dom.leftBottomBlocker.remove(); this.dom.leftTopBlocker.remove(); this.dom.rightBottomBlocker.remove(); this.dom.rightTopBlocker.remove(); }; /** * Gets the correct CSS for the cell, header or footer based on options provided * * @param header Whether this cell is a header or a footer * @param dist The distance that the cell should be moved away from the edge * @param lr Indicator of fixing to the left or the right * @returns An object containing the correct css */ FixedColumns.prototype._getCellCSS = function (header, dist, lr) { if (lr === 'left') { return this.s.rtl ? { position: 'sticky', right: dist + 'px' } : { left: dist + 'px', position: 'sticky' }; } else { return this.s.rtl ? { left: dist + (header ? this.s.barWidth : 0) + 'px', position: 'sticky' } : { position: 'sticky', right: dist + (header ? this.s.barWidth : 0) + 'px' }; } }; /** * Gets the css that is required to clear the fixing to a side * * @param lr Indicator of fixing to the left or the right * @returns An object containing the correct css */ FixedColumns.prototype._clearCellCSS = function (lr) { if (lr === 'left') { return !this.s.rtl ? { left: '', position: '' } : { position: '', right: '' }; } else { return !this.s.rtl ? { position: '', right: '' } : { left: '', position: '' }; } }; FixedColumns.prototype._setKeyTableListener = function () { var _this = this; this.s.dt.on('key-focus.dt.dtfc', function (e, dt, cell) { var cellPos = $$1(cell.node()).offset(); var scroll = $$1($$1(_this.s.dt.table().node()).closest('div.dataTables_scrollBody')); // If there are fixed columns to the left if (_this.c.left > 0) { // Get the rightmost left fixed column header, it's position and it's width var rightMost = $$1(_this.s.dt.column(_this.c.left - 1).header()); var rightMostPos = rightMost.offset(); var rightMostWidth = rightMost.outerWidth(); // If the current highlighted cell is left of the rightmost cell on the screen if (cellPos.left < rightMostPos.left + rightMostWidth) { // Scroll it into view var currScroll = scroll.scrollLeft(); scroll.scrollLeft(currScroll - (rightMostPos.left + rightMostWidth - cellPos.left)); } } // If there are fixed columns to the right if (_this.c.right > 0) { // Get the number of columns and the width of the cell as doing right side calc var numCols = _this.s.dt.columns().data().toArray().length; var cellWidth = $$1(cell.node()).outerWidth(); // Get the leftmost right fixed column header and it's position var leftMost = $$1(_this.s.dt.column(numCols - _this.c.right).header()); var leftMostPos = leftMost.offset(); // If the current highlighted cell is right of the leftmost cell on the screen if (cellPos.left + cellWidth > leftMostPos.left) { // Scroll it into view var currScroll = scroll.scrollLeft(); scroll.scrollLeft(currScroll - (leftMostPos.left - (cellPos.left + cellWidth))); } } }); // Whenever a draw occurs there is potential for the data to have changed and therefore also the column widths // Therefore it is necessary to recalculate the values for the fixed columns this.s.dt.on('draw.dt.dtfc', function () { _this._addStyles(); }); this.s.dt.on('column-reorder.dt.dtfc', function () { _this._addStyles(); }); this.s.dt.on('column-visibility.dt.dtfc', function (e, settings, column, state, recalc) { if (recalc && !settings.bDestroying) { setTimeout(function () { _this._addStyles(); }, 50); } }); }; FixedColumns.version = '4.3.0'; FixedColumns.classes = { fixedLeft: 'dtfc-fixed-left', fixedRight: 'dtfc-fixed-right', leftBottomBlocker: 'dtfc-left-bottom-blocker', leftTopBlocker: 'dtfc-left-top-blocker', rightBottomBlocker: 'dtfc-right-bottom-blocker', rightTopBlocker: 'dtfc-right-top-blocker', tableFixedLeft: 'dtfc-has-left', tableFixedRight: 'dtfc-has-right' }; FixedColumns.defaults = { i18n: { button: 'FixedColumns' }, left: 1, right: 0 }; return FixedColumns; }()); /*! FixedColumns 4.3.0 * © SpryMedia Ltd - datatables.net/license */ setJQuery($); $.fn.dataTable.FixedColumns = FixedColumns; $.fn.DataTable.FixedColumns = FixedColumns; var apiRegister = DataTable.Api.register; apiRegister('fixedColumns()', function () { return this; }); apiRegister('fixedColumns().left()', function (newVal) { var ctx = this.context[0]; if (newVal !== undefined) { ctx._fixedColumns.left(newVal); return this; } else { return ctx._fixedColumns.left(); } }); apiRegister('fixedColumns().right()', function (newVal) { var ctx = this.context[0]; if (newVal !== undefined) { ctx._fixedColumns.right(newVal); return this; } else { return ctx._fixedColumns.right(); } }); DataTable.ext.buttons.fixedColumns = { action: function (e, dt, node, config) { if ($(node).attr('active')) { $(node).removeAttr('active').removeClass('active'); dt.fixedColumns().left(0); dt.fixedColumns().right(0); } else { $(node).attr('active', 'true').addClass('active'); dt.fixedColumns().left(config.config.left); dt.fixedColumns().right(config.config.right); } }, config: { left: 1, right: 0 }, init: function (dt, node, config) { if (dt.settings()[0]._fixedColumns === undefined) { _init(dt.settings(), config); } $(node).attr('active', 'true').addClass('active'); dt.button(node).text(config.text || dt.i18n('buttons.fixedColumns', dt.settings()[0]._fixedColumns.c.i18n.button)); }, text: null }; function _init(settings, options) { if (options === void 0) { options = null; } var api = new DataTable.Api(settings); var opts = options ? options : api.init().fixedColumns || DataTable.defaults.fixedColumns; var fixedColumns = new FixedColumns(api, opts); return fixedColumns; } // Attach a listener to the document which listens for DataTables initialisation // events so we can automatically initialise $(document).on('plugin-init.dt', function (e, settings) { if (e.namespace !== 'dt') { return; } if (settings.oInit.fixedColumns || DataTable.defaults.fixedColumns) { if (!settings._fixedColumns) { _init(settings, null); } } }); })(); return DataTable; })); /*! FixedHeader 3.4.0 * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; /** * @summary FixedHeader * @description Fix a table's header or footer, so it is always visible while * scrolling * @version 3.4.0 * @author SpryMedia Ltd (www.sprymedia.co.uk) * @contact www.sprymedia.co.uk * @copyright SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * 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 */ var _instCounter = 0; var FixedHeader = function (dt, config) { // Sanity check - you just know it will happen if (!(this instanceof FixedHeader)) { throw "FixedHeader must be initialised with the 'new' keyword."; } // Allow a boolean true for defaults if (config === true) { config = {}; } dt = new DataTable.Api(dt); this.c = $.extend(true, {}, FixedHeader.defaults, config); this.s = { dt: dt, position: { theadTop: 0, tbodyTop: 0, tfootTop: 0, tfootBottom: 0, width: 0, left: 0, tfootHeight: 0, theadHeight: 0, windowHeight: $(window).height(), visible: true }, headerMode: null, footerMode: null, autoWidth: dt.settings()[0].oFeatures.bAutoWidth, namespace: '.dtfc' + _instCounter++, scrollLeft: { header: -1, footer: -1 }, enable: true, autoDisable: false }; this.dom = { floatingHeader: null, thead: $(dt.table().header()), tbody: $(dt.table().body()), tfoot: $(dt.table().footer()), header: { host: null, floating: null, floatingParent: $('<div class="dtfh-floatingparent">'), placeholder: null }, footer: { host: null, floating: null, floatingParent: $('<div class="dtfh-floatingparent">'), placeholder: null } }; this.dom.header.host = this.dom.thead.parent(); this.dom.footer.host = this.dom.tfoot.parent(); var dtSettings = dt.settings()[0]; if (dtSettings._fixedHeader) { throw 'FixedHeader already initialised on table ' + dtSettings.nTable.id; } dtSettings._fixedHeader = this; this._constructor(); }; /* * Variable: FixedHeader * Purpose: Prototype for FixedHeader * Scope: global */ $.extend(FixedHeader.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * API methods */ /** * Kill off FH and any events */ destroy: function () { var dom = this.dom; this.s.dt.off('.dtfc'); $(window).off(this.s.namespace); // Remove clones of FC blockers if (dom.header.rightBlocker) { dom.header.rightBlocker.remove(); } if (dom.header.leftBlocker) { dom.header.leftBlocker.remove(); } if (dom.footer.rightBlocker) { dom.footer.rightBlocker.remove(); } if (dom.footer.leftBlocker) { dom.footer.leftBlocker.remove(); } if (this.c.header) { this._modeChange('in-place', 'header', true); } if (this.c.footer && dom.tfoot.length) { this._modeChange('in-place', 'footer', true); } }, /** * Enable / disable the fixed elements * * @param {boolean} enable `true` to enable, `false` to disable */ enable: function (enable, update, type) { this.s.enable = enable; this.s.enableType = type; if (update || update === undefined) { this._positions(); this._scroll(true); } }, /** * Get enabled status */ enabled: function () { return this.s.enable; }, /** * Set header offset * * @param {int} new value for headerOffset */ headerOffset: function (offset) { if (offset !== undefined) { this.c.headerOffset = offset; this.update(); } return this.c.headerOffset; }, /** * Set footer offset * * @param {int} new value for footerOffset */ footerOffset: function (offset) { if (offset !== undefined) { this.c.footerOffset = offset; this.update(); } return this.c.footerOffset; }, /** * Recalculate the position of the fixed elements and force them into place */ update: function (force) { var table = this.s.dt.table().node(); // Update should only do something if enabled by the dev. if (!this.s.enable && !this.s.autoDisable) { return; } if ($(table).is(':visible')) { this.s.autoDisable = false; this.enable(true, false); } else { this.s.autoDisable = true; this.enable(false, false); } // Don't update if header is not in the document atm (due to // async events) if ($(table).children('thead').length === 0) { return; } this._positions(); this._scroll(force !== undefined ? force : true); }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constructor */ /** * FixedHeader constructor - adding the required event listeners and * simple initialisation * * @private */ _constructor: function () { var that = this; var dt = this.s.dt; $(window) .on('scroll' + this.s.namespace, function () { that._scroll(); }) .on( 'resize' + this.s.namespace, DataTable.util.throttle(function () { that.s.position.windowHeight = $(window).height(); that.update(); }, 50) ); var autoHeader = $('.fh-fixedHeader'); if (!this.c.headerOffset && autoHeader.length) { this.c.headerOffset = autoHeader.outerHeight(); } var autoFooter = $('.fh-fixedFooter'); if (!this.c.footerOffset && autoFooter.length) { this.c.footerOffset = autoFooter.outerHeight(); } dt.on( 'column-reorder.dt.dtfc column-visibility.dt.dtfc column-sizing.dt.dtfc responsive-display.dt.dtfc', function (e, ctx) { that.update(); } ).on('draw.dt.dtfc', function (e, ctx) { // For updates from our own table, don't reclone, but for all others, do that.update(ctx === dt.settings()[0] ? false : true); }); dt.on('destroy.dtfc', function () { that.destroy(); }); this._positions(); this._scroll(); }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods */ /** * Clone a fixed item to act as a place holder for the original element * which is moved into a clone of the table element, and moved around the * document to give the fixed effect. * * @param {string} item 'header' or 'footer' * @param {boolean} force Force the clone to happen, or allow automatic * decision (reuse existing if available) * @private */ _clone: function (item, force) { var that = this; var dt = this.s.dt; var itemDom = this.dom[item]; var itemElement = item === 'header' ? this.dom.thead : this.dom.tfoot; // If footer and scrolling is enabled then we don't clone // Instead the table's height is decreased accordingly - see `_scroll()` if (item === 'footer' && this._scrollEnabled()) { return; } if (!force && itemDom.floating) { // existing floating element - reuse it itemDom.floating.removeClass('fixedHeader-floating fixedHeader-locked'); } else { if (itemDom.floating) { if (itemDom.placeholder !== null) { itemDom.placeholder.remove(); } this._unsize(item); itemDom.floating.children().detach(); itemDom.floating.remove(); } var tableNode = $(dt.table().node()); var scrollBody = $(tableNode.parent()); var scrollEnabled = this._scrollEnabled(); itemDom.floating = $(dt.table().node().cloneNode(false)) .attr('aria-hidden', 'true') .css({ 'table-layout': 'fixed', top: 0, left: 0 }) .removeAttr('id') .append(itemElement); itemDom.floatingParent .css({ width: scrollBody.width(), overflow: 'hidden', height: 'fit-content', position: 'fixed', left: scrollEnabled ? tableNode.offset().left + scrollBody.scrollLeft() : 0 }) .css( item === 'header' ? { top: this.c.headerOffset, bottom: '' } : { top: '', bottom: this.c.footerOffset } ) .addClass(item === 'footer' ? 'dtfh-floatingparentfoot' : 'dtfh-floatingparenthead') .append(itemDom.floating) .appendTo('body'); this._stickyPosition(itemDom.floating, '-'); var scrollLeftUpdate = function () { var scrollLeft = scrollBody.scrollLeft(); that.s.scrollLeft = { footer: scrollLeft, header: scrollLeft }; itemDom.floatingParent.scrollLeft(that.s.scrollLeft.header); }; scrollLeftUpdate(); scrollBody.off('scroll.dtfh').on('scroll.dtfh', scrollLeftUpdate); // Insert a fake thead/tfoot into the DataTable to stop it jumping around itemDom.placeholder = itemElement.clone(false); itemDom.placeholder.find('*[id]').removeAttr('id'); itemDom.host.prepend(itemDom.placeholder); // Clone widths this._matchWidths(itemDom.placeholder, itemDom.floating); } }, /** * This method sets the sticky position of the header elements to match fixed columns * @param {JQuery<HTMLElement>} el * @param {string} sign */ _stickyPosition: function (el, sign) { if (this._scrollEnabled()) { var that = this; var rtl = $(that.s.dt.table().node()).css('direction') === 'rtl'; el.find('th').each(function () { // Find out if fixed header has previously set this column if ($(this).css('position') === 'sticky') { var right = $(this).css('right'); var left = $(this).css('left'); if (right !== 'auto' && !rtl) { // New position either adds or dismisses the barWidth var potential = +right.replace(/px/g, '') + (sign === '-' ? -1 : 1) * that.s.dt.settings()[0].oBrowser.barWidth; $(this).css('right', potential > 0 ? potential : 0); } else if (left !== 'auto' && rtl) { var potential = +left.replace(/px/g, '') + (sign === '-' ? -1 : 1) * that.s.dt.settings()[0].oBrowser.barWidth; $(this).css('left', potential > 0 ? potential : 0); } } }); } }, /** * Copy widths from the cells in one element to another. This is required * for the footer as the footer in the main table takes its sizes from the * header columns. That isn't present in the footer so to have it still * align correctly, the sizes need to be copied over. It is also required * for the header when auto width is not enabled * * @param {jQuery} from Copy widths from * @param {jQuery} to Copy widths to * @private */ _matchWidths: function (from, to) { var get = function (name) { return $(name, from) .map(function () { return ( $(this) .css('width') .replace(/[^\d\.]/g, '') * 1 ); }) .toArray(); }; var set = function (name, toWidths) { $(name, to).each(function (i) { $(this).css({ width: toWidths[i], minWidth: toWidths[i] }); }); }; var thWidths = get('th'); var tdWidths = get('td'); set('th', thWidths); set('td', tdWidths); }, /** * Remove assigned widths from the cells in an element. This is required * when inserting the footer back into the main table so the size is defined * by the header columns and also when auto width is disabled in the * DataTable. * * @param {string} item The `header` or `footer` * @private */ _unsize: function (item) { var el = this.dom[item].floating; if (el && (item === 'footer' || (item === 'header' && !this.s.autoWidth))) { $('th, td', el).css({ width: '', minWidth: '' }); } else if (el && item === 'header') { $('th, td', el).css('min-width', ''); } }, /** * Reposition the floating elements to take account of horizontal page * scroll * * @param {string} item The `header` or `footer` * @param {int} scrollLeft Document scrollLeft * @private */ _horizontal: function (item, scrollLeft) { var itemDom = this.dom[item]; var position = this.s.position; var lastScrollLeft = this.s.scrollLeft; if (itemDom.floating && lastScrollLeft[item] !== scrollLeft) { // If scrolling is enabled we need to match the floating header to the body if (this._scrollEnabled()) { var newScrollLeft = $($(this.s.dt.table().node()).parent()).scrollLeft(); itemDom.floating.scrollLeft(newScrollLeft); itemDom.floatingParent.scrollLeft(newScrollLeft); } lastScrollLeft[item] = scrollLeft; } }, /** * Change from one display mode to another. Each fixed item can be in one * of: * * * `in-place` - In the main DataTable * * `in` - Floating over the DataTable * * `below` - (Header only) Fixed to the bottom of the table body * * `above` - (Footer only) Fixed to the top of the table body * * @param {string} mode Mode that the item should be shown in * @param {string} item 'header' or 'footer' * @param {boolean} forceChange Force a redraw of the mode, even if already * in that mode. * @private */ _modeChange: function (mode, item, forceChange) { var dt = this.s.dt; var itemDom = this.dom[item]; var position = this.s.position; // Just determine if scroll is enabled once var scrollEnabled = this._scrollEnabled(); // If footer and scrolling is enabled then we don't clone // Instead the table's height is decreased accordingly - see `_scroll()` if (item === 'footer' && scrollEnabled) { return; } // It isn't trivial to add a !important css attribute... var importantWidth = function (w) { itemDom.floating.attr('style', function (i, s) { return (s || '') + 'width: ' + w + 'px !important;'; }); // If not scrolling also have to update the floatingParent if (!scrollEnabled) { itemDom.floatingParent.attr('style', function (i, s) { return (s || '') + 'width: ' + w + 'px !important;'; }); } }; // Record focus. Browser's will cause input elements to loose focus if // they are inserted else where in the doc var tablePart = this.dom[item === 'footer' ? 'tfoot' : 'thead']; var focus = $.contains(tablePart[0], document.activeElement) ? document.activeElement : null; var scrollBody = $($(this.s.dt.table().node()).parent()); if (mode === 'in-place') { // Insert the header back into the table's real header if (itemDom.placeholder) { itemDom.placeholder.remove(); itemDom.placeholder = null; } this._unsize(item); if (item === 'header') { itemDom.host.prepend(tablePart); } else { itemDom.host.append(tablePart); } if (itemDom.floating) { itemDom.floating.remove(); itemDom.floating = null; this._stickyPosition(itemDom.host, '+'); } if (itemDom.floatingParent) { itemDom.floatingParent.remove(); } $($(itemDom.host.parent()).parent()).scrollLeft(scrollBody.scrollLeft()); } else if (mode === 'in') { // Remove the header from the read header and insert into a fixed // positioned floating table clone this._clone(item, forceChange); // Get useful position values var scrollOffset = scrollBody.offset(); var windowTop = $(document).scrollTop(); var windowHeight = $(window).height(); var windowBottom = windowTop + windowHeight; var bodyTop = scrollEnabled ? scrollOffset.top : position.tbodyTop; var bodyBottom = scrollEnabled ? scrollOffset.top + scrollBody.outerHeight() : position.tfootTop; // Calculate the amount that the footer or header needs to be shuffled var shuffle = item === 'footer' ? // footer and top of body isn't on screen bodyTop > windowBottom ? // Yes - push the footer below position.tfootHeight : // No - bottom set to the gap between the top of the body and the bottom of the window bodyTop + position.tfootHeight - windowBottom : // Otherwise must be a header so get the difference from the bottom of the // desired floating header and the bottom of the table body windowTop + this.c.headerOffset + position.theadHeight - bodyBottom; // Set the top or bottom based off of the offset and the shuffle value var prop = item === 'header' ? 'top' : 'bottom'; var val = this.c[item + 'Offset'] - (shuffle > 0 ? shuffle : 0); itemDom.floating.addClass('fixedHeader-floating'); itemDom.floatingParent .css(prop, val) .css({ left: position.left, height: item === 'header' ? position.theadHeight : position.tfootHeight, 'z-index': 2 }) .append(itemDom.floating); importantWidth(position.width); if (item === 'footer') { itemDom.floating.css('top', ''); } } else if (mode === 'below') { // only used for the header // Fix the position of the floating header at base of the table body this._clone(item, forceChange); itemDom.floating.addClass('fixedHeader-locked'); itemDom.floatingParent.css({ position: 'absolute', top: position.tfootTop - position.theadHeight, left: position.left + 'px' }); importantWidth(position.width); } else if (mode === 'above') { // only used for the footer // Fix the position of the floating footer at top of the table body this._clone(item, forceChange); itemDom.floating.addClass('fixedHeader-locked'); itemDom.floatingParent.css({ position: 'absolute', top: position.tbodyTop, left: position.left + 'px' }); importantWidth(position.width); } // Restore focus if it was lost if (focus && focus !== document.activeElement) { setTimeout(function () { focus.focus(); }, 10); } this.s.scrollLeft.header = -1; this.s.scrollLeft.footer = -1; this.s[item + 'Mode'] = mode; }, /** * Cache the positional information that is required for the mode * calculations that FixedHeader performs. * * @private */ _positions: function () { var dt = this.s.dt; var table = dt.table(); var position = this.s.position; var dom = this.dom; var tableNode = $(table.node()); var scrollEnabled = this._scrollEnabled(); // Need to use the header and footer that are in the main table, // regardless of if they are clones, since they hold the positions we // want to measure from var thead = $(dt.table().header()); var tfoot = $(dt.table().footer()); var tbody = dom.tbody; var scrollBody = tableNode.parent(); position.visible = tableNode.is(':visible'); position.width = tableNode.outerWidth(); position.left = tableNode.offset().left; position.theadTop = thead.offset().top; position.tbodyTop = scrollEnabled ? scrollBody.offset().top : tbody.offset().top; position.tbodyHeight = scrollEnabled ? scrollBody.outerHeight() : tbody.outerHeight(); position.theadHeight = thead.outerHeight(); position.theadBottom = position.theadTop + position.theadHeight; if (tfoot.length) { position.tfootTop = position.tbodyTop + position.tbodyHeight; //tfoot.offset().top; position.tfootBottom = position.tfootTop + tfoot.outerHeight(); position.tfootHeight = tfoot.outerHeight(); } else { position.tfootTop = position.tbodyTop + tbody.outerHeight(); position.tfootBottom = position.tfootTop; position.tfootHeight = position.tfootTop; } }, /** * Mode calculation - determine what mode the fixed items should be placed * into. * * @param {boolean} forceChange Force a redraw of the mode, even if already * in that mode. * @private */ _scroll: function (forceChange) { if (this.s.dt.settings()[0].bDestroying) { return; } // ScrollBody details var scrollEnabled = this._scrollEnabled(); var scrollBody = $(this.s.dt.table().node()).parent(); var scrollOffset = scrollBody.offset(); var scrollHeight = scrollBody.outerHeight(); // Window details var windowLeft = $(document).scrollLeft(); var windowTop = $(document).scrollTop(); var windowHeight = $(window).height(); var windowBottom = windowHeight + windowTop; var position = this.s.position; var headerMode, footerMode; // Body Details var bodyTop = scrollEnabled ? scrollOffset.top : position.tbodyTop; var bodyLeft = scrollEnabled ? scrollOffset.left : position.left; var bodyBottom = scrollEnabled ? scrollOffset.top + scrollHeight : position.tfootTop; var bodyWidth = scrollEnabled ? scrollBody.outerWidth() : position.tbodyWidth; var windowBottom = windowTop + windowHeight; if (this.c.header) { if (!this.s.enable) { headerMode = 'in-place'; } // The header is in it's normal place if the body top is lower than // the scroll of the window plus the headerOffset and the height of the header else if ( !position.visible || windowTop + this.c.headerOffset + position.theadHeight <= bodyTop ) { headerMode = 'in-place'; } // The header should be floated if else if ( // The scrolling plus the header offset plus the height of the header is lower than the top of the body windowTop + this.c.headerOffset + position.theadHeight > bodyTop && // And the scrolling at the top plus the header offset is above the bottom of the body windowTop + this.c.headerOffset + position.theadHeight < bodyBottom ) { headerMode = 'in'; var scrollBody = $($(this.s.dt.table().node()).parent()); // Further to the above, If the scrolling plus the header offset plus the header height is lower // than the bottom of the table a shuffle is required so have to force the calculation if ( windowTop + this.c.headerOffset + position.theadHeight > bodyBottom || this.dom.header.floatingParent === undefined ) { forceChange = true; } else { this.dom.header.floatingParent .css({ top: this.c.headerOffset, position: 'fixed' }) .append(this.dom.header.floating); } } // Anything else and the view is below the table else { headerMode = 'below'; } if (forceChange || headerMode !== this.s.headerMode) { this._modeChange(headerMode, 'header', forceChange); } this._horizontal('header', windowLeft); } var header = { offset: { top: 0, left: 0 }, height: 0 }; var footer = { offset: { top: 0, left: 0 }, height: 0 }; if (this.c.footer && this.dom.tfoot.length) { if (!this.s.enable) { footerMode = 'in-place'; } else if ( !position.visible || position.tfootBottom + this.c.footerOffset <= windowBottom ) { footerMode = 'in-place'; } else if ( bodyBottom + position.tfootHeight + this.c.footerOffset > windowBottom && bodyTop + this.c.footerOffset < windowBottom ) { footerMode = 'in'; forceChange = true; } else { footerMode = 'above'; } if (forceChange || footerMode !== this.s.footerMode) { this._modeChange(footerMode, 'footer', forceChange); } this._horizontal('footer', windowLeft); var getOffsetHeight = function (el) { return { offset: el.offset(), height: el.outerHeight() }; }; header = this.dom.header.floating ? getOffsetHeight(this.dom.header.floating) : getOffsetHeight(this.dom.thead); footer = this.dom.footer.floating ? getOffsetHeight(this.dom.footer.floating) : getOffsetHeight(this.dom.tfoot); // If scrolling is enabled and the footer is off the screen if (scrollEnabled && footer.offset.top > windowTop) { // && footer.offset.top >= windowBottom) { // Calculate the gap between the top of the scrollBody and the top of the window var overlap = windowTop - scrollOffset.top; // The new height is the bottom of the window var newHeight = windowBottom + // If the gap between the top of the scrollbody and the window is more than // the height of the header then the top of the table is still visible so add that gap // Doing this has effectively calculated the height from the top of the table to the bottom of the current page (overlap > -header.height ? overlap : 0) - // Take from that // The top of the header plus (header.offset.top + // The header height if the standard header is present (overlap < -header.height ? header.height : 0) + // And the height of the footer footer.height); // Don't want a negative height if (newHeight < 0) { newHeight = 0; } // At the end of the above calculation the space between the header (top of the page if floating) // and the point just above the footer should be the new value for the height of the table. scrollBody.outerHeight(newHeight); // Need some rounding here as sometimes very small decimal places are encountered // If the actual height is bigger or equal to the height we just applied then the footer is "Floating" if (Math.round(scrollBody.outerHeight()) >= Math.round(newHeight)) { $(this.dom.tfoot.parent()).addClass('fixedHeader-floating'); } // Otherwise max-width has kicked in so it is not floating else { $(this.dom.tfoot.parent()).removeClass('fixedHeader-floating'); } } } if (this.dom.header.floating) { this.dom.header.floatingParent.css('left', bodyLeft - windowLeft); } if (this.dom.footer.floating) { this.dom.footer.floatingParent.css('left', bodyLeft - windowLeft); } // If fixed columns is being used on this table then the blockers need to be copied across // Cloning these is cleaner than creating as our own as it will keep consistency with fixedColumns automatically // ASSUMING that the class remains the same if (this.s.dt.settings()[0]._fixedColumns !== undefined) { var adjustBlocker = function (side, end, el) { if (el === undefined) { var blocker = $('div.dtfc-' + side + '-' + end + '-blocker'); el = blocker.length === 0 ? null : blocker.clone().css('z-index', 1); } if (el !== null) { if (headerMode === 'in' || headerMode === 'below') { el.appendTo('body').css({ top: end === 'top' ? header.offset.top : footer.offset.top, left: side === 'right' ? bodyLeft + bodyWidth - el.width() : bodyLeft }); } else { el.detach(); } } return el; }; // Adjust all blockers this.dom.header.rightBlocker = adjustBlocker( 'right', 'top', this.dom.header.rightBlocker ); this.dom.header.leftBlocker = adjustBlocker('left', 'top', this.dom.header.leftBlocker); this.dom.footer.rightBlocker = adjustBlocker( 'right', 'bottom', this.dom.footer.rightBlocker ); this.dom.footer.leftBlocker = adjustBlocker( 'left', 'bottom', this.dom.footer.leftBlocker ); } }, /** * Function to check if scrolling is enabled on the table or not * @returns Boolean value indicating if scrolling on the table is enabled or not */ _scrollEnabled: function () { var oScroll = this.s.dt.settings()[0].oScroll; if (oScroll.sY !== '' || oScroll.sX !== '') { return true; } return false; } }); /** * Version * @type {String} * @static */ FixedHeader.version = '3.4.0'; /** * Defaults * @type {Object} * @static */ FixedHeader.defaults = { header: true, footer: false, headerOffset: 0, footerOffset: 0 }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * DataTables interfaces */ // Attach for constructor access $.fn.dataTable.FixedHeader = FixedHeader; $.fn.DataTable.FixedHeader = FixedHeader; // DataTables creation - check if the FixedHeader option has been defined on the // table and if so, initialise $(document).on('init.dt.dtfh', function (e, settings, json) { if (e.namespace !== 'dt') { return; } var init = settings.oInit.fixedHeader; var defaults = DataTable.defaults.fixedHeader; if ((init || defaults) && !settings._fixedHeader) { var opts = $.extend({}, defaults, init); if (init !== false) { new FixedHeader(settings, opts); } } }); // DataTables API methods DataTable.Api.register('fixedHeader()', function () {}); DataTable.Api.register('fixedHeader.adjust()', function () { return this.iterator('table', function (ctx) { var fh = ctx._fixedHeader; if (fh) { fh.update(); } }); }); DataTable.Api.register('fixedHeader.enable()', function (flag) { return this.iterator('table', function (ctx) { var fh = ctx._fixedHeader; flag = flag !== undefined ? flag : true; if (fh && flag !== fh.enabled()) { fh.enable(flag); } }); }); DataTable.Api.register('fixedHeader.enabled()', function () { if (this.context.length) { var fh = this.context[0]._fixedHeader; if (fh) { return fh.enabled(); } } return false; }); DataTable.Api.register('fixedHeader.disable()', function () { return this.iterator('table', function (ctx) { var fh = ctx._fixedHeader; if (fh && fh.enabled()) { fh.enable(false); } }); }); $.each(['header', 'footer'], function (i, el) { DataTable.Api.register('fixedHeader.' + el + 'Offset()', function (offset) { var ctx = this.context; if (offset === undefined) { return ctx.length && ctx[0]._fixedHeader ? ctx[0]._fixedHeader[el + 'Offset']() : undefined; } return this.iterator('table', function (ctx) { var fh = ctx._fixedHeader; if (fh) { fh[el + 'Offset'](offset); } }); }); }); return DataTable; })); /*! KeyTable 2.11.0 * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; /** * @summary KeyTable * @description Spreadsheet like keyboard navigation for DataTables * @version 2.11.0 * @file dataTables.keyTable.js * @author SpryMedia Ltd * @contact datatables.net * @copyright Copyright SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * 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 */ var namespaceCounter = 0; var editorNamespaceCounter = 0; var KeyTable = function (dt, opts) { // Sanity check that we are using DataTables 1.10 or newer if (!DataTable.versionCheck || !DataTable.versionCheck('1.10.8')) { throw 'KeyTable requires DataTables 1.10.8 or newer'; } // User and defaults configuration object this.c = $.extend(true, {}, DataTable.defaults.keyTable, KeyTable.defaults, opts); // Internal settings this.s = { /** @type {DataTable.Api} DataTables' API instance */ dt: new DataTable.Api(dt), /** Indicate when the DataTable is redrawing - take no action on key presses */ dtDrawing: false, enable: true, /** @type {bool} Flag for if a draw is triggered by focus */ focusDraw: false, /** @type {bool} Flag to indicate when waiting for a draw to happen. * Will ignore key presses at this point */ waitingForDraw: false, /** @type {object} Information about the last cell that was focused */ lastFocus: null, /** @type {string} Unique namespace per instance */ namespace: '.keyTable-' + namespaceCounter++, /** @type {Node} Input element for tabbing into the table */ tabInput: null }; // DOM items this.dom = {}; // Check if row reorder has already been initialised on this table var settings = this.s.dt.settings()[0]; var exisiting = settings.keytable; if (exisiting) { return exisiting; } settings.keytable = this; this._constructor(); }; $.extend(KeyTable.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * API methods for DataTables API interface */ /** * Blur the table's cell focus */ blur: function () { this._blur(); }, /** * Enable cell focus for the table * * @param {string} state Can be `true`, `false` or `-string navigation-only` */ enable: function (state) { this.s.enable = state; }, /** * Get enable status */ enabled: function () { return this.s.enable; }, /** * Focus on a cell * @param {integer} row Row index * @param {integer} column Column index */ focus: function (row, column) { this._focus(this.s.dt.cell(row, column)); }, /** * Is the cell focused * @param {object} cell Cell index to check * @returns {boolean} true if focused, false otherwise */ focused: function (cell) { var lastFocus = this.s.lastFocus; if (!lastFocus) { return false; } var lastIdx = this.s.lastFocus.cell.index(); return cell.row === lastIdx.row && cell.column === lastIdx.column; }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constructor */ /** * Initialise the KeyTable instance * * @private */ _constructor: function () { this._tabInput(); var that = this; var dt = this.s.dt; var table = $(dt.table().node()); var namespace = this.s.namespace; var editorBlock = false; // Need to be able to calculate the cell positions relative to the table if (table.css('position') === 'static') { table.css('position', 'relative'); } // Click to focus $(dt.table().body()).on('click' + namespace, 'th, td', function (e) { if (that.s.enable === false) { return; } var cell = dt.cell(this); if (!cell.any()) { return; } that._focus(cell, null, false, e); }); // Key events $(document).on('keydown' + namespace, function (e) { if (!editorBlock && !that.s.dtDrawing) { that._key(e); } else { e.preventDefault(); } }); // Click blur if (this.c.blurable) { $(document).on('mousedown' + namespace, function (e) { // Click on the search input will blur focus if ($(e.target).parents('.dataTables_filter, .dt-search').length) { that._blur(); } // If the click was inside the DataTables container, don't blur if ($(e.target).parents().filter(dt.table().container()).length) { return; } // Don't blur in Editor form if ($(e.target).parents('div.DTE').length) { return; } // Or an Editor date input if ( $(e.target).parents('div.editor-datetime').length || $(e.target).parents('div.dt-datetime').length ) { return; } //If the click was inside the fixed columns container, don't blur if ($(e.target).parents().filter('.DTFC_Cloned').length) { return; } that._blur(); }); } if (this.c.editor) { var editor = this.c.editor; // Need to disable KeyTable when the main editor is shown editor.on('open.keyTableMain', function (e, mode, action) { if (mode !== 'inline' && that.s.enable) { that.enable(false); editor.one('close' + namespace, function () { that.enable(true); }); } }); if (this.c.editOnFocus) { dt.on( 'key-focus' + namespace + ' key-refocus' + namespace, function (e, dt, cell, orig) { that._editor(null, orig, true); } ); } // Activate Editor when a key is pressed (will be ignored, if // already active). dt.on('key' + namespace, function (e, dt, key, cell, orig) { that._editor(key, orig, false); }); // Active editing on double click - it will already have focus from // the click event handler above $(dt.table().body()).on('dblclick' + namespace, 'th, td', function (e) { if (that.s.enable === false) { return; } var cell = dt.cell(this); if (!cell.any()) { return; } if (that.s.lastFocus && this !== that.s.lastFocus.cell.node()) { return; } that._editor(null, e, true); }); // While Editor is busy processing, we don't want to process any key events editor .on('preSubmit', function () { editorBlock = true; }) .on('preSubmitCancelled', function () { editorBlock = false; }) .on('submitComplete', function () { editorBlock = false; }); } // Stave saving // if ( dt.settings()[0].oFeatures.bStateSave ) { dt.on('stateSaveParams' + namespace, function (e, s, d) { d.keyTable = that.s.lastFocus ? that.s.lastFocus.cell.index() : null; }); // } dt.on('column-visibility' + namespace, function (e) { that._tabInput(); }); dt.on('column-reorder' + namespace, function (e, s, d) { // Need to update the last focus cell's index var lastFocus = that.s.lastFocus; if (lastFocus && lastFocus.cell) { var curr = lastFocus.relative.column; // Manipulate the API instance to correct the column index lastFocus.cell[0][0].column = d.mapping.indexOf(curr); lastFocus.relative.column = d.mapping.indexOf(curr); } }); // When the table is about to do a draw we need to block key // handling. This is only important for async draws - i.e. // server-side processing. dt.on('preDraw' + namespace + ' scroller-will-draw' + namespace, function (e) { that.s.dtDrawing = true; }); // Redraw - retain focus on the current cell dt.on('draw' + namespace, function (e) { that.s.dtDrawing = false; that._tabInput(); if (that.s.focusDraw) { return; } var lastFocus = that.s.lastFocus; if (lastFocus) { var relative = that.s.lastFocus.relative; var info = dt.page.info(); var row = relative.row; if (info.recordsDisplay === 0) { return; } // If the refocus is outside the current draw zone - // don't attempt to refocus onto it if (row < info.start || row > info.start + info.length) { return; } // Reverse if needed if (row >= info.recordsDisplay) { row = info.recordsDisplay - 1; } that._focus(row, relative.column, true, e); } }); // Clipboard support if (this.c.clipboard) { this._clipboard(); } dt.on('destroy' + namespace, function () { that._blur(true); // Event tidy up dt.off(namespace); $(dt.table().body()) .off('click' + namespace, 'th, td') .off('dblclick' + namespace, 'th, td'); $(document) .off('mousedown' + namespace) .off('keydown' + namespace) .off('copy' + namespace) .off('paste' + namespace); }); // Initial focus comes from state or options var state = dt.state.loaded(); if (state && state.keyTable) { // Wait until init is done dt.one('init', function () { var cell = dt.cell(state.keyTable); // Ensure that the saved cell still exists if (cell.any()) { cell.focus(); } }); } else if (this.c.focus) { dt.cell(this.c.focus).focus(); } }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods */ /** * Blur the control * * @param {boolean} [noEvents=false] Don't trigger updates / events (for destroying) * @private */ _blur: function (noEvents) { if (!this.s.enable || !this.s.lastFocus) { return; } var cell = this.s.lastFocus.cell; $(cell.node()).removeClass(this.c.className); this.s.lastFocus = null; if (!noEvents) { this._updateFixedColumns(cell.index().column); this._emitEvent('key-blur', [this.s.dt, cell]); } }, /** * Clipboard interaction handlers * * @private */ _clipboard: function () { var dt = this.s.dt; var that = this; var namespace = this.s.namespace; // IE8 doesn't support getting selected text if (!window.getSelection) { return; } $(document).on('copy' + namespace, function (ejq) { var e = ejq.originalEvent; var selection = window.getSelection().toString(); var focused = that.s.lastFocus; // Only copy cell text to clipboard if there is no other selection // and there is a focused cell if (!selection && focused) { e.clipboardData.setData( 'text/plain', focused.cell.render(that.c.clipboardOrthogonal) ); e.preventDefault(); } }); $(document).on('paste' + namespace, function (ejq) { var e = ejq.originalEvent; var focused = that.s.lastFocus; var activeEl = document.activeElement; var editor = that.c.editor; var pastedText; if (focused && (!activeEl || activeEl.nodeName.toLowerCase() === 'body')) { e.preventDefault(); if (window.clipboardData && window.clipboardData.getData) { // IE pastedText = window.clipboardData.getData('Text'); } else if (e.clipboardData && e.clipboardData.getData) { // Everything else pastedText = e.clipboardData.getData('text/plain'); } if (editor) { // Got Editor - need to activate inline editing, // set the value and submit var options = that._inlineOptions(focused.cell.index()); editor .inline(options.cell, options.field, options.options) .set(editor.displayed()[0], pastedText) .submit(); } else { // No editor, so just dump the data in focused.cell.data(pastedText); dt.draw(false); } } }); }, /** * Get an array of the column indexes that KeyTable can operate on. This * is a merge of the user supplied columns and the visible columns. * * @private */ _columns: function () { var dt = this.s.dt; var user = dt.columns(this.c.columns).indexes(); var out = []; dt.columns(':visible').every(function (i) { if (user.indexOf(i) !== -1) { out.push(i); } }); return out; }, /** * Perform excel like navigation for Editor by triggering an edit on key * press * * @param {integer} key Key code for the pressed key * @param {object} orig Original event * @private */ _editor: function (key, orig, hardEdit) { // If nothing focused, we can't take any action if (!this.s.lastFocus) { return; } // DataTables draw event if (orig && orig.type === 'draw') { return; } var that = this; var dt = this.s.dt; var editor = this.c.editor; var editCell = this.s.lastFocus.cell; var namespace = this.s.namespace + 'e' + editorNamespaceCounter++; // Do nothing if there is already an inline edit in this cell if ($('div.DTE', editCell.node()).length) { return; } // Don't activate Editor on control key presses if ( key !== null && ((key >= 0x00 && key <= 0x09) || key === 0x0b || key === 0x0c || (key >= 0x0e && key <= 0x1f) || (key >= 0x70 && key <= 0x7b) || (key >= 0x7f && key <= 0x9f)) ) { return; } if (orig) { orig.stopPropagation(); // Return key should do nothing - for textareas it would empty the // contents if (key === 13) { orig.preventDefault(); } } var editInline = function () { var options = that._inlineOptions(editCell.index()); editor .one('open' + namespace, function () { // Remove cancel open editor.off('cancelOpen' + namespace); // Excel style - select all text if (!hardEdit) { $( 'div.DTE_Field_InputControl input, div.DTE_Field_InputControl textarea' ).select(); } // Reduce the keys the Keys listens for dt.keys.enable(hardEdit ? 'tab-only' : 'navigation-only'); // On blur of the navigation submit dt.on('key-blur.editor', function (e, dt, cell) { // When Editor has its own blur enabled - do nothing here if (editor.s.editOpts.onBlur === 'submit') { return; } if (editor.displayed() && cell.node() === editCell.node()) { editor.submit(); } }); // Highlight the cell a different colour on full edit if (hardEdit) { $(dt.table().container()).addClass('dtk-focus-alt'); } // If the dev cancels the submit, we need to return focus editor.on('preSubmitCancelled' + namespace, function () { setTimeout(function () { that._focus(editCell, null, false); }, 50); }); editor.on('submitUnsuccessful' + namespace, function () { that._focus(editCell, null, false); }); // Restore full key navigation on close editor.one('close' + namespace, function () { dt.keys.enable(true); dt.off('key-blur.editor'); editor.off(namespace); $(dt.table().container()).removeClass('dtk-focus-alt'); if (that.s.returnSubmit) { that.s.returnSubmit = false; that._emitEvent('key-return-submit', [dt, editCell]); } }); }) .one('cancelOpen' + namespace, function () { // `preOpen` can cancel the display of the form, so it // might be that the open event handler isn't needed editor.off(namespace); }) .inline(options.cell, options.field, options.options); }; // Editor 1.7 listens for `return` on keyup, so if return is the trigger // key, we need to wait for `keyup` otherwise Editor would just submit // the content triggered by this keypress. if (key === 13) { hardEdit = true; $(document).one('keyup', function () { // immediately removed editInline(); }); } else { editInline(); } }, _inlineOptions: function (cellIdx) { if (this.c.editorOptions) { return this.c.editorOptions(cellIdx); } return { cell: cellIdx, field: undefined, options: undefined }; }, /** * Emit an event on the DataTable for listeners * * @param {string} name Event name * @param {array} args Event arguments * @private */ _emitEvent: function (name, args) { return this.s.dt.iterator('table', function (ctx, i) { return $(ctx.nTable).triggerHandler(name, args); }); }, /** * Focus on a particular cell, shifting the table's paging if required * * @param {DataTables.Api|integer} row Can be given as an API instance that * contains the cell to focus or as an integer. As the latter it is the * visible row index (from the whole data set) - NOT the data index * @param {integer} [column] Not required if a cell is given as the first * parameter. Otherwise this is the column data index for the cell to * focus on * @param {boolean} [shift=true] Should the viewport be moved to show cell * @private */ _focus: function (row, column, shift, originalEvent) { var that = this; var dt = this.s.dt; var pageInfo = dt.page.info(); var lastFocus = this.s.lastFocus; if (!originalEvent) { originalEvent = null; } if (!this.s.enable) { return; } if (typeof row !== 'number') { // Its an API instance - check that there is actually a row if (!row.any()) { return; } // Convert the cell to a row and column var index = row.index(); column = index.column; row = dt.rows({ filter: 'applied', order: 'applied' }).indexes().indexOf(index.row); // Don't focus rows that were filtered out. if (row < 0) { return; } // For server-side processing normalise the row by adding the start // point, since `rows().indexes()` includes only rows that are // available at the client-side if (pageInfo.serverSide) { row += pageInfo.start; } } // Is the row on the current page? If not, we need to redraw to show the // page if ( pageInfo.length !== -1 && (row < pageInfo.start || row >= pageInfo.start + pageInfo.length) ) { this.s.focusDraw = true; this.s.waitingForDraw = true; dt.one('draw', function () { that.s.focusDraw = false; that.s.waitingForDraw = false; that._focus(row, column, undefined, originalEvent); }) .page(Math.floor(row / pageInfo.length)) .draw(false); return; } // In the available columns? if ($.inArray(column, this._columns()) === -1) { return; } // De-normalise the server-side processing row, so we select the row // in its displayed position if (pageInfo.serverSide) { row -= pageInfo.start; } // Get the cell from the current position - ignoring any cells which might // not have been rendered (therefore can't use `:eq()` selector). var cells = dt.cells(null, column, { search: 'applied', order: 'applied' }).flatten(); var cell = dt.cell(cells[row]); // Prefocus check - this event allows a focus action to be disallowed. var preFocus = this._emitEvent('key-prefocus', [this.s.dt, cell, originalEvent || null]); if (preFocus.indexOf(false) !== -1) { return; } if (lastFocus) { // Don't trigger a refocus on the same cell if (lastFocus.node === cell.node()) { this._emitEvent('key-refocus', [this.s.dt, cell, originalEvent || null]); return; } // Otherwise blur the old focus this._blur(); } // Clear focus from other tables this._removeOtherFocus(); var node = $(cell.node()); node.addClass(this.c.className); this._updateFixedColumns(column); // Shift viewpoint and page to make cell visible if (shift === undefined || shift === true) { this._scroll($(window), $(document.body), node, 'offset'); var bodyParent = dt.table().body().parentNode; if (bodyParent !== dt.table().header().parentNode) { var parent = $(bodyParent.parentNode); this._scroll(parent, parent, node, 'position'); } } // Event and finish var info = dt.page.info(); this.s.lastFocus = { cell: cell, node: cell.node(), relative: { row: info.start + dt.rows({ page: 'current' }).indexes().indexOf(cell.index().row), column: cell.index().column } }; this._emitEvent('key-focus', [this.s.dt, cell, originalEvent || null]); dt.state.save(); }, /** * Handle key press * * @param {object} e Event * @private */ _key: function (e) { // If we are waiting for a draw to happen from another key event, then // do nothing for this new key press. if (this.s.waitingForDraw) { e.preventDefault(); return; } // Ignore key presses in an Editor inline create row - it is not navigatable // by KeyTable if ($(e.target).closest('.dte-inlineAdd').length) { return; } var enable = this.s.enable; this.s.returnSubmit = (enable === 'navigation-only' || enable === 'tab-only') && e.keyCode === 13 ? true : false; var navEnable = enable === true || enable === 'navigation-only'; if (!enable) { return; } if ((e.keyCode === 0 || e.ctrlKey || e.metaKey || e.altKey) && !(e.ctrlKey && e.altKey)) { return; } // If not focused, then there is no key action to take var lastFocus = this.s.lastFocus; if (!lastFocus) { return; } // And the last focus still exists! if (!this.s.dt.cell(lastFocus.node).any()) { this.s.lastFocus = null; return; } var that = this; var dt = this.s.dt; var scrolling = this.s.dt.settings()[0].oScroll.sY ? true : false; // If we are not listening for this key, do nothing if (this.c.keys && $.inArray(e.keyCode, this.c.keys) === -1) { return; } switch (e.keyCode) { case 9: // tab // `enable` can be tab-only e.preventDefault(); this._keyAction(function () { that._shift(e, e.shiftKey ? 'left' : 'right', true); }); break; case 27: // esc if (this.c.blurable && enable === true) { this._blur(); } break; case 33: // page up (previous page) case 34: // page down (next page) if (navEnable && !scrolling) { e.preventDefault(); this._keyAction(function () { dt.page(e.keyCode === 33 ? 'previous' : 'next').draw(false); }); } break; case 35: // end (end of current page) case 36: // home (start of current page) if (navEnable) { e.preventDefault(); this._keyAction(function () { var indexes = dt.cells({ page: 'current' }).indexes(); var colIndexes = that._columns(); that._focus( dt.cell(indexes[e.keyCode === 35 ? indexes.length - 1 : colIndexes[0]]), null, true, e ); }); } break; case 37: // left arrow if (navEnable) { this._keyAction(function () { that._shift(e, 'left'); }); } break; case 38: // up arrow if (navEnable) { this._keyAction(function () { that._shift(e, 'up'); }); } break; case 39: // right arrow if (navEnable) { this._keyAction(function () { that._shift(e, 'right'); }); } break; case 40: // down arrow if (navEnable) { this._keyAction(function () { that._shift(e, 'down'); }); } break; case 113: // F2 - Excel like hard edit if (this.c.editor) { this._editor(null, e, true); break; } // else fallthrough default: // Everything else - pass through only when fully enabled if (enable === true) { this._emitEvent('key', [dt, e.keyCode, this.s.lastFocus.cell, e]); } break; } }, /** * Whether we perform a key shift action immediately or not depends * upon if Editor is being used. If it is, then we wait until it * completes its action * @param {*} action Function to trigger when ready */ _keyAction: function (action) { var editor = this.c.editor; if (editor && editor.mode()) { editor.submit(action); } else { action(); } }, /** * Remove focus from all tables other than this one */ _removeOtherFocus: function () { var thisTable = this.s.dt.table().node(); $.fn.dataTable.tables({ api: true }).iterator('table', function (settings) { if (this.table().node() !== thisTable) { this.cell.blur(); } }); }, /** * Scroll a container to make a cell visible in it. This can be used for * both DataTables scrolling and native window scrolling. * * @param {jQuery} container Scrolling container * @param {jQuery} scroller Item being scrolled * @param {jQuery} cell Cell in the scroller * @param {string} posOff `position` or `offset` - which to use for the * calculation. `offset` for the document, otherwise `position` * @private */ _scroll: function (container, scroller, cell, posOff) { var offset = cell[posOff](); var height = cell.outerHeight(); var width = cell.outerWidth(); var scrollTop = scroller.scrollTop(); var scrollLeft = scroller.scrollLeft(); var containerHeight = container.height(); var containerWidth = container.width(); // If Scroller is being used, the table can be `position: absolute` and that // needs to be taken account of in the offset. If no Scroller, this will be 0 if (posOff === 'position') { offset.top += parseInt(cell.closest('table').css('top'), 10); } // Top correction (partially in view) if (offset.top < scrollTop && offset.top + height > scrollTop - 5) { scroller.scrollTop(offset.top); } // Left correction if (offset.left < scrollLeft) { scroller.scrollLeft(offset.left); } // Bottom correction plus in view correction. Note that the magic 5 is to allow // for the edge just passing the bottom of the view if ( offset.top + height > scrollTop + containerHeight && offset.top < scrollTop + containerHeight + 5 && height < containerHeight ) { scroller.scrollTop(offset.top + height - containerHeight); } // Right correction if (offset.left + width > scrollLeft + containerWidth && width < containerWidth) { scroller.scrollLeft(offset.left + width - containerWidth); } }, /** * Calculate a single offset movement in the table - up, down, left and * right and then perform the focus if possible * * @param {object} e Event object * @param {string} direction Movement direction * @param {boolean} keyBlurable `true` if the key press can result in the * table being blurred. This is so arrow keys won't blur the table, but * tab will. * @private */ _shift: function (e, direction, keyBlurable) { var dt = this.s.dt; var pageInfo = dt.page.info(); var rows = pageInfo.recordsDisplay; var columns = this._columns(); var last = this.s.lastFocus; if (!last) { return; } var currentCell = last.cell; if (!currentCell) { return; } var currRow = dt .rows({ filter: 'applied', order: 'applied' }) .indexes() .indexOf(currentCell.index().row); // When server-side processing, `rows().indexes()` only gives the rows // that are available at the client-side, so we need to normalise the // row's current position by the display start point if (pageInfo.serverSide) { currRow += pageInfo.start; } var currCol = dt.columns(columns).indexes().indexOf(currentCell.index().column); var row = currRow, column = columns[currCol]; // row is the display, column is an index // If the direction is rtl then the logic needs to be inverted from this point forwards if ($(dt.table().node()).css('direction') === 'rtl') { if (direction === 'right') { direction = 'left'; } else if (direction === 'left') { direction = 'right'; } } if (direction === 'right') { if (currCol >= columns.length - 1) { row++; column = columns[0]; } else { column = columns[currCol + 1]; } } else if (direction === 'left') { if (currCol === 0) { row--; column = columns[columns.length - 1]; } else { column = columns[currCol - 1]; } } else if (direction === 'up') { row--; } else if (direction === 'down') { row++; } if (row >= 0 && row < rows && $.inArray(column, columns) !== -1) { if (e) { e.preventDefault(); } this._focus(row, column, true, e); } else if (!keyBlurable || !this.c.blurable) { // No new focus, but if the table isn't blurable, then don't loose // focus if (e) { e.preventDefault(); } } else { this._blur(); } }, /** * Create and insert a hidden input element that can receive focus on behalf * of the table * * @private */ _tabInput: function () { var that = this; var dt = this.s.dt; var tabIndex = this.c.tabIndex !== null ? this.c.tabIndex : dt.settings()[0].iTabIndex; if (tabIndex == -1) { return; } // Only create the input element once on first class if (!this.s.tabInput) { var div = $('<div><input type="text" tabindex="' + tabIndex + '"/></div>').css({ position: 'absolute', height: 1, width: 0, overflow: 'hidden' }); div.children().on('focus', function (e) { var cell = dt.cell(':eq(0)', that._columns(), { page: 'current' }); if (cell.any()) { that._focus(cell, null, true, e); } }); this.s.tabInput = div; } // Insert the input element into the first cell in the table's body var cell = this.s.dt .cell(':eq(0)', '0:visible', { page: 'current', order: 'current' }) .node(); if (cell) { $(cell).prepend(this.s.tabInput); } }, /** * Update fixed columns if they are enabled and if the cell we are * focusing is inside a fixed column * @param {integer} column Index of the column being changed * @private */ _updateFixedColumns: function (column) { var dt = this.s.dt; var settings = dt.settings()[0]; if (settings._oFixedColumns) { var leftCols = settings._oFixedColumns.s.iLeftColumns; var rightCols = settings.aoColumns.length - settings._oFixedColumns.s.iRightColumns; if (column < leftCols || column >= rightCols) { dt.fixedColumns().update(); } } } }); /** * KeyTable default settings for initialisation * * @namespace * @name KeyTable.defaults * @static */ KeyTable.defaults = { /** * Can focus be removed from the table * @type {Boolean} */ blurable: true, /** * Class to give to the focused cell * @type {String} */ className: 'focus', /** * Enable or disable clipboard support * @type {Boolean} */ clipboard: true, /** * Orthogonal data that should be copied to clipboard * @type {string} */ clipboardOrthogonal: 'display', /** * Columns that can be focused. This is automatically merged with the * visible columns as only visible columns can gain focus. * @type {String} */ columns: '', // all /** * Editor instance to automatically perform Excel like navigation * @type {Editor} */ editor: null, /** * Trigger editing immediately on focus * @type {boolean} */ editOnFocus: false, /** * Options to pass to Editor's inline method * @type {function} */ editorOptions: null, /** * Select a cell to automatically select on start up. `null` for no * automatic selection * @type {cell-selector} */ focus: null, /** * Array of keys to listen for * @type {null|array} */ keys: null, /** * Tab index for where the table should sit in the document's tab flow * @type {integer|null} */ tabIndex: null }; KeyTable.version = '2.11.0'; $.fn.dataTable.KeyTable = KeyTable; $.fn.DataTable.KeyTable = KeyTable; DataTable.Api.register('cell.blur()', function () { return this.iterator('table', function (ctx) { if (ctx.keytable) { ctx.keytable.blur(); } }); }); DataTable.Api.register('cell().focus()', function () { return this.iterator('cell', function (ctx, row, column) { if (ctx.keytable) { ctx.keytable.focus(row, column); } }); }); DataTable.Api.register('keys.disable()', function () { return this.iterator('table', function (ctx) { if (ctx.keytable) { ctx.keytable.enable(false); } }); }); DataTable.Api.register('keys.enable()', function (opts) { return this.iterator('table', function (ctx) { if (ctx.keytable) { ctx.keytable.enable(opts === undefined ? true : opts); } }); }); DataTable.Api.register('keys.enabled()', function (opts) { var ctx = this.context; if (ctx.length) { return ctx[0].keytable ? ctx[0].keytable.enabled() : false; } return false; }); DataTable.Api.register('keys.move()', function (dir) { return this.iterator('table', function (ctx) { if (ctx.keytable) { ctx.keytable._shift(null, dir, false); } }); }); // Cell selector DataTable.ext.selector.cell.push(function (settings, opts, cells) { var focused = opts.focused; var kt = settings.keytable; var out = []; if (!kt || focused === undefined) { return cells; } for (var i = 0, ien = cells.length; i < ien; i++) { if ( (focused === true && kt.focused(cells[i])) || (focused === false && !kt.focused(cells[i])) ) { out.push(cells[i]); } } return out; }); // Attach a listener to the document which listens for DataTables initialisation // events so we can automatically initialise $(document).on('preInit.dt.dtk', function (e, settings, json) { if (e.namespace !== 'dt') { return; } var init = settings.oInit.keys; var defaults = DataTable.defaults.keys; if (init || defaults) { var opts = $.extend({}, defaults, init); if (init !== false) { new KeyTable(settings, opts); } } }); return DataTable; })); /*! Responsive 2.5.0 * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; /** * @summary Responsive * @description Responsive tables plug-in for DataTables * @version 2.5.0 * @author SpryMedia Ltd * @copyright SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * 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 */ /** * Responsive is a plug-in for the DataTables library that makes use of * DataTables' ability to change the visibility of columns, changing the * visibility of columns so the displayed columns fit into the table container. * The end result is that complex tables will be dynamically adjusted to fit * into the viewport, be it on a desktop, tablet or mobile browser. * * Responsive for DataTables has two modes of operation, which can used * individually or combined: * * * Class name based control - columns assigned class names that match the * breakpoint logic can be shown / hidden as required for each breakpoint. * * Automatic control - columns are automatically hidden when there is no * room left to display them. Columns removed from the right. * * In additional to column visibility control, Responsive also has built into * options to use DataTables' child row display to show / hide the information * from the table that has been hidden. There are also two modes of operation * for this child row display: * * * Inline - when the control element that the user can use to show / hide * child rows is displayed inside the first column of the table. * * Column - where a whole column is dedicated to be the show / hide control. * * Initialisation of Responsive is performed by: * * * Adding the class `responsive` or `dt-responsive` to the table. In this case * Responsive will automatically be initialised with the default configuration * options when the DataTable is created. * * Using the `responsive` option in the DataTables configuration options. This * can also be used to specify the configuration options, or simply set to * `true` to use the defaults. * * @class * @param {object} settings DataTables settings object for the host table * @param {object} [opts] Configuration options * @requires jQuery 1.7+ * @requires DataTables 1.10.3+ * * @example * $('#example').DataTable( { * responsive: true * } ); * } ); */ var Responsive = function (settings, opts) { // Sanity check that we are using DataTables 1.10 or newer if (!DataTable.versionCheck || !DataTable.versionCheck('1.10.10')) { throw 'DataTables Responsive requires DataTables 1.10.10 or newer'; } this.s = { childNodeStore: {}, columns: [], current: [], dt: new DataTable.Api(settings) }; // Check if responsive has already been initialised on this table if (this.s.dt.settings()[0].responsive) { return; } // details is an object, but for simplicity the user can give it as a string // or a boolean if (opts && typeof opts.details === 'string') { opts.details = { type: opts.details }; } else if (opts && opts.details === false) { opts.details = { type: false }; } else if (opts && opts.details === true) { opts.details = { type: 'inline' }; } this.c = $.extend(true, {}, Responsive.defaults, DataTable.defaults.responsive, opts); settings.responsive = this; this._constructor(); }; $.extend(Responsive.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constructor */ /** * Initialise the Responsive instance * * @private */ _constructor: function () { var that = this; var dt = this.s.dt; var dtPrivateSettings = dt.settings()[0]; var oldWindowWidth = $(window).innerWidth(); dt.settings()[0]._responsive = this; // Use DataTables' throttle function to avoid processor thrashing on // resize $(window).on( 'resize.dtr orientationchange.dtr', DataTable.util.throttle(function () { // iOS has a bug whereby resize can fire when only scrolling // See: http://stackoverflow.com/questions/8898412 var width = $(window).innerWidth(); if (width !== oldWindowWidth) { that._resize(); oldWindowWidth = width; } }) ); // DataTables doesn't currently trigger an event when a row is added, so // we need to hook into its private API to enforce the hidden rows when // new data is added dtPrivateSettings.oApi._fnCallbackReg( dtPrivateSettings, 'aoRowCreatedCallback', function (tr, data, idx) { if ($.inArray(false, that.s.current) !== -1) { $('>td, >th', tr).each(function (i) { var idx = dt.column.index('toData', i); if (that.s.current[idx] === false) { $(this).css('display', 'none'); } }); } } ); // Destroy event handler dt.on('destroy.dtr', function () { dt.off('.dtr'); $(dt.table().body()).off('.dtr'); $(window).off('resize.dtr orientationchange.dtr'); dt.cells('.dtr-control').nodes().to$().removeClass('dtr-control'); // Restore the columns that we've hidden $.each(that.s.current, function (i, val) { if (val === false) { that._setColumnVis(i, true); } }); }); // Reorder the breakpoints array here in case they have been added out // of order this.c.breakpoints.sort(function (a, b) { return a.width < b.width ? 1 : a.width > b.width ? -1 : 0; }); this._classLogic(); this._resizeAuto(); // Details handler var details = this.c.details; if (details.type !== false) { that._detailsInit(); // DataTables will trigger this event on every column it shows and // hides individually dt.on('column-visibility.dtr', function () { // Use a small debounce to allow multiple columns to be set together if (that._timer) { clearTimeout(that._timer); } that._timer = setTimeout(function () { that._timer = null; that._classLogic(); that._resizeAuto(); that._resize(true); that._redrawChildren(); }, 100); }); // Redraw the details box on each draw which will happen if the data // has changed. This is used until DataTables implements a native // `updated` event for rows dt.on('draw.dtr', function () { that._redrawChildren(); }); $(dt.table().node()).addClass('dtr-' + details.type); } dt.on('column-reorder.dtr', function (e, settings, details) { that._classLogic(); that._resizeAuto(); that._resize(true); }); // Change in column sizes means we need to calc dt.on('column-sizing.dtr', function () { that._resizeAuto(); that._resize(); }); // DT2 let's us tell it if we are hiding columns dt.on('column-calc.dt', function (e, d) { var curr = that.s.current; for (var i = 0; i < curr.length; i++) { var idx = d.visible.indexOf(i); if (curr[i] === false && idx >= 0) { d.visible.splice(idx, 1); } } }); // On Ajax reload we want to reopen any child rows which are displayed // by responsive dt.on('preXhr.dtr', function () { var rowIds = []; dt.rows().every(function () { if (this.child.isShown()) { rowIds.push(this.id(true)); } }); dt.one('draw.dtr', function () { that._resizeAuto(); that._resize(); dt.rows(rowIds).every(function () { that._detailsDisplay(this, false); }); }); }); dt.on('draw.dtr', function () { that._controlClass(); }).on('init.dtr', function (e, settings, details) { if (e.namespace !== 'dt') { return; } that._resizeAuto(); that._resize(); // If columns were hidden, then DataTables needs to adjust the // column sizing if ($.inArray(false, that.s.current)) { dt.columns.adjust(); } }); // First pass - draw the table for the current viewport size this._resize(); }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods */ /** * Get and store nodes from a cell - use for node moving renderers * * @param {*} dt DT instance * @param {*} row Row index * @param {*} col Column index */ _childNodes: function (dt, row, col) { var name = row + '-' + col; if (this.s.childNodeStore[name]) { return this.s.childNodeStore[name]; } // https://jsperf.com/childnodes-array-slice-vs-loop var nodes = []; var children = dt.cell(row, col).node().childNodes; for (var i = 0, ien = children.length; i < ien; i++) { nodes.push(children[i]); } this.s.childNodeStore[name] = nodes; return nodes; }, /** * Restore nodes from the cache to a table cell * * @param {*} dt DT instance * @param {*} row Row index * @param {*} col Column index */ _childNodesRestore: function (dt, row, col) { var name = row + '-' + col; if (!this.s.childNodeStore[name]) { return; } var node = dt.cell(row, col).node(); var store = this.s.childNodeStore[name]; var parent = store[0].parentNode; var parentChildren = parent.childNodes; var a = []; for (var i = 0, ien = parentChildren.length; i < ien; i++) { a.push(parentChildren[i]); } for (var j = 0, jen = a.length; j < jen; j++) { node.appendChild(a[j]); } this.s.childNodeStore[name] = undefined; }, /** * Calculate the visibility for the columns in a table for a given * breakpoint. The result is pre-determined based on the class logic if * class names are used to control all columns, but the width of the table * is also used if there are columns which are to be automatically shown * and hidden. * * @param {string} breakpoint Breakpoint name to use for the calculation * @return {array} Array of boolean values initiating the visibility of each * column. * @private */ _columnsVisiblity: function (breakpoint) { var dt = this.s.dt; var columns = this.s.columns; var i, ien; // Create an array that defines the column ordering based first on the // column's priority, and secondly the column index. This allows the // columns to be removed from the right if the priority matches var order = columns .map(function (col, idx) { return { columnIdx: idx, priority: col.priority }; }) .sort(function (a, b) { if (a.priority !== b.priority) { return a.priority - b.priority; } return a.columnIdx - b.columnIdx; }); // Class logic - determine which columns are in this breakpoint based // on the classes. If no class control (i.e. `auto`) then `-` is used // to indicate this to the rest of the function var display = $.map(columns, function (col, i) { if (dt.column(i).visible() === false) { return 'not-visible'; } return col.auto && col.minWidth === null ? false : col.auto === true ? '-' : $.inArray(breakpoint, col.includeIn) !== -1; }); // Auto column control - first pass: how much width is taken by the // ones that must be included from the non-auto columns var requiredWidth = 0; for (i = 0, ien = display.length; i < ien; i++) { if (display[i] === true) { requiredWidth += columns[i].minWidth; } } // Second pass, use up any remaining width for other columns. For // scrolling tables we need to subtract the width of the scrollbar. It // may not be requires which makes this sub-optimal, but it would // require another full redraw to make complete use of those extra few // pixels var scrolling = dt.settings()[0].oScroll; var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0; var widthAvailable = dt.table().container().offsetWidth - bar; var usedWidth = widthAvailable - requiredWidth; // Control column needs to always be included. This makes it sub- // optimal in terms of using the available with, but to stop layout // thrashing or overflow. Also we need to account for the control column // width first so we know how much width is available for the other // columns, since the control column might not be the first one shown for (i = 0, ien = display.length; i < ien; i++) { if (columns[i].control) { usedWidth -= columns[i].minWidth; } } // Allow columns to be shown (counting by priority and then right to // left) until we run out of room var empty = false; for (i = 0, ien = order.length; i < ien; i++) { var colIdx = order[i].columnIdx; if (display[colIdx] === '-' && !columns[colIdx].control && columns[colIdx].minWidth) { // Once we've found a column that won't fit we don't let any // others display either, or columns might disappear in the // middle of the table if (empty || usedWidth - columns[colIdx].minWidth < 0) { empty = true; display[colIdx] = false; } else { display[colIdx] = true; } usedWidth -= columns[colIdx].minWidth; } } // Determine if the 'control' column should be shown (if there is one). // This is the case when there is a hidden column (that is not the // control column). The two loops look inefficient here, but they are // trivial and will fly through. We need to know the outcome from the // first , before the action in the second can be taken var showControl = false; for (i = 0, ien = columns.length; i < ien; i++) { if (!columns[i].control && !columns[i].never && display[i] === false) { showControl = true; break; } } for (i = 0, ien = columns.length; i < ien; i++) { if (columns[i].control) { display[i] = showControl; } // Replace not visible string with false from the control column detection above if (display[i] === 'not-visible') { display[i] = false; } } // Finally we need to make sure that there is at least one column that // is visible if ($.inArray(true, display) === -1) { display[0] = true; } return display; }, /** * Create the internal `columns` array with information about the columns * for the table. This includes determining which breakpoints the column * will appear in, based upon class names in the column, which makes up the * vast majority of this method. * * @private */ _classLogic: function () { var that = this; var calc = {}; var breakpoints = this.c.breakpoints; var dt = this.s.dt; var columns = dt .columns() .eq(0) .map(function (i) { var column = this.column(i); var className = column.header().className; var priority = dt.settings()[0].aoColumns[i].responsivePriority; var dataPriority = column.header().getAttribute('data-priority'); if (priority === undefined) { priority = dataPriority === undefined || dataPriority === null ? 10000 : dataPriority * 1; } return { className: className, includeIn: [], auto: false, control: false, never: className.match(/\b(dtr\-)?never\b/) ? true : false, priority: priority }; }); // Simply add a breakpoint to `includeIn` array, ensuring that there are // no duplicates var add = function (colIdx, name) { var includeIn = columns[colIdx].includeIn; if ($.inArray(name, includeIn) === -1) { includeIn.push(name); } }; var column = function (colIdx, name, operator, matched) { var size, i, ien; if (!operator) { columns[colIdx].includeIn.push(name); } else if (operator === 'max-') { // Add this breakpoint and all smaller size = that._find(name).width; for (i = 0, ien = breakpoints.length; i < ien; i++) { if (breakpoints[i].width <= size) { add(colIdx, breakpoints[i].name); } } } else if (operator === 'min-') { // Add this breakpoint and all larger size = that._find(name).width; for (i = 0, ien = breakpoints.length; i < ien; i++) { if (breakpoints[i].width >= size) { add(colIdx, breakpoints[i].name); } } } else if (operator === 'not-') { // Add all but this breakpoint for (i = 0, ien = breakpoints.length; i < ien; i++) { if (breakpoints[i].name.indexOf(matched) === -1) { add(colIdx, breakpoints[i].name); } } } }; // Loop over each column and determine if it has a responsive control // class columns.each(function (col, i) { var classNames = col.className.split(' '); var hasClass = false; // Split the class name up so multiple rules can be applied if needed for (var k = 0, ken = classNames.length; k < ken; k++) { var className = classNames[k].trim(); if (className === 'all' || className === 'dtr-all') { // Include in all hasClass = true; col.includeIn = $.map(breakpoints, function (a) { return a.name; }); return; } else if (className === 'none' || className === 'dtr-none' || col.never) { // Include in none (default) and no auto hasClass = true; return; } else if (className === 'control' || className === 'dtr-control') { // Special column that is only visible, when one of the other // columns is hidden. This is used for the details control hasClass = true; col.control = true; return; } $.each(breakpoints, function (j, breakpoint) { // Does this column have a class that matches this breakpoint? var brokenPoint = breakpoint.name.split('-'); var re = new RegExp( '(min\\-|max\\-|not\\-)?(' + brokenPoint[0] + ')(\\-[_a-zA-Z0-9])?' ); var match = className.match(re); if (match) { hasClass = true; if (match[2] === brokenPoint[0] && match[3] === '-' + brokenPoint[1]) { // Class name matches breakpoint name fully column(i, breakpoint.name, match[1], match[2] + match[3]); } else if (match[2] === brokenPoint[0] && !match[3]) { // Class name matched primary breakpoint name with no qualifier column(i, breakpoint.name, match[1], match[2]); } } }); } // If there was no control class, then automatic sizing is used if (!hasClass) { col.auto = true; } }); this.s.columns = columns; }, /** * Update the cells to show the correct control class / button * @private */ _controlClass: function () { if (this.c.details.type === 'inline') { var dt = this.s.dt; var columnsVis = this.s.current; var firstVisible = $.inArray(true, columnsVis); // Remove from any cells which shouldn't have it dt.cells( null, function (idx) { return idx !== firstVisible; }, { page: 'current' } ) .nodes() .to$() .filter('.dtr-control') .removeClass('dtr-control'); dt.cells(null, firstVisible, { page: 'current' }).nodes().to$().addClass('dtr-control'); } }, /** * Show the details for the child row * * @param {DataTables.Api} row API instance for the row * @param {boolean} update Update flag * @private */ _detailsDisplay: function (row, update) { var that = this; var dt = this.s.dt; var details = this.c.details; var event = function (res) { $(row.node()).toggleClass('parent', res !== false); $(dt.table().node()).triggerHandler('responsive-display.dt', [dt, row, res, update]); }; if (details && details.type !== false) { var renderer = typeof details.renderer === 'string' ? Responsive.renderer[details.renderer]() : details.renderer; var res = details.display( row, update, function () { return renderer.call(that, dt, row[0], that._detailsObj(row[0])); }, function () { event(false); } ); if (typeof res === 'boolean') { event(res); } } }, /** * Initialisation for the details handler * * @private */ _detailsInit: function () { var that = this; var dt = this.s.dt; var details = this.c.details; // The inline type always uses the first child as the target if (details.type === 'inline') { details.target = 'td.dtr-control, th.dtr-control'; } // Keyboard accessibility dt.on('draw.dtr', function () { that._tabIndexes(); }); that._tabIndexes(); // Initial draw has already happened $(dt.table().body()).on('keyup.dtr', 'td, th', function (e) { if (e.keyCode === 13 && $(this).data('dtr-keyboard')) { $(this).click(); } }); // type.target can be a string jQuery selector or a column index var target = details.target; var selector = typeof target === 'string' ? target : 'td, th'; if (target !== undefined || target !== null) { // Click handler to show / hide the details rows when they are available $(dt.table().body()).on('click.dtr mousedown.dtr mouseup.dtr', selector, function (e) { // If the table is not collapsed (i.e. there is no hidden columns) // then take no action if (!$(dt.table().node()).hasClass('collapsed')) { return; } // Check that the row is actually a DataTable's controlled node if ($.inArray($(this).closest('tr').get(0), dt.rows().nodes().toArray()) === -1) { return; } // For column index, we determine if we should act or not in the // handler - otherwise it is already okay if (typeof target === 'number') { var targetIdx = target < 0 ? dt.columns().eq(0).length + target : target; if (dt.cell(this).index().column !== targetIdx) { return; } } // $().closest() includes itself in its check var row = dt.row($(this).closest('tr')); // Check event type to do an action if (e.type === 'click') { // The renderer is given as a function so the caller can execute it // only when they need (i.e. if hiding there is no point is running // the renderer) that._detailsDisplay(row, false); } else if (e.type === 'mousedown') { // For mouse users, prevent the focus ring from showing $(this).css('outline', 'none'); } else if (e.type === 'mouseup') { // And then re-allow at the end of the click $(this).trigger('blur').css('outline', ''); } }); } }, /** * Get the details to pass to a renderer for a row * @param {int} rowIdx Row index * @private */ _detailsObj: function (rowIdx) { var that = this; var dt = this.s.dt; return $.map(this.s.columns, function (col, i) { // Never and control columns should not be passed to the renderer if (col.never || col.control) { return; } var dtCol = dt.settings()[0].aoColumns[i]; return { className: dtCol.sClass, columnIndex: i, data: dt.cell(rowIdx, i).render(that.c.orthogonal), hidden: dt.column(i).visible() && !that.s.current[i], rowIndex: rowIdx, title: dtCol.sTitle !== null ? dtCol.sTitle : $(dt.column(i).header()).text() }; }); }, /** * Find a breakpoint object from a name * * @param {string} name Breakpoint name to find * @return {object} Breakpoint description object * @private */ _find: function (name) { var breakpoints = this.c.breakpoints; for (var i = 0, ien = breakpoints.length; i < ien; i++) { if (breakpoints[i].name === name) { return breakpoints[i]; } } }, /** * Re-create the contents of the child rows as the display has changed in * some way. * * @private */ _redrawChildren: function () { var that = this; var dt = this.s.dt; dt.rows({ page: 'current' }).iterator('row', function (settings, idx) { that._detailsDisplay(dt.row(idx), true); }); }, /** * Alter the table display for a resized viewport. This involves first * determining what breakpoint the window currently is in, getting the * column visibilities to apply and then setting them. * * @param {boolean} forceRedraw Force a redraw * @private */ _resize: function (forceRedraw) { var that = this; var dt = this.s.dt; var width = $(window).innerWidth(); var breakpoints = this.c.breakpoints; var breakpoint = breakpoints[0].name; var columns = this.s.columns; var i, ien; var oldVis = this.s.current.slice(); // Determine what breakpoint we are currently at for (i = breakpoints.length - 1; i >= 0; i--) { if (width <= breakpoints[i].width) { breakpoint = breakpoints[i].name; break; } } // Show the columns for that break point var columnsVis = this._columnsVisiblity(breakpoint); this.s.current = columnsVis; // Set the class before the column visibility is changed so event // listeners know what the state is. Need to determine if there are // any columns that are not visible but can be shown var collapsedClass = false; for (i = 0, ien = columns.length; i < ien; i++) { if ( columnsVis[i] === false && !columns[i].never && !columns[i].control && !dt.column(i).visible() === false ) { collapsedClass = true; break; } } $(dt.table().node()).toggleClass('collapsed', collapsedClass); var changed = false; var visible = 0; dt.columns() .eq(0) .each(function (colIdx, i) { if (columnsVis[i] === true) { visible++; } if (forceRedraw || columnsVis[i] !== oldVis[i]) { changed = true; that._setColumnVis(colIdx, columnsVis[i]); } }); // Always need to update the display, regardless of if it has changed or not, so nodes // can be re-inserted for listHiddenNodes this._redrawChildren(); if (changed) { // Inform listeners of the change $(dt.table().node()).trigger('responsive-resize.dt', [dt, this.s.current]); // If no records, update the "No records" display element if (dt.page.info().recordsDisplay === 0) { $('td', dt.table().body()).eq(0).attr('colspan', visible); } } that._controlClass(); }, /** * Determine the width of each column in the table so the auto column hiding * has that information to work with. This method is never going to be 100% * perfect since column widths can change slightly per page, but without * seriously compromising performance this is quite effective. * * @private */ _resizeAuto: function () { var dt = this.s.dt; var columns = this.s.columns; var that = this; // Are we allowed to do auto sizing? if (!this.c.auto) { return; } // Are there any columns that actually need auto-sizing, or do they all // have classes defined if ( $.inArray( true, $.map(columns, function (c) { return c.auto; }) ) === -1 ) { return; } // Need to restore all children. They will be reinstated by a re-render if (!$.isEmptyObject(this.s.childNodeStore)) { $.each(this.s.childNodeStore, function (key) { var idx = key.split('-'); that._childNodesRestore(dt, idx[0] * 1, idx[1] * 1); }); } // Clone the table with the current data in it var tableWidth = dt.table().node().offsetWidth; var columnWidths = dt.columns; var clonedTable = dt.table().node().cloneNode(false); var clonedHeader = $(dt.table().header().cloneNode(false)).appendTo(clonedTable); var clonedBody = $(dt.table().body()).clone(false, false).empty().appendTo(clonedTable); // use jQuery because of IE8 clonedTable.style.width = 'auto'; // Header var headerCells = dt .columns() .header() .filter(function (idx) { return dt.column(idx).visible(); }) .to$() .clone(false) .css('display', 'table-cell') .css('width', 'auto') .css('min-width', 0); // Body rows - we don't need to take account of DataTables' column // visibility since we implement our own here (hence the `display` set) $(clonedBody) .append($(dt.rows({ page: 'current' }).nodes()).clone(false)) .find('th, td') .css('display', ''); // Footer var footer = dt.table().footer(); if (footer) { var clonedFooter = $(footer.cloneNode(false)).appendTo(clonedTable); var footerCells = dt .columns() .footer() .filter(function (idx) { return dt.column(idx).visible(); }) .to$() .clone(false) .css('display', 'table-cell'); $('<tr/>').append(footerCells).appendTo(clonedFooter); } $('<tr/>').append(headerCells).appendTo(clonedHeader); // In the inline case extra padding is applied to the first column to // give space for the show / hide icon. We need to use this in the // calculation if (this.c.details.type === 'inline') { $(clonedTable).addClass('dtr-inline collapsed'); } // It is unsafe to insert elements with the same name into the DOM // multiple times. For example, cloning and inserting a checked radio // clears the chcecked state of the original radio. $(clonedTable).find('[name]').removeAttr('name'); // A position absolute table would take the table out of the flow of // our container element, bypassing the height and width (Scroller) $(clonedTable).css('position', 'relative'); var inserted = $('<div/>') .css({ width: 1, height: 1, overflow: 'hidden', clear: 'both' }) .append(clonedTable); inserted.insertBefore(dt.table().node()); // The cloned header now contains the smallest that each column can be headerCells.each(function (i) { var idx = dt.column.index('fromVisible', i); columns[idx].minWidth = this.offsetWidth || 0; }); inserted.remove(); }, /** * Get the state of the current hidden columns - controlled by Responsive only */ _responsiveOnlyHidden: function () { var dt = this.s.dt; return $.map(this.s.current, function (v, i) { // If the column is hidden by DataTables then it can't be hidden by // Responsive! if (dt.column(i).visible() === false) { return true; } return v; }); }, /** * Set a column's visibility. * * We don't use DataTables' column visibility controls in order to ensure * that column visibility can Responsive can no-exist. Since only IE8+ is * supported (and all evergreen browsers of course) the control of the * display attribute works well. * * @param {integer} col Column index * @param {boolean} showHide Show or hide (true or false) * @private */ _setColumnVis: function (col, showHide) { var that = this; var dt = this.s.dt; var display = showHide ? '' : 'none'; // empty string will remove the attr $(dt.column(col).header()).css('display', display).toggleClass('dtr-hidden', !showHide); $(dt.column(col).footer()).css('display', display).toggleClass('dtr-hidden', !showHide); dt.column(col).nodes().to$().css('display', display).toggleClass('dtr-hidden', !showHide); // If the are child nodes stored, we might need to reinsert them if (!$.isEmptyObject(this.s.childNodeStore)) { dt.cells(null, col) .indexes() .each(function (idx) { that._childNodesRestore(dt, idx.row, idx.column); }); } }, /** * Update the cell tab indexes for keyboard accessibility. This is called on * every table draw - that is potentially inefficient, but also the least * complex option given that column visibility can change on the fly. Its a * shame user-focus was removed from CSS 3 UI, as it would have solved this * issue with a single CSS statement. * * @private */ _tabIndexes: function () { var dt = this.s.dt; var cells = dt.cells({ page: 'current' }).nodes().to$(); var ctx = dt.settings()[0]; var target = this.c.details.target; cells.filter('[data-dtr-keyboard]').removeData('[data-dtr-keyboard]'); if (typeof target === 'number') { dt.cells(null, target, { page: 'current' }) .nodes() .to$() .attr('tabIndex', ctx.iTabIndex) .data('dtr-keyboard', 1); } else { // This is a bit of a hack - we need to limit the selected nodes to just // those of this table if (target === 'td:first-child, th:first-child') { target = '>td:first-child, >th:first-child'; } $(target, dt.rows({ page: 'current' }).nodes()) .attr('tabIndex', ctx.iTabIndex) .data('dtr-keyboard', 1); } } }); /** * List of default breakpoints. Each item in the array is an object with two * properties: * * * `name` - the breakpoint name. * * `width` - the breakpoint width * * @name Responsive.breakpoints * @static */ Responsive.breakpoints = [ { name: 'desktop', width: Infinity }, { name: 'tablet-l', width: 1024 }, { name: 'tablet-p', width: 768 }, { name: 'mobile-l', width: 480 }, { name: 'mobile-p', width: 320 } ]; /** * Display methods - functions which define how the hidden data should be shown * in the table. * * @namespace * @name Responsive.defaults * @static */ Responsive.display = { childRow: function (row, update, render) { if (update) { if ($(row.node()).hasClass('parent')) { row.child(render(), 'child').show(); return true; } } else { if (!row.child.isShown()) { row.child(render(), 'child').show(); return true; } else { row.child(false); return false; } } }, childRowImmediate: function (row, update, render) { if ((!update && row.child.isShown()) || !row.responsive.hasHidden()) { // User interaction and the row is show, or nothing to show row.child(false); return false; } else { // Display row.child(render(), 'child').show(); return true; } }, // This is a wrapper so the modal options for Bootstrap and jQuery UI can // have options passed into them. This specific one doesn't need to be a // function but it is for consistency in the `modal` name modal: function (options) { return function (row, update, render, closeCallback) { if (!update) { // Show a modal var close = function () { modal.remove(); // will tidy events for us $(document).off('keypress.dtr'); $(row.node()).removeClass('parent'); closeCallback(); }; var modal = $('<div class="dtr-modal"/>') .append( $('<div class="dtr-modal-display"/>') .append( $('<div class="dtr-modal-content"/>') .data('dtr-row-idx', row.index()) .append(render()) ) .append( $('<div class="dtr-modal-close">×</div>').click(function () { close(); }) ) ) .append( $('<div class="dtr-modal-background"/>').click(function () { close(); }) ) .appendTo('body'); $(row.node()).addClass('parent'); $(document).on('keyup.dtr', function (e) { if (e.keyCode === 27) { e.stopPropagation(); close(); } }); } else { var modal = $('div.dtr-modal-content'); if (modal.length && row.index() === modal.data('dtr-row-idx')) { modal.empty().append(render()); } else { // Modal not shown, nothing to update return null; } } if (options && options.header) { $('div.dtr-modal-content').prepend('<h2>' + options.header(row) + '</h2>'); } return true; }; } }; /** * Display methods - functions which define how the hidden data should be shown * in the table. * * @namespace * @name Responsive.defaults * @static */ Responsive.renderer = { listHiddenNodes: function () { return function (api, rowIdx, columns) { var that = this; var ul = $('<ul data-dtr-index="' + rowIdx + '" class="dtr-details"/>'); var found = false; var data = $.each(columns, function (i, col) { if (col.hidden) { var klass = col.className ? 'class="' + col.className + '"' : ''; $( '<li ' + klass + ' data-dtr-index="' + col.columnIndex + '" data-dt-row="' + col.rowIndex + '" data-dt-column="' + col.columnIndex + '">' + '<span class="dtr-title">' + col.title + '</span> ' + '</li>' ) .append( $('<span class="dtr-data"/>').append( that._childNodes(api, col.rowIndex, col.columnIndex) ) ) // api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) ) .appendTo(ul); found = true; } }); return found ? ul : false; }; }, listHidden: function () { return function (api, rowIdx, columns) { var data = $.map(columns, function (col) { var klass = col.className ? 'class="' + col.className + '"' : ''; return col.hidden ? '<li ' + klass + ' data-dtr-index="' + col.columnIndex + '" data-dt-row="' + col.rowIndex + '" data-dt-column="' + col.columnIndex + '">' + '<span class="dtr-title">' + col.title + '</span> ' + '<span class="dtr-data">' + col.data + '</span>' + '</li>' : ''; }).join(''); return data ? $('<ul data-dtr-index="' + rowIdx + '" class="dtr-details"/>').append(data) : false; }; }, tableAll: function (options) { options = $.extend( { tableClass: '' }, options ); return function (api, rowIdx, columns) { var data = $.map(columns, function (col) { var klass = col.className ? 'class="' + col.className + '"' : ''; return ( '<tr ' + klass + ' data-dt-row="' + col.rowIndex + '" data-dt-column="' + col.columnIndex + '">' + '<td>' + col.title + ':' + '</td> ' + '<td>' + col.data + '</td>' + '</tr>' ); }).join(''); return $('<table class="' + options.tableClass + ' dtr-details" width="100%"/>').append( data ); }; } }; /** * Responsive default settings for initialisation * * @namespace * @name Responsive.defaults * @static */ Responsive.defaults = { /** * List of breakpoints for the instance. Note that this means that each * instance can have its own breakpoints. Additionally, the breakpoints * cannot be changed once an instance has been creased. * * @type {Array} * @default Takes the value of `Responsive.breakpoints` */ breakpoints: Responsive.breakpoints, /** * Enable / disable auto hiding calculations. It can help to increase * performance slightly if you disable this option, but all columns would * need to have breakpoint classes assigned to them * * @type {Boolean} * @default `true` */ auto: true, /** * Details control. If given as a string value, the `type` property of the * default object is set to that value, and the defaults used for the rest * of the object - this is for ease of implementation. * * The object consists of the following properties: * * * `display` - A function that is used to show and hide the hidden details * * `renderer` - function that is called for display of the child row data. * The default function will show the data from the hidden columns * * `target` - Used as the selector for what objects to attach the child * open / close to * * `type` - `false` to disable the details display, `inline` or `column` * for the two control types * * @type {Object|string} */ details: { display: Responsive.display.childRow, renderer: Responsive.renderer.listHidden(), target: 0, type: 'inline' }, /** * Orthogonal data request option. This is used to define the data type * requested when Responsive gets the data to show in the child row. * * @type {String} */ orthogonal: 'display' }; /* * API */ var Api = $.fn.dataTable.Api; // Doesn't do anything - work around for a bug in DT... Not documented Api.register('responsive()', function () { return this; }); Api.register('responsive.index()', function (li) { li = $(li); return { column: li.data('dtr-index'), row: li.parent().data('dtr-index') }; }); Api.register('responsive.rebuild()', function () { return this.iterator('table', function (ctx) { if (ctx._responsive) { ctx._responsive._classLogic(); } }); }); Api.register('responsive.recalc()', function () { return this.iterator('table', function (ctx) { if (ctx._responsive) { ctx._responsive._resizeAuto(); ctx._responsive._resize(); } }); }); Api.register('responsive.hasHidden()', function () { var ctx = this.context[0]; return ctx._responsive ? $.inArray(false, ctx._responsive._responsiveOnlyHidden()) !== -1 : false; }); Api.registerPlural('columns().responsiveHidden()', 'column().responsiveHidden()', function () { return this.iterator( 'column', function (settings, column) { return settings._responsive ? settings._responsive._responsiveOnlyHidden()[column] : false; }, 1 ); }); /** * Version information * * @name Responsive.version * @static */ Responsive.version = '2.5.0'; $.fn.dataTable.Responsive = Responsive; $.fn.DataTable.Responsive = Responsive; // Attach a listener to the document which listens for DataTables initialisation // events so we can automatically initialise $(document).on('preInit.dt.dtr', function (e, settings, json) { if (e.namespace !== 'dt') { return; } if ( $(settings.nTable).hasClass('responsive') || $(settings.nTable).hasClass('dt-responsive') || settings.oInit.responsive || DataTable.defaults.responsive ) { var init = settings.oInit.responsive; if (init !== false) { new Responsive(settings, $.isPlainObject(init) ? init : {}); } } }); return DataTable; })); /*! DataTables styling wrapper for Responsive * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net-dt', 'datatables.net-responsive'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net-dt')(root, $); } if ( ! $.fn.dataTable.Responsive ) { require('datatables.net-responsive')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; return DataTable; })); /*! RowReorder 1.4.1 * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; /** * @summary RowReorder * @description Row reordering extension for DataTables * @version 1.4.1 * @file dataTables.rowReorder.js * @author SpryMedia Ltd * @contact datatables.net * @copyright Copyright 2015-2023 SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * 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 */ /** * RowReorder provides the ability in DataTables to click and drag rows to * reorder them. When a row is dropped the data for the rows effected will be * updated to reflect the change. Normally this data point should also be the * column being sorted upon in the DataTable but this does not need to be the * case. RowReorder implements a "data swap" method - so the rows being * reordered take the value of the data point from the row that used to occupy * the row's new position. * * Initialisation is done by either: * * * `rowReorder` parameter in the DataTable initialisation object * * `new $.fn.dataTable.RowReorder( table, opts )` after DataTables * initialisation. * * @class * @param {object} settings DataTables settings object for the host table * @param {object} [opts] Configuration options * @requires jQuery 1.7+ * @requires DataTables 1.10.7+ */ var RowReorder = function (dt, opts) { // Sanity check that we are using DataTables 1.10 or newer if (!DataTable.versionCheck || !DataTable.versionCheck('1.10.8')) { throw 'DataTables RowReorder requires DataTables 1.10.8 or newer'; } // User and defaults configuration object this.c = $.extend(true, {}, DataTable.defaults.rowReorder, RowReorder.defaults, opts); // Internal settings this.s = { /** @type {integer} Scroll body top cache */ bodyTop: null, /** @type {DataTable.Api} DataTables' API instance */ dt: new DataTable.Api(dt), /** @type {function} Data fetch function */ getDataFn: DataTable.ext.oApi._fnGetObjectDataFn(this.c.dataSrc), /** @type {array} Pixel positions for row insertion calculation */ middles: null, /** @type {Object} Cached dimension information for use in the mouse move event handler */ scroll: {}, /** @type {integer} Interval object used for smooth scrolling */ scrollInterval: null, /** @type {function} Data set function */ setDataFn: DataTable.ext.oApi._fnSetObjectDataFn(this.c.dataSrc), /** @type {Object} Mouse down information */ start: { top: 0, left: 0, offsetTop: 0, offsetLeft: 0, nodes: [], rowIndex: 0 }, /** @type {integer} Window height cached value */ windowHeight: 0, /** @type {integer} Document outer height cached value */ documentOuterHeight: 0, /** @type {integer} DOM clone outer height cached value */ domCloneOuterHeight: 0, /** @type {integer} Flag used for signing if the drop is enabled or not */ dropAllowed: true }; // DOM items this.dom = { /** @type {jQuery} Cloned row being moved around */ clone: null, cloneParent: null, /** @type {jQuery} DataTables scrolling container */ dtScroll: $('div.dataTables_scrollBody', this.s.dt.table().container()) }; // Check if row reorder has already been initialised on this table var settings = this.s.dt.settings()[0]; var exisiting = settings.rowreorder; if (exisiting) { return exisiting; } if (!this.dom.dtScroll.length) { this.dom.dtScroll = $(this.s.dt.table().container(), 'tbody'); } settings.rowreorder = this; this._constructor(); }; $.extend(RowReorder.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constructor */ /** * Initialise the RowReorder instance * * @private */ _constructor: function () { var that = this; var dt = this.s.dt; var table = $(dt.table().node()); // Need to be able to calculate the row positions relative to the table if (table.css('position') === 'static') { table.css('position', 'relative'); } // listen for mouse down on the target column - we have to implement // this rather than using HTML5 drag and drop as drag and drop doesn't // appear to work on table rows at this time. Also mobile browsers are // not supported. // Use `table().container()` rather than just the table node for IE8 - // otherwise it only works once... $(dt.table().container()).on( 'mousedown.rowReorder touchstart.rowReorder', this.c.selector, function (e) { if (!that.c.enable) { return; } // Ignore excluded children of the selector if ($(e.target).is(that.c.excludedChildren)) { return true; } var tr = $(this).closest('tr'); var row = dt.row(tr); // Double check that it is a DataTable row if (row.any()) { that._emitEvent('pre-row-reorder', { node: row.node(), index: row.index() }); that._mouseDown(e, tr); return false; } } ); dt.on('destroy.rowReorder', function () { $(dt.table().container()).off('.rowReorder'); dt.off('.rowReorder'); }); this._keyup = this._keyup.bind(this); }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods */ /** * Cache the measurements that RowReorder needs in the mouse move handler * to attempt to speed things up, rather than reading from the DOM. * * @private */ _cachePositions: function () { var dt = this.s.dt; // Frustratingly, if we add `position:relative` to the tbody, the // position is still relatively to the parent. So we need to adjust // for that var headerHeight = $(dt.table().node()).find('thead').outerHeight(); // Need to pass the nodes through jQuery to get them in document order, // not what DataTables thinks it is, since we have been altering the // order var nodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); var middles = $.map(nodes, function (node, i) { var top = $(node).position().top - headerHeight; return (top + top + $(node).outerHeight()) / 2; }); this.s.middles = middles; this.s.bodyTop = $(dt.table().body()).offset().top; this.s.windowHeight = $(window).height(); this.s.documentOuterHeight = $(document).outerHeight(); this.s.bodyArea = this._calcBodyArea(); }, /** * Clone a row so it can be floated around the screen * * @param {jQuery} target Node to be cloned * @private */ _clone: function (target) { var dt = this.s.dt; var clone = $(dt.table().node().cloneNode(false)) .addClass('dt-rowReorder-float') .append('<tbody/>') .append(target.clone(false)); // Match the table and column widths - read all sizes before setting // to reduce reflows var tableWidth = target.outerWidth(); var tableHeight = target.outerHeight(); var scrollBody = $($(this.s.dt.table().node()).parent()); var scrollWidth = scrollBody.width(); var scrollLeft = scrollBody.scrollLeft(); var sizes = target.children().map(function () { return $(this).width(); }); clone .width(tableWidth) .height(tableHeight) .find('tr') .children() .each(function (i) { this.style.width = sizes[i] + 'px'; }); var cloneParent = $('<div>') .addClass('dt-rowReorder-float-parent') .width(scrollWidth) .append(clone) .appendTo('body') .scrollLeft(scrollLeft); // Insert into the document to have it floating around this.dom.clone = clone; this.dom.cloneParent = cloneParent; this.s.domCloneOuterHeight = clone.outerHeight(); }, /** * Update the cloned item's position in the document * * @param {object} e Event giving the mouse's position * @private */ _clonePosition: function (e) { var start = this.s.start; var topDiff = this._eventToPage(e, 'Y') - start.top; var leftDiff = this._eventToPage(e, 'X') - start.left; var snap = this.c.snapX; var left; var top = topDiff + start.offsetTop; if (snap === true) { left = start.offsetLeft; } else if (typeof snap === 'number') { left = start.offsetLeft + snap; } else { left = leftDiff + start.offsetLeft + this.dom.cloneParent.scrollLeft(); } if (top < 0) { top = 0; } else if (top + this.s.domCloneOuterHeight > this.s.documentOuterHeight) { top = this.s.documentOuterHeight - this.s.domCloneOuterHeight; } this.dom.cloneParent.css({ top: top, left: left }); }, /** * Emit an event on the DataTable for listeners * * @param {string} name Event name * @param {array} args Event arguments * @private */ _emitEvent: function ( name, args ) { var ret; this.s.dt.iterator( 'table', function ( ctx, i ) { var innerRet = $(ctx.nTable).triggerHandler( name+'.dt', args ); if (innerRet !== undefined) { ret = innerRet; } } ); return ret; }, /** * Get pageX/Y position from an event, regardless of if it is a mouse or * touch event. * * @param {object} e Event * @param {string} pos X or Y (must be a capital) * @private */ _eventToPage: function (e, pos) { if (e.type.indexOf('touch') !== -1) { return e.originalEvent.touches[0]['page' + pos]; } return e['page' + pos]; }, /** * Mouse down event handler. Read initial positions and add event handlers * for the move. * * @param {object} e Mouse event * @param {jQuery} target TR element that is to be moved * @private */ _mouseDown: function (e, target) { var that = this; var dt = this.s.dt; var start = this.s.start; var cancelable = this.c.cancelable; var offset = target.offset(); start.top = this._eventToPage(e, 'Y'); start.left = this._eventToPage(e, 'X'); start.offsetTop = offset.top; start.offsetLeft = offset.left; start.nodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); this._cachePositions(); this._clone(target); this._clonePosition(e); var bodyY = this._eventToPage(e, 'Y') - this.s.bodyTop; start.rowIndex = this._calcRowIndexByPos(bodyY); this.dom.target = target; target.addClass('dt-rowReorder-moving'); $(document) .on('mouseup.rowReorder touchend.rowReorder', function (e) { that._mouseUp(e); }) .on('mousemove.rowReorder touchmove.rowReorder', function (e) { that._mouseMove(e); }); // Check if window is x-scrolling - if not, disable it for the duration // of the drag if ($(window).width() === $(document).width()) { $(document.body).addClass('dt-rowReorder-noOverflow'); } // Cache scrolling information so mouse move doesn't need to read. // This assumes that the window and DT scroller will not change size // during an row drag, which I think is a fair assumption var scrollWrapper = this.dom.dtScroll; this.s.scroll = { windowHeight: $(window).height(), windowWidth: $(window).width(), dtTop: scrollWrapper.length ? scrollWrapper.offset().top : null, dtLeft: scrollWrapper.length ? scrollWrapper.offset().left : null, dtHeight: scrollWrapper.length ? scrollWrapper.outerHeight() : null, dtWidth: scrollWrapper.length ? scrollWrapper.outerWidth() : null }; // Add keyup handler if dragging is cancelable if (cancelable) { $(document).on('keyup', this._keyup); } }, /** * Mouse move event handler - move the cloned row and shuffle the table's * rows if required. * * @param {object} e Mouse event * @private */ _mouseMove: function (e) { this._clonePosition(e); var start = this.s.start; var cancelable = this.c.cancelable; if (cancelable) { var bodyArea = this.s.bodyArea; var cloneArea = this._calcCloneParentArea(); this.s.dropAllowed = this._rectanglesIntersect(bodyArea, cloneArea); this.s.dropAllowed ? $(this.dom.cloneParent).removeClass('drop-not-allowed') : $(this.dom.cloneParent).addClass('drop-not-allowed'); } // Transform the mouse position into a position in the table's body var bodyY = this._eventToPage(e, 'Y') - this.s.bodyTop; var middles = this.s.middles; var insertPoint = null; // Determine where the row should be inserted based on the mouse // position for (var i = 0, ien = middles.length; i < ien; i++) { if (bodyY < middles[i]) { insertPoint = i; break; } } if (insertPoint === null) { insertPoint = middles.length; } if (cancelable) { if (!this.s.dropAllowed) { // Move the row back to its original position becasuse the drop is not allowed insertPoint = start.rowIndex > this.s.lastInsert ? start.rowIndex + 1 : start.rowIndex; } this.dom.target.toggleClass('dt-rowReorder-moving', this.s.dropAllowed); } this._moveTargetIntoPosition(insertPoint); this._shiftScroll(e); }, /** * Mouse up event handler - release the event handlers and perform the * table updates * * @param {object} e Mouse event * @private */ _mouseUp: function (e) { var that = this; var dt = this.s.dt; var i, ien; var dataSrc = this.c.dataSrc; var dropAllowed = this.s.dropAllowed; if (!dropAllowed) { that._cancel(); return; } // Calculate the difference var startNodes = this.s.start.nodes; var endNodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); var idDiff = {}; var fullDiff = []; var diffNodes = []; var getDataFn = this.s.getDataFn; var setDataFn = this.s.setDataFn; for (i = 0, ien = startNodes.length; i < ien; i++) { if (startNodes[i] !== endNodes[i]) { var id = dt.row(endNodes[i]).id(); var endRowData = dt.row(endNodes[i]).data(); var startRowData = dt.row(startNodes[i]).data(); if (id) { idDiff[id] = getDataFn(startRowData); } fullDiff.push({ node: endNodes[i], oldData: getDataFn(endRowData), newData: getDataFn(startRowData), newPosition: i, oldPosition: $.inArray(endNodes[i], startNodes) }); diffNodes.push(endNodes[i]); } } // Create event args var eventArgs = [ fullDiff, { dataSrc: dataSrc, nodes: diffNodes, values: idDiff, triggerRow: dt.row(this.dom.target), originalEvent: e } ]; // Emit event var eventResult = this._emitEvent( 'row-reorder', eventArgs ); if (eventResult === false) { that._cancel(); return; } // Remove cloned elements, handlers, etc this._cleanupDragging(); var update = function () { if (that.c.update) { for (i = 0, ien = fullDiff.length; i < ien; i++) { var row = dt.row(fullDiff[i].node); var rowData = row.data(); setDataFn(rowData, fullDiff[i].newData); // Invalidate the cell that has the same data source as the dataSrc dt.columns().every(function () { if (this.dataSrc() === dataSrc) { dt.cell(fullDiff[i].node, this.index()).invalidate('data'); } }); } // Trigger row reordered event that._emitEvent('row-reordered', eventArgs); dt.draw(false); } }; // Editor interface if (this.c.editor) { // Disable user interaction while Editor is submitting this.c.enable = false; this.c.editor .edit(diffNodes, false, $.extend({ submit: 'changed' }, this.c.formOptions)) .multiSet(dataSrc, idDiff) .one('preSubmitCancelled.rowReorder', function () { that.c.enable = true; that.c.editor.off('.rowReorder'); dt.draw(false); }) .one('submitUnsuccessful.rowReorder', function () { dt.draw(false); }) .one('submitSuccess.rowReorder', function () { update(); }) .one('submitComplete', function () { that.c.enable = true; that.c.editor.off('.rowReorder'); }) .submit(); } else { update(); } }, /** * Moves the current target into the given position within the table * and caches the new positions * * @param {integer} insertPoint Position * @private */ _moveTargetIntoPosition: function (insertPoint) { var dt = this.s.dt; // Perform the DOM shuffle if it has changed from last time if (this.s.lastInsert === null || this.s.lastInsert !== insertPoint) { var nodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); var insertPlacement = ''; if (insertPoint > this.s.lastInsert) { this.dom.target.insertAfter(nodes[insertPoint - 1]); insertPlacement = 'after'; } else { this.dom.target.insertBefore(nodes[insertPoint]); insertPlacement = 'before'; } this._cachePositions(); this.s.lastInsert = insertPoint; this._emitEvent('row-reorder-changed', { insertPlacement, insertPoint, row: dt.row(this.dom.target) }); } }, /** * Removes the cloned elements, event handlers, scrolling intervals, etc * * @private */ _cleanupDragging: function () { var cancelable = this.c.cancelable; this.dom.clone.remove(); this.dom.cloneParent.remove(); this.dom.clone = null; this.dom.cloneParent = null; this.dom.target.removeClass('dt-rowReorder-moving'); //this.dom.target = null; $(document).off('.rowReorder'); $(document.body).removeClass('dt-rowReorder-noOverflow'); clearInterval(this.s.scrollInterval); this.s.scrollInterval = null; if (cancelable) { $(document).off('keyup', this._keyup); } }, /** * Move the window and DataTables scrolling during a drag to scroll new * content into view. * * This matches the `_shiftScroll` method used in AutoFill, but only * horizontal scrolling is considered here. * * @param {object} e Mouse move event object * @private */ _shiftScroll: function (e) { var that = this; var dt = this.s.dt; var scroll = this.s.scroll; var runInterval = false; var scrollSpeed = 5; var buffer = 65; var windowY = e.pageY - document.body.scrollTop, windowVert, dtVert; // Window calculations - based on the mouse position in the window, // regardless of scrolling if (windowY < $(window).scrollTop() + buffer) { windowVert = scrollSpeed * -1; } else if (windowY > scroll.windowHeight + $(window).scrollTop() - buffer) { windowVert = scrollSpeed; } // DataTables scrolling calculations - based on the table's position in // the document and the mouse position on the page if (scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer) { dtVert = scrollSpeed * -1; } else if (scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer) { dtVert = scrollSpeed; } // This is where it gets interesting. We want to continue scrolling // without requiring a mouse move, so we need an interval to be // triggered. The interval should continue until it is no longer needed, // but it must also use the latest scroll commands (for example consider // that the mouse might move from scrolling up to scrolling left, all // with the same interval running. We use the `scroll` object to "pass" // this information to the interval. Can't use local variables as they // wouldn't be the ones that are used by an already existing interval! if (windowVert || dtVert) { scroll.windowVert = windowVert; scroll.dtVert = dtVert; runInterval = true; } else if (this.s.scrollInterval) { // Don't need to scroll - remove any existing timer clearInterval(this.s.scrollInterval); this.s.scrollInterval = null; } // If we need to run the interval to scroll and there is no existing // interval (if there is an existing one, it will continue to run) if (!this.s.scrollInterval && runInterval) { this.s.scrollInterval = setInterval(function () { // Don't need to worry about setting scroll <0 or beyond the // scroll bound as the browser will just reject that. if (scroll.windowVert) { var top = $(document).scrollTop(); $(document).scrollTop(top + scroll.windowVert); if (top !== $(document).scrollTop()) { var move = parseFloat(that.dom.cloneParent.css('top')); that.dom.cloneParent.css('top', move + scroll.windowVert); } } // DataTables scrolling if (scroll.dtVert) { var scroller = that.dom.dtScroll[0]; if (scroll.dtVert) { scroller.scrollTop += scroll.dtVert; } } }, 20); } }, /** * Calculates the current area of the table body and returns it as a rectangle * * @private */ _calcBodyArea: function (e) { var dt = this.s.dt; var offset = $(dt.table().body()).offset(); var area = { left: offset.left, top: offset.top, right: offset.left + $(dt.table().body()).width(), bottom: offset.top + $(dt.table().body()).height() }; return area; }, /** * Calculates the current area of the cloned parent element and returns it as a rectangle * * @private */ _calcCloneParentArea: function (e) { var dt = this.s.dt; var offset = $(this.dom.cloneParent).offset(); var area = { left: offset.left, top: offset.top, right: offset.left + $(this.dom.cloneParent).width(), bottom: offset.top + $(this.dom.cloneParent).height() }; return area; }, /** * Returns whether the given reactangles intersect or not * * @private */ _rectanglesIntersect: function (a, b) { var noOverlap = a.left >= b.right || b.left >= a.right || a.top >= b.bottom || b.top >= a.bottom; return !noOverlap; }, /** * Calculates the index of the row which lays under the given Y position or * returns -1 if no such row * * @param {integer} insertPoint Position * @private */ _calcRowIndexByPos: function (bodyY) { // Determine where the row is located based on the mouse // position var dt = this.s.dt; var nodes = $.unique(dt.rows({ page: 'current' }).nodes().toArray()); var rowIndex = -1; var headerHeight = $(dt.table().node()).find('thead').outerHeight(); $.each(nodes, function (i, node) { var top = $(node).position().top - headerHeight; var bottom = top + $(node).outerHeight(); if (bodyY >= top && bodyY <= bottom) { rowIndex = i; } }); return rowIndex; }, /** * Handles key up events and cancels the dragging if ESC key is pressed * * @param {object} e Mouse move event object * @private */ _keyup: function (e) { var cancelable = this.c.cancelable; if (cancelable && e.which === 27) { // ESC key is up e.preventDefault(); this._cancel(); } }, /** * Cancels the dragging, moves target back into its original position * and cleans up the dragging * * @param {object} e Mouse move event object * @private */ _cancel: function () { var start = this.s.start; var insertPoint = start.rowIndex > this.s.lastInsert ? start.rowIndex + 1 : start.rowIndex; this._moveTargetIntoPosition(insertPoint); this._cleanupDragging(); // Emit event this._emitEvent('row-reorder-canceled', [this.s.start.rowIndex]); } }); /** * RowReorder default settings for initialisation * * @namespace * @name RowReorder.defaults * @static */ RowReorder.defaults = { /** * Data point in the host row's data source object for where to get and set * the data to reorder. This will normally also be the sorting column. * * @type {Number} */ dataSrc: 0, /** * Editor instance that will be used to perform the update * * @type {DataTable.Editor} */ editor: null, /** * Enable / disable RowReorder's user interaction * @type {Boolean} */ enable: true, /** * Form options to pass to Editor when submitting a change in the row order. * See the Editor `from-options` object for details of the options * available. * @type {Object} */ formOptions: {}, /** * Drag handle selector. This defines the element that when dragged will * reorder a row. * * @type {String} */ selector: 'td:first-child', /** * Optionally lock the dragged row's x-position. This can be `true` to * fix the position match the host table's, `false` to allow free movement * of the row, or a number to define an offset from the host table. * * @type {Boolean|number} */ snapX: false, /** * Update the table's data on drop * * @type {Boolean} */ update: true, /** * Selector for children of the drag handle selector that mouseDown events * will be passed through to and drag will not activate * * @type {String} */ excludedChildren: 'a', /** * Enable / disable the canceling of the drag & drop interaction * * @type {Boolean} */ cancelable: false }; /* * API */ var Api = $.fn.dataTable.Api; // Doesn't do anything - work around for a bug in DT... Not documented Api.register('rowReorder()', function () { return this; }); Api.register('rowReorder.enable()', function (toggle) { if (toggle === undefined) { toggle = true; } return this.iterator('table', function (ctx) { if (ctx.rowreorder) { ctx.rowreorder.c.enable = toggle; } }); }); Api.register('rowReorder.disable()', function () { return this.iterator('table', function (ctx) { if (ctx.rowreorder) { ctx.rowreorder.c.enable = false; } }); }); /** * Version information * * @name RowReorder.version * @static */ RowReorder.version = '1.4.1'; $.fn.dataTable.RowReorder = RowReorder; $.fn.DataTable.RowReorder = RowReorder; // Attach a listener to the document which listens for DataTables initialisation // events so we can automatically initialise $(document).on('init.dt.dtr', function (e, settings, json) { if (e.namespace !== 'dt') { return; } var init = settings.oInit.rowReorder; var defaults = DataTable.defaults.rowReorder; if (init || defaults) { var opts = $.extend({}, init, defaults); if (init !== false) { new RowReorder(settings, opts); } } }); return DataTable; })); /*! Scroller 2.3.0 * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; /** * @summary Scroller * @description Virtual rendering for DataTables * @version 2.3.0 * @copyright SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license/mit * * 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 */ /** * Scroller is a virtual rendering plug-in for DataTables which allows large * datasets to be drawn on screen every quickly. What the virtual rendering means * is that only the visible portion of the table (and a bit to either side to make * the scrolling smooth) is drawn, while the scrolling container gives the * visual impression that the whole table is visible. This is done by making use * of the pagination abilities of DataTables and moving the table around in the * scrolling container DataTables adds to the page. The scrolling container is * forced to the height it would be for the full table display using an extra * element. * * Note that rows in the table MUST all be the same height. Information in a cell * which expands on to multiple lines will cause some odd behaviour in the scrolling. * * Scroller is initialised by simply including the letter 'S' in the sDom for the * table you want to have this feature enabled on. Note that the 'S' must come * AFTER the 't' parameter in `dom`. * * Key features include: * <ul class="limit_length"> * <li>Speed! The aim of Scroller for DataTables is to make rendering large data sets fast</li> * <li>Full compatibility with deferred rendering in DataTables for maximum speed</li> * <li>Display millions of rows</li> * <li>Integration with state saving in DataTables (scrolling position is saved)</li> * <li>Easy to use</li> * </ul> * * @class * @constructor * @global * @param {object} dt DataTables settings object or API instance * @param {object} [opts={}] Configuration object for Scroller. Options * are defined by {@link Scroller.defaults} * * @requires jQuery 1.7+ * @requires DataTables 1.11.0+ */ var Scroller = function (dt, opts) { /* Sanity check - you just know it will happen */ if (!(this instanceof Scroller)) { alert( "Scroller warning: Scroller must be initialised with the 'new' keyword." ); return; } if (opts === undefined) { opts = {}; } var dtApi = $.fn.dataTable.Api(dt); /** * Settings object which contains customisable information for the Scroller instance * @namespace * @private * @extends Scroller.defaults */ this.s = { /** * DataTables settings object * @type object * @default Passed in as first parameter to constructor */ dt: dtApi.settings()[0], /** * DataTables API instance * @type DataTable.Api */ dtApi: dtApi, /** * Pixel location of the top of the drawn table in the viewport * @type int * @default 0 */ tableTop: 0, /** * Pixel location of the bottom of the drawn table in the viewport * @type int * @default 0 */ tableBottom: 0, /** * Pixel location of the boundary for when the next data set should be loaded and drawn * when scrolling up the way. * @type int * @default 0 * @private */ redrawTop: 0, /** * Pixel location of the boundary for when the next data set should be loaded and drawn * when scrolling down the way. Note that this is actually calculated as the offset from * the top. * @type int * @default 0 * @private */ redrawBottom: 0, /** * Auto row height or not indicator * @type bool * @default 0 */ autoHeight: true, /** * Number of rows calculated as visible in the visible viewport * @type int * @default 0 */ viewportRows: 0, /** * setTimeout reference for state saving, used when state saving is enabled in the DataTable * and when the user scrolls the viewport in order to stop the cookie set taking too much * CPU! * @type int * @default 0 */ stateTO: null, stateSaveThrottle: function () {}, /** * setTimeout reference for the redraw, used when server-side processing is enabled in the * DataTables in order to prevent DoSing the server * @type int * @default null */ drawTO: null, heights: { jump: null, page: null, virtual: null, scroll: null, /** * Height of rows in the table * @type int * @default 0 */ row: null, /** * Pixel height of the viewport * @type int * @default 0 */ viewport: null, labelHeight: 0, xbar: 0 }, topRowFloat: 0, scrollDrawDiff: null, loaderVisible: false, forceReposition: false, baseRowTop: 0, baseScrollTop: 0, mousedown: false, lastScrollTop: 0 }; // @todo The defaults should extend a `c` property and the internal settings // only held in the `s` property. At the moment they are mixed this.s = $.extend(this.s, Scroller.oDefaults, opts); // Workaround for row height being read from height object (see above comment) this.s.heights.row = this.s.rowHeight; /** * DOM elements used by the class instance * @private * @namespace * */ this.dom = { force: document.createElement('div'), label: $('<div class="dts_label">0</div>'), scroller: null, table: null, loader: null }; // Attach the instance to the DataTables instance so it can be accessed in // future. Don't initialise Scroller twice on the same table if (this.s.dt.oScroller) { return; } this.s.dt.oScroller = this; /* Let's do it */ this.construct(); }; $.extend(Scroller.prototype, { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public methods - to be exposed via the DataTables API */ /** * Calculate and store information about how many rows are to be displayed * in the scrolling viewport, based on current dimensions in the browser's * rendering. This can be particularly useful if the table is initially * drawn in a hidden element - for example in a tab. * @param {bool} [redraw=true] Redraw the table automatically after the recalculation, with * the new dimensions forming the basis for the draw. * @returns {void} */ measure: function (redraw) { if (this.s.autoHeight) { this._calcRowHeight(); } var heights = this.s.heights; if (heights.row) { heights.viewport = this._parseHeight( $(this.dom.scroller).css('max-height') ); this.s.viewportRows = parseInt(heights.viewport / heights.row, 10) + 1; this.s.dt._iDisplayLength = this.s.viewportRows * this.s.displayBuffer; } var label = this.dom.label.outerHeight(); heights.xbar = this.dom.scroller.offsetHeight - this.dom.scroller.clientHeight; heights.labelHeight = label; if (redraw === undefined || redraw) { this.s.dt.oInstance.fnDraw(false); } }, /** * Get information about current displayed record range. This corresponds to * the information usually displayed in the "Info" block of the table. * * @returns {object} info as an object: * { * start: {int}, // the 0-indexed record at the top of the viewport * end: {int}, // the 0-indexed record at the bottom of the viewport * } */ pageInfo: function () { var dt = this.s.dt, iScrollTop = this.dom.scroller.scrollTop, iTotal = dt.fnRecordsDisplay(), iPossibleEnd = Math.ceil( this.pixelsToRow( iScrollTop + this.s.heights.viewport, false, this.s.ani ) ); return { start: Math.floor(this.pixelsToRow(iScrollTop, false, this.s.ani)), end: iTotal < iPossibleEnd ? iTotal - 1 : iPossibleEnd - 1 }; }, /** * Calculate the row number that will be found at the given pixel position * (y-scroll). * * Please note that when the height of the full table exceeds 1 million * pixels, Scroller switches into a non-linear mode for the scrollbar to fit * all of the records into a finite area, but this function returns a linear * value (relative to the last non-linear positioning). * @param {int} pixels Offset from top to calculate the row number of * @param {int} [intParse=true] If an integer value should be returned * @param {int} [virtual=false] Perform the calculations in the virtual domain * @returns {int} Row index */ pixelsToRow: function (pixels, intParse, virtual) { var diff = pixels - this.s.baseScrollTop; var row = virtual ? (this._domain('physicalToVirtual', this.s.baseScrollTop) + diff) / this.s.heights.row : diff / this.s.heights.row + this.s.baseRowTop; return intParse || intParse === undefined ? parseInt(row, 10) : row; }, /** * Calculate the pixel position from the top of the scrolling container for * a given row * @param {int} iRow Row number to calculate the position of * @returns {int} Pixels */ rowToPixels: function (rowIdx, intParse, virtual) { var pixels; var diff = rowIdx - this.s.baseRowTop; if (virtual) { pixels = this._domain('virtualToPhysical', this.s.baseScrollTop); pixels += diff * this.s.heights.row; } else { pixels = this.s.baseScrollTop; pixels += diff * this.s.heights.row; } return intParse || intParse === undefined ? parseInt(pixels, 10) : pixels; }, /** * Calculate the row number that will be found at the given pixel position (y-scroll) * @param {int} row Row index to scroll to * @param {bool} [animate=true] Animate the transition or not * @returns {void} */ scrollToRow: function (row, animate) { var that = this; var ani = false; var px = this.rowToPixels(row); // We need to know if the table will redraw or not before doing the // scroll. If it will not redraw, then we need to use the currently // displayed table, and scroll with the physical pixels. Otherwise, we // need to calculate the table's new position from the virtual // transform. var preRows = ((this.s.displayBuffer - 1) / 2) * this.s.viewportRows; var drawRow = row - preRows; if (drawRow < 0) { drawRow = 0; } if ( (px > this.s.redrawBottom || px < this.s.redrawTop) && this.s.dt._iDisplayStart !== drawRow ) { ani = true; px = this._domain('virtualToPhysical', row * this.s.heights.row); // If we need records outside the current draw region, but the new // scrolling position is inside that (due to the non-linear nature // for larger numbers of records), we need to force position update. if (this.s.redrawTop < px && px < this.s.redrawBottom) { this.s.forceReposition = true; animate = false; } } if (animate === undefined || animate) { this.s.ani = ani; $(this.dom.scroller).animate( { scrollTop: px }, function () { // This needs to happen after the animation has completed and // the final scroll event fired setTimeout(function () { that.s.ani = false; }, 250); } ); } else { $(this.dom.scroller).scrollTop(px); } }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constructor */ /** * Initialisation for Scroller * @returns {void} * @private */ construct: function () { var that = this; var dt = this.s.dtApi; /* Sanity check */ if (!this.s.dt.oFeatures.bPaginate) { throw new Error( 'Pagination must be enabled for Scroller to operate' ); } /* Insert a div element that we can use to force the DT scrolling container to * the height that would be required if the whole table was being displayed */ this.dom.force.style.position = 'relative'; this.dom.force.style.top = '0px'; this.dom.force.style.left = '0px'; this.dom.force.style.width = '1px'; this.dom.scroller = dt.table().node().parentNode; this.dom.scroller.appendChild(this.dom.force); this.dom.scroller.style.position = 'relative'; this.dom.table = $('>table', this.dom.scroller)[0]; this.dom.table.style.position = 'absolute'; this.dom.table.style.top = '0px'; this.dom.table.style.left = '0px'; // Add class to 'announce' that we are a Scroller table $(dt.table().container()).addClass('dts DTS'); this.dom.label.appendTo(this.dom.scroller); /* Initial size calculations */ if (this.s.heights.row && this.s.heights.row != 'auto') { this.s.autoHeight = false; } // Scrolling callback to see if a page change is needed this.s.ingnoreScroll = true; $(this.dom.scroller).on('scroll.dt-scroller', function (e) { that._scroll.call(that); }); // In iOS we catch the touchstart event in case the user tries to scroll // while the display is already scrolling $(this.dom.scroller).on('touchstart.dt-scroller', function () { that._scroll.call(that); }); $(this.dom.scroller) .on('mousedown.dt-scroller', function () { that.s.mousedown = true; }) .on('mouseup.dt-scroller', function () { that.s.labelVisible = false; that.s.mousedown = false; that.dom.label.css('display', 'none'); }); // On resize, update the information element, since the number of rows shown might change $(window).on('resize.dt-scroller', function () { that.measure(false); that._info(); }); // Add a state saving parameter to the DT state saving so we can restore the exact // position of the scrolling. var initialStateSave = true; var loadedState = dt.state.loaded(); dt.on('stateSaveParams.scroller', function (e, settings, data) { if (initialStateSave && loadedState) { data.scroller = loadedState.scroller; initialStateSave = false; if (data.scroller) { that.s.lastScrollTop = data.scroller.scrollTop; } } else { // Need to used the saved position on init data.scroller = { topRow: that.s.topRowFloat, baseScrollTop: that.s.baseScrollTop, baseRowTop: that.s.baseRowTop, scrollTop: that.s.lastScrollTop }; } }); dt.on('stateLoadParams.scroller', function (e, settings, data) { if (data.scroller !== undefined) { that.scrollToRow(data.scroller.topRow); } }); if (loadedState && loadedState.scroller) { this.s.topRowFloat = loadedState.scroller.topRow; this.s.baseScrollTop = loadedState.scroller.baseScrollTop; this.s.baseRowTop = loadedState.scroller.baseRowTop; } this.measure(false); that.s.stateSaveThrottle = DataTable.util.throttle(function () { that.s.dtApi.state.save(); }, 500); dt.on('init.scroller', function () { that.measure(false); // Setting to `jump` will instruct _draw to calculate the scroll top // position that.s.scrollType = 'jump'; that._draw(); // Update the scroller when the DataTable is redrawn dt.on('draw.scroller', function () { that._draw(); }); }); // Set height before the draw happens, allowing everything else to update // on draw complete without worry for roder. dt.on('preDraw.dt.scroller', function () { that._scrollForce(); }); // Destructor dt.on('destroy.scroller', function () { $(window).off('resize.dt-scroller'); $(that.dom.scroller).off('.dt-scroller'); $(that.s.dt.nTable).off('.scroller'); $(that.s.dt.nTableWrapper).removeClass('DTS'); $('div.DTS_Loading', that.dom.scroller.parentNode).remove(); that.dom.table.style.position = ''; that.dom.table.style.top = ''; that.dom.table.style.left = ''; }); }, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Private methods */ /** * Automatic calculation of table row height. This is just a little tricky here as using * initialisation DataTables has tale the table out of the document, so we need to create * a new table and insert it into the document, calculate the row height and then whip the * table out. * @returns {void} * @private */ _calcRowHeight: function () { var dt = this.s.dt; var origTable = dt.nTable; var nTable = origTable.cloneNode(false); var tbody = $('<tbody/>').appendTo(nTable); var dtClasses = dt.oClasses; // Different locations for classes in DT2 var classes = DataTable.versionCheck('2') ? { container: dtClasses.container, scroller: dtClasses.scrolling.container, body: dtClasses.scrolling.body } : { container: dtClasses.sWrapper, scroller: dtClasses.sScrollWrapper, body: dtClasses.sScrollBody }; var container = $( '<div class="' + classes.container + ' DTS"><div class="' + classes.scroller + '"><div class="' + classes.body + '"></div></div></div>' ); // Want 3 rows in the sizing table so :first-child and :last-child // CSS styles don't come into play - take the size of the middle row $('tbody tr:lt(4)', origTable).clone().appendTo(tbody); var rowsCount = $('tr', tbody).length; if (rowsCount === 1) { tbody.prepend('<tr><td> </td></tr>'); tbody.append('<tr><td> </td></tr>'); } else { for (; rowsCount < 3; rowsCount++) { tbody.append('<tr><td> </td></tr>'); } } $('div.' + classes.body, container).append(nTable); // If initialised using `dom`, use the holding element as the insert point var insertEl = this.s.dt.nHolding || origTable.parentNode; if (!$(insertEl).is(':visible')) { insertEl = 'body'; } // Remove form element links as they might select over others (particularly radio and checkboxes) container.find('input').removeAttr('name'); container.appendTo(insertEl); this.s.heights.row = $('tr', tbody).eq(1).outerHeight(); container.remove(); }, /** * Draw callback function which is fired when the DataTable is redrawn. The main function of * this method is to position the drawn table correctly the scrolling container for the rows * that is displays as a result of the scrolling position. * @returns {void} * @private */ _draw: function () { var that = this, heights = this.s.heights, iScrollTop = this.dom.scroller.scrollTop, iTableHeight = $(this.s.dt.nTable).height(), displayStart = this.s.dt._iDisplayStart, displayLen = this.s.dt._iDisplayLength, displayEnd = this.s.dt.fnRecordsDisplay(), viewportEndY = iScrollTop + heights.viewport; // Disable the scroll event listener while we are updating the DOM this.s.skip = true; // If paging is reset if ( (this.s.dt.bSorted || this.s.dt.bFiltered) && displayStart === 0 && !this.s.dt._drawHold ) { this.s.topRowFloat = 0; } iScrollTop = this.s.scrollType === 'jump' ? this._domain( 'virtualToPhysical', this.s.topRowFloat * heights.row ) : iScrollTop; // Store positional information so positional calculations can be based // upon the current table draw position this.s.baseScrollTop = iScrollTop; this.s.baseRowTop = this.s.topRowFloat; // Position the table in the virtual scroller var tableTop = iScrollTop - (this.s.topRowFloat - displayStart) * heights.row; if (displayStart === 0) { tableTop = 0; } else if (displayStart + displayLen >= displayEnd) { tableTop = heights.scroll - iTableHeight; } else { var iTableBottomY = tableTop + iTableHeight; if (iTableBottomY < viewportEndY) { // The last row of the data is above the end of the viewport. // This means the background is visible, which is not what the user expects. var newTableTop = viewportEndY - iTableHeight; var diffPx = newTableTop - tableTop; this.s.baseScrollTop += diffPx + 1; // Update start row number in footer. tableTop = newTableTop; // Move table so last line of data is at the bottom of the viewport. } } this.dom.table.style.top = tableTop + 'px'; /* Cache some information for the scroller */ this.s.tableTop = tableTop; this.s.tableBottom = iTableHeight + this.s.tableTop; // Calculate the boundaries for where a redraw will be triggered by the // scroll event listener var boundaryPx = (iScrollTop - this.s.tableTop) * this.s.boundaryScale; this.s.redrawTop = iScrollTop - boundaryPx; this.s.redrawBottom = iScrollTop + boundaryPx > heights.scroll - heights.viewport - heights.row ? heights.scroll - heights.viewport - heights.row : iScrollTop + boundaryPx; this.s.skip = false; if (that.s.ingnoreScroll) { // Restore the scrolling position that was saved by DataTable's state // saving Note that this is done on the second draw when data is Ajax // sourced, and the first draw when DOM soured if ( this.s.dt.oFeatures.bStateSave && this.s.dt.oLoadedState !== null && typeof this.s.dt.oLoadedState.scroller != 'undefined' ) { // A quirk of DataTables is that the draw callback will occur on an // empty set if Ajax sourced, but not if server-side processing. var ajaxSourced = (this.s.dt.sAjaxSource || that.s.dt.ajax) && !this.s.dt.oFeatures.bServerSide ? true : false; if ( (ajaxSourced && this.s.dt.iDraw >= 2) || (!ajaxSourced && this.s.dt.iDraw >= 1) ) { setTimeout(function () { $(that.dom.scroller).scrollTop( that.s.dt.oLoadedState.scroller.scrollTop ); // In order to prevent layout thrashing we need another // small delay setTimeout(function () { that.s.ingnoreScroll = false; }, 0); }, 0); } } else { that.s.ingnoreScroll = false; } } // Because of the order of the DT callbacks, the info update will // take precedence over the one we want here. So a 'thread' break is // needed. Only add the thread break if bInfo is set if (this.s.dt.oFeatures.bInfo) { setTimeout(function () { that._info.call(that); }, 0); } $(this.s.dt.nTable).triggerHandler('position.dts.dt', tableTop); }, /** * Convert from one domain to another. The physical domain is the actual * pixel count on the screen, while the virtual is if we had browsers which * had scrolling containers of infinite height (i.e. the absolute value) * * @param {string} dir Domain transform direction, `virtualToPhysical` or * `physicalToVirtual` * @returns {number} Calculated transform * @private */ _domain: function (dir, val) { var heights = this.s.heights; var diff; var magic = 10000; // the point at which the non-linear calculations start to happen // If the virtual and physical height match, then we use a linear // transform between the two, allowing the scrollbar to be linear if (heights.virtual === heights.scroll) { return val; } // In the first 10k pixels and the last 10k pixels, we want the scrolling // to be linear. After that it can be non-linear. It would be unusual for // anyone to mouse wheel through that much. if (val < magic) { return val; } else if ( dir === 'virtualToPhysical' && val >= heights.virtual - magic ) { diff = heights.virtual - val; return heights.scroll - diff; } else if (dir === 'physicalToVirtual' && val >= heights.scroll - magic) { diff = heights.scroll - val; return heights.virtual - diff; } // Otherwise, we want a non-linear scrollbar to take account of the // redrawing regions at the start and end of the table, otherwise these // can stutter badly - on large tables 30px (for example) scroll might // be hundreds of rows, so the table would be redrawing every few px at // the start and end. Use a simple linear eq. to stop this, effectively // causing a kink in the scrolling ratio. It does mean the scrollbar is // non-linear, but with such massive data sets, the scrollbar is going // to be a best guess anyway var m = (heights.virtual - magic - magic) / (heights.scroll - magic - magic); var c = magic - m * magic; return dir === 'virtualToPhysical' ? (val - c) / m : m * val + c; }, /** * Update any information elements that are controlled by the DataTable based on the scrolling * viewport and what rows are visible in it. This function basically acts in the same way as * _fnUpdateInfo in DataTables, and effectively replaces that function. * @returns {void} * @private */ _info: function () { if (!this.s.dt.oFeatures.bInfo) { return; } var dt = this.s.dt, dtApi = this.s.dtApi, language = dt.oLanguage, info = dtApi.page.info(), total = info.recordsDisplay, max = info.recordsTotal, start = Math.floor(this.s.topRowFloat) + 1, possibleEnd = start + Math.floor(this.s.heights.viewport / this.s.heights.row), end = possibleEnd > total ? total : possibleEnd, result; if (total === 0 && total == max) { /* Empty record set */ result = language.sInfoEmpty + language.sInfoPostFix; } else if (total === 0) { // Empty record set after filtering result = language.sInfoEmpty + ' ' + language.sInfoFiltered + language.sInfoPostFix; } else if (total == max) { // Normal record set result = language.sInfo + language.sInfoPostFix; } else { // Record set after filtering result = language.sInfo; ' ' + language.sInfoFiltered + language.sInfoPostFix; } result = this._macros(result, start, end, max, total); var callback = language.fnInfoCallback; if (callback) { result = callback.call( dt.oInstance, dt, start, end, max, total, result ); } // DT 1.x features var n = dt.aanFeatures.i; if (typeof n != 'undefined') { for (var i = 0, iLen = n.length; i < iLen; i++) { $(n[i]).html(result); } $(dt.nTable).triggerHandler('info.dt'); } // DT 2.x features $('div.dt-info', dtApi.table().container()).each(function () { $(this).html(result); dtApi.trigger('info', [this, result]); }); }, /** * String replacement for info display. Basically the same as what DataTables does. * * @param {*} str * @param {*} start * @param {*} end * @param {*} max * @param {*} total * @returns Formatted string */ _macros: function (str, start, end, max, total) { var api = this.s.dtApi; var settings = this.s.dt; var formatter = settings.fnFormatNumber; return str .replace(/_START_/g, formatter.call(settings, start)) .replace(/_END_/g, formatter.call(settings, end)) .replace(/_MAX_/g, formatter.call(settings, max)) .replace(/_TOTAL_/g, formatter.call(settings, total)) .replace(/_ENTRIES_/g, api.i18n('entries', '')) .replace(/_ENTRIES-MAX_/g, api.i18n('entries', '', max)) .replace(/_ENTRIES-TOTAL_/g, api.i18n('entries', '', total)); }, /** * Parse CSS height property string as number * * An attempt is made to parse the string as a number. Currently supported units are 'px', * 'vh', and 'rem'. 'em' is partially supported; it works as long as the parent element's * font size matches the body element. Zero is returned for unrecognized strings. * @param {string} cssHeight CSS height property string * @returns {number} height * @private */ _parseHeight: function (cssHeight) { var height; var matches = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))(px|em|rem|vh)$/.exec( cssHeight ); if (matches === null) { return 0; } var value = parseFloat(matches[1]); var unit = matches[2]; if (unit === 'px') { height = value; } else if (unit === 'vh') { height = (value / 100) * $(window).height(); } else if (unit === 'rem') { height = value * parseFloat($(':root').css('font-size')); } else if (unit === 'em') { height = value * parseFloat($('body').css('font-size')); } return height ? height : 0; }, /** * Scrolling function - fired whenever the scrolling position is changed. * This method needs to use the stored values to see if the table should be * redrawn as we are moving towards the end of the information that is * currently drawn or not. If needed, then it will redraw the table based on * the new position. * @returns {void} * @private */ _scroll: function () { var that = this, heights = this.s.heights, iScrollTop = this.dom.scroller.scrollTop, iTopRow; if (this.s.skip) { return; } if (this.s.ingnoreScroll) { return; } if (iScrollTop === this.s.lastScrollTop) { return; } /* If the table has been sorted or filtered, then we use the redraw that * DataTables as done, rather than performing our own */ if (this.s.dt.bFiltered || this.s.dt.bSorted) { this.s.lastScrollTop = 0; return; } /* We don't want to state save on every scroll event - that's heavy * handed, so use a timeout to update the state saving only when the * scrolling has finished */ clearTimeout(this.s.stateTO); this.s.stateTO = setTimeout(function () { that.s.dtApi.state.save(); }, 250); this.s.scrollType = Math.abs(iScrollTop - this.s.lastScrollTop) > heights.viewport ? 'jump' : 'cont'; this.s.topRowFloat = this.s.scrollType === 'cont' ? this.pixelsToRow(iScrollTop, false, false) : this._domain('physicalToVirtual', iScrollTop) / heights.row; if (this.s.topRowFloat < 0) { this.s.topRowFloat = 0; } /* Check if the scroll point is outside the trigger boundary which would required * a DataTables redraw */ if ( this.s.forceReposition || iScrollTop < this.s.redrawTop || iScrollTop > this.s.redrawBottom ) { var preRows = Math.ceil( ((this.s.displayBuffer - 1) / 2) * this.s.viewportRows ); iTopRow = parseInt(this.s.topRowFloat, 10) - preRows; this.s.forceReposition = false; if (iTopRow <= 0) { /* At the start of the table */ iTopRow = 0; } else if ( iTopRow + this.s.dt._iDisplayLength > this.s.dt.fnRecordsDisplay() ) { /* At the end of the table */ iTopRow = this.s.dt.fnRecordsDisplay() - this.s.dt._iDisplayLength; if (iTopRow < 0) { iTopRow = 0; } } else if (iTopRow % 2 !== 0) { // For the row-striping classes (odd/even) we want only to start // on evens otherwise the stripes will change between draws and // look rubbish iTopRow++; } // Store calcuated value, in case the following condition is not met, but so // that the draw function will still use it. this.s.targetTop = iTopRow; if (iTopRow != this.s.dt._iDisplayStart) { /* Cache the new table position for quick lookups */ this.s.tableTop = $(this.s.dt.nTable).offset().top; this.s.tableBottom = $(this.s.dt.nTable).height() + this.s.tableTop; var draw = function () { that.s.dt._iDisplayStart = that.s.targetTop; that.s.dtApi.draw('page'); }; /* Do the DataTables redraw based on the calculated start point - note that when * using server-side processing we introduce a small delay to not DoS the server... */ if (this.s.dt.oFeatures.bServerSide) { this.s.forceReposition = true; // This is used only for KeyTable and is not currently publicly // documented. Open question - is it useful for anything else? $(this.s.dt.nTable).triggerHandler('scroller-will-draw.dt'); if (DataTable.versionCheck('2')) { that.s.dtApi.processing(true); } else { this.s.dt.oApi._fnProcessingDisplay(this.s.dt, true); } clearTimeout(this.s.drawTO); this.s.drawTO = setTimeout(draw, this.s.serverWait); } else { draw(); } } } else { this.s.topRowFloat = this.pixelsToRow(iScrollTop, false, true); } /* Update the table's information display for what is now in the viewport */ this._info(); this.s.lastScrollTop = iScrollTop; this.s.stateSaveThrottle(); if (this.s.scrollType === 'jump' && this.s.mousedown) { this.s.labelVisible = true; } if (this.s.labelVisible) { var labelFactor = (heights.viewport - heights.labelHeight - heights.xbar) / heights.scroll; this.dom.label .html( this.s.dt.fnFormatNumber( parseInt(this.s.topRowFloat, 10) + 1 ) ) .css('top', iScrollTop + iScrollTop * labelFactor) .css('display', 'block'); } }, /** * Force the scrolling container to have height beyond that of just the * table that has been drawn so the user can scroll the whole data set. * * Note that if the calculated required scrolling height exceeds a maximum * value (1 million pixels - hard-coded) the forcing element will be set * only to that maximum value and virtual / physical domain transforms will * be used to allow Scroller to display tables of any number of records. * @returns {void} * @private */ _scrollForce: function () { var heights = this.s.heights; var max = 1000000; heights.virtual = heights.row * this.s.dt.fnRecordsDisplay(); heights.scroll = heights.virtual; if (heights.scroll > max) { heights.scroll = max; } // Minimum height so there is always a row visible (the 'no rows found' // if reduced to zero filtering) this.dom.force.style.height = heights.scroll > this.s.heights.row ? heights.scroll + 'px' : this.s.heights.row + 'px'; } }); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Statics * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Scroller default settings for initialisation * @namespace * @name Scroller.defaults * @static */ Scroller.defaults = { /** * Scroller uses the boundary scaling factor to decide when to redraw the table - which it * typically does before you reach the end of the currently loaded data set (in order to * allow the data to look continuous to a user scrolling through the data). If given as 0 * then the table will be redrawn whenever the viewport is scrolled, while 1 would not * redraw the table until the currently loaded data has all been shown. You will want * something in the middle - the default factor of 0.5 is usually suitable. * @type float * @default 0.5 * @static */ boundaryScale: 0.5, /** * The display buffer is what Scroller uses to calculate how many rows it should pre-fetch * for scrolling. Scroller automatically adjusts DataTables' display length to pre-fetch * rows that will be shown in "near scrolling" (i.e. just beyond the current display area). * The value is based upon the number of rows that can be displayed in the viewport (i.e. * a value of 1), and will apply the display range to records before before and after the * current viewport - i.e. a factor of 3 will allow Scroller to pre-fetch 1 viewport's worth * of rows before the current viewport, the current viewport's rows and 1 viewport's worth * of rows after the current viewport. Adjusting this value can be useful for ensuring * smooth scrolling based on your data set. * @type int * @default 7 * @static */ displayBuffer: 9, /** * Scroller will attempt to automatically calculate the height of rows for it's internal * calculations. However the height that is used can be overridden using this parameter. * @type int|string * @default auto * @static */ rowHeight: 'auto', /** * When using server-side processing, Scroller will wait a small amount of time to allow * the scrolling to finish before requesting more data from the server. This prevents * you from DoSing your own server! The wait time can be configured by this parameter. * @type int * @default 200 * @static */ serverWait: 200 }; Scroller.oDefaults = Scroller.defaults; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constants * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Scroller version * @type String * @default See code * @name Scroller.version * @static */ Scroller.version = '2.3.0'; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Initialisation * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ // Attach a listener to the document which listens for DataTables initialisation // events so we can automatically initialise $(document).on('preInit.dt.dtscroller', function (e, settings) { if (e.namespace !== 'dt') { return; } var init = settings.oInit.scroller; var defaults = DataTable.defaults.scroller; if (init || defaults) { var opts = $.extend({}, init, defaults); if (init !== false) { new Scroller(settings, opts); } } }); // Attach Scroller to DataTables so it can be accessed as an 'extra' $.fn.dataTable.Scroller = Scroller; $.fn.DataTable.Scroller = Scroller; // DataTables 1.10 API method aliases var Api = $.fn.dataTable.Api; Api.register('scroller()', function () { return this; }); // Undocumented and deprecated - is it actually useful at all? Api.register('scroller().rowToPixels()', function (rowIdx, intParse, virtual) { var ctx = this.context; if (ctx.length && ctx[0].oScroller) { return ctx[0].oScroller.rowToPixels(rowIdx, intParse, virtual); } // undefined }); // Undocumented and deprecated - is it actually useful at all? Api.register('scroller().pixelsToRow()', function (pixels, intParse, virtual) { var ctx = this.context; if (ctx.length && ctx[0].oScroller) { return ctx[0].oScroller.pixelsToRow(pixels, intParse, virtual); } // undefined }); // `scroller().scrollToRow()` is undocumented and deprecated. Use `scroller.toPosition() Api.register( ['scroller().scrollToRow()', 'scroller.toPosition()'], function (idx, ani) { this.iterator('table', function (ctx) { if (ctx.oScroller) { ctx.oScroller.scrollToRow(idx, ani); } }); return this; } ); Api.register('row().scrollTo()', function (ani) { var that = this; this.iterator('row', function (ctx, rowIdx) { if (ctx.oScroller) { var displayIdx = that .rows({ order: 'applied', search: 'applied' }) .indexes() .indexOf(rowIdx); ctx.oScroller.scrollToRow(displayIdx, ani); } }); return this; }); Api.register('scroller.measure()', function (redraw) { this.iterator('table', function (ctx) { if (ctx.oScroller) { ctx.oScroller.measure(redraw); } }); return this; }); Api.register('scroller.page()', function () { var ctx = this.context; if (ctx.length && ctx[0].oScroller) { return ctx[0].oScroller.pageInfo(); } // undefined }); return DataTable; })); /*! Select for DataTables 1.7.0 * © SpryMedia Ltd - datatables.net/license/mit */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; // Version information for debugger DataTable.select = {}; DataTable.select.version = '1.7.0'; DataTable.select.init = function (dt) { var ctx = dt.settings()[0]; if (ctx._select) { return; } var savedSelected = dt.state.loaded(); var selectAndSave = function (e, settings, data) { if (data === null || data.select === undefined) { return; } // Clear any currently selected rows, before restoring state // None will be selected on first initialisation if (dt.rows({ selected: true }).any()) { dt.rows().deselect(); } if (data.select.rows !== undefined) { dt.rows(data.select.rows).select(); } if (dt.columns({ selected: true }).any()) { dt.columns().deselect(); } if (data.select.columns !== undefined) { dt.columns(data.select.columns).select(); } if (dt.cells({ selected: true }).any()) { dt.cells().deselect(); } if (data.select.cells !== undefined) { for (var i = 0; i < data.select.cells.length; i++) { dt.cell(data.select.cells[i].row, data.select.cells[i].column).select(); } } dt.state.save(); }; dt.on('stateSaveParams', function (e, settings, data) { data.select = {}; data.select.rows = dt.rows({ selected: true }).ids(true).toArray(); data.select.columns = dt.columns({ selected: true })[0]; data.select.cells = dt.cells({ selected: true })[0].map(function (coords) { return { row: dt.row(coords.row).id(true), column: coords.column }; }); }) .on('stateLoadParams', selectAndSave) .one('init', function () { selectAndSave(undefined, undefined, savedSelected); }); var init = ctx.oInit.select; var defaults = DataTable.defaults.select; var opts = init === undefined ? defaults : init; // Set defaults var items = 'row'; var style = 'api'; var blurable = false; var toggleable = true; var info = true; var selector = 'td, th'; var className = 'selected'; var setStyle = false; ctx._select = {}; // Initialisation customisations if (opts === true) { style = 'os'; setStyle = true; } else if (typeof opts === 'string') { style = opts; setStyle = true; } else if ($.isPlainObject(opts)) { if (opts.blurable !== undefined) { blurable = opts.blurable; } if (opts.toggleable !== undefined) { toggleable = opts.toggleable; } if (opts.info !== undefined) { info = opts.info; } if (opts.items !== undefined) { items = opts.items; } if (opts.style !== undefined) { style = opts.style; setStyle = true; } else { style = 'os'; setStyle = true; } if (opts.selector !== undefined) { selector = opts.selector; } if (opts.className !== undefined) { className = opts.className; } } dt.select.selector(selector); dt.select.items(items); dt.select.style(style); dt.select.blurable(blurable); dt.select.toggleable(toggleable); dt.select.info(info); ctx._select.className = className; // Sort table based on selected rows. Requires Select Datatables extension $.fn.dataTable.ext.order['select-checkbox'] = function (settings, col) { return this.api() .column(col, { order: 'index' }) .nodes() .map(function (td) { if (settings._select.items === 'row') { return $(td).parent().hasClass(settings._select.className); } else if (settings._select.items === 'cell') { return $(td).hasClass(settings._select.className); } return false; }); }; // If the init options haven't enabled select, but there is a selectable // class name, then enable if (!setStyle && $(dt.table().node()).hasClass('selectable')) { dt.select.style('os'); } }; /* Select is a collection of API methods, event handlers, event emitters and buttons (for the `Buttons` extension) for DataTables. It provides the following features, with an overview of how they are implemented: ## Selection of rows, columns and cells. Whether an item is selected or not is stored in: * rows: a `_select_selected` property which contains a boolean value of the DataTables' `aoData` object for each row * columns: a `_select_selected` property which contains a boolean value of the DataTables' `aoColumns` object for each column * cells: a `_selected_cells` property which contains an array of boolean values of the `aoData` object for each row. The array is the same length as the columns array, with each element of it representing a cell. This method of using boolean flags allows Select to operate when nodes have not been created for rows / cells (DataTables' defer rendering feature). ## API methods A range of API methods are available for triggering selection and de-selection of rows. Methods are also available to configure the selection events that can be triggered by an end user (such as which items are to be selected). To a large extent, these of API methods *is* Select. It is basically a collection of helper functions that can be used to select items in a DataTable. Configuration of select is held in the object `_select` which is attached to the DataTables settings object on initialisation. Select being available on a table is not optional when Select is loaded, but its default is for selection only to be available via the API - so the end user wouldn't be able to select rows without additional configuration. The `_select` object contains the following properties: ``` { items:string - Can be `rows`, `columns` or `cells`. Defines what item will be selected if the user is allowed to activate row selection using the mouse. style:string - Can be `none`, `single`, `multi` or `os`. Defines the interaction style when selecting items blurable:boolean - If row selection can be cleared by clicking outside of the table toggleable:boolean - If row selection can be cancelled by repeated clicking on the row info:boolean - If the selection summary should be shown in the table information elements } ``` In addition to the API methods, Select also extends the DataTables selector options for rows, columns and cells adding a `selected` option to the selector options object, allowing the developer to select only selected items or unselected items. ## Mouse selection of items Clicking on items can be used to select items. This is done by a simple event handler that will select the items using the API methods. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Local functions */ /** * Add one or more cells to the selection when shift clicking in OS selection * style cell selection. * * Cell range is more complicated than row and column as we want to select * in the visible grid rather than by index in sequence. For example, if you * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1 * should also be selected (and not 1-3, 1-4. etc) * * @param {DataTable.Api} dt DataTable * @param {object} idx Cell index to select to * @param {object} last Cell index to select from * @private */ function cellRange(dt, idx, last) { var indexes; var columnIndexes; var rowIndexes; var selectColumns = function (start, end) { if (start > end) { var tmp = end; end = start; start = tmp; } var record = false; return dt .columns(':visible') .indexes() .filter(function (i) { if (i === start) { record = true; } if (i === end) { // not else if, as start might === end record = false; return true; } return record; }); }; var selectRows = function (start, end) { var indexes = dt.rows({ search: 'applied' }).indexes(); // Which comes first - might need to swap if (indexes.indexOf(start) > indexes.indexOf(end)) { var tmp = end; end = start; start = tmp; } var record = false; return indexes.filter(function (i) { if (i === start) { record = true; } if (i === end) { record = false; return true; } return record; }); }; if (!dt.cells({ selected: true }).any() && !last) { // select from the top left cell to this one columnIndexes = selectColumns(0, idx.column); rowIndexes = selectRows(0, idx.row); } else { // Get column indexes between old and new columnIndexes = selectColumns(last.column, idx.column); rowIndexes = selectRows(last.row, idx.row); } indexes = dt.cells(rowIndexes, columnIndexes).flatten(); if (!dt.cells(idx, { selected: true }).any()) { // Select range dt.cells(indexes).select(); } else { // Deselect range dt.cells(indexes).deselect(); } } /** * Disable mouse selection by removing the selectors * * @param {DataTable.Api} dt DataTable to remove events from * @private */ function disableMouseSelection(dt) { var ctx = dt.settings()[0]; var selector = ctx._select.selector; $(dt.table().container()) .off('mousedown.dtSelect', selector) .off('mouseup.dtSelect', selector) .off('click.dtSelect', selector); $('body').off('click.dtSelect' + _safeId(dt.table().node())); } /** * Attach mouse listeners to the table to allow mouse selection of items * * @param {DataTable.Api} dt DataTable to remove events from * @private */ function enableMouseSelection(dt) { var container = $(dt.table().container()); var ctx = dt.settings()[0]; var selector = ctx._select.selector; var matchSelection; container .on('mousedown.dtSelect', selector, function (e) { // Disallow text selection for shift clicking on the table so multi // element selection doesn't look terrible! if (e.shiftKey || e.metaKey || e.ctrlKey) { container .css('-moz-user-select', 'none') .one('selectstart.dtSelect', selector, function () { return false; }); } if (window.getSelection) { matchSelection = window.getSelection(); } }) .on('mouseup.dtSelect', selector, function () { // Allow text selection to occur again, Mozilla style (tested in FF // 35.0.1 - still required) container.css('-moz-user-select', ''); }) .on('click.dtSelect', selector, function (e) { var items = dt.select.items(); var idx; // If text was selected (click and drag), then we shouldn't change // the row's selected state if (matchSelection) { var selection = window.getSelection(); // If the element that contains the selection is not in the table, we can ignore it // This can happen if the developer selects text from the click event if ( !selection.anchorNode || $(selection.anchorNode).closest('table')[0] === dt.table().node() ) { if (selection !== matchSelection) { return; } } } var ctx = dt.settings()[0]; var wrapperClass = dt.settings()[0].oClasses.sWrapper.trim().replace(/ +/g, '.'); // Ignore clicks inside a sub-table if ($(e.target).closest('div.' + wrapperClass)[0] != dt.table().container()) { return; } var cell = dt.cell($(e.target).closest('td, th')); // Check the cell actually belongs to the host DataTable (so child // rows, etc, are ignored) if (!cell.any()) { return; } var event = $.Event('user-select.dt'); eventTrigger(dt, event, [items, cell, e]); if (event.isDefaultPrevented()) { return; } var cellIndex = cell.index(); if (items === 'row') { idx = cellIndex.row; typeSelect(e, dt, ctx, 'row', idx); } else if (items === 'column') { idx = cell.index().column; typeSelect(e, dt, ctx, 'column', idx); } else if (items === 'cell') { idx = cell.index(); typeSelect(e, dt, ctx, 'cell', idx); } ctx._select_lastCell = cellIndex; }); // Blurable $('body').on('click.dtSelect' + _safeId(dt.table().node()), function (e) { if (ctx._select.blurable) { // If the click was inside the DataTables container, don't blur if ($(e.target).parents().filter(dt.table().container()).length) { return; } // Ignore elements which have been removed from the DOM (i.e. paging // buttons) if ($(e.target).parents('html').length === 0) { return; } // Don't blur in Editor form if ($(e.target).parents('div.DTE').length) { return; } var event = $.Event('select-blur.dt'); eventTrigger(dt, event, [e.target, e]); if (event.isDefaultPrevented()) { return; } clear(ctx, true); } }); } /** * Trigger an event on a DataTable * * @param {DataTable.Api} api DataTable to trigger events on * @param {boolean} selected true if selected, false if deselected * @param {string} type Item type acting on * @param {boolean} any Require that there are values before * triggering * @private */ function eventTrigger(api, type, args, any) { if (any && !api.flatten().length) { return; } if (typeof type === 'string') { type = type + '.dt'; } args.unshift(api); $(api.table().node()).trigger(type, args); } /** * Update the information element of the DataTable showing information about the * items selected. This is done by adding tags to the existing text * * @param {DataTable.Api} api DataTable to update * @private */ function info(api) { var ctx = api.settings()[0]; if (!ctx._select.info || !ctx.aanFeatures.i) { return; } if (api.select.style() === 'api') { return; } var rows = api.rows({ selected: true }).flatten().length; var columns = api.columns({ selected: true }).flatten().length; var cells = api.cells({ selected: true }).flatten().length; var add = function (el, name, num) { el.append( $('<span class="select-item"/>').append( api.i18n( 'select.' + name + 's', { _: '%d ' + name + 's selected', 0: '', 1: '1 ' + name + ' selected' }, num ) ) ); }; // Internal knowledge of DataTables to loop over all information elements $.each(ctx.aanFeatures.i, function (i, el) { el = $(el); var output = $('<span class="select-info"/>'); add(output, 'row', rows); add(output, 'column', columns); add(output, 'cell', cells); var exisiting = el.children('span.select-info'); if (exisiting.length) { exisiting.remove(); } if (output.text() !== '') { el.append(output); } }); } /** * Initialisation of a new table. Attach event handlers and callbacks to allow * Select to operate correctly. * * This will occur _after_ the initial DataTables initialisation, although * before Ajax data is rendered, if there is ajax data * * @param {DataTable.settings} ctx Settings object to operate on * @private */ function init(ctx) { var api = new DataTable.Api(ctx); ctx._select_init = true; // Row callback so that classes can be added to rows and cells if the item // was selected before the element was created. This will happen with the // `deferRender` option enabled. // // This method of attaching to `aoRowCreatedCallback` is a hack until // DataTables has proper events for row manipulation If you are reviewing // this code to create your own plug-ins, please do not do this! ctx.aoRowCreatedCallback.push({ fn: function (row, data, index) { var i, ien; var d = ctx.aoData[index]; // Row if (d._select_selected) { $(row).addClass(ctx._select.className); } // Cells and columns - if separated out, we would need to do two // loops, so it makes sense to combine them into a single one for (i = 0, ien = ctx.aoColumns.length; i < ien; i++) { if ( ctx.aoColumns[i]._select_selected || (d._selected_cells && d._selected_cells[i]) ) { $(d.anCells[i]).addClass(ctx._select.className); } } }, sName: 'select-deferRender' }); // On Ajax reload we want to reselect all rows which are currently selected, // if there is an rowId (i.e. a unique value to identify each row with) api.on('preXhr.dt.dtSelect', function (e, settings) { if (settings !== api.settings()[0]) { // Not triggered by our DataTable! return; } // note that column selection doesn't need to be cached and then // reselected, as they are already selected var rows = api .rows({ selected: true }) .ids(true) .filter(function (d) { return d !== undefined; }); var cells = api .cells({ selected: true }) .eq(0) .map(function (cellIdx) { var id = api.row(cellIdx.row).id(true); return id ? { row: id, column: cellIdx.column } : undefined; }) .filter(function (d) { return d !== undefined; }); // On the next draw, reselect the currently selected items api.one('draw.dt.dtSelect', function () { api.rows(rows).select(); // `cells` is not a cell index selector, so it needs a loop if (cells.any()) { cells.each(function (id) { api.cells(id.row, id.column).select(); }); } }); }); // Update the table information element with selected item summary api.on('draw.dtSelect.dt select.dtSelect.dt deselect.dtSelect.dt info.dt', function () { info(api); api.state.save(); }); // Clean up and release api.on('destroy.dtSelect', function () { // Remove class directly rather than calling deselect - which would trigger events $(api.rows({ selected: true }).nodes()).removeClass(api.settings()[0]._select.className); disableMouseSelection(api); api.off('.dtSelect'); $('body').off('.dtSelect' + _safeId(api.table().node())); }); } /** * Add one or more items (rows or columns) to the selection when shift clicking * in OS selection style * * @param {DataTable.Api} dt DataTable * @param {string} type Row or column range selector * @param {object} idx Item index to select to * @param {object} last Item index to select from * @private */ function rowColumnRange(dt, type, idx, last) { // Add a range of rows from the last selected row to this one var indexes = dt[type + 's']({ search: 'applied' }).indexes(); var idx1 = $.inArray(last, indexes); var idx2 = $.inArray(idx, indexes); if (!dt[type + 's']({ selected: true }).any() && idx1 === -1) { // select from top to here - slightly odd, but both Windows and Mac OS // do this indexes.splice($.inArray(idx, indexes) + 1, indexes.length); } else { // reverse so we can shift click 'up' as well as down if (idx1 > idx2) { var tmp = idx2; idx2 = idx1; idx1 = tmp; } indexes.splice(idx2 + 1, indexes.length); indexes.splice(0, idx1); } if (!dt[type](idx, { selected: true }).any()) { // Select range dt[type + 's'](indexes).select(); } else { // Deselect range - need to keep the clicked on row selected indexes.splice($.inArray(idx, indexes), 1); dt[type + 's'](indexes).deselect(); } } /** * Clear all selected items * * @param {DataTable.settings} ctx Settings object of the host DataTable * @param {boolean} [force=false] Force the de-selection to happen, regardless * of selection style * @private */ function clear(ctx, force) { if (force || ctx._select.style === 'single') { var api = new DataTable.Api(ctx); api.rows({ selected: true }).deselect(); api.columns({ selected: true }).deselect(); api.cells({ selected: true }).deselect(); } } /** * Select items based on the current configuration for style and items. * * @param {object} e Mouse event object * @param {DataTables.Api} dt DataTable * @param {DataTable.settings} ctx Settings object of the host DataTable * @param {string} type Items to select * @param {int|object} idx Index of the item to select * @private */ function typeSelect(e, dt, ctx, type, idx) { var style = dt.select.style(); var toggleable = dt.select.toggleable(); var isSelected = dt[type](idx, { selected: true }).any(); if (isSelected && !toggleable) { return; } if (style === 'os') { if (e.ctrlKey || e.metaKey) { // Add or remove from the selection dt[type](idx).select(!isSelected); } else if (e.shiftKey) { if (type === 'cell') { cellRange(dt, idx, ctx._select_lastCell || null); } else { rowColumnRange( dt, type, idx, ctx._select_lastCell ? ctx._select_lastCell[type] : null ); } } else { // No cmd or shift click - deselect if selected, or select // this row only var selected = dt[type + 's']({ selected: true }); if (isSelected && selected.flatten().length === 1) { dt[type](idx).deselect(); } else { selected.deselect(); dt[type](idx).select(); } } } else if (style == 'multi+shift') { if (e.shiftKey) { if (type === 'cell') { cellRange(dt, idx, ctx._select_lastCell || null); } else { rowColumnRange( dt, type, idx, ctx._select_lastCell ? ctx._select_lastCell[type] : null ); } } else { dt[type](idx).select(!isSelected); } } else { dt[type](idx).select(!isSelected); } } function _safeId(node) { return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-'); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * DataTables selectors */ // row and column are basically identical just assigned to different properties // and checking a different array, so we can dynamically create the functions to // reduce the code size $.each( [ { type: 'row', prop: 'aoData' }, { type: 'column', prop: 'aoColumns' } ], function (i, o) { DataTable.ext.selector[o.type].push(function (settings, opts, indexes) { var selected = opts.selected; var data; var out = []; if (selected !== true && selected !== false) { return indexes; } for (var i = 0, ien = indexes.length; i < ien; i++) { data = settings[o.prop][indexes[i]]; if ( (selected === true && data._select_selected === true) || (selected === false && !data._select_selected) ) { out.push(indexes[i]); } } return out; }); } ); DataTable.ext.selector.cell.push(function (settings, opts, cells) { var selected = opts.selected; var rowData; var out = []; if (selected === undefined) { return cells; } for (var i = 0, ien = cells.length; i < ien; i++) { rowData = settings.aoData[cells[i].row]; if ( (selected === true && rowData._selected_cells && rowData._selected_cells[cells[i].column] === true) || (selected === false && (!rowData._selected_cells || !rowData._selected_cells[cells[i].column])) ) { out.push(cells[i]); } } return out; }); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * DataTables API * * For complete documentation, please refer to the docs/api directory or the * DataTables site */ // Local variables to improve compression var apiRegister = DataTable.Api.register; var apiRegisterPlural = DataTable.Api.registerPlural; apiRegister('select()', function () { return this.iterator('table', function (ctx) { DataTable.select.init(new DataTable.Api(ctx)); }); }); apiRegister('select.blurable()', function (flag) { if (flag === undefined) { return this.context[0]._select.blurable; } return this.iterator('table', function (ctx) { ctx._select.blurable = flag; }); }); apiRegister('select.toggleable()', function (flag) { if (flag === undefined) { return this.context[0]._select.toggleable; } return this.iterator('table', function (ctx) { ctx._select.toggleable = flag; }); }); apiRegister('select.info()', function (flag) { if (flag === undefined) { return this.context[0]._select.info; } return this.iterator('table', function (ctx) { ctx._select.info = flag; }); }); apiRegister('select.items()', function (items) { if (items === undefined) { return this.context[0]._select.items; } return this.iterator('table', function (ctx) { ctx._select.items = items; eventTrigger(new DataTable.Api(ctx), 'selectItems', [items]); }); }); // Takes effect from the _next_ selection. None disables future selection, but // does not clear the current selection. Use the `deselect` methods for that apiRegister('select.style()', function (style) { if (style === undefined) { return this.context[0]._select.style; } return this.iterator('table', function (ctx) { if (!ctx._select) { DataTable.select.init(new DataTable.Api(ctx)); } if (!ctx._select_init) { init(ctx); } ctx._select.style = style; // Add / remove mouse event handlers. They aren't required when only // API selection is available var dt = new DataTable.Api(ctx); disableMouseSelection(dt); if (style !== 'api') { enableMouseSelection(dt); } eventTrigger(new DataTable.Api(ctx), 'selectStyle', [style]); }); }); apiRegister('select.selector()', function (selector) { if (selector === undefined) { return this.context[0]._select.selector; } return this.iterator('table', function (ctx) { disableMouseSelection(new DataTable.Api(ctx)); ctx._select.selector = selector; if (ctx._select.style !== 'api') { enableMouseSelection(new DataTable.Api(ctx)); } }); }); apiRegisterPlural('rows().select()', 'row().select()', function (select) { var api = this; if (select === false) { return this.deselect(); } this.iterator('row', function (ctx, idx) { clear(ctx); ctx.aoData[idx]._select_selected = true; $(ctx.aoData[idx].nTr).addClass(ctx._select.className); }); this.iterator('table', function (ctx, i) { eventTrigger(api, 'select', ['row', api[i]], true); }); return this; }); apiRegister('row().selected()', function () { var ctx = this.context[0]; if (ctx && this.length && ctx.aoData[this[0]] && ctx.aoData[this[0]]._select_selected) { return true; } return false; }); apiRegisterPlural('columns().select()', 'column().select()', function (select) { var api = this; if (select === false) { return this.deselect(); } this.iterator('column', function (ctx, idx) { clear(ctx); ctx.aoColumns[idx]._select_selected = true; var column = new DataTable.Api(ctx).column(idx); $(column.header()).addClass(ctx._select.className); $(column.footer()).addClass(ctx._select.className); column.nodes().to$().addClass(ctx._select.className); }); this.iterator('table', function (ctx, i) { eventTrigger(api, 'select', ['column', api[i]], true); }); return this; }); apiRegister('column().selected()', function () { var ctx = this.context[0]; if (ctx && this.length && ctx.aoColumns[this[0]] && ctx.aoColumns[this[0]]._select_selected) { return true; } return false; }); apiRegisterPlural('cells().select()', 'cell().select()', function (select) { var api = this; if (select === false) { return this.deselect(); } this.iterator('cell', function (ctx, rowIdx, colIdx) { clear(ctx); var data = ctx.aoData[rowIdx]; if (data._selected_cells === undefined) { data._selected_cells = []; } data._selected_cells[colIdx] = true; if (data.anCells) { $(data.anCells[colIdx]).addClass(ctx._select.className); } }); this.iterator('table', function (ctx, i) { eventTrigger(api, 'select', ['cell', api.cells(api[i]).indexes().toArray()], true); }); return this; }); apiRegister('cell().selected()', function () { var ctx = this.context[0]; if (ctx && this.length) { var row = ctx.aoData[this[0][0].row]; if (row && row._selected_cells && row._selected_cells[this[0][0].column]) { return true; } } return false; }); apiRegisterPlural('rows().deselect()', 'row().deselect()', function () { var api = this; this.iterator('row', function (ctx, idx) { ctx.aoData[idx]._select_selected = false; ctx._select_lastCell = null; $(ctx.aoData[idx].nTr).removeClass(ctx._select.className); }); this.iterator('table', function (ctx, i) { eventTrigger(api, 'deselect', ['row', api[i]], true); }); return this; }); apiRegisterPlural('columns().deselect()', 'column().deselect()', function () { var api = this; this.iterator('column', function (ctx, idx) { ctx.aoColumns[idx]._select_selected = false; var api = new DataTable.Api(ctx); var column = api.column(idx); $(column.header()).removeClass(ctx._select.className); $(column.footer()).removeClass(ctx._select.className); // Need to loop over each cell, rather than just using // `column().nodes()` as cells which are individually selected should // not have the `selected` class removed from them api.cells(null, idx) .indexes() .each(function (cellIdx) { var data = ctx.aoData[cellIdx.row]; var cellSelected = data._selected_cells; if (data.anCells && (!cellSelected || !cellSelected[cellIdx.column])) { $(data.anCells[cellIdx.column]).removeClass(ctx._select.className); } }); }); this.iterator('table', function (ctx, i) { eventTrigger(api, 'deselect', ['column', api[i]], true); }); return this; }); apiRegisterPlural('cells().deselect()', 'cell().deselect()', function () { var api = this; this.iterator('cell', function (ctx, rowIdx, colIdx) { var data = ctx.aoData[rowIdx]; if (data._selected_cells !== undefined) { data._selected_cells[colIdx] = false; } // Remove class only if the cells exist, and the cell is not column // selected, in which case the class should remain (since it is selected // in the column) if (data.anCells && !ctx.aoColumns[colIdx]._select_selected) { $(data.anCells[colIdx]).removeClass(ctx._select.className); } }); this.iterator('table', function (ctx, i) { eventTrigger(api, 'deselect', ['cell', api[i]], true); }); return this; }); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Buttons */ function i18n(label, def) { return function (dt) { return dt.i18n('buttons.' + label, def); }; } // Common events with suitable namespaces function namespacedEvents(config) { var unique = config._eventNamespace; return 'draw.dt.DT' + unique + ' select.dt.DT' + unique + ' deselect.dt.DT' + unique; } function enabled(dt, config) { if ($.inArray('rows', config.limitTo) !== -1 && dt.rows({ selected: true }).any()) { return true; } if ($.inArray('columns', config.limitTo) !== -1 && dt.columns({ selected: true }).any()) { return true; } if ($.inArray('cells', config.limitTo) !== -1 && dt.cells({ selected: true }).any()) { return true; } return false; } var _buttonNamespace = 0; $.extend(DataTable.ext.buttons, { selected: { text: i18n('selected', 'Selected'), className: 'buttons-selected', limitTo: ['rows', 'columns', 'cells'], init: function (dt, node, config) { var that = this; config._eventNamespace = '.select' + _buttonNamespace++; // .DT namespace listeners are removed by DataTables automatically // on table destroy dt.on(namespacedEvents(config), function () { that.enable(enabled(dt, config)); }); this.disable(); }, destroy: function (dt, node, config) { dt.off(config._eventNamespace); } }, selectedSingle: { text: i18n('selectedSingle', 'Selected single'), className: 'buttons-selected-single', init: function (dt, node, config) { var that = this; config._eventNamespace = '.select' + _buttonNamespace++; dt.on(namespacedEvents(config), function () { var count = dt.rows({ selected: true }).flatten().length + dt.columns({ selected: true }).flatten().length + dt.cells({ selected: true }).flatten().length; that.enable(count === 1); }); this.disable(); }, destroy: function (dt, node, config) { dt.off(config._eventNamespace); } }, selectAll: { text: i18n('selectAll', 'Select all'), className: 'buttons-select-all', action: function (e, dt, node, config) { var items = this.select.items(); var mod = config.selectorModifier; if (mod) { if (typeof mod === 'function') { mod = mod.call(dt, e, dt, node, config); } this[items + 's'](mod).select(); } else { this[items + 's']().select(); } } // selectorModifier can be specified }, selectNone: { text: i18n('selectNone', 'Deselect all'), className: 'buttons-select-none', action: function () { clear(this.settings()[0], true); }, init: function (dt, node, config) { var that = this; config._eventNamespace = '.select' + _buttonNamespace++; dt.on(namespacedEvents(config), function () { var count = dt.rows({ selected: true }).flatten().length + dt.columns({ selected: true }).flatten().length + dt.cells({ selected: true }).flatten().length; that.enable(count > 0); }); this.disable(); }, destroy: function (dt, node, config) { dt.off(config._eventNamespace); } }, showSelected: { text: i18n('showSelected', 'Show only selected'), className: 'buttons-show-selected', action: function (e, dt, node, conf) { // Works by having a filtering function which will reduce to the selected // items only. So we can re-reference the function it gets stored in the // `conf` object if (conf._filter) { var idx = DataTable.ext.search.indexOf(conf._filter); if (idx !== -1) { DataTable.ext.search.splice(idx, 1); conf._filter = null; } this.active(false); } else { var fn = function (s, data, idx) { // Need to be sure we are operating on our table! if (s !== dt.settings()[0]) { return true; } let row = s.aoData[idx]; return row._select_selected; }; conf._filter = fn; DataTable.ext.search.push(fn); this.active(true); } dt.draw(); } } }); $.each(['Row', 'Column', 'Cell'], function (i, item) { var lc = item.toLowerCase(); DataTable.ext.buttons['select' + item + 's'] = { text: i18n('select' + item + 's', 'Select ' + lc + 's'), className: 'buttons-select-' + lc + 's', action: function () { this.select.items(lc); }, init: function (dt) { var that = this; dt.on('selectItems.dt.DT', function (e, ctx, items) { that.active(items === lc); }); } }; }); $.fn.DataTable.select = DataTable.select; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Initialisation */ // DataTables creation - check if select has been defined in the options. Note // this required that the table be in the document! If it isn't then something // needs to trigger this method unfortunately. The next major release of // DataTables will rework the events and address this. $(document).on('preInit.dt.dtSelect', function (e, ctx) { if (e.namespace !== 'dt') { return; } DataTable.select.init(new DataTable.Api(ctx)); }); return DataTable; }));