/*! DataTables 1.10.2 * ©2008-2014 SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables * @version 1.10.2 * @file jquery.dataTables.js * @author SpryMedia Ltd (www.sprymedia.co.uk) * @contact www.sprymedia.co.uk/contact * @copyright Copyright 2008-2014 SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license * * This source file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. * * For details please refer to: http://www.datatables.net */ /*jslint evil: true, undef: true, browser: true */ /*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidateRow,_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,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnScrollBarWidth,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ (/** @lends */function (window, document, undefined) { (function (factory) { "use strict"; if (typeof define === 'function' && define.amd) { // Define as an AMD module if possible define('datatables', ['jquery'], factory); } else if (typeof exports === 'object') { // Node/CommonJS factory(require('jquery')); } else if (jQuery && !jQuery.fn.dataTable) { // Define using browser globals otherwise // Prevent multiple instantiations if the script is loaded twice factory(jQuery); } } (/** @lends */function ($) { "use strict"; /** * DataTables is a plug-in for the jQuery Javascript library. It is a highly * flexible tool, based upon the foundations of progressive enhancement, * which will add advanced interaction controls to any HTML table. For a * full list of features please refer to * [DataTables.net](href="http://datatables.net). * * Note that the `DataTable` object is not a global variable but is aliased * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may * be accessed. * * @class * @param {object} [init={}] Configuration object for DataTables. Options * are defined by {@link DataTable.defaults} * @requires jQuery 1.7+ * * @example * // Basic initialisation * $(document).ready( function { * $('#example').dataTable(); * } ); * * @example * // Initialisation with configuration options - in this case, disable * // pagination and sorting. * $(document).ready( function { * $('#example').dataTable( { * "paginate": false, * "sort": false * } ); * } ); */ var DataTable; /* * It is useful to have variables which are scoped locally so only the * DataTables functions can access them and they don't leak into global space. * At the same time these functions are often useful over multiple files in the * core and API, so we list, or at least document, all variables which are used * by DataTables as private variables here. This also ensures that there is no * clashing of variable names and that they can easily referenced for reuse. */ // Defined else where // _selector_run // _selector_opts // _selector_first // _selector_row_indexes var _ext; // DataTable.ext var _Api; // DataTable.Api var _api_register; // DataTable.Api.register var _api_registerPlural; // DataTable.Api.registerPlural var _re_dic = {}; var _re_new_lines = /[\r\n]/g; var _re_html = /<.*?>/g; var _re_date_start = /^[\w\+\-]/; var _re_date_end = /[\w\+\-]$/; // Escape regular expression special characters var _re_escape_regex = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-'].join('|\\') + ')', 'g'); // U+2009 is thin space and U+202F is narrow no-break space, both used in many // standards as thousands separators var _re_formatted_numeric = /[',$£€¥%\u2009\u202F]/g; 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' ? num.replace(/\./g, '').replace(_re_dic[decimalPoint], '.') : num; }; var _isNumber = function (d, decimalPoint, formatted) { var strType = typeof d === 'string'; if (decimalPoint && strType) { d = _numToDecimal(d, decimalPoint); } if (formatted && strType) { d = d.replace(_re_formatted_numeric, ''); } return _empty(d) || (!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++) { 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 _stripHtml = function (d) { return d.replace(_re_html, ''); }; /** * Find the unique elements in a source array. * * @param {array} src Source array * @return {array} Array of unique items * @ignore */ var _unique = function (src) { // 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; }; /** * 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; //console.log( key, match ); if (match[1] === 'o') { _fnHungarianMap(o[key]); } } }); o._hungarianMap = map; } /** * Convert from camel case parameters to Hungarian, based on a Hungarian map * created by _fnHungarianMap. * @param {object} src The model object which holds all parameters that can be * mapped. * @param {object} user The object to convert from camel case to Hungarian. * @param {boolean} force When set to `true`, properties which already have a * Hungarian value in the `user` object will be overwritten. Otherwise they * won't be. * @memberof DataTable#oApi */ function _fnCamelToHungarian(src, user, force) { if (!src._hungarianMap) { _fnHungarianMap(src); } var hungarianKey; $.each(user, function (key, val) { hungarianKey = src._hungarianMap[key]; if (hungarianKey !== undefined && (force || user[hungarianKey] === undefined)) { // For objects, we need to buzz down into the object to copy parameters if (hungarianKey.charAt(0) === 'o') { // Copy the camelCase options over to the hungarian if (!user[hungarianKey]) { user[hungarianKey] = {}; } $.extend(true, user[hungarianKey], user[key]); _fnCamelToHungarian(src[hungarianKey], user[hungarianKey], force); } else { user[hungarianKey] = user[key]; } } }); } /** * Language compatibility - when certain options are given, and others aren't, we * need to duplicate the values over, in order to provide backwards compatibility * with older language files. * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnLanguageCompat(lang) { var defaults = DataTable.defaults.oLanguage; var zeroRecords = lang.sZeroRecords; /* Backwards compatibility - if there is no sEmptyTable given, then use the same as * sZeroRecords - assuming that is given. */ if (!lang.sEmptyTable && zeroRecords && defaults.sEmptyTable === "No data available in table") { _fnMap(lang, lang, 'sZeroRecords', 'sEmptyTable'); } /* Likewise with loading records */ if (!lang.sLoadingRecords && zeroRecords && defaults.sLoadingRecords === "Loading...") { _fnMap(lang, lang, 'sZeroRecords', 'sLoadingRecords'); } // Old parameter name of the thousands separator mapped onto the new if (lang.sInfoThousands) { lang.sThousands = lang.sInfoThousands; } var decimal = lang.sDecimal; if (decimal) { _addNumericSort(decimal); } } /** * Map one parameter onto another * @param {object} o Object to map * @param {*} knew The new parameter name * @param {*} old The old parameter name */ var _fnCompatMap = function (o, knew, old) { if (o[knew] !== undefined) { o[old] = o[knew]; } }; /** * Provide backwards compatibility for the main DT options. Note that the new * options are mapped onto the old parameters, so this is an external interface * change only. * @param {object} init Object to map */ function _fnCompatOpts(init) { _fnCompatMap(init, 'ordering', 'bSort'); _fnCompatMap(init, 'orderMulti', 'bSortMulti'); _fnCompatMap(init, 'orderClasses', 'bSortClasses'); _fnCompatMap(init, 'orderCellsTop', 'bSortCellsTop'); _fnCompatMap(init, 'order', 'aaSorting'); _fnCompatMap(init, 'orderFixed', 'aaSortingFixed'); _fnCompatMap(init, 'paging', 'bPaginate'); _fnCompatMap(init, 'pagingType', 'sPaginationType'); _fnCompatMap(init, 'pageLength', 'iDisplayLength'); _fnCompatMap(init, 'searching', 'bFilter'); // 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'); } /** * Browser feature detection for capabilities, quirks * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnBrowserDetect(settings) { var browser = settings.oBrowser; // Scrolling feature / quirks detection var n = $('
') .css({ position: 'absolute', top: 0, left: 0, height: 1, width: 1, overflow: 'hidden' }) .append( $('
') .css({ position: 'absolute', top: 1, left: 1, width: 100, overflow: 'scroll' }) .append( $('
') .css({ width: '100%', height: 10 }) ) ) .appendTo('body'); var test = n.find('.test'); // 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 = test[0].offsetWidth === 100; // In rtl text layout, some browsers (most, but not all) will place the // scrollbar on the left, rather than the right. browser.bScrollbarLeft = test.offset().left !== 1; n.remove(); } /** * 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, null); } /** * Apply options for a column * @param {object} oSettings dataTables settings object * @param {int} iCol column index to consider * @param {object} oOptions object with sType, bVisible and bSearchable etc * @memberof DataTable#oApi */ function _fnColumnOptions(oSettings, iCol, oOptions) { var oCol = oSettings.aoColumns[iCol]; var oClasses = oSettings.oClasses; var th = $(oCol.nTh); // Try to get width information from the DOM. We can't get it from CSS // as we'd need to parse the CSS stylesheet. `width` option can override if (!oCol.sWidthOrig) { // Width attribute oCol.sWidthOrig = th.attr('width') || null; // Style attribute var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); if (t) { oCol.sWidthOrig = t[1]; } } /* User specified column options */ if (oOptions !== undefined && oOptions !== null) { // Backwards compatibility _fnCompatCols(oOptions); // Map camel case parameters to their Hungarian counterparts _fnCamelToHungarian(DataTable.defaults.column, oOptions); /* Backwards compatibility for mDataProp */ if (oOptions.mDataProp !== undefined && !oOptions.mData) { oOptions.mData = oOptions.mDataProp; } if (oOptions.sType) { oCol._sManualType = oOptions.sType; } // `class` is a reserved word in Javascript, so we need to provide // the ability to use a valid name for the camel case input if (oOptions.className && !oOptions.sClass) { oOptions.sClass = oOptions.className; } $.extend(oCol, oOptions); _fnMap(oCol, oOptions, "sWidth", "sWidthOrig"); /* iDataSort to be applied (backwards compatibility), but aDataSort will take * priority if defined */ if (typeof oOptions.iDataSort === 'number') { oCol.aDataSort = [oOptions.iDataSort]; } _fnMap(oCol, oOptions, "aDataSort"); } /* Cache the data get and set functions for speed */ var mDataSrc = oCol.mData; var mData = _fnGetObjectDataFn(mDataSrc); var mRender = oCol.mRender ? _fnGetObjectDataFn(oCol.mRender) : null; var attrTest = function (src) { return typeof src === 'string' && src.indexOf('@') !== -1; }; oCol._bAttrSrc = $.isPlainObject(mDataSrc) && ( attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) ); oCol.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); }; /* Feature sorting overrides column specific when off */ if (!oSettings.oFeatures.bSort) { oCol.bSortable = false; th.addClass(oClasses.sSortableNone); // Have to add class here as order event isn't called } /* Check that the class assignment is correct for sorting */ var bAsc = $.inArray('asc', oCol.asSorting) !== -1; var bDesc = $.inArray('desc', oCol.asSorting) !== -1; if (!oCol.bSortable || (!bAsc && !bDesc)) { oCol.sSortingClass = oClasses.sSortableNone; oCol.sSortingClassJUI = ""; } else if (bAsc && !bDesc) { oCol.sSortingClass = oClasses.sSortableAsc; oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; } else if (!bAsc && bDesc) { oCol.sSortingClass = oClasses.sSortableDesc; oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; } else { oCol.sSortingClass = oClasses.sSortable; oCol.sSortingClassJUI = oClasses.sSortJUI; } } /** * Adjust the table column widths for new data. Note: you would probably want to * do a redraw after calling this function! * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnAdjustColumnSizing(settings) { /* Not interested in doing column width calculation if auto-width is disabled */ if (settings.oFeatures.bAutoWidth !== false) { var columns = settings.aoColumns; _fnCalculateColumnWidths(settings); for (var i = 0, iLen = columns.length; i < iLen; i++) { columns[i].nTh.style.width = columns[i].sWidth; } } var scroll = settings.oScroll; if (scroll.sY !== '' || scroll.sX !== '') { _fnScrollDraw(settings); } _fnCallbackFire(settings, null, 'column-sizing', [settings]); } /** * Covert the index of a visible column to the index in the data array (take account * of hidden columns) * @param {object} oSettings dataTables settings object * @param {int} iMatch Visible column index to lookup * @returns {int} i the data index * @memberof DataTable#oApi */ function _fnVisibleToColumnIndex(oSettings, iMatch) { var aiVis = _fnGetColumns(oSettings, 'bVisible'); return typeof aiVis[iMatch] === 'number' ? aiVis[iMatch] : null; } /** * Covert the index of an index in the data array and convert it to the visible * column index (take account of hidden columns) * @param {int} iMatch Column index to lookup * @param {object} oSettings dataTables settings object * @returns {int} i the data index * @memberof DataTable#oApi */ function _fnColumnIndexToVisible(oSettings, iMatch) { var aiVis = _fnGetColumns(oSettings, 'bVisible'); var iPos = $.inArray(iMatch, aiVis); return iPos !== -1 ? iPos : null; } /** * Get the number of visible columns * @param {object} oSettings dataTables settings object * @returns {int} i the number of visible columns * @memberof DataTable#oApi */ function _fnVisbleColumns(oSettings) { return _fnGetColumns(oSettings, 'bVisible').length; } /** * Get an array of column indexes that match a given property * @param {object} oSettings dataTables settings object * @param {string} sParam Parameter in aoColumns to look for - typically * bVisible or bSearchable * @returns {array} Array of indexes with matched properties * @memberof DataTable#oApi */ function _fnGetColumns(oSettings, sParam) { var a = []; $.map(oSettings.aoColumns, function (val, i) { if (val[sParam]) { a.push(i); } }); return a; } /** * 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); // Doesn't match, so break early, since this type can't // apply to this column. Also, HTML is a special case since // it is so similar to `string`. Just a single match is // needed for a column to be html type if (!detectedType || detectedType === 'html') { break; } } // Type is valid for all data points in the column - use this // type if (detectedType) { col.sType = detectedType; break; } } // Fall back - if no type was detected, always use string if (!col.sType) { col.sType = 'string'; } } } } /** * Take the column definitions and static columns arrays and calculate how * they relate to column indexes. The callback function will then apply the * definition found for a column to a suitable configuration object. * @param {object} oSettings dataTables settings object * @param {array} aoColDefs The aoColumnDefs array that is to be applied * @param {array} aoCols The aoColumns array that defines columns individually * @param {function} fn Callback function - takes two parameters, the calculated * column index and the definition for that column. * @memberof DataTable#oApi */ function _fnApplyColumnDefs(oSettings, aoColDefs, aoCols, fn) { var i, iLen, j, jLen, k, kLen, def; var columns = oSettings.aoColumns; // Column definitions with aTargets if (aoColDefs) { /* Loop over the definitions array - loop in reverse so first instance has priority */ for (i = aoColDefs.length - 1; i >= 0; i--) { def = aoColDefs[i]; /* Each definition can target multiple columns, as it is an array */ var aTargets = def.targets !== undefined ? def.targets : def.aTargets; if (!$.isArray(aTargets)) { aTargets = [aTargets]; } for (j = 0, jLen = aTargets.length; j < jLen; j++) { if (typeof aTargets[j] === 'number' && aTargets[j] >= 0) { /* Add columns that we don't yet know about */ while (columns.length <= aTargets[j]) { _fnAddColumn(oSettings); } /* Integer, basic index */ fn(aTargets[j], def); } else if (typeof aTargets[j] === 'number' && aTargets[j] < 0) { /* Negative integer, right to left column counting */ fn(columns.length + aTargets[j], def); } else if (typeof aTargets[j] === 'string') { /* Class name matching on TH element */ for (k = 0, kLen = columns.length; k < kLen; k++) { if (aTargets[j] == "_all" || $(columns[k].nTh).hasClass(aTargets[j])) { fn(k, def); } } } } } } // Statically defined columns array if (aoCols) { for (i = 0, iLen = aoCols.length; i < iLen; i++) { fn(i, aoCols[i]); } } } /** * Add a data array to the table, creating DOM node etc. This is the parallel to * _fnGatherData, but for adding rows from a Javascript source, rather than a * DOM source. * @param {object} oSettings dataTables settings object * @param {array} aData data array to be added * @param {node} [nTr] TR element to add to the table - optional. If not given, * DataTables will create a row automatically * @param {array} [anTds] Array of TD|TH elements for the row - must be given * if nTr is. * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed * @memberof DataTable#oApi */ function _fnAddData(oSettings, aDataIn, nTr, anTds) { /* Create the object for storing information about this new row */ var iRow = oSettings.aoData.length; var oData = $.extend(true, {}, DataTable.models.oRow, { src: nTr ? 'dom' : 'data' }); oData._aData = aDataIn; oSettings.aoData.push(oData); /* Create the cells */ var nTd, sThisType; var columns = oSettings.aoColumns; for (var i = 0, iLen = columns.length; i < iLen; i++) { // When working with a row, the data source object must be populated. In // all other cases, the data source object is already populated, so we // don't overwrite it, which might break bindings etc if (nTr) { _fnSetCellData(oSettings, iRow, i, _fnGetCellData(oSettings, iRow, i)); } columns[i].sType = null; } /* Add to the display array */ oSettings.aiDisplayMaster.push(iRow); /* Create the DOM information, or register it if already present */ if (nTr || !oSettings.oFeatures.bDeferRender) { _fnCreateTr(oSettings, iRow, nTr, anTds); } return iRow; } /** * Add one or more TR elements to the table. Generally we'd expect to * use this for reading data from a DOM sourced table, but it could be * used for an TR element. Note that if a TR is given, it is used (i.e. * it is not cloned). * @param {object} settings dataTables settings object * @param {array|node|jQuery} trs The TR element(s) to add to the table * @returns {array} Array of indexes for the added rows * @memberof DataTable#oApi */ function _fnAddTr(settings, trs) { var row; // Allow an individual node to be passed in if (!(trs instanceof $)) { trs = $(trs); } return trs.map(function (i, el) { row = _fnGetRowElements(settings, el); return _fnAddData(settings, row.data, el, row.cells); }); } /** * Take a TR element and convert it to an index in aoData * @param {object} oSettings dataTables settings object * @param {node} n the TR element to find * @returns {int} index if the node is found, null if not * @memberof DataTable#oApi */ function _fnNodeToDataIndex(oSettings, n) { return (n._DT_RowIndex !== undefined) ? n._DT_RowIndex : null; } /** * Take a TD element and convert it into a column data index (not the visible index) * @param {object} oSettings dataTables settings object * @param {int} iRow The row number the TD/TH can be found in * @param {node} n The TD/TH element to find * @returns {int} index if the node is found, -1 if not * @memberof DataTable#oApi */ function _fnNodeToColumnIndex(oSettings, iRow, n) { return $.inArray(n, oSettings.aoData[iRow].anCells); } /** * Get the data for a given cell from the internal cache, taking into account data mapping * @param {object} settings dataTables settings object * @param {int} rowIdx aoData row id * @param {int} colIdx Column index * @param {string} type data get type ('display', 'type' 'filter' 'sort') * @returns {*} Cell data * @memberof DataTable#oApi */ function _fnGetCellData(settings, rowIdx, colIdx, type) { var draw = settings.iDraw; var col = settings.aoColumns[colIdx]; var rowData = settings.aoData[rowIdx]._aData; var defaultContent = col.sDefaultContent; var cellData = col.fnGetData(rowData, type, { settings: settings, row: rowIdx, col: colIdx }); if (cellData === undefined) { if (settings.iDrawError != draw && defaultContent === null) { _fnLog(settings, 0, "Requested unknown parameter " + (typeof col.mData == 'function' ? '{function}' : "'" + col.mData + "'") + " for row " + rowIdx, 4); settings.iDrawError = draw; } return defaultContent; } /* When the data source is null, we can use default column data */ if ((cellData === rowData || cellData === null) && defaultContent !== null) { cellData = defaultContent; } else if (typeof cellData === 'function') { // If the data source is a function, then we run it and use the return, // executing in the scope of the data object (for instances) return cellData.call(rowData); } if (cellData === null && type == 'display') { return ''; } return cellData; } /** * Set the value for a specific cell, into the internal data cache * @param {object} settings dataTables settings object * @param {int} rowIdx aoData row id * @param {int} colIdx Column index * @param {*} val Value to set * @memberof DataTable#oApi */ function _fnSetCellData(settings, rowIdx, colIdx, val) { var col = settings.aoColumns[colIdx]; var rowData = settings.aoData[rowIdx]._aData; col.fnSetData(rowData, val, { settings: settings, row: rowIdx, col: colIdx }); } // Private variable that is used to match action syntax in the data property object var __reArray = /\[.*?\]$/; var __reFn = /\(\)$/; /** * Split string on periods, taking into account escaped periods * @param {string} str String to split * @return {array} Split string */ function _fnSplitObjNotation(str) { return $.map(str.match(/(\\.|[^\.])+/g), function (s) { return s.replace(/\\./g, '.'); }); } /** * Return a function that can be used to get data from a source object, taking * into account the ability to use nested objects as a source * @param {string|int|function} mSource The data source for the object * @returns {function} Data get function * @memberof DataTable#oApi */ function _fnGetObjectDataFn(mSource) { if ($.isPlainObject(mSource)) { /* Build an object of get functions, and wrap them in a single call */ var o = {}; $.each(mSource, function (key, val) { if (val) { o[key] = _fnGetObjectDataFn(val); } }); return function (data, type, row, meta) { var t = o[type] || o._; return t !== undefined ? t(data, type, row, meta) : data; }; } else if (mSource === null) { /* Give an empty string for rendering / sorting etc */ return function (data) { // type, row and meta also passed, but not used return data; }; } else if (typeof mSource === 'function') { return function (data, type, row, meta) { return mSource(data, type, row, meta); }; } else if (typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1)) { /* If there is a . in the source string then the data source is in a * nested object so we loop over the data for each level to get the next * level down. On each loop we test for undefined, and if found immediately * return. This allows entire objects to be missing and sDefaultContent to * be used if defined, rather than throwing an error */ var fetchData = function (data, type, src) { var arrayNotation, funcNotation, out, innerSrc; if (src !== "") { var a = _fnSplitObjNotation(src); for (var i = 0, iLen = a.length; i < iLen; i++) { // Check if we are dealing with special notation arrayNotation = a[i].match(__reArray); funcNotation = a[i].match(__reFn); if (arrayNotation) { // Array notation a[i] = a[i].replace(__reArray, ''); // Condition allows simply [] to be passed in if (a[i] !== "") { data = data[a[i]]; } out = []; // Get the remainder of the nested object to get a.splice(0, i + 1); innerSrc = a.join('.'); // Traverse each entry in the array getting the properties requested for (var j = 0, jLen = data.length; j < jLen; j++) { out.push(fetchData(data[j], type, innerSrc)); } // If a string is given in between the array notation indicators, that // is used to join the strings together, otherwise an array is returned var join = arrayNotation[0].substring(1, arrayNotation[0].length - 1); data = (join === "") ? out : out.join(join); // The inner call to fetchData has already traversed through the remainder // of the source requested, so we exit from the loop break; } else if (funcNotation) { // Function call a[i] = a[i].replace(__reFn, ''); data = data[a[i]](); continue; } if (data === null || data[a[i]] === undefined) { return undefined; } data = data[a[i]]; } } return data; }; return function (data, type) { // row and meta also passed, but not used return fetchData(data, type, mSource); }; } else { /* Array or flat object mapping */ return function (data, type) { // row and meta also passed, but not used return data[mSource]; }; } } /** * Return a function that can be used to set data from a source object, taking * into account the ability to use nested objects as a source * @param {string|int|function} mSource The data source for the object * @returns {function} Data set function * @memberof DataTable#oApi */ function _fnSetObjectDataFn(mSource) { if ($.isPlainObject(mSource)) { /* Unlike get, only the underscore (global) option is used for for * setting data since we don't know the type here. This is why an object * option is not documented for `mData` (which is read/write), but it is * for `mRender` which is read only. */ return _fnSetObjectDataFn(mSource._); } else if (mSource === null) { /* Nothing to do when the data source is null */ return function () { }; } else if (typeof mSource === 'function') { return function (data, val, meta) { mSource(data, 'set', val, meta); }; } else if (typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1)) { /* Like the get, we need to get data from a nested object */ var setData = function (data, val, src) { var a = _fnSplitObjNotation(src), b; var aLast = a[a.length - 1]; var arrayNotation, funcNotation, o, innerSrc; for (var i = 0, iLen = a.length - 1; i < iLen; i++) { // Check if we are dealing with an array notation request arrayNotation = a[i].match(__reArray); funcNotation = a[i].match(__reFn); if (arrayNotation) { a[i] = a[i].replace(__reArray, ''); data[a[i]] = []; // Get the remainder of the nested object to set so we can recurse b = a.slice(); b.splice(0, i + 1); innerSrc = b.join('.'); // Traverse each entry in the array setting the properties requested for (var j = 0, jLen = val.length; j < jLen; j++) { o = {}; setData(o, val[j], innerSrc); data[a[i]].push(o); } // The inner call to setData has already traversed through the remainder // of the source and has set the data, thus we can exit here return; } else if (funcNotation) { // Function call a[i] = a[i].replace(__reFn, ''); data = data[a[i]](val); } // If the nested object doesn't currently exist - since we are // trying to set the value - create it if (data[a[i]] === null || data[a[i]] === undefined) { data[a[i]] = {}; } data = data[a[i]]; } // Last item in the input - i.e, the actual set if (aLast.match(__reFn)) { // Function call data = data[aLast.replace(__reFn, '')](val); } else { // If array notation is used, we just want to strip it and use the property name // and assign the value. If it isn't used, then we get the result we want anyway data[aLast.replace(__reArray, '')] = val; } }; return function (data, val) { // meta is also passed in, but not used return setData(data, val, mSource); }; } else { /* Array or flat object mapping */ return function (data, val) { // meta is also passed in, but not used data[mSource] = val; }; } } /** * Return an array with the full table data * @param {object} oSettings dataTables settings object * @returns array {array} aData Master data array * @memberof DataTable#oApi */ function _fnGetDataMaster(settings) { return _pluck(settings.aoData, '_aData'); } /** * Nuke the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnClearTable(settings) { settings.aoData.length = 0; settings.aiDisplayMaster.length = 0; settings.aiDisplay.length = 0; } /** * 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 * @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 _fnInvalidateRow(settings, rowIdx, src, column) { var row = settings.aoData[rowIdx]; var i, ien; // 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).data; } else { // Reading from data object, update the DOM var cells = row.anCells; var cell; if (cells) { for (i = 0, ien = cells.length; i < ien; i++) { cell = cells[i]; // 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); } cells[i].innerHTML = _fnGetCellData(settings, rowIdx, i, 'display'); } } } 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 (column !== undefined) { cols[column].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(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 * @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) { var d = [], tds = [], td = row.firstChild, name, col, o, i = 0, contents, columns = settings.aoColumns; var attr = function (str, data, td) { if (typeof str === 'string') { var idx = str.indexOf('@'); if (idx !== -1) { var src = str.substring(idx + 1); o['@' + src] = td.getAttribute(src); } } }; var cellProcess = function (cell) { col = columns[i]; contents = $.trim(cell.innerHTML); if (col && col._bAttrSrc) { o = { display: contents }; attr(col.mData.sort, o, cell); attr(col.mData.type, o, cell); attr(col.mData.filter, o, cell); d.push(o); } else { d.push(contents); } i++; }; if (td) { // `tr` element 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]); } } return { data: d, cells: tds }; } /** * Create a new TR element (and it's TD children) for a row * @param {object} oSettings dataTables settings object * @param {int} iRow Row to consider * @param {node} [nTrIn] TR element to add to the table - optional. If not given, * DataTables will create a row automatically * @param {array} [anTds] Array of TD|TH elements for the row - must be given * if nTr is. * @memberof DataTable#oApi */ function _fnCreateTr(oSettings, iRow, nTrIn, anTds) { var row = oSettings.aoData[iRow], rowData = row._aData, cells = [], nTr, nTd, oCol, i, iLen; if (row.nTr === null) { nTr = nTrIn || document.createElement('tr'); row.nTr = nTr; row.anCells = cells; /* Use a private property on the node to allow reserve mapping from the node * to the aoData array for fast look up */ nTr._DT_RowIndex = iRow; /* Special parameters can be given by the data source to be used on the row */ _fnRowAttributes(row); /* Process each column */ for (i = 0, iLen = oSettings.aoColumns.length; i < iLen; i++) { oCol = oSettings.aoColumns[i]; nTd = nTrIn ? anTds[i] : document.createElement(oCol.sCellType); cells.push(nTd); // Need to create the HTML if new, or if a rendering function is defined if (!nTrIn || oCol.mRender || oCol.mData !== i) { nTd.innerHTML = _fnGetCellData(oSettings, iRow, i, 'display'); } /* Add user defined class */ if (oCol.sClass) { nTd.className += ' ' + oCol.sClass; } // Visibility - add or remove as required if (oCol.bVisible && !nTrIn) { nTr.appendChild(nTd); } else if (!oCol.bVisible && nTrIn) { nTd.parentNode.removeChild(nTd); } if (oCol.fnCreatedCell) { oCol.fnCreatedCell.call(oSettings.oInstance, nTd, _fnGetCellData(oSettings, iRow, i), rowData, iRow, i ); } } _fnCallbackFire(oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow]); } // Remove once webkit bug 131819 and Chromium bug 365619 have been resolved // and deployed row.nTr.setAttribute('role', 'row'); } /** * Add attributes to a row based on the special `DT_*` parameters in a data * source object. * @param {object} DataTables row object for the row to be modified * @memberof DataTable#oApi */ function _fnRowAttributes(row) { var tr = row.nTr; var data = row._aData; if (tr) { if (data.DT_RowId) { tr.id = data.DT_RowId; } 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_RowData) { $(tr).data(data.DT_RowData); } } } /** * Create the HTML header for the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnBuildHead(oSettings) { var i, ien, cell, row, column; var thead = oSettings.nTHead; var tfoot = oSettings.nTFoot; var createHeader = $('th, td', thead).length === 0; var classes = oSettings.oClasses; var columns = oSettings.aoColumns; if (createHeader) { row = $('').appendTo(thead); } for (i = 0, ien = columns.length; i < ien; i++) { column = columns[i]; cell = $(column.nTh).addClass(column.sClass); if (createHeader) { cell.appendTo(row); } // 1.11 move into sorting if (oSettings.oFeatures.bSort) { cell.addClass(column.sSortingClass); if (column.bSortable !== false) { cell .attr('tabindex', oSettings.iTabIndex) .attr('aria-controls', oSettings.sTableId); _fnSortAttachListener(oSettings, column.nTh, i); } } if (column.sTitle != cell.html()) { cell.html(column.sTitle); } _fnRenderer(oSettings, 'header')( oSettings, cell, column, classes ); } if (createHeader) { _fnDetectHeader(oSettings.aoHeader, thead); } /* ARIA role for the rows */ $(thead).find('>tr').attr('role', 'row'); /* Deal with the footer - add classes if required */ $(thead).find('>tr>th, >tr>td').addClass(classes.sHeaderTH); $(tfoot).find('>tr>th, >tr>td').addClass(classes.sFooterTH); // Cache the footer cells. Note that we only take the cells from the first // row in the footer. If there is more than one row the user wants to // interact with, they need to use the table().foot() method. Note also this // allows cells to be used for multiple columns using colspan if (tfoot !== null) { var cells = oSettings.aoFooter[0]; for (i = 0, ien = cells.length; i < ien; i++) { column = columns[i]; column.nTf = cells[i].cell; if (column.sClass) { $(column.nTf).addClass(column.sClass); } } } } /** * Draw the header (or footer) element based on the column visibility states. The * methodology here is to use the layout array from _fnDetectHeader, modified for * the instantaneous column visibility, to construct the new layout. The grid is * traversed over cell at a time in a rows x columns grid fashion, although each * cell insert can cover multiple elements in the grid - which is tracks using the * aApplied array. Cell inserts in the grid will only occur where there isn't * already a cell in that position. * @param {object} oSettings dataTables settings object * @param array {objects} aoSource Layout array from _fnDetectHeader * @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc, * @memberof DataTable#oApi */ function _fnDrawHead(oSettings, aoSource, bIncludeHidden) { var i, iLen, j, jLen, k, kLen, n, nLocalTr; var aoLocal = []; var aApplied = []; var iColumns = oSettings.aoColumns.length; var iRowspan, iColspan; if (!aoSource) { return; } if (bIncludeHidden === undefined) { bIncludeHidden = false; } /* Make a copy of the master layout array, but without the visible columns in it */ for (i = 0, iLen = aoSource.length; i < iLen; i++) { aoLocal[i] = aoSource[i].slice(); aoLocal[i].nTr = aoSource[i].nTr; /* Remove any columns which are currently hidden */ for (j = iColumns - 1; j >= 0; j--) { if (!oSettings.aoColumns[j].bVisible && !bIncludeHidden) { aoLocal[i].splice(j, 1); } } /* Prep the applied array - it needs an element for each row */ aApplied.push([]); } for (i = 0, iLen = aoLocal.length; i < iLen; i++) { nLocalTr = aoLocal[i].nTr; /* All cells are going to be replaced, so empty out the row */ if (nLocalTr) { while ((n = nLocalTr.firstChild)) { nLocalTr.removeChild(n); } } for (j = 0, jLen = aoLocal[i].length; j < jLen; j++) { iRowspan = 1; iColspan = 1; /* Check to see if there is already a cell (row/colspan) covering our target * insert point. If there is, then there is nothing to do. */ if (aApplied[i][j] === undefined) { nLocalTr.appendChild(aoLocal[i][j].cell); aApplied[i][j] = 1; /* Expand the cell to cover as many rows as needed */ while (aoLocal[i + iRowspan] !== undefined && aoLocal[i][j].cell == aoLocal[i + iRowspan][j].cell) { aApplied[i + iRowspan][j] = 1; iRowspan++; } /* Expand the cell to cover as many columns as needed */ while (aoLocal[i][j + iColspan] !== undefined && aoLocal[i][j].cell == aoLocal[i][j + iColspan].cell) { /* Must update the applied array over the rows for the columns */ for (k = 0; k < iRowspan; k++) { aApplied[i + k][j + iColspan] = 1; } iColspan++; } /* Do the actual expansion in the DOM */ $(aoLocal[i][j].cell) .attr('rowspan', iRowspan) .attr('colspan', iColspan); } } } } /** * Insert the required TR nodes into the table for display * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnDraw(oSettings) { /* Provide a pre-callback function which can be used to cancel the draw is false is returned */ var aPreDraw = _fnCallbackFire(oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings]); if ($.inArray(false, aPreDraw) !== -1) { _fnProcessingDisplay(oSettings, false); return; } var i, iLen, n; var anRows = []; var iRowCount = 0; var asStripeClasses = oSettings.asStripeClasses; var iStripes = asStripeClasses.length; var iOpenRows = oSettings.aoOpenRows.length; var oLang = oSettings.oLanguage; var iInitDisplayStart = oSettings.iInitDisplayStart; var bServerSide = _fnDataSource(oSettings) == 'ssp'; var aiDisplay = oSettings.aiDisplay; oSettings.bDrawing = true; /* Check and see if we have an initial draw position from state saving */ if (iInitDisplayStart !== undefined && iInitDisplayStart !== -1) { oSettings._iDisplayStart = bServerSide ? iInitDisplayStart : iInitDisplayStart >= oSettings.fnRecordsDisplay() ? 0 : iInitDisplayStart; oSettings.iInitDisplayStart = -1; } var iDisplayStart = oSettings._iDisplayStart; var iDisplayEnd = oSettings.fnDisplayEnd(); /* Server-side processing draw intercept */ if (oSettings.bDeferLoading) { oSettings.bDeferLoading = false; oSettings.iDraw++; _fnProcessingDisplay(oSettings, false); } else if (!bServerSide) { oSettings.iDraw++; } else if (!oSettings.bDestroying && !_fnAjaxUpdate(oSettings)) { return; } if (aiDisplay.length !== 0) { var iStart = bServerSide ? 0 : iDisplayStart; var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd; for (var j = iStart; j < iEnd; j++) { var iDataIndex = aiDisplay[j]; var aoData = oSettings.aoData[iDataIndex]; if (aoData.nTr === null) { _fnCreateTr(oSettings, iDataIndex); } var nRow = aoData.nTr; /* Remove the old striping classes and then add the new one */ if (iStripes !== 0) { var sStripe = asStripeClasses[iRowCount % iStripes]; if (aoData._sRowStripe != sStripe) { $(nRow).removeClass(aoData._sRowStripe).addClass(sStripe); aoData._sRowStripe = sStripe; } } /* Row callback functions - might want to manipulate the row */ _fnCallbackFire(oSettings, 'aoRowCallback', null, [nRow, aoData._aData, iRowCount, j]); anRows.push(nRow); iRowCount++; } } else { /* Table is empty - create a row with an empty message in it */ var sZero = oLang.sZeroRecords; if (oSettings.iDraw == 1 && _fnDataSource(oSettings) == 'ajax') { sZero = oLang.sLoadingRecords; } else if (oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0) { sZero = oLang.sEmptyTable; } anRows[0] = $('', {'class': iStripes ? asStripeClasses[0] : ''}) .append($('', { 'valign': 'top', 'colSpan': _fnVisbleColumns(oSettings), 'class': oSettings.oClasses.sRowEmpty }).html(sZero))[0]; } /* Header and footer callbacks */ _fnCallbackFire(oSettings, 'aoHeaderCallback', 'header', [$(oSettings.nTHead).children('tr')[0], _fnGetDataMaster(oSettings), iDisplayStart, iDisplayEnd, aiDisplay]); _fnCallbackFire(oSettings, 'aoFooterCallback', 'footer', [$(oSettings.nTFoot).children('tr')[0], _fnGetDataMaster(oSettings), iDisplayStart, iDisplayEnd, aiDisplay]); var body = $(oSettings.nTBody); body.children().detach(); body.append($(anRows)); /* Call all required callback functions for the end of a draw */ _fnCallbackFire(oSettings, 'aoDrawCallback', 'draw', [oSettings]); /* Draw is complete, sorting and filtering must be as well */ oSettings.bSorted = false; oSettings.bFiltered = false; oSettings.bDrawing = false; } /** * Redraw the table - taking account of the various features which are enabled * @param {object} oSettings dataTables settings object * @param {boolean} [holdPosition] Keep the current paging position. By default * the paging is reset to the first page * @memberof DataTable#oApi */ function _fnReDraw(settings, holdPosition) { var features = settings.oFeatures, sort = features.bSort, filter = features.bFilter; if (sort) { _fnSort(settings); } if (filter) { _fnFilterComplete(settings, settings.oPreviousSearch); } else { // No filtering, so we want to just use the display master settings.aiDisplay = settings.aiDisplayMaster.slice(); } if (holdPosition !== true) { settings._iDisplayStart = 0; } // Let any modules know about the draw hold position state (used by // scrolling internally) settings._drawHold = holdPosition; _fnDraw(settings); settings._drawHold = false; } /** * Add the options to the page HTML for the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnAddOptionsHtml(oSettings) { var classes = oSettings.oClasses; var table = $(oSettings.nTable); var holding = $('
').insertBefore(table); // Holding element for speed var features = oSettings.oFeatures; // All DataTables are wrapped in a div var insert = $('
', { id: oSettings.sTableId + '_wrapper', 'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' ' + classes.sNoFooter) }); oSettings.nHolding = holding[0]; oSettings.nTableWrapper = insert[0]; oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling; /* Loop over the user set positioning and place the elements as needed */ var aDom = oSettings.sDom.split(''); var featureNode, cOption, nNewNode, cNext, sAttr, j; for (var i = 0; i < aDom.length; i++) { featureNode = null; cOption = aDom[i]; if (cOption == '<') { /* New container div */ nNewNode = $('
')[0]; /* Check to see if we should append an id and/or a class name to the container */ cNext = aDom[i + 1]; if (cNext == "'" || cNext == '"') { sAttr = ""; j = 2; while (aDom[i + j] != cNext) { sAttr += aDom[i + j]; j++; } /* Replace jQuery UI constants @todo depreciated */ if (sAttr == "H") { sAttr = classes.sJUIHeader; } else if (sAttr == "F") { sAttr = classes.sJUIFooter; } /* The attribute can be in the format of "#id.class", "#id" or "class" This logic * breaks the string into parts and applies them as needed */ if (sAttr.indexOf('.') != -1) { var aSplit = sAttr.split('.'); nNewNode.id = aSplit[0].substr(1, aSplit[0].length - 1); nNewNode.className = aSplit[1]; } else if (sAttr.charAt(0) == "#") { nNewNode.id = sAttr.substr(1, sAttr.length - 1); } else { nNewNode.className = sAttr; } i += j; /* Move along the position array */ } insert.append(nNewNode); insert = $(nNewNode); } else if (cOption == '>') { /* End container div */ insert = insert.parent(); } // @todo Move options into their own plugins? else if (cOption == 'l' && features.bPaginate && features.bLengthChange) { /* Length */ featureNode = _fnFeatureHtmlLength(oSettings); } else if (cOption == 'f' && features.bFilter) { /* Filter */ featureNode = _fnFeatureHtmlFilter(oSettings); } else if (cOption == 'r' && features.bProcessing) { /* pRocessing */ featureNode = _fnFeatureHtmlProcessing(oSettings); } else if (cOption == 't') { /* Table */ featureNode = _fnFeatureHtmlTable(oSettings); } else if (cOption == 'i' && features.bInfo) { /* Info */ featureNode = _fnFeatureHtmlInfo(oSettings); } else if (cOption == 'p' && features.bPaginate) { /* Pagination */ featureNode = _fnFeatureHtmlPaginate(oSettings); } else if (DataTable.ext.feature.length !== 0) { /* Plug-in features */ var aoFeatures = DataTable.ext.feature; for (var k = 0, kLen = aoFeatures.length; k < kLen; k++) { if (cOption == aoFeatures[k].cFeature) { featureNode = aoFeatures[k].fnInit(oSettings); break; } } } /* Add to the 2D features array */ if (featureNode) { var aanFeatures = oSettings.aanFeatures; if (!aanFeatures[cOption]) { aanFeatures[cOption] = []; } aanFeatures[cOption].push(featureNode); insert.append(featureNode); } } /* Built our DOM structure - replace the holding div with what we want */ holding.replaceWith(insert); } /** * Use the DOM source to create up an array of header cells. The idea here is to * create a layout grid (array) of rows x columns, which contains a reference * to the cell that that point in the grid (regardless of col/rowspan), such that * any column / row could be removed and the new grid constructed * @param array {object} aLayout Array to store the calculated layout in * @param {node} nThead The header/footer element for the table * @memberof DataTable#oApi */ function _fnDetectHeader(aLayout, nThead) { var nTrs = $(nThead).children('tr'); var nTr, nCell; var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan; var bUnique; var fnShiftCol = function (a, i, j) { var k = a[i]; while (k[j]) { j++; } return j; }; aLayout.splice(0, aLayout.length); /* We know how many rows there are in the layout - so prep it */ for (i = 0, iLen = nTrs.length; i < iLen; i++) { aLayout.push([]); } /* Calculate a layout array */ for (i = 0, iLen = nTrs.length; i < iLen; i++) { nTr = nTrs[i]; iColumn = 0; /* For every cell in the row... */ nCell = nTr.firstChild; while (nCell) { if (nCell.nodeName.toUpperCase() == "TD" || nCell.nodeName.toUpperCase() == "TH") { /* Get the col and rowspan attributes from the DOM and sanitise them */ iColspan = nCell.getAttribute('colspan') * 1; iRowspan = nCell.getAttribute('rowspan') * 1; iColspan = (!iColspan || iColspan === 0 || iColspan === 1) ? 1 : iColspan; iRowspan = (!iRowspan || iRowspan === 0 || iRowspan === 1) ? 1 : iRowspan; /* There might be colspan cells already in this row, so shift our target * accordingly */ iColShifted = fnShiftCol(aLayout, i, iColumn); /* Cache calculation for unique columns */ bUnique = iColspan === 1 ? true : false; /* If there is col / rowspan, copy the information into the layout grid */ for (l = 0; l < iColspan; l++) { for (k = 0; k < iRowspan; k++) { aLayout[i + k][iColShifted + l] = { "cell": nCell, "unique": bUnique }; aLayout[i + k].nTr = nTr; } } } nCell = nCell.nextSibling; } } } /** * Get an array of unique th elements, one for each column * @param {object} oSettings dataTables settings object * @param {node} nHeader automatically detect the layout from this node - optional * @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional * @returns array {node} aReturn list of unique th's * @memberof DataTable#oApi */ function _fnGetUniqueThs(oSettings, nHeader, aLayout) { var aReturn = []; if (!aLayout) { aLayout = oSettings.aoHeader; if (nHeader) { aLayout = []; _fnDetectHeader(aLayout, nHeader); } } for (var i = 0, iLen = aLayout.length; i < iLen; i++) { for (var j = 0, jLen = aLayout[i].length; j < jLen; j++) { if (aLayout[i][j].unique && (!aReturn[j] || !oSettings.bSortCellsTop)) { aReturn[j] = aLayout[i][j].cell; } } } return aReturn; } /** * Create an Ajax call based on the table's settings, taking into account that * parameters can have multiple forms, and backwards compatibility. * * @param {object} oSettings dataTables settings object * @param {array} data Data to send to the server, required by * DataTables - may be augmented by developer callbacks * @param {function} fn Callback function to run when data is obtained */ function _fnBuildAjax(oSettings, data, fn) { // Compatibility with 1.9-, allow fnServerData and event to manipulate _fnCallbackFire(oSettings, 'aoServerParams', 'serverParams', [data]); // Convert to object based for 1.10+ if using the old array scheme which can // come from server-side processing or serverParams if (data && $.isArray(data)) { var tmp = {}; var rbracket = /(.*?)\[\]$/; $.each(data, function (key, val) { var match = val.name.match(rbracket); if (match) { // Support for arrays var name = match[0]; if (!tmp[name]) { tmp[name] = []; } tmp[name].push(val.value); } else { tmp[val.name] = val.value; } }); data = tmp; } var ajaxData; var ajax = oSettings.ajax; var instance = oSettings.oInstance; if ($.isPlainObject(ajax) && ajax.data) { ajaxData = ajax.data; var newData = $.isFunction(ajaxData) ? ajaxData(data) : // fn can manipulate data or return an object ajaxData; // object or array to merge // If the function returned an object, use that alone data = $.isFunction(ajaxData) && newData ? newData : $.extend(true, data, newData); // Remove the data property as we've resolved it already and don't want // jQuery to do it again (it is restored at the end of the function) delete ajax.data; } var baseAjax = { "data": data, "success": function (json) { var error = json.error || json.sError; if (error) { oSettings.oApi._fnLog(oSettings, 0, error); } oSettings.json = json; _fnCallbackFire(oSettings, null, 'xhr', [oSettings, json]); fn(json); }, "dataType": "json", "cache": false, "type": oSettings.sServerMethod, "error": function (xhr, error, thrown) { var log = oSettings.oApi._fnLog; if (error == "parsererror") { log(oSettings, 0, 'Invalid JSON response', 1); } else if (xhr.readyState === 4) { log(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}; }), fn, oSettings ); } else if (oSettings.sAjaxSource || typeof ajax === 'string') { // DataTables 1.9- compatibility oSettings.jqXHR = $.ajax($.extend(baseAjax, { url: ajax || oSettings.sAjaxSource })); } else if ($.isFunction(ajax)) { // Is a function - let the caller define what needs to be done oSettings.jqXHR = ajax.call(instance, data, fn, oSettings); } else { // Object to extend the base settings oSettings.jqXHR = $.ajax($.extend(baseAjax, ajax)); // Restore for next time around ajax.data = ajaxData; } } /** * Update the table using an Ajax call * @param {object} settings dataTables settings object * @returns {boolean} Block the table drawing or not * @memberof DataTable#oApi */ function _fnAjaxUpdate(settings) { if (settings.bAjaxDataGet) { settings.iDraw++; _fnProcessingDisplay(settings, true); _fnBuildAjax( settings, _fnAjaxParameters(settings), function (json) { _fnAjaxUpdateDraw(settings, json); } ); return false; } return true; } /** * Build up the parameters in an object needed for a server-side processing * request. Note that this is basically done twice, is different ways - a modern * method which is used by default in DataTables 1.10 which uses objects and * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if * the sAjaxSource option is used in the initialisation, or the legacyAjax * option is set. * @param {object} oSettings dataTables settings object * @returns {bool} block the table drawing or not * @memberof DataTable#oApi */ function _fnAjaxParameters(settings) { var columns = settings.aoColumns, columnCount = columns.length, features = settings.oFeatures, preSearch = settings.oPreviousSearch, preColSearch = settings.aoPreSearchCols, i, data = [], dataProp, column, columnSearch, sort = _fnSortFlatten(settings), displayStart = settings._iDisplayStart, displayLength = features.bPaginate !== false ? settings._iDisplayLength : -1; var param = function (name, value) { data.push({'name': name, 'value': value}); }; // DataTables 1.9- compatible method param('sEcho', settings.iDraw); param('iColumns', columnCount); param('sColumns', _pluck(columns, 'sName').join(',')); param('iDisplayStart', displayStart); param('iDisplayLength', displayLength); // DataTables 1.10+ method var d = { draw: settings.iDraw, columns: [], order: [], start: displayStart, length: displayLength, search: { value: preSearch.sSearch, regex: preSearch.bRegex } }; for (i = 0; i < columnCount; i++) { column = columns[i]; columnSearch = preColSearch[i]; dataProp = typeof column.mData == "function" ? 'function' : column.mData; d.columns.push({ data: dataProp, name: column.sName, searchable: column.bSearchable, orderable: column.bSortable, search: { value: columnSearch.sSearch, regex: columnSearch.bRegex } }); param("mDataProp_" + i, dataProp); if (features.bFilter) { param('sSearch_' + i, columnSearch.sSearch); param('bRegex_' + i, columnSearch.bRegex); param('bSearchable_' + i, column.bSearchable); } if (features.bSort) { param('bSortable_' + i, column.bSortable); } } if (features.bFilter) { param('sSearch', preSearch.sSearch); param('bRegex', preSearch.bRegex); } if (features.bSort) { $.each(sort, function (i, val) { d.order.push({column: val.col, dir: val.dir}); param('iSortCol_' + i, val.col); param('sSortDir_' + i, val.dir); }); param('iSortingCols', sort.length); } // If the legacy.ajax parameter is null, then we automatically decide which // form to use, based on sAjaxSource var legacy = DataTable.ext.legacy.ajax; if (legacy === null) { return settings.sAjaxSource ? data : d; } // Otherwise, if legacy has been specified then we use that to decide on the // form return legacy ? data : d; } /** * Data the data from the server (nuking the old) and redraw the table * @param {object} oSettings dataTables settings object * @param {object} json json data return from the server. * @param {string} json.sEcho Tracking flag for DataTables to match requests * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering * @param {array} json.aaData The data to display on this page * @param {string} [json.sColumns] Column ordering (sName, comma separated) * @memberof DataTable#oApi */ function _fnAjaxUpdateDraw(settings, json) { // v1.10 uses camelCase variables, while 1.9 uses Hungarian notation. // Support both var compat = function (old, modern) { return json[old] !== undefined ? json[old] : json[modern]; }; var draw = compat('sEcho', 'draw'); var recordsTotal = compat('iTotalRecords', 'recordsTotal'); var rocordsFiltered = compat('iTotalDisplayRecords', 'recordsFiltered'); if (draw) { // Protect against out of sequence returns if (draw * 1 < settings.iDraw) { return; } settings.iDraw = draw * 1; } _fnClearTable(settings); settings._iRecordsTotal = parseInt(recordsTotal, 10); settings._iRecordsDisplay = parseInt(rocordsFiltered, 10); var data = _fnAjaxDataSrc(settings, json); for (var i = 0, ien = data.length; i < ien; i++) { _fnAddData(settings, data[i]); } settings.aiDisplay = settings.aiDisplayMaster.slice(); settings.bAjaxDataGet = false; _fnDraw(settings); if (!settings._bInitComplete) { _fnInitComplete(settings, json); } settings.bAjaxDataGet = true; _fnProcessingDisplay(settings, false); } /** * Get the data from the JSON data source to use for drawing a table. Using * `_fnGetObjectDataFn` allows the data to be sourced from a property of the * source object, or from a processing function. * @param {object} oSettings dataTables settings object * @param {object} json Data source object / array from the server * @return {array} Array of data to use */ function _fnAjaxDataSrc(oSettings, json) { var dataSrc = $.isPlainObject(oSettings.ajax) && oSettings.ajax.dataSrc !== undefined ? oSettings.ajax.dataSrc : oSettings.sAjaxDataProp; // Compatibility with 1.9-. // Compatibility with 1.9-. In order to read from aaData, check if the // default has been changed, if not, check for aaData if (dataSrc === 'data') { return json.aaData || json[dataSrc]; } return dataSrc !== "" ? _fnGetObjectDataFn(dataSrc)(json) : json; } /** * Generate the node required for filtering text * @returns {node} Filter control element * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnFeatureHtmlFilter(settings) { var classes = settings.oClasses; var tableId = settings.sTableId; var language = settings.oLanguage; var previousSearch = settings.oPreviousSearch; var features = settings.aanFeatures; var input = ''; var str = language.sSearch; str = str.match(/_INPUT_/) ? str.replace('_INPUT_', input) : str + input; var filter = $('
', { 'id': !features.f ? tableId + '_filter' : null, 'class': classes.sFilter }) .append($('