').attr('data-dt-column', 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 ] );
}
/**
* 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 ];
/* 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;
}
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" );
}
/* Cache the data get and set functions for speed */
var mDataSrc = oCol.mData;
var mData = _fnGetObjectDataFn( mDataSrc );
// The `render` option can be given as an array to access the helper rendering methods.
// The first element is the rendering method to use, the rest are the parameters to pass
if ( oCol.mRender && Array.isArray( oCol.mRender ) ) {
var copy = oCol.mRender.slice();
var name = copy.shift();
oCol.mRender = DataTable.render[name].apply(window, copy);
}
oCol._render = 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 oCol._render && type ?
oCol._render( 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;
}
}
/**
* 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 )
{
_fnCalculateColumnWidths( settings );
_fnColumnSizes( settings );
var scroll = settings.oScroll;
if ( scroll.sY !== '' || scroll.sX !== '') {
_fnScrollDraw( settings );
}
_fnCallbackFire( settings, null, 'column-sizing', [settings] );
}
/**
* Apply column sizes
*
* @param {*} settings DataTables settings object
*/
function _fnColumnSizes ( settings )
{
var cols = settings.aoColumns;
for (var i=0 ; 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= 0 )
{
/* Add columns that we don't yet know about */
while( columns.length <= target )
{
_fnAddColumn( oSettings );
}
/* Integer, basic index */
fn( target, def );
}
else if ( typeof target === 'number' && target < 0 )
{
/* Negative integer, right to left column counting */
fn( columns.length+target, def );
}
else if ( typeof target === 'string' )
{
for ( k=0, kLen=columns.length ; k=0 if successful (index of new aoData entry), -1 if failed
* @memberof DataTable#oApi
*/
function _fnAddData ( settings, dataIn, tr, tds )
{
/* Create the object for storing information about this new row */
var rowIdx = settings.aoData.length;
var rowModel = $.extend( true, {}, DataTable.models.oRow, {
src: tr ? 'dom' : 'data',
idx: rowIdx
} );
rowModel._aData = dataIn;
settings.aoData.push( rowModel );
var columns = settings.aoColumns;
for ( var i=0, iLen=columns.length ; i').appendTo(target)
}
// Add the number of cells needed to make up to the number of columns
if (row.length === 1) {
var cellCount = 0;
$('td, th', row).each(function () {
cellCount += this.colSpan;
});
for ( i=cellCount, iLen=columns.length ; i')
.html( columns[i][titleProp] || '' )
.appendTo( row );
}
}
}
var detected = _fnDetectHeader( settings, target, true );
if (side === 'header') {
settings.aoHeader = detected;
$('tr', target).addClass(classes.thead.row);
}
else {
settings.aoFooter = detected;
$('tr', target).addClass(classes.tfoot.row);
}
// Every cell needs to be passed through the renderer
$(target).children('tr').children('th, td')
.each( function () {
_fnRenderer( settings, side )(
settings, $(this), classes
);
} );
}
/**
* Build a layout structure for a header or footer
*
* @param {*} settings DataTables settings
* @param {*} source Source layout array
* @param {*} incColumns What columns should be included
* @returns Layout array in column index order
*/
function _fnHeaderLayout( settings, source, incColumns )
{
var row, column, cell;
var local = [];
var structure = [];
var columns = settings.aoColumns;
var columnCount = columns.length;
var rowspan, colspan;
if ( ! source ) {
return;
}
// Default is to work on only visible columns
if ( ! incColumns ) {
incColumns = _range(columnCount)
.filter(function (idx) {
return columns[idx].bVisible;
});
}
// Make a copy of the master layout array, but with only the columns we want
for ( row=0 ; row' )
.append( $('
', {
'colSpan': _fnVisibleColumns( settings ),
'class': settings.oClasses.empty.row
} ).html( zero ) )[0];
}
/**
* Expand the layout items into an object for the rendering function
*/
function _layoutItems (row, align, items) {
if ( Array.isArray(items)) {
for (var i=0 ; i')
.attr({
id: settings.sTableId+'_wrapper',
'class': classes.container
})
.insertBefore(table);
settings.nTableWrapper = insert[0];
if (settings.sDom) {
// Legacy
_fnLayoutDom(settings, settings.sDom, insert);
}
else {
var top = _layoutArray( settings, settings.layout, 'top' );
var bottom = _layoutArray( settings, settings.layout, 'bottom' );
var renderer = _fnRenderer( settings, 'layout' );
// Everything above - the renderer will actually insert the contents into the document
top.forEach(function (item) {
renderer( settings, insert, item );
});
// The table - always the center of attention
renderer( settings, insert, {
full: {
table: true,
contents: [ _fnFeatureHtmlTable(settings) ]
}
} );
// Everything below
bottom.forEach(function (item) {
renderer( settings, insert, item );
});
}
// Processing floats on top, so it isn't an inserted feature
_processingHtml( settings );
}
/**
* Draw the table with the legacy DOM property
* @param {*} settings DT settings object
* @param {*} dom DOM string
* @param {*} insert Insert point
*/
function _fnLayoutDom( settings, dom, insert )
{
var parts = dom.match(/(".*?")|('.*?')|./g);
var featureNode, option, newNode, next, attr;
for ( var i=0 ; i');
// Check to see if we should append an id and/or a class name to the container
next = parts[i+1];
if ( next[0] == "'" || next[0] == '"' ) {
attr = next.replace(/['"]/g, '');
var id = '', className;
/* 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 ( attr.indexOf('.') != -1 ) {
var split = attr.split('.');
id = split[0];
className = split[1];
}
else if ( attr[0] == "#" ) {
id = attr;
}
else {
className = attr;
}
newNode
.attr('id', id.substring(1))
.addClass(className);
i++; // Move along the position array
}
insert.append( newNode );
insert = newNode;
}
else if ( option == '>' ) {
// End container div
insert = insert.parent();
}
else if ( option == 't' ) {
// Table
featureNode = _fnFeatureHtmlTable( settings );
}
else
{
DataTable.ext.feature.forEach(function(feature) {
if ( option == feature.cFeature ) {
featureNode = feature.fnInit( settings );
}
});
}
// Add to the display
if ( featureNode ) {
insert.append( featureNode );
}
}
}
/**
* 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 at that point in the grid (regardless of col/rowspan), such that
* any column / row could be removed and the new grid constructed
* @param {node} thead The header/footer element for the table
* @returns {array} Calculated layout array
* @memberof DataTable#oApi
*/
function _fnDetectHeader ( settings, thead, write )
{
var columns = settings.aoColumns;
var rows = $(thead).children('tr');
var row, cell;
var i, k, l, iLen, shifted, column, colspan, rowspan;
var titleRow = settings.titleRow;
var isHeader = thead && thead.nodeName.toLowerCase() === 'thead';
var layout = [];
var unique;
var shift = function ( a, i, j ) {
var k = a[i];
while ( k[j] ) {
j++;
}
return j;
};
// We know how many rows there are in the layout - so prep it
for ( i=0, iLen=rows.length ; i')
.addClass('dt-column-' + headerFooter)
.append(cell.childNodes)
.appendTo(cell);
}
}
// If there is col / rowspan, copy the information into the layout grid
for ( l=0 ; l= 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) {
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);
}
// Microsoft often wrap JSON as a string in another JSON object
// Let's handle that automatically
if (json.d && typeof json.d === 'string') {
try {
json = JSON.parse(json.d);
} catch (e) {
// noop
}
}
oSettings.json = json;
_fnCallbackFire(oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR], true);
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 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 = {
url: typeof ajax === 'string' ? ajax : '',
data: data,
success: callback,
dataType: 'json',
cache: false,
type: oSettings.sServerMethod,
error: function (xhr, error) {
var ret = _fnCallbackFire(
oSettings,
null,
'xhr',
[oSettings, null, oSettings.jqXHR],
true
);
if (ret.indexOf(true) === -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);
}
};
// If `ajax` option is an object, extend and override our default base
if ($.isPlainObject(ajax)) {
$.extend(baseAjax, ajax);
}
// 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, baseAjax], true);
// Custom Ajax option to submit the parameters as a JSON string
if (baseAjax.submitAs === 'json' && typeof data === 'object') {
baseAjax.data = JSON.stringify(data);
if (!baseAjax.contentType) {
baseAjax.contentType = 'application/json; charset=utf-8';
}
}
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 if (ajax.url === '') {
// No url, so don't load any data. Just apply an empty data array
// to the object for the callback.
var empty = {};
_fnAjaxDataSrc(oSettings, empty, []);
callback(empty);
}
else {
// Object to extend the base settings
oSettings.jqXHR = $.ajax(baseAjax);
}
// Restore for next time around
if (ajaxData) {
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);
_fnBuildAjax(settings, _fnAjaxParameters(settings), function (json) {
_fnAjaxUpdateDraw(settings, json);
});
}
/**
* Build up the parameters in an object needed for a server-side processing
* request.
* @param {object} oSettings dataTables settings object
* @returns {bool} block the table drawing or not
* @memberof DataTable#oApi
*/
function _fnAjaxParameters(settings) {
var columns = settings.aoColumns,
features = settings.oFeatures,
preSearch = settings.oPreviousSearch,
preColSearch = settings.aoPreSearchCols,
colData = function (idx, prop) {
return typeof columns[idx][prop] === 'function' ? 'function' : columns[idx][prop];
};
return {
draw: settings.iDraw,
columns: columns.map(function (column, i) {
return {
data: colData(i, 'mData'),
name: column.sName,
searchable: column.bSearchable,
orderable: column.bSortable,
search: {
value: preColSearch[i].search,
regex: preColSearch[i].regex,
fixed: Object.keys(column.searchFixed)
.map(function (name) {
return {
name: name,
term: typeof column.searchFixed[name] !== 'function'
? column.searchFixed[name].toString()
: 'function'
};
})
}
};
}),
order: _fnSortFlatten(settings).map(function (val) {
return {
column: val.col,
dir: val.dir,
name: colData(val.col, 'sName')
};
}),
start: settings._iDisplayStart,
length: features.bPaginate ? settings._iDisplayLength : -1,
search: {
value: preSearch.search,
regex: preSearch.regex,
fixed: Object.keys(settings.searchFixed)
.map(function (name) {
return {
name: name,
term: typeof settings.searchFixed[name] !== 'function'
? settings.searchFixed[name].toString()
: 'function'
};
})
}
};
}
/**
* 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) {
var data = _fnAjaxDataSrc(settings, json);
var draw = _fnAjaxDataSrcParam(settings, 'draw', json);
var recordsTotal = _fnAjaxDataSrcParam(settings, 'recordsTotal', json);
var recordsFiltered = _fnAjaxDataSrcParam(settings, 'recordsFiltered', json);
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, iLen = data.length; i < iLen; i++) {
_fnAddData(settings, data[i]);
}
settings.aiDisplay = settings.aiDisplayMaster.slice();
_fnColumnTypes(settings);
_fnDraw(settings, true);
_fnInitComplete(settings);
_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} settings dataTables settings object
* @param {object} json Data source object / array from the server
* @return {array} Array of data to use
*/
function _fnAjaxDataSrc(settings, json, write) {
var dataProp = 'data';
if ($.isPlainObject(settings.ajax) && settings.ajax.dataSrc !== undefined) {
// Could in inside a `dataSrc` object, or not!
var dataSrc = settings.ajax.dataSrc;
// string, function and object are valid types
if (typeof dataSrc === 'string' || typeof dataSrc === 'function') {
dataProp = dataSrc;
}
else if (dataSrc.data !== undefined) {
dataProp = dataSrc.data;
}
}
if (!write) {
if (dataProp === 'data') {
// If the default, then we still want to support the old style, and safely ignore
// it if possible
return json.aaData || json[dataProp];
}
return dataProp !== '' ? _fnGetObjectDataFn(dataProp)(json) : json;
}
// set
_fnSetObjectDataFn(dataProp)(json, write);
}
/**
* Very similar to _fnAjaxDataSrc, but for the other SSP properties
* @param {*} settings DataTables settings object
* @param {*} param Target parameter
* @param {*} json JSON data
* @returns Resolved value
*/
function _fnAjaxDataSrcParam(settings, param, json) {
var dataSrc = $.isPlainObject(settings.ajax) ? settings.ajax.dataSrc : null;
if (dataSrc && dataSrc[param]) {
// Get from custom location
return _fnGetObjectDataFn(dataSrc[param])(json);
}
// else - Default behaviour
var old = '';
// Legacy support
if (param === 'draw') {
old = 'sEcho';
}
else if (param === 'recordsTotal') {
old = 'iTotalRecords';
}
else if (param === 'recordsFiltered') {
old = 'iTotalDisplayRecords';
}
return json[old] !== undefined ? json[old] : json[param];
}
/**
* Filter the table using both the global filter and column based filtering
* @param {object} settings dataTables settings object
* @param {object} input search information
* @memberof DataTable#oApi
*/
function _fnFilterComplete ( settings, input )
{
var columnsSearch = settings.aoPreSearchCols;
// In server-side processing all filtering is done by the server, so no point hanging around here
if ( _fnDataSource( settings ) != 'ssp' )
{
// Check if any of the rows were invalidated
_fnFilterData( settings );
// Start from the full data set
settings.aiDisplay = settings.aiDisplayMaster.slice();
// Global filter first
_fnFilter( settings.aiDisplay, settings, input.search, input );
$.each(settings.searchFixed, function (name, term) {
_fnFilter(settings.aiDisplay, settings, term, {});
});
// Then individual column filters
for ( var i=0 ; i 1) {
not.push('(?!'+word+')');
}
word = '';
}
return word.replace(/"/g, '');
} );
var match = not.length
? not.join('')
: '';
var boundary = options.boundary
? '\\b'
: '';
search = '^(?=.*?'+boundary+a.join( ')(?=.*?'+boundary )+')('+match+'.)*$';
}
return new RegExp( search, options.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 = $('
')[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 data = settings.aoData;
var column;
var j, jen, filterData, cellData, row;
var wasInvalidated = false;
for ( var rowIdx=0 ; rowIdx 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 if ( action === 'ellipsis' )
{
return;
}
else
{
_fnLog( settings, 0, "Unknown paging action: "+action, 5 );
}
var changed = settings._iDisplayStart !== start;
settings._iDisplayStart = start;
_fnCallbackFire( settings, null, changed ? 'page' : 'page-nc', [settings] );
if ( changed && redraw ) {
_fnDraw( settings );
}
return changed;
}
/**
* Generate the node required for the processing node
* @param {object} settings DataTables settings object
*/
function _processingHtml ( settings )
{
var table = settings.nTable;
var scrolling = settings.oScroll.sX !== '' || settings.oScroll.sY !== '';
if ( settings.oFeatures.bProcessing ) {
var n = $('', {
'id': settings.sTableId + '_processing',
'class': settings.oClasses.processing.container,
'role': 'status'
} )
.html( settings.oLanguage.sProcessing )
.append('
');
// Different positioning depending on if scrolling is enabled or not
if (scrolling) {
n.prependTo( $('div.dt-scroll', settings.nTableWrapper) );
}
else {
n.insertBefore( table );
}
$(table).on( 'processing.dt.DT', function (e, s, show) {
n.css( 'display', show ? 'block' : 'none' );
} );
}
}
/**
* Display or hide the processing indicator
* @param {object} settings DataTables settings object
* @param {bool} show Show the processing indicator (true) or not (false)
*/
function _fnProcessingDisplay ( settings, show )
{
// Ignore cases when we are still redrawing
if (settings.bDrawing && show === false) {
return;
}
_fnCallbackFire( settings, null, 'processing', [settings, show] );
}
/**
* Show the processing element if an action takes longer than a given time
*
* @param {*} settings DataTables settings object
* @param {*} enable Do (true) or not (false) async processing (local feature enablement)
* @param {*} run Function to run
*/
function _fnProcessingRun( settings, enable, run ) {
if (! enable) {
// Immediate execution, synchronous
run();
}
else {
_fnProcessingDisplay(settings, true);
// Allow the processing display to show if needed
setTimeout(function () {
run();
_fnProcessingDisplay(settings, false);
}, 0);
}
}
/**
* 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.scrolling;
var caption = settings.captionNode;
var captionSide = caption ? caption._captionSide : null;
var headerClone = $( table[0].cloneNode(false) );
var footerClone = $( table[0].cloneNode(false) );
var footer = table.children('tfoot');
var _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.container } )
.append(
$(_div, { 'class': classes.header.self } )
.css( {
overflow: 'hidden',
position: 'relative',
border: 0,
width: scrollX ? size(scrollX) : '100%'
} )
.append(
$(_div, { 'class': classes.header.inner } )
.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.body } )
.css( {
position: 'relative',
overflow: 'auto',
width: size( scrollX )
} )
.append( table )
);
if ( footer ) {
scroller.append(
$(_div, { 'class': classes.footer.self } )
.css( {
overflow: 'hidden',
border: 0,
width: scrollX ? size(scrollX) : '100%'
} )
.append(
$(_div, { 'class': classes.footer.inner } )
.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
$(scrollBody).on( 'scroll.DT', function () {
var scrollLeft = this.scrollLeft;
scrollHead.scrollLeft = scrollLeft;
if ( footer ) {
scrollFoot.scrollLeft = scrollLeft;
}
} );
// When focus is put on the header cells, we might need to scroll the body
$('th, td', scrollHead).on('focus', function () {
var scrollLeft = scrollHead.scrollLeft;
scrollBody.scrollLeft = scrollLeft;
if ( footer ) {
scrollBody.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(_fnScrollDraw);
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. Correct colgroup > col values if needed
* 3. Copy colgroup > col over to header and footer
* 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,
barWidth = scroll.iBarWidth,
divHeader = $(settings.nScrollHead),
divHeaderInner = divHeader.children('div'),
divHeaderTable = divHeaderInner.children('table'),
divBodyEl = settings.nScrollBody,
divBody = $(divBodyEl),
divFooter = $(settings.nScrollFoot),
divFooterInner = divFooter.children('div'),
divFooterTable = divFooterInner.children('table'),
header = $(settings.nTHead),
table = $(settings.nTable),
footer = settings.nTFoot && $('th, td', settings.nTFoot).length ? $(settings.nTFoot) : null,
browser = settings.oBrowser,
headerCopy, footerCopy;
// 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();
// Clone the current header and footer elements and then place it into the inner table
headerCopy = header.clone().prependTo( table );
headerCopy.find('th, td').removeAttr('tabindex');
headerCopy.find('[id]').removeAttr('id');
if ( footer ) {
footerCopy = footer.clone().prependTo( table );
footerCopy.find('[id]').removeAttr('id');
}
// 2. Correct colgroup > col values if needed
// It is possible that the cell sizes are smaller than the content, so we need to
// correct colgroup>col for such cases. This can happen if the auto width detection
// uses a cell which has a longer string, but isn't the widest! For example
// "Chief Executive Officer (CEO)" is the longest string in the demo, but
// "Systems Administrator" is actually the widest string since it doesn't collapse.
// Note the use of translating into a column index to get the `col` element. This
// is because of Responsive which might remove `col` elements, knocking the alignment
// of the indexes out.
if (settings.aiDisplay.length) {
// Get the column sizes from the first row in the table. This should really be a
// [].find, but it wasn't supported in Chrome until Sept 2015, and DT has 10 year
// browser support
var firstTr = null;
var start = _fnDataSource( settings ) !== 'ssp'
? settings._iDisplayStart
: 0;
for (i=start ; i col is set to and correct if needed
for (var i=0 ; i');
});
if ( footer ) {
$('th, td', footerCopy).each(function () {
$(this.childNodes).wrapAll('');
});
}
// 4. Clean up
// Figure out if there are scrollbar present - if so then we need the header and footer to
// provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
var isScrolling = Math.floor(table.height()) > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
var paddingSide = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
// Set the width's of the header and footer tables
var outerWidth = table.outerWidth();
divHeaderTable.css('width', _fnStringToCss( outerWidth ));
divHeaderInner
.css('width', _fnStringToCss( outerWidth ))
.css(paddingSide, isScrolling ? barWidth+"px" : "0px");
if ( footer ) {
divFooterTable.css('width', _fnStringToCss( outerWidth ));
divFooterInner
.css('width', _fnStringToCss( outerWidth ))
.css(paddingSide, isScrolling ? barWidth+"px" : "0px");
}
// Correct DOM ordering for colgroup - comes before the thead
table.children('colgroup').prependTo(table);
// Remove tabindex from the hidden row elements
table.find('thead, tfoot').find('[tabindex]').removeAttr('tabindex');
table.find('thead, tfoot').find('role').removeAttr('role');
// 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;
}
}
/**
* Calculate the width of columns for the table
* @param {object} settings dataTables settings object
* @memberof DataTable#oApi
*/
function _fnCalculateColumnWidths ( settings )
{
// Not interested in doing column width calculation if auto-width is disabled
if (! settings.oFeatures.bAutoWidth) {
return;
}
var
table = settings.nTable,
columns = settings.aoColumns,
scroll = settings.oScroll,
scrollY = scroll.sY,
scrollX = scroll.sX,
scrollXInner = scroll.sXInner,
visibleColumns = _fnGetColumns( settings, 'bVisible' ),
tableWidthAttr = table.getAttribute('width'), // from DOM element
tableContainer = table.parentNode,
i, j, column, columnIdx;
var styleWidth = table.style.width;
var containerWidth = _fnWrapperWidth(settings);
// Don't re-run for the same width as the last time
if (containerWidth === settings.containerWidth) {
return false;
}
settings.containerWidth = containerWidth;
// If there is no width applied as a CSS style or as an attribute, we assume that
// the width is intended to be 100%, which is usually is in CSS, but it is very
// difficult to correctly parse the rules to get the final result.
if ( ! styleWidth && ! tableWidthAttr) {
table.style.width = '100%';
styleWidth = '100%';
}
if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
tableWidthAttr = styleWidth;
}
// Let plug-ins know that we are doing a recalc, in case they have changed any of the
// visible columns their own way (e.g. Responsive uses display:none).
_fnCallbackFire(
settings,
null,
'column-calc',
{visible: visibleColumns},
false
);
// Construct a worst case table with the widest, 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.cloneNode())
.css( 'visibility', 'hidden' )
.css( 'margin', 0 )
.removeAttr( 'id' );
// Clean up the table body
tmpTable.append('')
// 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
.append( $(settings.nTHead).clone() )
.append( $(settings.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
tmpTable.find('thead th, thead td').each( function () {
// Get the `width` from the header layout
var width = _fnColumnsSumWidth( settings, this, true, false );
if ( width ) {
this.style.width = width;
// 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 ( scrollX ) {
this.style.minWidth = width;
$( this ).append( $('').css( {
width: width,
margin: 0,
padding: 0,
border: 0,
height: 1
} ) );
}
}
else {
this.style.width = '';
}
} );
// Get the widest strings for each of the visible columns and add them to
// our table to create a "worst case"
var longestData = [];
for ( i=0 ; i').appendTo( tmpTable.find('tbody') );
for ( j=0 ; j')
.addClass(autoClass)
.addClass(column.sClass)
.append(insert)
.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 = $('').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.outerWidth() < tableContainer.clientWidth && tableWidthAttr ) {
tmpTable.outerWidth( tableContainer.clientWidth );
}
}
else if ( scrollY ) {
tmpTable.outerWidth( tableContainer.clientWidth );
}
else if ( tableWidthAttr ) {
tmpTable.outerWidth( tableWidthAttr );
}
// Get the width of each column in the constructed table
var total = 0;
var bodyCells = tmpTable.find('tbody tr').eq(0).children();
for ( i=0 ; i')
.css({
width: '100%',
height: 0
})
.addClass('dt-autosize')
.appendTo(settings.nTableWrapper);
settings.resizeObserver = new ResizeObserver(function (e) {
if (first) {
first = false;
}
else {
resize();
}
});
settings.resizeObserver.observe(resizer[0]);
}
else {
// For old browsers, the best we can do is listen for a window resize
$(window).on('resize.DT-'+settings.sInstance, resize);
}
settings._reszEvt = true;
}
}
/**
* Get the width of the DataTables wrapper element
*
* @param {*} settings DataTables settings object
* @returns Width
*/
function _fnWrapperWidth(settings) {
return $(settings.nTableWrapper).is(':visible')
? $(settings.nTableWrapper).width()
: 0;
}
/**
* Get the widest strings for each column.
*
* It is very difficult to determine what the widest string actually is due to variable character
* width and kerning. Doing an exact calculation with the DOM or even Canvas would kill performance
* and this is a critical point, so we use two techniques to determine a collection of the longest
* strings from the column, which will likely contain the widest strings:
*
* 1) Get the top three longest strings from the column
* 2) Get the top three widest words (i.e. an unbreakable phrase)
*
* @param {object} settings dataTables settings object
* @param {int} colIdx column of interest
* @returns {string[]} Array of the longest strings
* @memberof DataTable#oApi
*/
function _fnGetWideStrings( settings, colIdx )
{
var column = settings.aoColumns[colIdx];
// Do we need to recalculate (i.e. was invalidated), or just use the cached data?
if (! column.wideStrings) {
var allStrings = [];
var collection = [];
// Create an array with the string information for the column
for ( var i=0, iLen=settings.aiDisplayMaster.length ; i/gi, ' ')
.replace(/