You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1688 lines
47 KiB
1688 lines
47 KiB
/*! FixedColumns 3.3.2 |
|
* ©2010-2020 SpryMedia Ltd - datatables.net/license |
|
*/ |
|
|
|
/** |
|
* @summary FixedColumns |
|
* @description Freeze columns in place on a scrolling DataTable |
|
* @version 3.3.2 |
|
* @file dataTables.fixedColumns.js |
|
* @author SpryMedia Ltd (www.sprymedia.co.uk) |
|
* @contact www.sprymedia.co.uk/contact |
|
* @copyright Copyright 2010-2020 SpryMedia Ltd. |
|
* |
|
* This source file is free software, available under the following license: |
|
* MIT license - http://datatables.net/license/mit |
|
* |
|
* This source file is distributed in the hope that it will be useful, but |
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. |
|
* |
|
* For details please refer to: http://www.datatables.net |
|
*/ |
|
(function( factory ){ |
|
if ( typeof define === 'function' && define.amd ) { |
|
// AMD |
|
define( ['jquery', 'datatables.net'], function ( $ ) { |
|
return factory( $, window, document ); |
|
} ); |
|
} |
|
else if ( typeof exports === 'object' ) { |
|
// CommonJS |
|
module.exports = function (root, $) { |
|
if ( ! root ) { |
|
root = window; |
|
} |
|
|
|
if ( ! $ || ! $.fn.dataTable ) { |
|
$ = require('datatables.net')(root, $).$; |
|
} |
|
|
|
return factory( $, root, root.document ); |
|
}; |
|
} |
|
else { |
|
// Browser |
|
factory( jQuery, window, document ); |
|
} |
|
}(function( $, window, document, undefined ) { |
|
'use strict'; |
|
var DataTable = $.fn.dataTable; |
|
var _firefoxScroll; |
|
|
|
/** |
|
* When making use of DataTables' x-axis scrolling feature, you may wish to |
|
* fix the left most column in place. This plug-in for DataTables provides |
|
* exactly this option (note for non-scrolling tables, please use the |
|
* FixedHeader plug-in, which can fix headers and footers). Key |
|
* features include: |
|
* |
|
* * Freezes the left or right most columns to the side of the table |
|
* * Option to freeze two or more columns |
|
* * Full integration with DataTables' scrolling options |
|
* * Speed - FixedColumns is fast in its operation |
|
* |
|
* @class |
|
* @constructor |
|
* @global |
|
* @param {object} dt DataTables instance. With DataTables 1.10 this can also |
|
* be a jQuery collection, a jQuery selector, DataTables API instance or |
|
* settings object. |
|
* @param {object} [init={}] Configuration object for FixedColumns. Options are |
|
* defined by {@link FixedColumns.defaults} |
|
* |
|
* @requires jQuery 1.7+ |
|
* @requires DataTables 1.8.0+ |
|
* |
|
* @example |
|
* var table = $('#example').dataTable( { |
|
* "scrollX": "100%" |
|
* } ); |
|
* new $.fn.dataTable.fixedColumns( table ); |
|
*/ |
|
var FixedColumns = function ( dt, init ) { |
|
var that = this; |
|
|
|
/* Sanity check - you just know it will happen */ |
|
if ( ! ( this instanceof FixedColumns ) ) { |
|
alert( "FixedColumns warning: FixedColumns must be initialised with the 'new' keyword." ); |
|
return; |
|
} |
|
|
|
if ( init === undefined || init === true ) { |
|
init = {}; |
|
} |
|
|
|
// Use the DataTables Hungarian notation mapping method, if it exists to |
|
// provide forwards compatibility for camel case variables |
|
var camelToHungarian = $.fn.dataTable.camelToHungarian; |
|
if ( camelToHungarian ) { |
|
camelToHungarian( FixedColumns.defaults, FixedColumns.defaults, true ); |
|
camelToHungarian( FixedColumns.defaults, init ); |
|
} |
|
|
|
// v1.10 allows the settings object to be got form a number of sources |
|
var dtSettings = new $.fn.dataTable.Api( dt ).settings()[0]; |
|
|
|
/** |
|
* Settings object which contains customisable information for FixedColumns instance |
|
* @namespace |
|
* @extends FixedColumns.defaults |
|
* @private |
|
*/ |
|
this.s = { |
|
/** |
|
* DataTables settings objects |
|
* @type object |
|
* @default Obtained from DataTables instance |
|
*/ |
|
"dt": dtSettings, |
|
|
|
/** |
|
* Number of columns in the DataTable - stored for quick access |
|
* @type int |
|
* @default Obtained from DataTables instance |
|
*/ |
|
"iTableColumns": dtSettings.aoColumns.length, |
|
|
|
/** |
|
* Original outer widths of the columns as rendered by DataTables - used to calculate |
|
* the FixedColumns grid bounding box |
|
* @type array.<int> |
|
* @default [] |
|
*/ |
|
"aiOuterWidths": [], |
|
|
|
/** |
|
* Original inner widths of the columns as rendered by DataTables - used to apply widths |
|
* to the columns |
|
* @type array.<int> |
|
* @default [] |
|
*/ |
|
"aiInnerWidths": [], |
|
|
|
|
|
/** |
|
* Is the document layout right-to-left |
|
* @type boolean |
|
*/ |
|
rtl: $(dtSettings.nTable).css('direction') === 'rtl' |
|
}; |
|
|
|
|
|
/** |
|
* DOM elements used by the class instance |
|
* @namespace |
|
* @private |
|
* |
|
*/ |
|
this.dom = { |
|
/** |
|
* DataTables scrolling element |
|
* @type node |
|
* @default null |
|
*/ |
|
"scroller": null, |
|
|
|
/** |
|
* DataTables header table |
|
* @type node |
|
* @default null |
|
*/ |
|
"header": null, |
|
|
|
/** |
|
* DataTables body table |
|
* @type node |
|
* @default null |
|
*/ |
|
"body": null, |
|
|
|
/** |
|
* DataTables footer table |
|
* @type node |
|
* @default null |
|
*/ |
|
"footer": null, |
|
|
|
/** |
|
* Display grid elements |
|
* @namespace |
|
*/ |
|
"grid": { |
|
/** |
|
* Grid wrapper. This is the container element for the 3x3 grid |
|
* @type node |
|
* @default null |
|
*/ |
|
"wrapper": null, |
|
|
|
/** |
|
* DataTables scrolling element. This element is the DataTables |
|
* component in the display grid (making up the main table - i.e. |
|
* not the fixed columns). |
|
* @type node |
|
* @default null |
|
*/ |
|
"dt": null, |
|
|
|
/** |
|
* Left fixed column grid components |
|
* @namespace |
|
*/ |
|
"left": { |
|
"wrapper": null, |
|
"head": null, |
|
"body": null, |
|
"foot": null |
|
}, |
|
|
|
/** |
|
* Right fixed column grid components |
|
* @namespace |
|
*/ |
|
"right": { |
|
"wrapper": null, |
|
"head": null, |
|
"body": null, |
|
"foot": null |
|
} |
|
}, |
|
|
|
/** |
|
* Cloned table nodes |
|
* @namespace |
|
*/ |
|
"clone": { |
|
/** |
|
* Left column cloned table nodes |
|
* @namespace |
|
*/ |
|
"left": { |
|
/** |
|
* Cloned header table |
|
* @type node |
|
* @default null |
|
*/ |
|
"header": null, |
|
|
|
/** |
|
* Cloned body table |
|
* @type node |
|
* @default null |
|
*/ |
|
"body": null, |
|
|
|
/** |
|
* Cloned footer table |
|
* @type node |
|
* @default null |
|
*/ |
|
"footer": null |
|
}, |
|
|
|
/** |
|
* Right column cloned table nodes |
|
* @namespace |
|
*/ |
|
"right": { |
|
/** |
|
* Cloned header table |
|
* @type node |
|
* @default null |
|
*/ |
|
"header": null, |
|
|
|
/** |
|
* Cloned body table |
|
* @type node |
|
* @default null |
|
*/ |
|
"body": null, |
|
|
|
/** |
|
* Cloned footer table |
|
* @type node |
|
* @default null |
|
*/ |
|
"footer": null |
|
} |
|
} |
|
}; |
|
|
|
if ( dtSettings._oFixedColumns ) { |
|
throw 'FixedColumns already initialised on this table'; |
|
} |
|
|
|
/* Attach the instance to the DataTables instance so it can be accessed easily */ |
|
dtSettings._oFixedColumns = this; |
|
|
|
/* Let's do it */ |
|
if ( ! dtSettings._bInitComplete ) |
|
{ |
|
dtSettings.oApi._fnCallbackReg( dtSettings, 'aoInitComplete', function () { |
|
that._fnConstruct( init ); |
|
}, 'FixedColumns' ); |
|
} |
|
else |
|
{ |
|
this._fnConstruct( init ); |
|
} |
|
}; |
|
|
|
|
|
|
|
$.extend( FixedColumns.prototype , { |
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
* Public methods |
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
|
|
/** |
|
* Update the fixed columns - including headers and footers. Note that FixedColumns will |
|
* automatically update the display whenever the host DataTable redraws. |
|
* @returns {void} |
|
* @example |
|
* var table = $('#example').dataTable( { |
|
* "scrollX": "100%" |
|
* } ); |
|
* var fc = new $.fn.dataTable.fixedColumns( table ); |
|
* |
|
* // at some later point when the table has been manipulated.... |
|
* fc.fnUpdate(); |
|
*/ |
|
"fnUpdate": function () |
|
{ |
|
this._fnDraw( true ); |
|
}, |
|
|
|
|
|
/** |
|
* Recalculate the resizes of the 3x3 grid that FixedColumns uses for display of the table. |
|
* This is useful if you update the width of the table container. Note that FixedColumns will |
|
* perform this function automatically when the window.resize event is fired. |
|
* @returns {void} |
|
* @example |
|
* var table = $('#example').dataTable( { |
|
* "scrollX": "100%" |
|
* } ); |
|
* var fc = new $.fn.dataTable.fixedColumns( table ); |
|
* |
|
* // Resize the table container and then have FixedColumns adjust its layout.... |
|
* $('#content').width( 1200 ); |
|
* fc.fnRedrawLayout(); |
|
*/ |
|
"fnRedrawLayout": function () |
|
{ |
|
this._fnColCalc(); |
|
this._fnGridLayout(); |
|
this.fnUpdate(); |
|
}, |
|
|
|
|
|
/** |
|
* Mark a row such that it's height should be recalculated when using 'semiauto' row |
|
* height matching. This function will have no effect when 'none' or 'auto' row height |
|
* matching is used. |
|
* @param {Node} nTr TR element that should have it's height recalculated |
|
* @returns {void} |
|
* @example |
|
* var table = $('#example').dataTable( { |
|
* "scrollX": "100%" |
|
* } ); |
|
* var fc = new $.fn.dataTable.fixedColumns( table ); |
|
* |
|
* // manipulate the table - mark the row as needing an update then update the table |
|
* // this allows the redraw performed by DataTables fnUpdate to recalculate the row |
|
* // height |
|
* fc.fnRecalculateHeight(); |
|
* table.fnUpdate( $('#example tbody tr:eq(0)')[0], ["insert date", 1, 2, 3 ... ]); |
|
*/ |
|
"fnRecalculateHeight": function ( nTr ) |
|
{ |
|
delete nTr._DTTC_iHeight; |
|
nTr.style.height = 'auto'; |
|
}, |
|
|
|
|
|
/** |
|
* Set the height of a given row - provides cross browser compatibility |
|
* @param {Node} nTarget TR element that should have it's height recalculated |
|
* @param {int} iHeight Height in pixels to set |
|
* @returns {void} |
|
* @example |
|
* var table = $('#example').dataTable( { |
|
* "scrollX": "100%" |
|
* } ); |
|
* var fc = new $.fn.dataTable.fixedColumns( table ); |
|
* |
|
* // You may want to do this after manipulating a row in the fixed column |
|
* fc.fnSetRowHeight( $('#example tbody tr:eq(0)')[0], 50 ); |
|
*/ |
|
"fnSetRowHeight": function ( nTarget, iHeight ) |
|
{ |
|
nTarget.style.height = iHeight+"px"; |
|
}, |
|
|
|
|
|
/** |
|
* Get data index information about a row or cell in the table body. |
|
* This function is functionally identical to fnGetPosition in DataTables, |
|
* taking the same parameter (TH, TD or TR node) and returning exactly the |
|
* the same information (data index information). THe difference between |
|
* the two is that this method takes into account the fixed columns in the |
|
* table, so you can pass in nodes from the master table, or the cloned |
|
* tables and get the index position for the data in the main table. |
|
* @param {node} node TR, TH or TD element to get the information about |
|
* @returns {int} If nNode is given as a TR, then a single index is |
|
* returned, or if given as a cell, an array of [row index, column index |
|
* (visible), column index (all)] is given. |
|
*/ |
|
"fnGetPosition": function ( node ) |
|
{ |
|
var idx; |
|
var inst = this.s.dt.oInstance; |
|
|
|
if ( ! $(node).parents('.DTFC_Cloned').length ) |
|
{ |
|
// Not in a cloned table |
|
return inst.fnGetPosition( node ); |
|
} |
|
else |
|
{ |
|
// Its in the cloned table, so need to look up position |
|
if ( node.nodeName.toLowerCase() === 'tr' ) { |
|
idx = $(node).index(); |
|
return inst.fnGetPosition( $('tr', this.s.dt.nTBody)[ idx ] ); |
|
} |
|
else |
|
{ |
|
var colIdx = $(node).index(); |
|
idx = $(node.parentNode).index(); |
|
var row = inst.fnGetPosition( $('tr', this.s.dt.nTBody)[ idx ] ); |
|
|
|
return [ |
|
row, |
|
colIdx, |
|
inst.oApi._fnVisibleToColumnIndex( this.s.dt, colIdx ) |
|
]; |
|
} |
|
} |
|
}, |
|
|
|
fnToFixedNode: function ( rowIdx, colIdx ) |
|
{ |
|
var found; |
|
|
|
if ( colIdx < this.s.iLeftColumns ) { |
|
found = $(this.dom.clone.left.body).find('[data-dt-row='+rowIdx+'][data-dt-column='+colIdx+']'); |
|
} |
|
else if ( colIdx >= this.s.iRightColumns ) { |
|
found = $(this.dom.clone.right.body).find('[data-dt-row='+rowIdx+'][data-dt-column='+colIdx+']'); |
|
} |
|
|
|
if ( found && found.length ) { |
|
return found[0]; |
|
} |
|
|
|
// Fallback - non-fixed node |
|
var table = new $.fn.dataTable.Api(this.s.dt); |
|
return table.cell(rowIdx, colIdx).node(); |
|
}, |
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
* Private methods (they are of course public in JS, but recommended as private) |
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
|
|
/** |
|
* Initialisation for FixedColumns |
|
* @param {Object} oInit User settings for initialisation |
|
* @returns {void} |
|
* @private |
|
*/ |
|
"_fnConstruct": function ( oInit ) |
|
{ |
|
var i, iLen, iWidth, |
|
that = this; |
|
|
|
/* Sanity checking */ |
|
if ( typeof this.s.dt.oInstance.fnVersionCheck != 'function' || |
|
this.s.dt.oInstance.fnVersionCheck( '1.8.0' ) !== true ) |
|
{ |
|
alert( "FixedColumns "+FixedColumns.VERSION+" required DataTables 1.8.0 or later. "+ |
|
"Please upgrade your DataTables installation" ); |
|
return; |
|
} |
|
|
|
if ( this.s.dt.oScroll.sX === "" ) |
|
{ |
|
this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "FixedColumns is not needed (no "+ |
|
"x-scrolling in DataTables enabled), so no action will be taken. Use 'FixedHeader' for "+ |
|
"column fixing when scrolling is not enabled" ); |
|
return; |
|
} |
|
|
|
/* Apply the settings from the user / defaults */ |
|
this.s = $.extend( true, this.s, FixedColumns.defaults, oInit ); |
|
|
|
/* Set up the DOM as we need it and cache nodes */ |
|
var classes = this.s.dt.oClasses; |
|
this.dom.grid.dt = $(this.s.dt.nTable).parents('div.'+classes.sScrollWrapper)[0]; |
|
this.dom.scroller = $('div.'+classes.sScrollBody, this.dom.grid.dt )[0]; |
|
|
|
/* Set up the DOM that we want for the fixed column layout grid */ |
|
this._fnColCalc(); |
|
this._fnGridSetup(); |
|
|
|
/* Event handlers */ |
|
var mouseController; |
|
var mouseDown = false; |
|
|
|
// When the mouse is down (drag scroll) the mouse controller cannot |
|
// change, as the browser keeps the original element as the scrolling one |
|
$(this.s.dt.nTableWrapper).on( 'mousedown.DTFC', function (e) { |
|
if ( e.button === 0 ) { |
|
mouseDown = true; |
|
|
|
$(document).one( 'mouseup', function () { |
|
mouseDown = false; |
|
} ); |
|
} |
|
} ); |
|
|
|
// When the body is scrolled - scroll the left and right columns |
|
$(this.dom.scroller) |
|
.on( 'mouseover.DTFC touchstart.DTFC', function () { |
|
if ( ! mouseDown ) { |
|
mouseController = 'main'; |
|
} |
|
} ) |
|
.on( 'scroll.DTFC', function (e) { |
|
if ( ! mouseController && e.originalEvent ) { |
|
mouseController = 'main'; |
|
} |
|
|
|
if ( mouseController === 'main' || mouseController === 'key' ) { |
|
if ( that.s.iLeftColumns > 0 ) { |
|
that.dom.grid.left.liner.scrollTop = that.dom.scroller.scrollTop; |
|
} |
|
if ( that.s.iRightColumns > 0 ) { |
|
that.dom.grid.right.liner.scrollTop = that.dom.scroller.scrollTop; |
|
} |
|
} |
|
} ); |
|
|
|
var wheelType = 'onwheel' in document.createElement('div') ? |
|
'wheel.DTFC' : |
|
'mousewheel.DTFC'; |
|
|
|
if ( that.s.iLeftColumns > 0 ) { |
|
// When scrolling the left column, scroll the body and right column |
|
$(that.dom.grid.left.liner) |
|
.on( 'mouseover.DTFC touchstart.DTFC', function () { |
|
if ( ! mouseDown && mouseController !== 'key' ) { |
|
mouseController = 'left'; |
|
} |
|
} ) |
|
.on( 'scroll.DTFC', function ( e ) { |
|
if ( ! mouseController && e.originalEvent ) { |
|
mouseController = 'left'; |
|
} |
|
|
|
if ( mouseController === 'left' ) { |
|
that.dom.scroller.scrollTop = that.dom.grid.left.liner.scrollTop; |
|
if ( that.s.iRightColumns > 0 ) { |
|
that.dom.grid.right.liner.scrollTop = that.dom.grid.left.liner.scrollTop; |
|
} |
|
} |
|
} ) |
|
.on( wheelType, function(e) { |
|
mouseController = 'left'; |
|
|
|
// Pass horizontal scrolling through |
|
var xDelta = e.type === 'wheel' ? |
|
-e.originalEvent.deltaX : |
|
e.originalEvent.wheelDeltaX; |
|
that.dom.scroller.scrollLeft -= xDelta; |
|
} ); |
|
|
|
// Header will not trigger scroll on left column, but might on `main` (sorting) |
|
$(that.dom.grid.left.head).on( 'mouseover.DTFC touchstart.DTFC', function () { |
|
mouseController = 'main'; |
|
}); |
|
} |
|
|
|
if ( that.s.iRightColumns > 0 ) { |
|
// When scrolling the right column, scroll the body and the left column |
|
$(that.dom.grid.right.liner) |
|
.on( 'mouseover.DTFC touchstart.DTFC', function () { |
|
if ( ! mouseDown && mouseController !== 'key' ) { |
|
mouseController = 'right'; |
|
} |
|
} ) |
|
.on( 'scroll.DTFC', function ( e ) { |
|
if ( ! mouseController && e.originalEvent ) { |
|
mouseController = 'right'; |
|
} |
|
|
|
if ( mouseController === 'right' ) { |
|
that.dom.scroller.scrollTop = that.dom.grid.right.liner.scrollTop; |
|
if ( that.s.iLeftColumns > 0 ) { |
|
that.dom.grid.left.liner.scrollTop = that.dom.grid.right.liner.scrollTop; |
|
} |
|
} |
|
} ) |
|
.on( wheelType, function(e) { |
|
mouseController = 'right'; |
|
|
|
// Pass horizontal scrolling through |
|
var xDelta = e.type === 'wheel' ? |
|
-e.originalEvent.deltaX : |
|
e.originalEvent.wheelDeltaX; |
|
that.dom.scroller.scrollLeft -= xDelta; |
|
} ); |
|
|
|
$(that.dom.grid.right.head).on( 'mouseover.DTFC touchstart.DTFC', function () { |
|
mouseController = 'main'; |
|
}); |
|
} |
|
|
|
$(window).on( 'resize.DTFC', function () { |
|
that._fnGridLayout.call( that ); |
|
} ); |
|
|
|
var bFirstDraw = true; |
|
var jqTable = $(this.s.dt.nTable); |
|
|
|
jqTable |
|
.on( 'draw.dt.DTFC', function () { |
|
that._fnColCalc(); |
|
that._fnDraw.call( that, bFirstDraw ); |
|
bFirstDraw = false; |
|
} ) |
|
.on('key-focus.dt.DTFC', function () { |
|
// KeyTable navigation needs to be main focused |
|
mouseController = 'key'; |
|
}) |
|
.on( 'column-sizing.dt.DTFC', function () { |
|
that._fnColCalc(); |
|
that._fnGridLayout( that ); |
|
} ) |
|
.on( 'column-visibility.dt.DTFC', function ( e, settings, column, vis, recalc ) { |
|
if ( recalc === undefined || recalc ) { |
|
that._fnColCalc(); |
|
that._fnGridLayout( that ); |
|
that._fnDraw( true ); |
|
} |
|
} ) |
|
.on( 'select.dt.DTFC deselect.dt.DTFC', function ( e, dt, type, indexes ) { |
|
if ( e.namespace === 'dt' ) { |
|
that._fnDraw( false ); |
|
} |
|
} ) |
|
.on( 'position.dts.dt.DTFC', function (e, tableTop) { |
|
// Sync up with Scroller |
|
if (that.dom.grid.left.body) { |
|
$(that.dom.grid.left.body).find('table').eq(0).css('top', tableTop); |
|
} |
|
|
|
if (that.dom.grid.right.body) { |
|
$(that.dom.grid.right.body).find('table').eq(0).css('top', tableTop); |
|
} |
|
} ) |
|
.on( 'destroy.dt.DTFC', function () { |
|
jqTable.off( '.DTFC' ); |
|
|
|
$(that.dom.scroller).off( '.DTFC' ); |
|
$(window).off( '.DTFC' ); |
|
$(that.s.dt.nTableWrapper).off( '.DTFC' ); |
|
|
|
$(that.dom.grid.left.liner).off( '.DTFC '+wheelType ); |
|
$(that.dom.grid.left.wrapper).remove(); |
|
|
|
$(that.dom.grid.right.liner).off( '.DTFC '+wheelType ); |
|
$(that.dom.grid.right.wrapper).remove(); |
|
} ); |
|
|
|
/* Get things right to start with - note that due to adjusting the columns, there must be |
|
* another redraw of the main table. It doesn't need to be a full redraw however. |
|
*/ |
|
this._fnGridLayout(); |
|
this.s.dt.oInstance.fnDraw(false); |
|
}, |
|
|
|
|
|
/** |
|
* Calculate the column widths for the grid layout |
|
* @returns {void} |
|
* @private |
|
*/ |
|
"_fnColCalc": function () |
|
{ |
|
var that = this; |
|
var iLeftWidth = 0; |
|
var iRightWidth = 0; |
|
|
|
this.s.aiInnerWidths = []; |
|
this.s.aiOuterWidths = []; |
|
|
|
$.each( this.s.dt.aoColumns, function (i, col) { |
|
var th = $(col.nTh); |
|
var border; |
|
|
|
if ( ! th.filter(':visible').length ) { |
|
that.s.aiInnerWidths.push( 0 ); |
|
that.s.aiOuterWidths.push( 0 ); |
|
} |
|
else |
|
{ |
|
// Inner width is used to assign widths to cells |
|
// Outer width is used to calculate the container |
|
var iWidth = th.outerWidth(); |
|
|
|
// When working with the left most-cell, need to add on the |
|
// table's border to the outerWidth, since we need to take |
|
// account of it, but it isn't in any cell |
|
if ( that.s.aiOuterWidths.length === 0 ) { |
|
border = $(that.s.dt.nTable).css('border-left-width'); |
|
iWidth += typeof border === 'string' && border.indexOf('px') === -1 ? |
|
1 : |
|
parseInt( border, 10 ); |
|
} |
|
|
|
// Likewise with the final column on the right |
|
if ( that.s.aiOuterWidths.length === that.s.dt.aoColumns.length-1 ) { |
|
border = $(that.s.dt.nTable).css('border-right-width'); |
|
iWidth += typeof border === 'string' && border.indexOf('px') === -1 ? |
|
1 : |
|
parseInt( border, 10 ); |
|
} |
|
|
|
that.s.aiOuterWidths.push( iWidth ); |
|
that.s.aiInnerWidths.push( th.width() ); |
|
|
|
if ( i < that.s.iLeftColumns ) |
|
{ |
|
iLeftWidth += iWidth; |
|
} |
|
|
|
if ( that.s.iTableColumns-that.s.iRightColumns <= i ) |
|
{ |
|
iRightWidth += iWidth; |
|
} |
|
} |
|
} ); |
|
|
|
this.s.iLeftWidth = iLeftWidth; |
|
this.s.iRightWidth = iRightWidth; |
|
}, |
|
|
|
|
|
/** |
|
* Set up the DOM for the fixed column. The way the layout works is to create a 1x3 grid |
|
* for the left column, the DataTable (for which we just reuse the scrolling element DataTable |
|
* puts into the DOM) and the right column. In each of he two fixed column elements there is a |
|
* grouping wrapper element and then a head, body and footer wrapper. In each of these we then |
|
* place the cloned header, body or footer tables. This effectively gives as 3x3 grid structure. |
|
* @returns {void} |
|
* @private |
|
*/ |
|
"_fnGridSetup": function () |
|
{ |
|
var that = this; |
|
var oOverflow = this._fnDTOverflow(); |
|
var block; |
|
|
|
this.dom.body = this.s.dt.nTable; |
|
this.dom.header = this.s.dt.nTHead.parentNode; |
|
this.dom.header.parentNode.parentNode.style.position = "relative"; |
|
|
|
var nSWrapper = |
|
$('<div class="DTFC_ScrollWrapper" style="position:relative; clear:both;">'+ |
|
'<div class="DTFC_LeftWrapper" style="position:absolute; top:0; left:0;" aria-hidden="true">'+ |
|
'<div class="DTFC_LeftHeadWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+ |
|
'<div class="DTFC_LeftBodyWrapper" style="position:relative; top:0; left:0; height:0; overflow:hidden;">'+ |
|
'<div class="DTFC_LeftBodyLiner" style="position:relative; top:0; left:0; overflow-y:scroll;"></div>'+ |
|
'</div>'+ |
|
'<div class="DTFC_LeftFootWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+ |
|
'</div>'+ |
|
'<div class="DTFC_RightWrapper" style="position:absolute; top:0; right:0;" aria-hidden="true">'+ |
|
'<div class="DTFC_RightHeadWrapper" style="position:relative; top:0; left:0;">'+ |
|
'<div class="DTFC_RightHeadBlocker DTFC_Blocker" style="position:absolute; top:0; bottom:0;"></div>'+ |
|
'</div>'+ |
|
'<div class="DTFC_RightBodyWrapper" style="position:relative; top:0; left:0; height:0; overflow:hidden;">'+ |
|
'<div class="DTFC_RightBodyLiner" style="position:relative; top:0; left:0; overflow-y:scroll;"></div>'+ |
|
'</div>'+ |
|
'<div class="DTFC_RightFootWrapper" style="position:relative; top:0; left:0;">'+ |
|
'<div class="DTFC_RightFootBlocker DTFC_Blocker" style="position:absolute; top:0; bottom:0;"></div>'+ |
|
'</div>'+ |
|
'</div>'+ |
|
'</div>')[0]; |
|
var nLeft = nSWrapper.childNodes[0]; |
|
var nRight = nSWrapper.childNodes[1]; |
|
|
|
this.dom.grid.dt.parentNode.insertBefore( nSWrapper, this.dom.grid.dt ); |
|
nSWrapper.appendChild( this.dom.grid.dt ); |
|
|
|
this.dom.grid.wrapper = nSWrapper; |
|
|
|
if ( this.s.iLeftColumns > 0 ) |
|
{ |
|
this.dom.grid.left.wrapper = nLeft; |
|
this.dom.grid.left.head = nLeft.childNodes[0]; |
|
this.dom.grid.left.body = nLeft.childNodes[1]; |
|
this.dom.grid.left.liner = $('div.DTFC_LeftBodyLiner', nSWrapper)[0]; |
|
|
|
nSWrapper.appendChild( nLeft ); |
|
} |
|
|
|
if ( this.s.iRightColumns > 0 ) |
|
{ |
|
this.dom.grid.right.wrapper = nRight; |
|
this.dom.grid.right.head = nRight.childNodes[0]; |
|
this.dom.grid.right.body = nRight.childNodes[1]; |
|
this.dom.grid.right.liner = $('div.DTFC_RightBodyLiner', nSWrapper)[0]; |
|
|
|
nRight.style.right = oOverflow.bar+"px"; |
|
|
|
block = $('div.DTFC_RightHeadBlocker', nSWrapper)[0]; |
|
block.style.width = oOverflow.bar+"px"; |
|
block.style.right = -oOverflow.bar+"px"; |
|
this.dom.grid.right.headBlock = block; |
|
|
|
block = $('div.DTFC_RightFootBlocker', nSWrapper)[0]; |
|
block.style.width = oOverflow.bar+"px"; |
|
block.style.right = -oOverflow.bar+"px"; |
|
this.dom.grid.right.footBlock = block; |
|
|
|
nSWrapper.appendChild( nRight ); |
|
} |
|
|
|
if ( this.s.dt.nTFoot ) |
|
{ |
|
this.dom.footer = this.s.dt.nTFoot.parentNode; |
|
if ( this.s.iLeftColumns > 0 ) |
|
{ |
|
this.dom.grid.left.foot = nLeft.childNodes[2]; |
|
} |
|
if ( this.s.iRightColumns > 0 ) |
|
{ |
|
this.dom.grid.right.foot = nRight.childNodes[2]; |
|
} |
|
} |
|
|
|
// RTL support - swap the position of the left and right columns (#48) |
|
if ( this.s.rtl ) { |
|
$('div.DTFC_RightHeadBlocker', nSWrapper).css( { |
|
left: -oOverflow.bar+'px', |
|
right: '' |
|
} ); |
|
} |
|
}, |
|
|
|
|
|
/** |
|
* Style and position the grid used for the FixedColumns layout |
|
* @returns {void} |
|
* @private |
|
*/ |
|
"_fnGridLayout": function () |
|
{ |
|
var that = this; |
|
var oGrid = this.dom.grid; |
|
var iWidth = $(oGrid.wrapper).width(); |
|
var iBodyHeight = this.s.dt.nTable.parentNode.offsetHeight; |
|
var iFullHeight = this.s.dt.nTable.parentNode.parentNode.offsetHeight; |
|
var oOverflow = this._fnDTOverflow(); |
|
var iLeftWidth = this.s.iLeftWidth; |
|
var iRightWidth = this.s.iRightWidth; |
|
var rtl = $(this.dom.body).css('direction') === 'rtl'; |
|
var wrapper; |
|
var scrollbarAdjust = function ( node, width ) { |
|
if ( ! oOverflow.bar ) { |
|
// If there is no scrollbar (Macs) we need to hide the auto scrollbar |
|
node.style.width = (width+20)+"px"; |
|
node.style.paddingRight = "20px"; |
|
node.style.boxSizing = "border-box"; |
|
} |
|
else if ( that._firefoxScrollError() ) { |
|
// See the above function for why this is required |
|
if ( $(node).height() > 34 ) { |
|
node.style.width = (width+oOverflow.bar)+"px"; |
|
} |
|
} |
|
else { |
|
// Otherwise just overflow by the scrollbar |
|
node.style.width = (width+oOverflow.bar)+"px"; |
|
} |
|
}; |
|
|
|
// When x scrolling - don't paint the fixed columns over the x scrollbar |
|
if ( oOverflow.x ) |
|
{ |
|
iBodyHeight -= oOverflow.bar; |
|
} |
|
|
|
oGrid.wrapper.style.height = iFullHeight+"px"; |
|
|
|
if ( this.s.iLeftColumns > 0 ) |
|
{ |
|
wrapper = oGrid.left.wrapper; |
|
wrapper.style.width = iLeftWidth+'px'; |
|
wrapper.style.height = '1px'; |
|
|
|
// Swap the position of the left and right columns for rtl (#48) |
|
// This is always up against the edge, scrollbar on the far side |
|
if ( rtl ) { |
|
wrapper.style.left = ''; |
|
wrapper.style.right = 0; |
|
} |
|
else { |
|
wrapper.style.left = 0; |
|
wrapper.style.right = ''; |
|
} |
|
|
|
oGrid.left.body.style.height = iBodyHeight+"px"; |
|
if ( oGrid.left.foot ) { |
|
oGrid.left.foot.style.top = (oOverflow.x ? oOverflow.bar : 0)+"px"; // shift footer for scrollbar |
|
} |
|
|
|
scrollbarAdjust( oGrid.left.liner, iLeftWidth ); |
|
oGrid.left.liner.style.height = iBodyHeight+"px"; |
|
oGrid.left.liner.style.maxHeight = iBodyHeight+"px"; |
|
} |
|
|
|
if ( this.s.iRightColumns > 0 ) |
|
{ |
|
wrapper = oGrid.right.wrapper; |
|
wrapper.style.width = iRightWidth+'px'; |
|
wrapper.style.height = '1px'; |
|
|
|
// Need to take account of the vertical scrollbar |
|
if ( this.s.rtl ) { |
|
wrapper.style.left = oOverflow.y ? oOverflow.bar+'px' : 0; |
|
wrapper.style.right = ''; |
|
} |
|
else { |
|
wrapper.style.left = ''; |
|
wrapper.style.right = oOverflow.y ? oOverflow.bar+'px' : 0; |
|
} |
|
|
|
oGrid.right.body.style.height = iBodyHeight+"px"; |
|
if ( oGrid.right.foot ) { |
|
oGrid.right.foot.style.top = (oOverflow.x ? oOverflow.bar : 0)+"px"; |
|
} |
|
|
|
scrollbarAdjust( oGrid.right.liner, iRightWidth ); |
|
oGrid.right.liner.style.height = iBodyHeight+"px"; |
|
oGrid.right.liner.style.maxHeight = iBodyHeight+"px"; |
|
|
|
oGrid.right.headBlock.style.display = oOverflow.y ? 'block' : 'none'; |
|
oGrid.right.footBlock.style.display = oOverflow.y ? 'block' : 'none'; |
|
} |
|
}, |
|
|
|
|
|
/** |
|
* Get information about the DataTable's scrolling state - specifically if the table is scrolling |
|
* on either the x or y axis, and also the scrollbar width. |
|
* @returns {object} Information about the DataTables scrolling state with the properties: |
|
* 'x', 'y' and 'bar' |
|
* @private |
|
*/ |
|
"_fnDTOverflow": function () |
|
{ |
|
var nTable = this.s.dt.nTable; |
|
var nTableScrollBody = nTable.parentNode; |
|
var out = { |
|
"x": false, |
|
"y": false, |
|
"bar": this.s.dt.oScroll.iBarWidth |
|
}; |
|
|
|
if ( nTable.offsetWidth > nTableScrollBody.clientWidth ) |
|
{ |
|
out.x = true; |
|
} |
|
|
|
if ( nTable.offsetHeight > nTableScrollBody.clientHeight ) |
|
{ |
|
out.y = true; |
|
} |
|
|
|
return out; |
|
}, |
|
|
|
|
|
/** |
|
* Clone and position the fixed columns |
|
* @returns {void} |
|
* @param {Boolean} bAll Indicate if the header and footer should be updated as well (true) |
|
* @private |
|
*/ |
|
"_fnDraw": function ( bAll ) |
|
{ |
|
this._fnGridLayout(); |
|
this._fnCloneLeft( bAll ); |
|
this._fnCloneRight( bAll ); |
|
|
|
$(this.dom.scroller).trigger('scroll'); |
|
|
|
/* Draw callback function */ |
|
if ( this.s.fnDrawCallback !== null ) |
|
{ |
|
this.s.fnDrawCallback.call( this, this.dom.clone.left, this.dom.clone.right ); |
|
} |
|
|
|
/* Event triggering */ |
|
$(this).trigger( 'draw.dtfc', { |
|
"leftClone": this.dom.clone.left, |
|
"rightClone": this.dom.clone.right |
|
} ); |
|
}, |
|
|
|
|
|
/** |
|
* Clone the right columns |
|
* @returns {void} |
|
* @param {Boolean} bAll Indicate if the header and footer should be updated as well (true) |
|
* @private |
|
*/ |
|
"_fnCloneRight": function ( bAll ) |
|
{ |
|
if ( this.s.iRightColumns <= 0 ) { |
|
return; |
|
} |
|
|
|
var that = this, |
|
i, jq, |
|
aiColumns = []; |
|
|
|
for ( i=this.s.iTableColumns-this.s.iRightColumns ; i<this.s.iTableColumns ; i++ ) { |
|
if ( this.s.dt.aoColumns[i].bVisible ) { |
|
aiColumns.push( i ); |
|
} |
|
} |
|
|
|
this._fnClone( this.dom.clone.right, this.dom.grid.right, aiColumns, bAll ); |
|
}, |
|
|
|
|
|
/** |
|
* Clone the left columns |
|
* @returns {void} |
|
* @param {Boolean} bAll Indicate if the header and footer should be updated as well (true) |
|
* @private |
|
*/ |
|
"_fnCloneLeft": function ( bAll ) |
|
{ |
|
if ( this.s.iLeftColumns <= 0 ) { |
|
return; |
|
} |
|
|
|
var that = this, |
|
i, jq, |
|
aiColumns = []; |
|
|
|
for ( i=0 ; i<this.s.iLeftColumns ; i++ ) { |
|
if ( this.s.dt.aoColumns[i].bVisible ) { |
|
aiColumns.push( i ); |
|
} |
|
} |
|
|
|
this._fnClone( this.dom.clone.left, this.dom.grid.left, aiColumns, bAll ); |
|
}, |
|
|
|
|
|
/** |
|
* Make a copy of the layout object for a header or footer element from DataTables. Note that |
|
* this method will clone the nodes in the layout object. |
|
* @returns {Array} Copy of the layout array |
|
* @param {Object} aoOriginal Layout array from DataTables (aoHeader or aoFooter) |
|
* @param {Object} aiColumns Columns to copy |
|
* @param {boolean} events Copy cell events or not |
|
* @private |
|
*/ |
|
"_fnCopyLayout": function ( aoOriginal, aiColumns, events ) |
|
{ |
|
var aReturn = []; |
|
var aClones = []; |
|
var aCloned = []; |
|
|
|
for ( var i=0, iLen=aoOriginal.length ; i<iLen ; i++ ) |
|
{ |
|
var aRow = []; |
|
aRow.nTr = $(aoOriginal[i].nTr).clone(events, false)[0]; |
|
|
|
for ( var j=0, jLen=this.s.iTableColumns ; j<jLen ; j++ ) |
|
{ |
|
if ( $.inArray( j, aiColumns ) === -1 ) |
|
{ |
|
continue; |
|
} |
|
|
|
var iCloned = $.inArray( aoOriginal[i][j].cell, aCloned ); |
|
if ( iCloned === -1 ) |
|
{ |
|
var nClone = $(aoOriginal[i][j].cell).clone(events, false)[0]; |
|
aClones.push( nClone ); |
|
aCloned.push( aoOriginal[i][j].cell ); |
|
|
|
aRow.push( { |
|
"cell": nClone, |
|
"unique": aoOriginal[i][j].unique |
|
} ); |
|
} |
|
else |
|
{ |
|
aRow.push( { |
|
"cell": aClones[ iCloned ], |
|
"unique": aoOriginal[i][j].unique |
|
} ); |
|
} |
|
} |
|
|
|
aReturn.push( aRow ); |
|
} |
|
|
|
return aReturn; |
|
}, |
|
|
|
|
|
/** |
|
* Clone the DataTable nodes and place them in the DOM (sized correctly) |
|
* @returns {void} |
|
* @param {Object} oClone Object containing the header, footer and body cloned DOM elements |
|
* @param {Object} oGrid Grid object containing the display grid elements for the cloned |
|
* column (left or right) |
|
* @param {Array} aiColumns Column indexes which should be operated on from the DataTable |
|
* @param {Boolean} bAll Indicate if the header and footer should be updated as well (true) |
|
* @private |
|
*/ |
|
"_fnClone": function ( oClone, oGrid, aiColumns, bAll ) |
|
{ |
|
var that = this, |
|
i, iLen, j, jLen, jq, nTarget, iColumn, nClone, iIndex, aoCloneLayout, |
|
jqCloneThead, aoFixedHeader, |
|
dt = this.s.dt; |
|
|
|
/* |
|
* Header |
|
*/ |
|
if ( bAll ) |
|
{ |
|
$(oClone.header).remove(); |
|
|
|
oClone.header = $(this.dom.header).clone(true, false)[0]; |
|
oClone.header.className += " DTFC_Cloned"; |
|
oClone.header.style.width = "100%"; |
|
oGrid.head.appendChild( oClone.header ); |
|
|
|
/* Copy the DataTables layout cache for the header for our floating column */ |
|
aoCloneLayout = this._fnCopyLayout( dt.aoHeader, aiColumns, true ); |
|
jqCloneThead = $('>thead', oClone.header); |
|
jqCloneThead.empty(); |
|
|
|
/* Add the created cloned TR elements to the table */ |
|
for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ ) |
|
{ |
|
jqCloneThead[0].appendChild( aoCloneLayout[i].nTr ); |
|
} |
|
|
|
/* Use the handy _fnDrawHead function in DataTables to do the rowspan/colspan |
|
* calculations for us |
|
*/ |
|
dt.oApi._fnDrawHead( dt, aoCloneLayout, true ); |
|
} |
|
else |
|
{ |
|
/* To ensure that we copy cell classes exactly, regardless of colspan, multiple rows |
|
* etc, we make a copy of the header from the DataTable again, but don't insert the |
|
* cloned cells, just copy the classes across. To get the matching layout for the |
|
* fixed component, we use the DataTables _fnDetectHeader method, allowing 1:1 mapping |
|
*/ |
|
aoCloneLayout = this._fnCopyLayout( dt.aoHeader, aiColumns, false ); |
|
aoFixedHeader=[]; |
|
|
|
dt.oApi._fnDetectHeader( aoFixedHeader, $('>thead', oClone.header)[0] ); |
|
|
|
for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ ) |
|
{ |
|
for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ ) |
|
{ |
|
aoFixedHeader[i][j].cell.className = aoCloneLayout[i][j].cell.className; |
|
|
|
// If jQuery UI theming is used we need to copy those elements as well |
|
$('span.DataTables_sort_icon', aoFixedHeader[i][j].cell).each( function () { |
|
this.className = $('span.DataTables_sort_icon', aoCloneLayout[i][j].cell)[0].className; |
|
} ); |
|
} |
|
} |
|
} |
|
this._fnEqualiseHeights( 'thead', this.dom.header, oClone.header ); |
|
|
|
/* |
|
* Body |
|
*/ |
|
if ( this.s.sHeightMatch == 'auto' ) |
|
{ |
|
/* Remove any heights which have been applied already and let the browser figure it out */ |
|
$('>tbody>tr', that.dom.body).css('height', 'auto'); |
|
} |
|
|
|
if ( oClone.body !== null ) |
|
{ |
|
$(oClone.body).remove(); |
|
oClone.body = null; |
|
} |
|
|
|
oClone.body = $(this.dom.body).clone(true)[0]; |
|
oClone.body.className += " DTFC_Cloned"; |
|
oClone.body.style.paddingBottom = dt.oScroll.iBarWidth+"px"; |
|
oClone.body.style.marginBottom = (dt.oScroll.iBarWidth*2)+"px"; /* For IE */ |
|
if ( oClone.body.getAttribute('id') !== null ) |
|
{ |
|
oClone.body.removeAttribute('id'); |
|
} |
|
|
|
$('>thead>tr', oClone.body).empty(); |
|
$('>tfoot', oClone.body).remove(); |
|
|
|
var nBody = $('tbody', oClone.body)[0]; |
|
$(nBody).empty(); |
|
if ( dt.aiDisplay.length > 0 ) |
|
{ |
|
/* Copy the DataTables' header elements to force the column width in exactly the |
|
* same way that DataTables does it - have the header element, apply the width and |
|
* colapse it down |
|
*/ |
|
var nInnerThead = $('>thead>tr', oClone.body)[0]; |
|
for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ ) |
|
{ |
|
iColumn = aiColumns[iIndex]; |
|
|
|
nClone = $(dt.aoColumns[iColumn].nTh).clone(true)[0]; |
|
nClone.innerHTML = ""; |
|
|
|
var oStyle = nClone.style; |
|
oStyle.paddingTop = "0"; |
|
oStyle.paddingBottom = "0"; |
|
oStyle.borderTopWidth = "0"; |
|
oStyle.borderBottomWidth = "0"; |
|
oStyle.height = 0; |
|
oStyle.width = that.s.aiInnerWidths[iColumn]+"px"; |
|
|
|
nInnerThead.appendChild( nClone ); |
|
} |
|
|
|
/* Add in the tbody elements, cloning form the master table */ |
|
$('>tbody>tr', that.dom.body).each( function (z) { |
|
var i = that.s.dt.oFeatures.bServerSide===false ? |
|
that.s.dt.aiDisplay[ that.s.dt._iDisplayStart+z ] : z; |
|
var aTds = that.s.dt.aoData[ i ].anCells || $(this).children('td, th'); |
|
|
|
var n = this.cloneNode(false); |
|
n.removeAttribute('id'); |
|
n.setAttribute( 'data-dt-row', i ); |
|
|
|
for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ ) |
|
{ |
|
iColumn = aiColumns[iIndex]; |
|
|
|
if ( aTds.length > 0 ) |
|
{ |
|
nClone = $( aTds[iColumn] ).clone(true, true)[0]; |
|
nClone.removeAttribute( 'id' ); |
|
nClone.setAttribute( 'data-dt-row', i ); |
|
nClone.setAttribute( 'data-dt-column', iColumn ); |
|
n.appendChild( nClone ); |
|
} |
|
} |
|
nBody.appendChild( n ); |
|
} ); |
|
} |
|
else |
|
{ |
|
$('>tbody>tr', that.dom.body).each( function (z) { |
|
nClone = this.cloneNode(true); |
|
nClone.className += ' DTFC_NoData'; |
|
$('td', nClone).html(''); |
|
nBody.appendChild( nClone ); |
|
} ); |
|
} |
|
|
|
oClone.body.style.width = "100%"; |
|
oClone.body.style.margin = "0"; |
|
oClone.body.style.padding = "0"; |
|
|
|
// Interop with Scroller - need to use a height forcing element in the |
|
// scrolling area in the same way that Scroller does in the body scroll. |
|
if ( dt.oScroller !== undefined ) |
|
{ |
|
var scrollerForcer = dt.oScroller.dom.force; |
|
|
|
if ( ! oGrid.forcer ) { |
|
oGrid.forcer = scrollerForcer.cloneNode( true ); |
|
oGrid.liner.appendChild( oGrid.forcer ); |
|
} |
|
else { |
|
oGrid.forcer.style.height = scrollerForcer.style.height; |
|
} |
|
} |
|
|
|
oGrid.liner.appendChild( oClone.body ); |
|
|
|
this._fnEqualiseHeights( 'tbody', that.dom.body, oClone.body ); |
|
|
|
/* |
|
* Footer |
|
*/ |
|
if ( dt.nTFoot !== null ) |
|
{ |
|
if ( bAll ) |
|
{ |
|
if ( oClone.footer !== null ) |
|
{ |
|
oClone.footer.parentNode.removeChild( oClone.footer ); |
|
} |
|
oClone.footer = $(this.dom.footer).clone(true, true)[0]; |
|
oClone.footer.className += " DTFC_Cloned"; |
|
oClone.footer.style.width = "100%"; |
|
oGrid.foot.appendChild( oClone.footer ); |
|
|
|
/* Copy the footer just like we do for the header */ |
|
aoCloneLayout = this._fnCopyLayout( dt.aoFooter, aiColumns, true ); |
|
var jqCloneTfoot = $('>tfoot', oClone.footer); |
|
jqCloneTfoot.empty(); |
|
|
|
for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ ) |
|
{ |
|
jqCloneTfoot[0].appendChild( aoCloneLayout[i].nTr ); |
|
} |
|
dt.oApi._fnDrawHead( dt, aoCloneLayout, true ); |
|
} |
|
else |
|
{ |
|
aoCloneLayout = this._fnCopyLayout( dt.aoFooter, aiColumns, false ); |
|
var aoCurrFooter=[]; |
|
|
|
dt.oApi._fnDetectHeader( aoCurrFooter, $('>tfoot', oClone.footer)[0] ); |
|
|
|
for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ ) |
|
{ |
|
for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ ) |
|
{ |
|
aoCurrFooter[i][j].cell.className = aoCloneLayout[i][j].cell.className; |
|
} |
|
} |
|
} |
|
this._fnEqualiseHeights( 'tfoot', this.dom.footer, oClone.footer ); |
|
} |
|
|
|
/* Equalise the column widths between the header footer and body - body get's priority */ |
|
var anUnique = dt.oApi._fnGetUniqueThs( dt, $('>thead', oClone.header)[0] ); |
|
$(anUnique).each( function (i) { |
|
iColumn = aiColumns[i]; |
|
this.style.width = that.s.aiInnerWidths[iColumn]+"px"; |
|
} ); |
|
|
|
if ( that.s.dt.nTFoot !== null ) |
|
{ |
|
anUnique = dt.oApi._fnGetUniqueThs( dt, $('>tfoot', oClone.footer)[0] ); |
|
$(anUnique).each( function (i) { |
|
iColumn = aiColumns[i]; |
|
this.style.width = that.s.aiInnerWidths[iColumn]+"px"; |
|
} ); |
|
} |
|
}, |
|
|
|
|
|
/** |
|
* From a given table node (THEAD etc), get a list of TR direct child elements |
|
* @param {Node} nIn Table element to search for TR elements (THEAD, TBODY or TFOOT element) |
|
* @returns {Array} List of TR elements found |
|
* @private |
|
*/ |
|
"_fnGetTrNodes": function ( nIn ) |
|
{ |
|
var aOut = []; |
|
for ( var i=0, iLen=nIn.childNodes.length ; i<iLen ; i++ ) |
|
{ |
|
if ( nIn.childNodes[i].nodeName.toUpperCase() == "TR" ) |
|
{ |
|
aOut.push( nIn.childNodes[i] ); |
|
} |
|
} |
|
return aOut; |
|
}, |
|
|
|
|
|
/** |
|
* Equalise the heights of the rows in a given table node in a cross browser way |
|
* @returns {void} |
|
* @param {String} nodeName Node type - thead, tbody or tfoot |
|
* @param {Node} original Original node to take the heights from |
|
* @param {Node} clone Copy the heights to |
|
* @private |
|
*/ |
|
"_fnEqualiseHeights": function ( nodeName, original, clone ) |
|
{ |
|
if ( this.s.sHeightMatch == 'none' && nodeName !== 'thead' && nodeName !== 'tfoot' ) |
|
{ |
|
return; |
|
} |
|
|
|
var that = this, |
|
i, iLen, iHeight, iHeight2, iHeightOriginal, iHeightClone, |
|
rootOriginal = original.getElementsByTagName(nodeName)[0], |
|
rootClone = clone.getElementsByTagName(nodeName)[0], |
|
jqBoxHack = $('>'+nodeName+'>tr:eq(0)', original).children(':first'), |
|
iBoxHack = jqBoxHack.outerHeight() - jqBoxHack.height(), |
|
anOriginal = this._fnGetTrNodes( rootOriginal ), |
|
anClone = this._fnGetTrNodes( rootClone ), |
|
heights = []; |
|
|
|
for ( i=0, iLen=anClone.length ; i<iLen ; i++ ) |
|
{ |
|
iHeightOriginal = anOriginal[i].offsetHeight; |
|
iHeightClone = anClone[i].offsetHeight; |
|
iHeight = iHeightClone > iHeightOriginal ? iHeightClone : iHeightOriginal; |
|
|
|
if ( this.s.sHeightMatch == 'semiauto' ) |
|
{ |
|
anOriginal[i]._DTTC_iHeight = iHeight; |
|
} |
|
|
|
heights.push( iHeight ); |
|
} |
|
|
|
for ( i=0, iLen=anClone.length ; i<iLen ; i++ ) |
|
{ |
|
anClone[i].style.height = heights[i]+"px"; |
|
anOriginal[i].style.height = heights[i]+"px"; |
|
} |
|
}, |
|
|
|
/** |
|
* Determine if the UA suffers from Firefox's overflow:scroll scrollbars |
|
* not being shown bug. |
|
* |
|
* Firefox doesn't draw scrollbars, even if it is told to using |
|
* overflow:scroll, if the div is less than 34px height. See bugs 292284 and |
|
* 781885. Using UA detection here since this is particularly hard to detect |
|
* using objects - its a straight up rendering error in Firefox. |
|
* |
|
* @return {boolean} True if Firefox error is present, false otherwise |
|
*/ |
|
_firefoxScrollError: function () { |
|
if ( _firefoxScroll === undefined ) { |
|
var test = $('<div/>') |
|
.css( { |
|
position: 'absolute', |
|
top: 0, |
|
left: 0, |
|
height: 10, |
|
width: 50, |
|
overflow: 'scroll' |
|
} ) |
|
.appendTo( 'body' ); |
|
|
|
// Make sure this doesn't apply on Macs with 0 width scrollbars |
|
_firefoxScroll = ( |
|
test[0].clientWidth === test[0].offsetWidth && this._fnDTOverflow().bar !== 0 |
|
); |
|
|
|
test.remove(); |
|
} |
|
|
|
return _firefoxScroll; |
|
} |
|
} ); |
|
|
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
* Statics |
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
|
|
/** |
|
* FixedColumns default settings for initialisation |
|
* @name FixedColumns.defaults |
|
* @namespace |
|
* @static |
|
*/ |
|
FixedColumns.defaults = /** @lends FixedColumns.defaults */{ |
|
/** |
|
* Number of left hand columns to fix in position |
|
* @type int |
|
* @default 1 |
|
* @static |
|
* @example |
|
* var = $('#example').dataTable( { |
|
* "scrollX": "100%" |
|
* } ); |
|
* new $.fn.dataTable.fixedColumns( table, { |
|
* "leftColumns": 2 |
|
* } ); |
|
*/ |
|
"iLeftColumns": 1, |
|
|
|
/** |
|
* Number of right hand columns to fix in position |
|
* @type int |
|
* @default 0 |
|
* @static |
|
* @example |
|
* var table = $('#example').dataTable( { |
|
* "scrollX": "100%" |
|
* } ); |
|
* new $.fn.dataTable.fixedColumns( table, { |
|
* "rightColumns": 1 |
|
* } ); |
|
*/ |
|
"iRightColumns": 0, |
|
|
|
/** |
|
* Draw callback function which is called when FixedColumns has redrawn the fixed assets |
|
* @type function(object, object):void |
|
* @default null |
|
* @static |
|
* @example |
|
* var table = $('#example').dataTable( { |
|
* "scrollX": "100%" |
|
* } ); |
|
* new $.fn.dataTable.fixedColumns( table, { |
|
* "drawCallback": function () { |
|
* alert( "FixedColumns redraw" ); |
|
* } |
|
* } ); |
|
*/ |
|
"fnDrawCallback": null, |
|
|
|
/** |
|
* Height matching algorthim to use. This can be "none" which will result in no height |
|
* matching being applied by FixedColumns (height matching could be forced by CSS in this |
|
* case), "semiauto" whereby the height calculation will be performed once, and the result |
|
* cached to be used again (fnRecalculateHeight can be used to force recalculation), or |
|
* "auto" when height matching is performed on every draw (slowest but must accurate) |
|
* @type string |
|
* @default semiauto |
|
* @static |
|
* @example |
|
* var table = $('#example').dataTable( { |
|
* "scrollX": "100%" |
|
* } ); |
|
* new $.fn.dataTable.fixedColumns( table, { |
|
* "heightMatch": "auto" |
|
* } ); |
|
*/ |
|
"sHeightMatch": "semiauto" |
|
}; |
|
|
|
|
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
* Constants |
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
|
|
/** |
|
* FixedColumns version |
|
* @name FixedColumns.version |
|
* @type String |
|
* @default See code |
|
* @static |
|
*/ |
|
FixedColumns.version = "3.3.2"; |
|
|
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
* DataTables API integration |
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
|
|
DataTable.Api.register( 'fixedColumns()', function () { |
|
return this; |
|
} ); |
|
|
|
DataTable.Api.register( 'fixedColumns().update()', function () { |
|
return this.iterator( 'table', function ( ctx ) { |
|
if ( ctx._oFixedColumns ) { |
|
ctx._oFixedColumns.fnUpdate(); |
|
} |
|
} ); |
|
} ); |
|
|
|
DataTable.Api.register( 'fixedColumns().relayout()', function () { |
|
return this.iterator( 'table', function ( ctx ) { |
|
if ( ctx._oFixedColumns ) { |
|
ctx._oFixedColumns.fnRedrawLayout(); |
|
} |
|
} ); |
|
} ); |
|
|
|
DataTable.Api.register( 'rows().recalcHeight()', function () { |
|
return this.iterator( 'row', function ( ctx, idx ) { |
|
if ( ctx._oFixedColumns ) { |
|
ctx._oFixedColumns.fnRecalculateHeight( this.row(idx).node() ); |
|
} |
|
} ); |
|
} ); |
|
|
|
DataTable.Api.register( 'fixedColumns().rowIndex()', function ( row ) { |
|
row = $(row); |
|
|
|
return row.parents('.DTFC_Cloned').length ? |
|
this.rows( { page: 'current' } ).indexes()[ row.index() ] : |
|
this.row( row ).index(); |
|
} ); |
|
|
|
DataTable.Api.register( 'fixedColumns().cellIndex()', function ( cell ) { |
|
cell = $(cell); |
|
|
|
if ( cell.parents('.DTFC_Cloned').length ) { |
|
var rowClonedIdx = cell.parent().index(); |
|
var rowIdx = this.rows( { page: 'current' } ).indexes()[ rowClonedIdx ]; |
|
var columnIdx; |
|
|
|
if ( cell.parents('.DTFC_LeftWrapper').length ) { |
|
columnIdx = cell.index(); |
|
} |
|
else { |
|
var columns = this.columns().flatten().length; |
|
columnIdx = columns - this.context[0]._oFixedColumns.s.iRightColumns + cell.index(); |
|
} |
|
|
|
return { |
|
row: rowIdx, |
|
column: this.column.index( 'toData', columnIdx ), |
|
columnVisible: columnIdx |
|
}; |
|
} |
|
else { |
|
return this.cell( cell ).index(); |
|
} |
|
} ); |
|
|
|
DataTable.Api.registerPlural( 'cells().fixedNodes()', 'cell().fixedNode()', function () { |
|
return this.iterator( 'cell', function ( settings, row, column ) { |
|
return settings._oFixedColumns |
|
? settings._oFixedColumns.fnToFixedNode( row, column ) |
|
: this.cell(row, column).node(); |
|
}, 1 ); |
|
} ); |
|
|
|
|
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
* Initialisation |
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
|
|
// Attach a listener to the document which listens for DataTables initialisation |
|
// events so we can automatically initialise |
|
$(document).on( 'init.dt.fixedColumns', function (e, settings) { |
|
if ( e.namespace !== 'dt' ) { |
|
return; |
|
} |
|
|
|
var init = settings.oInit.fixedColumns; |
|
var defaults = DataTable.defaults.fixedColumns; |
|
|
|
if ( init || defaults ) { |
|
var opts = $.extend( {}, init, defaults ); |
|
|
|
if ( init !== false ) { |
|
new FixedColumns( settings, opts ); |
|
} |
|
} |
|
} ); |
|
|
|
|
|
|
|
// Make FixedColumns accessible from the DataTables instance |
|
$.fn.dataTable.FixedColumns = FixedColumns; |
|
$.fn.DataTable.FixedColumns = FixedColumns; |
|
|
|
return FixedColumns; |
|
}));
|
|
|