Created
October 24, 2012 09:26
-
-
Save maxig/3945072 to your computer and use it in GitHub Desktop.
DataTable FixedHeader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* File: FixedHeader.js | |
* Version: 2.1.0.dev | |
* Description: "Fix" a header at the top of the table, so it scrolls with the table | |
* Author: Allan Jardine (www.sprymedia.co.uk) | |
* Created: Wed 16 Sep 2009 19:46:30 BST | |
* Language: Javascript | |
* License: GPL v2 or BSD 3 point style | |
* Project: Just a little bit of fun - enjoy :-) | |
* Contact: www.sprymedia.co.uk/contact | |
* | |
* Copyright 2009-2012 Allan Jardine, all rights reserved. | |
* | |
* This source file is free software, under either the GPL v2 license or a | |
* BSD style license, available at: | |
* http://datatables.net/license_gpl2 | |
* http://datatables.net/license_bsd | |
*/ | |
/* Global scope for FixedColumns */ | |
var FixedHeader; | |
(function (window, document, $) { | |
/* | |
* Function: FixedHeader | |
* Purpose: Provide 'fixed' header, footer and columns on an HTML table | |
* Returns: object:FixedHeader - must be called with 'new' | |
* Inputs: mixed:mTable - target table | |
* 1. DataTable object - when using FixedHeader with DataTables, or | |
* 2. HTML table node - when using FixedHeader without DataTables | |
* object:oInit - initialisation settings, with the following properties (each optional) | |
* bool:top - fix the header (default true) | |
* bool:bottom - fix the footer (default false) | |
* bool:left - fix the left most column (default false) | |
* bool:right - fix the right most column (default false) | |
* int:zTop - fixed header zIndex | |
* int:zBottom - fixed footer zIndex | |
* int:zLeft - fixed left zIndex | |
* int:zRight - fixed right zIndex | |
*/ | |
FixedHeader = function (mTable, oInit) { | |
/* Sanity check - you just know it will happen */ | |
if (typeof this.fnInit != 'function') { | |
alert("FixedHeader warning: FixedHeader must be initialised with the 'new' keyword."); | |
return; | |
} | |
var that = this; | |
var oSettings = { | |
"aoCache":[], | |
"oSides":{ | |
"top":true, | |
"bottom":false, | |
"left":false, | |
"right":false | |
}, | |
"oZIndexes":{ | |
"top":104, | |
"bottom":103, | |
"left":102, | |
"right":101 | |
}, | |
"oCloneOnDraw":{ | |
"top":true, | |
"bottom":false, | |
"left":true, | |
"right":true | |
}, | |
"oMes":{ | |
"iTableWidth":0, | |
"iTableHeight":0, | |
"iTableLeft":0, | |
"iTableRight":0, /* note this is left+width, not actually "right" */ | |
"iTableTop":0, | |
"iTableBottom":0 /* note this is top+height, not actually "bottom" */ | |
}, | |
"oOffset":{ | |
"top":0 | |
}, | |
"nTable":null, | |
"bUseAbsPos":false, | |
"bFooter":false, | |
"bInitComplete":false | |
}; | |
/* | |
* Function: fnGetSettings | |
* Purpose: Get the settings for this object | |
* Returns: object: - settings object | |
* Inputs: - | |
*/ | |
this.fnGetSettings = function () { | |
return oSettings; | |
}; | |
/* | |
* Function: fnUpdate | |
* Purpose: Update the positioning and copies of the fixed elements | |
* Returns: - | |
* Inputs: - | |
*/ | |
this.fnUpdate = function () { | |
this._fnUpdateClones(); | |
this._fnUpdatePositions(); | |
}; | |
/* | |
* Function: fnPosition | |
* Purpose: Update the positioning of the fixed elements | |
* Returns: - | |
* Inputs: - | |
*/ | |
this.fnPosition = function () { | |
this._fnUpdatePositions(); | |
}; | |
/* Let's do it */ | |
this.fnInit(mTable, oInit); | |
/* Store the instance on the DataTables object for easy access */ | |
if (typeof mTable.fnSettings == 'function') { | |
mTable._oPluginFixedHeader = this; | |
} | |
}; | |
/* | |
* Variable: FixedHeader | |
* Purpose: Prototype for FixedHeader | |
* Scope: global | |
*/ | |
FixedHeader.prototype = { | |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
* Initialisation | |
*/ | |
/* | |
* Function: fnInit | |
* Purpose: The "constructor" | |
* Returns: - | |
* Inputs: {as FixedHeader function} | |
*/ | |
fnInit:function (oTable, oInit) { | |
var s = this.fnGetSettings(); | |
var that = this; | |
/* Record the user definable settings */ | |
this.fnInitSettings(s, oInit); | |
/* DataTables specific stuff */ | |
if (typeof oTable.fnSettings == 'function') { | |
if (typeof oTable.fnVersionCheck == 'functon' && | |
oTable.fnVersionCheck('1.6.0') !== true) { | |
alert("FixedHeader 2 required DataTables 1.6.0 or later. " + | |
"Please upgrade your DataTables installation"); | |
return; | |
} | |
var oDtSettings = oTable.fnSettings(); | |
if (oDtSettings.oScroll.sX != "" || oDtSettings.oScroll.sY != "") { | |
alert("FixedHeader 2 is not supported with DataTables' scrolling mode at this time"); | |
return; | |
} | |
s.nTable = oDtSettings.nTable; | |
oDtSettings.aoDrawCallback.unshift({ | |
"fn":function () { | |
FixedHeader.fnMeasure(); | |
that._fnUpdateClones.call(that); | |
that._fnUpdatePositions.call(that); | |
}, | |
"sName":"FixedHeader" | |
}); | |
} | |
else { | |
s.nTable = oTable; | |
} | |
s.bFooter = ($('>tfoot', s.nTable).length > 0) ? true : false; | |
/* "Detect" browsers that don't support absolute positioing - or have bugs */ | |
s.bUseAbsPos = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0")); | |
/* Add the 'sides' that are fixed */ | |
if (s.oSides.top) { | |
s.aoCache.push(that._fnCloneTable("fixedHeader", "FixedHeader_Header", that._fnCloneThead)); | |
} | |
if (s.oSides.bottom) { | |
s.aoCache.push(that._fnCloneTable("fixedFooter", "FixedHeader_Footer", that._fnCloneTfoot)); | |
} | |
if (s.oSides.left) { | |
s.aoCache.push(that._fnCloneTable("fixedLeft", "FixedHeader_Left", that._fnCloneTLeft)); | |
} | |
if (s.oSides.right) { | |
s.aoCache.push(that._fnCloneTable("fixedRight", "FixedHeader_Right", that._fnCloneTRight)); | |
} | |
if (s.oSides.left && s.oSides.top) { | |
s.aoCache.push(that._fnCloneTable("fixedLeftHead", "FixedHeader_LeftHead", that._fnCloneTLeftHead)); | |
} | |
/* Event listeners for window movement */ | |
FixedHeader.afnScroll.push(function () { | |
that._fnUpdatePositions.call(that); | |
}); | |
$(window).resize(function () { | |
FixedHeader.fnMeasure(); | |
that._fnUpdateClones.call(that); | |
that._fnUpdatePositions.call(that); | |
}); | |
/* Get things right to start with */ | |
FixedHeader.fnMeasure(); | |
that._fnUpdateClones(); | |
that._fnUpdatePositions(); | |
s.bInitComplete = true; | |
}, | |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
* Support functions | |
*/ | |
/* | |
* Function: fnInitSettings | |
* Purpose: Take the user's settings and copy them to our local store | |
* Returns: - | |
* Inputs: object:s - the local settings object | |
* object:oInit - the user's settings object | |
*/ | |
fnInitSettings:function (s, oInit) { | |
if (typeof oInit != 'undefined') { | |
if (typeof oInit.top != 'undefined') { | |
s.oSides.top = oInit.top; | |
} | |
if (typeof oInit.bottom != 'undefined') { | |
s.oSides.bottom = oInit.bottom; | |
} | |
if (typeof oInit.left != 'undefined') { | |
s.oSides.left = oInit.left; | |
} | |
if (typeof oInit.right != 'undefined') { | |
s.oSides.right = oInit.right; | |
} | |
if (typeof oInit.zTop != 'undefined') { | |
s.oZIndexes.top = oInit.zTop; | |
} | |
if (typeof oInit.zBottom != 'undefined') { | |
s.oZIndexes.bottom = oInit.zBottom; | |
} | |
if (typeof oInit.zLeft != 'undefined') { | |
s.oZIndexes.left = oInit.zLeft; | |
} | |
if (typeof oInit.zRight != 'undefined') { | |
s.oZIndexes.right = oInit.zRight; | |
} | |
if (typeof oInit.offsetTop != 'undefined') { | |
s.oOffset.top = oInit.offsetTop; | |
} | |
if (typeof oInit.alwaysCloneTop != 'undefined') { | |
s.oCloneOnDraw.top = oInit.alwaysCloneTop; | |
} | |
if (typeof oInit.alwaysCloneBottom != 'undefined') { | |
s.oCloneOnDraw.bottom = oInit.alwaysCloneBottom; | |
} | |
if (typeof oInit.alwaysCloneLeft != 'undefined') { | |
s.oCloneOnDraw.left = oInit.alwaysCloneLeft; | |
} | |
if (typeof oInit.alwaysCloneRight != 'undefined') { | |
s.oCloneOnDraw.right = oInit.alwaysCloneRight; | |
} | |
} | |
/* Detect browsers which have poor position:fixed support so we can use absolute positions. | |
* This is much slower since the position must be updated for each scroll, but widens | |
* compatibility | |
*/ | |
s.bUseAbsPos = ($.browser.msie && | |
($.browser.version == "6.0" || $.browser.version == "7.0")); | |
}, | |
/* | |
* Function: _fnCloneTable | |
* Purpose: Clone the table node and do basic initialisation | |
* Returns: - | |
* Inputs: - | |
*/ | |
_fnCloneTable:function (sType, sClass, fnClone) { | |
var s = this.fnGetSettings(); | |
var nCTable; | |
/* We know that the table _MUST_ has a DIV wrapped around it, because this is simply how | |
* DataTables works. Therefore, we can set this to be relatively position (if it is not | |
* alreadu absolute, and use this as the base point for the cloned header | |
*/ | |
if ($(s.nTable.parentNode).css('position') != "absolute") { | |
s.nTable.parentNode.style.position = "relative"; | |
} | |
/* Just a shallow clone will do - we only want the table node */ | |
nCTable = s.nTable.cloneNode(false); | |
nCTable.removeAttribute('id'); | |
var nDiv = document.createElement('div'); | |
nDiv.style.position = "absolute"; | |
nDiv.style.top = "0px"; | |
nDiv.style.left = "0px"; | |
nDiv.className += " FixedHeader_Cloned " + sType + " " + sClass; | |
/* Set the zIndexes */ | |
if (sType == "fixedHeader") { | |
nDiv.style.zIndex = s.oZIndexes.top; | |
} | |
if (sType == "fixedFooter") { | |
nDiv.style.zIndex = s.oZIndexes.bottom; | |
} | |
if (sType == "fixedLeft") { | |
nDiv.style.zIndex = s.oZIndexes.left; | |
} | |
if (sType == "fixedLeftHead") { | |
nDiv.style.zIndex = s.oZIndexes.left; | |
} | |
else if (sType == "fixedRight") { | |
nDiv.style.zIndex = s.oZIndexes.right; | |
} | |
/* remove margins since we are going to position it absolutely */ | |
nCTable.style.margin = "0"; | |
/* Insert the newly cloned table into the DOM, on top of the "real" header */ | |
nDiv.appendChild(nCTable); | |
document.body.appendChild(nDiv); | |
return { | |
"nNode":nCTable, | |
"nWrapper":nDiv, | |
"sType":sType, | |
"sPosition":"", | |
"sTop":"", | |
"sLeft":"", | |
"fnClone":fnClone | |
}; | |
}, | |
/* | |
* Function: _fnUpdatePositions | |
* Purpose: Get the current positioning of the table in the DOM | |
* Returns: - | |
* Inputs: - | |
*/ | |
_fnMeasure:function () { | |
var | |
s = this.fnGetSettings(), | |
m = s.oMes, | |
jqTable = $(s.nTable), | |
oOffset = jqTable.offset(), | |
iParentScrollTop = this._fnSumScroll(s.nTable.parentNode, 'scrollTop'), | |
iParentScrollLeft = this._fnSumScroll(s.nTable.parentNode, 'scrollLeft'); | |
m.iTableWidth = jqTable.outerWidth(); | |
m.iTableHeight = jqTable.outerHeight(); | |
m.iTableLeft = oOffset.left + s.nTable.parentNode.scrollLeft; | |
m.iTableTop = oOffset.top + iParentScrollTop; | |
m.iTableRight = m.iTableLeft + m.iTableWidth; | |
m.iTableRight = FixedHeader.oDoc.iWidth - m.iTableLeft - m.iTableWidth; | |
m.iTableBottom = FixedHeader.oDoc.iHeight - m.iTableTop - m.iTableHeight; | |
}, | |
/* | |
* Function: _fnSumScroll | |
* Purpose: Sum node parameters all the way to the top | |
* Returns: int: sum | |
* Inputs: node:n - node to consider | |
* string:side - scrollTop or scrollLeft | |
*/ | |
_fnSumScroll:function (n, side) { | |
var i = n[side]; | |
while (n = n.parentNode) { | |
if (n.nodeName == 'HTML' || n.nodeName == 'BODY') { | |
break; | |
} | |
i = n[side]; | |
} | |
return i; | |
}, | |
/* | |
* Function: _fnUpdatePositions | |
* Purpose: Loop over the fixed elements for this table and update their positions | |
* Returns: - | |
* Inputs: - | |
*/ | |
_fnUpdatePositions:function () { | |
var s = this.fnGetSettings(); | |
this._fnMeasure(); | |
for (var i = 0, iLen = s.aoCache.length; i < iLen; i++) { | |
if (s.aoCache[i].sType == "fixedHeader") { | |
this._fnScrollFixedHeader(s.aoCache[i]); | |
} | |
else if (s.aoCache[i].sType == "fixedFooter") { | |
this._fnScrollFixedFooter(s.aoCache[i]); | |
} | |
else if (s.aoCache[i].sType == "fixedLeft") { | |
this._fnScrollHorizontalLeft(s.aoCache[i]); | |
} | |
else if (s.aoCache[i].sType == "fixedLeftHead") { | |
this._fnScrollHorizontalLeftHead(s.aoCache[i]); | |
} | |
else { | |
this._fnScrollHorizontalRight(s.aoCache[i]); | |
} | |
} | |
// Trigger custom event to update filters | |
$(this.fnGetSettings().nTable).trigger("updateSelects"); | |
}, | |
/* | |
* Function: _fnUpdateClones | |
* Purpose: Loop over the fixed elements for this table and call their cloning functions | |
* Returns: - | |
* Inputs: - | |
*/ | |
_fnUpdateClones:function () { | |
var s = this.fnGetSettings(); | |
for (var i = 0, iLen = s.aoCache.length; i < iLen; i++) { | |
s.aoCache[i].fnClone.call(this, s.aoCache[i]); | |
} | |
}, | |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
* Scrolling functions | |
*/ | |
/* | |
* Function: _fnScrollHorizontalLeft | |
* Purpose: Update the positioning of the scrolling elements | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnScrollHorizontalRight:function (oCache) { | |
var | |
s = this.fnGetSettings(), | |
oMes = s.oMes, | |
oWin = FixedHeader.oWin, | |
oDoc = FixedHeader.oDoc, | |
nTable = oCache.nWrapper, | |
iFixedWidth = $(nTable).outerWidth(); | |
if (oWin.iScrollRight < oMes.iTableRight) { | |
/* Fully right aligned */ | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', oMes.iTableTop + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', (oMes.iTableLeft + oMes.iTableWidth - iFixedWidth) + "px", 'left', nTable.style); | |
} | |
else if (oMes.iTableLeft < oDoc.iWidth - oWin.iScrollRight - iFixedWidth) { | |
/* Middle */ | |
if (s.bUseAbsPos) { | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', oMes.iTableTop + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', (oDoc.iWidth - oWin.iScrollRight - iFixedWidth) + "px", 'left', nTable.style); | |
} | |
else { | |
this._fnUpdateCache(oCache, 'sPosition', 'fixed', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oMes.iTableTop - oWin.iScrollTop) + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', (oWin.iWidth - iFixedWidth) + "px", 'left', nTable.style); | |
} | |
} | |
else { | |
/* Fully left aligned */ | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', oMes.iTableTop + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
}, | |
/* | |
* Function: _fnScrollHorizontalLeft | |
* Purpose: Update the positioning of the scrolling elements | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnScrollHorizontalLeft:function (oCache) { | |
var | |
s = this.fnGetSettings(), | |
oMes = s.oMes, | |
oWin = FixedHeader.oWin, | |
oDoc = FixedHeader.oDoc, | |
nTable = oCache.nWrapper, | |
iCellWidth = $(nTable).outerWidth(); | |
if (oWin.iScrollLeft < oMes.iTableLeft) { | |
/* Fully left align */ | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', oMes.iTableTop + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
else if (oWin.iScrollLeft < oMes.iTableLeft + oMes.iTableWidth - iCellWidth) { | |
/* Middle */ | |
if (s.bUseAbsPos) { | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', oMes.iTableTop + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oWin.iScrollLeft + "px", 'left', nTable.style); | |
} | |
else { | |
this._fnUpdateCache(oCache, 'sPosition', 'fixed', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oMes.iTableTop - oWin.iScrollTop) + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', "0px", 'left', nTable.style); | |
} | |
} | |
else { | |
/* Fully right align */ | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', oMes.iTableTop + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', (oMes.iTableLeft + oMes.iTableWidth - iCellWidth) + "px", 'left', nTable.style); | |
} | |
}, | |
/* | |
* Function: _fnScrollHorizontalLeftHead | |
* Purpose: Update the positioning of the scrolling elements | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnScrollHorizontalLeftHead:function (oCache) { | |
var | |
s = this.fnGetSettings(), | |
oMes = s.oMes, | |
oWin = FixedHeader.oWin, | |
oDoc = FixedHeader.oDoc, | |
nTable = oCache.nWrapper, | |
iCellWidth = $(nTable).outerWidth(), | |
iTbodyHeight = 0, | |
anTbodies = s.nTable.getElementsByTagName('tbody'); | |
for (var i = 0; i < anTbodies.length; ++i) { | |
iTbodyHeight += anTbodies[i].offsetHeight; | |
} | |
if (oMes.iTableTop > oWin.iScrollTop + s.oOffset.top) { | |
// Page top not reached | |
this._fnUpdateCache(oCache, 'sTop', oMes.iTableTop + "px", 'top', nTable.style); | |
if (oWin.iScrollLeft < oMes.iTableLeft) { | |
// Page left boundary not reached | |
this._fnUpdateCache(oCache, 'sPosition', "absolute", 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
else if (oWin.iScrollLeft < oMes.iTableLeft + oMes.iTableWidth - iCellWidth) { | |
// Reach left boundary | |
if (s.bUseAbsPos) { | |
// IF IE | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oWin.iScrollLeft + "px", 'left', nTable.style); | |
} | |
else { | |
this._fnUpdateCache(oCache, 'sPosition', 'fixed', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', "0px", 'left', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oMes.iTableTop - oWin.iScrollTop) + "px", 'top', nTable.style); | |
} | |
} | |
else { | |
// Fully right aligned (reached right table boundary) | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', (oMes.iTableLeft + oMes.iTableWidth - iCellWidth) + "px", 'left', nTable.style); | |
} | |
} | |
else if (oWin.iScrollTop + s.oOffset.top > oMes.iTableTop + iTbodyHeight) { | |
// Bottom of the table reached | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oMes.iTableTop + iTbodyHeight) + "px", 'top', nTable.style); | |
if (oWin.iScrollLeft < oMes.iTableLeft) { | |
// Page left boundary not reached | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
else if (oWin.iScrollLeft < oMes.iTableLeft + oMes.iTableWidth - iCellWidth) { | |
// Reach left boundary | |
if (s.bUseAbsPos) { | |
// IF IE | |
this._fnUpdateCache(oCache, 'sLeft', oWin.iScrollLeft + "px", 'left', nTable.style); | |
} | |
else { | |
this._fnUpdateCache(oCache, 'sPosition', 'fixed', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', "0px", 'left', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oMes.iTableTop + iTbodyHeight - oWin.iScrollTop) + "px", 'top', nTable.style); | |
} | |
} | |
else { | |
// Fully right aligned (reached right table boundary) | |
this._fnUpdateCache(oCache, 'sLeft', (oMes.iTableLeft + oMes.iTableWidth - iCellWidth) + "px", 'left', nTable.style); | |
} | |
} | |
else { | |
// In the middle of the table (page top reached) | |
if (s.bUseAbsPos) { | |
// IF IE | |
this._fnUpdateCache(oCache, 'sPosition', "absolute", 'position', nTable.style); | |
} | |
else { | |
this._fnUpdateCache(oCache, 'sPosition', 'fixed', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', s.oOffset.top + "px", 'top', nTable.style); | |
if (oWin.iScrollLeft < oMes.iTableLeft) { | |
// Page left boundary not reached | |
this._fnUpdateCache(oCache, 'sLeft', (oMes.iTableLeft - oWin.iScrollLeft) + "px", 'left', nTable.style); | |
} | |
else if (oWin.iScrollLeft < oMes.iTableLeft + oMes.iTableWidth - iCellWidth) { | |
// Reach left boundary | |
if (s.bUseAbsPos) { | |
// IF IE | |
// this._fnUpdateCache(oCache, 'sLeft', oWin.iScrollLeft + "px", 'left', nTable.style); | |
} | |
else { | |
this._fnUpdateCache(oCache, 'sLeft', "0px", 'left', nTable.style); | |
} | |
} | |
else { | |
// Fully right aligned (reached right table boundary) | |
this._fnUpdateCache(oCache, 'sLeft', (oMes.iTableLeft + oMes.iTableWidth - iCellWidth) + "px", 'left', nTable.style); | |
} | |
} | |
} | |
}, | |
/* | |
* Function: _fnScrollFixedFooter | |
* Purpose: Update the positioning of the scrolling elements | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnScrollFixedFooter:function (oCache) { | |
var | |
s = this.fnGetSettings(), | |
oMes = s.oMes, | |
oWin = FixedHeader.oWin, | |
oDoc = FixedHeader.oDoc, | |
nTable = oCache.nWrapper, | |
iTheadHeight = $("thead", s.nTable).outerHeight(), | |
iCellHeight = $(nTable).outerHeight(); | |
if (oWin.iScrollBottom < oMes.iTableBottom) { | |
/* Below */ | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oMes.iTableTop + oMes.iTableHeight - iCellHeight) + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
else if (oWin.iScrollBottom < oMes.iTableBottom + oMes.iTableHeight - iCellHeight - iTheadHeight) { | |
/* Middle */ | |
if (s.bUseAbsPos) { | |
this._fnUpdateCache(oCache, 'sPosition', "absolute", 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oDoc.iHeight - oWin.iScrollBottom - iCellHeight) + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
else { | |
this._fnUpdateCache(oCache, 'sPosition', 'fixed', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oWin.iHeight - iCellHeight) + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', (oMes.iTableLeft - oWin.iScrollLeft) + "px", 'left', nTable.style); | |
} | |
} | |
else { | |
/* Above */ | |
this._fnUpdateCache(oCache, 'sPosition', 'absolute', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oMes.iTableTop + iCellHeight) + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
}, | |
/* | |
* Function: _fnScrollFixedHeader | |
* Purpose: Update the positioning of the scrolling elements | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnScrollFixedHeader:function (oCache) { | |
var | |
s = this.fnGetSettings(), | |
oMes = s.oMes, | |
oWin = FixedHeader.oWin, | |
oDoc = FixedHeader.oDoc, | |
nTable = oCache.nWrapper, | |
iTbodyHeight = 0, | |
anTbodies = s.nTable.getElementsByTagName('tbody'); | |
for (var i = 0; i < anTbodies.length; ++i) { | |
iTbodyHeight += anTbodies[i].offsetHeight; | |
} | |
if (oMes.iTableTop > oWin.iScrollTop + s.oOffset.top) { | |
/* Above the table */ | |
this._fnUpdateCache(oCache, 'sPosition', "absolute", 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', oMes.iTableTop + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
else if (oWin.iScrollTop + s.oOffset.top > oMes.iTableTop + iTbodyHeight) { | |
/* At the bottom of the table */ | |
this._fnUpdateCache(oCache, 'sPosition', "absolute", 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', (oMes.iTableTop + iTbodyHeight) + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
else { | |
/* In the middle of the table */ | |
if (s.bUseAbsPos) { | |
this._fnUpdateCache(oCache, 'sPosition', "absolute", 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', oWin.iScrollTop + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', oMes.iTableLeft + "px", 'left', nTable.style); | |
} | |
else { | |
this._fnUpdateCache(oCache, 'sPosition', 'fixed', 'position', nTable.style); | |
this._fnUpdateCache(oCache, 'sTop', s.oOffset.top + "px", 'top', nTable.style); | |
this._fnUpdateCache(oCache, 'sLeft', (oMes.iTableLeft - oWin.iScrollLeft) + "px", 'left', nTable.style); | |
} | |
} | |
}, | |
/* | |
* Function: _fnUpdateCache | |
* Purpose: Check the cache and update cache and value if needed | |
* Returns: - | |
* Inputs: object:oCache - local cache object | |
* string:sCache - cache property | |
* string:sSet - value to set | |
* string:sProperty - object property to set | |
* object:oObj - object to update | |
*/ | |
_fnUpdateCache:function (oCache, sCache, sSet, sProperty, oObj) { | |
if (oCache[sCache] != sSet) { | |
oObj[sProperty] = sSet; | |
oCache[sCache] = sSet; | |
} | |
}, | |
/** | |
* Copy the classes of all child nodes from one element to another. This implies | |
* that the two have identical structure - no error checking is performed to that | |
* fact. | |
* @param {element} source Node to copy classes from | |
* @param {element} dest Node to copy classes too | |
*/ | |
_fnClassUpdate:function (source, dest) { | |
var that = this; | |
if (source.nodeName.toUpperCase() === "TR" || source.nodeName.toUpperCase() === "TH" || | |
source.nodeName.toUpperCase() === "TD" || source.nodeName.toUpperCase() === "SPAN") { | |
dest.className = source.className; | |
} | |
$(source).children().each(function (i) { | |
that._fnClassUpdate($(source).children()[i], $(dest).children()[i]); | |
}); | |
}, | |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
* Cloning functions | |
*/ | |
/* | |
* Function: _fnCloneThead | |
* Purpose: Clone the thead element | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnCloneThead:function (oCache) { | |
var s = this.fnGetSettings(); | |
var nTable = oCache.nNode; | |
if (s.bInitComplete && !s.oCloneOnDraw.top) { | |
this._fnClassUpdate($('thead', s.nTable)[0], $('thead', nTable)[0]); | |
return; | |
} | |
/* Set the wrapper width to match that of the cloned table */ | |
var iDtWidth = $(s.nTable).outerWidth(); | |
oCache.nWrapper.style.width = iDtWidth + "px"; | |
nTable.style.width = iDtWidth + "px"; | |
/* Remove any children the cloned table has */ | |
while (nTable.childNodes.length > 0) { | |
$('thead th', nTable).unbind('click'); | |
nTable.removeChild(nTable.childNodes[0]); | |
} | |
/* Clone the DataTables header */ | |
var nThead = $('thead', s.nTable).clone(true)[0]; | |
nTable.appendChild(nThead); | |
/* Copy the widths across - apparently a clone isn't good enough for this */ | |
var a = []; | |
var b = []; | |
jQuery("thead>tr th", s.nTable).each(function (i) { | |
a.push(jQuery(this).width()); | |
}); | |
jQuery("thead>tr td", s.nTable).each(function (i) { | |
b.push(jQuery(this).width()); | |
}); | |
jQuery("thead>tr th", s.nTable).each(function (i) { | |
jQuery("thead>tr th:eq(" + i + ")", nTable).width(a[i]); | |
$(this).width(a[i]); | |
}); | |
jQuery("thead>tr td", s.nTable).each(function (i) { | |
jQuery("thead>tr td:eq(" + i + ")", nTable).width(b[i]); | |
$(this).width(b[i]); | |
}); | |
// Stop DataTables 1.9 from putting a focus ring on the headers when | |
// clicked to sort | |
$('th.sorting, th.sorting_desc, th.sorting_asc', nTable).bind('click', function () { | |
this.blur(); | |
}); | |
}, | |
/* | |
* Function: _fnCloneTfoot | |
* Purpose: Clone the tfoot element | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnCloneTfoot:function (oCache) { | |
var s = this.fnGetSettings(); | |
var nTable = oCache.nNode; | |
/* Set the wrapper width to match that of the cloned table */ | |
oCache.nWrapper.style.width = $(s.nTable).outerWidth() + "px"; | |
/* Remove any children the cloned table has */ | |
while (nTable.childNodes.length > 0) { | |
nTable.removeChild(nTable.childNodes[0]); | |
} | |
/* Clone the DataTables footer */ | |
var nTfoot = $('tfoot', s.nTable).clone(true)[0]; | |
nTable.appendChild(nTfoot); | |
/* Copy the widths across - apparently a clone isn't good enough for this */ | |
$("tfoot:eq(0)>tr th", s.nTable).each(function (i) { | |
$("tfoot:eq(0)>tr th:eq(" + i + ")", nTable).width($(this).width()); | |
}); | |
$("tfoot:eq(0)>tr td", s.nTable).each(function (i) { | |
$("tfoot:eq(0)>tr th:eq(" + i + ")", nTable)[0].style.width($(this).width()); | |
}); | |
}, | |
/* | |
* Function: _fnCloneTLeft | |
* Purpose: Clone the left column | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnCloneTLeft:function (oCache) { | |
var s = this.fnGetSettings(); | |
var nTable = oCache.nNode; | |
var nBody = $('tbody', s.nTable)[0]; | |
var iCols = $('tbody tr:eq(0) td', s.nTable).length; | |
var bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0")); | |
/* Remove any children the cloned table has */ | |
while (nTable.childNodes.length > 0) { | |
nTable.removeChild(nTable.childNodes[0]); | |
} | |
/* Is this the most efficient way to do this - it looks horrible... */ | |
nTable.appendChild($("thead", s.nTable).clone(true)[0]); | |
nTable.appendChild($("tbody", s.nTable).clone(true)[0]); | |
if (s.bFooter) { | |
nTable.appendChild($("tfoot", s.nTable).clone(true)[0]); | |
} | |
/* Remove unneeded cells */ | |
$('thead tr', nTable).each(function (k) { | |
$('th:gt(0)', this).remove(); | |
}); | |
$('tfoot tr', nTable).each(function (k) { | |
$('th:gt(0)', this).remove(); | |
}); | |
$('tbody tr', nTable).each(function (k) { | |
$('td:gt(0)', this).remove(); | |
}); | |
this.fnEqualiseHeights('thead', nBody.parentNode, nTable); | |
this.fnEqualiseHeights('tbody', nBody.parentNode, nTable); | |
this.fnEqualiseHeights('tfoot', nBody.parentNode, nTable); | |
// Custom width counter | |
// var iWidth = $('thead tr th:eq(0)', s.nTable).outerWidth(); | |
// var iWidth = $('thead tr:eq(1) th:eq(0)', s.nTable).outerWidth(); | |
var iWidth = $('thead tr:eq(1) th:eq(0)', s.nTable).width(); | |
nTable.style.width = iWidth + "px"; | |
oCache.nWrapper.style.width = iWidth + "px"; | |
}, | |
/* | |
* Function: _fnCloneTRight | |
* Purpose: Clone the right most colun | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnCloneTRight:function (oCache) { | |
var s = this.fnGetSettings(); | |
var nBody = $('tbody', s.nTable)[0]; | |
var nTable = oCache.nNode; | |
var iCols = $('tbody tr:eq(0) td', s.nTable).length; | |
var bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0")); | |
/* Remove any children the cloned table has */ | |
while (nTable.childNodes.length > 0) { | |
nTable.removeChild(nTable.childNodes[0]); | |
} | |
/* Is this the most efficient way to do this - it looks horrible... */ | |
nTable.appendChild($("thead", s.nTable).clone(true)[0]); | |
nTable.appendChild($("tbody", s.nTable).clone(true)[0]); | |
if (s.bFooter) { | |
nTable.appendChild($("tfoot", s.nTable).clone(true)[0]); | |
} | |
$('thead tr th:not(:nth-child(' + iCols + 'n))', nTable).remove(); | |
$('tfoot tr th:not(:nth-child(' + iCols + 'n))', nTable).remove(); | |
/* Remove unneeded cells */ | |
$('tbody tr', nTable).each(function (k) { | |
$('td:lt(' + (iCols - 1) + ')', this).remove(); | |
}); | |
this.fnEqualiseHeights('thead', nBody.parentNode, nTable); | |
this.fnEqualiseHeights('tbody', nBody.parentNode, nTable); | |
this.fnEqualiseHeights('tfoot', nBody.parentNode, nTable); | |
var iWidth = $('thead tr th:eq(' + (iCols - 1) + ')', s.nTable).outerWidth(); | |
nTable.style.width = iWidth + "px"; | |
oCache.nWrapper.style.width = iWidth + "px"; | |
}, | |
/* | |
* Function: _fnCloneTLeftHead | |
* Purpose: Clone the left column header | |
* Returns: - | |
* Inputs: object:oCache - the cached values for this fixed element | |
*/ | |
_fnCloneTLeftHead:function (oCache) { | |
var s = this.fnGetSettings(); | |
var nTable = oCache.nNode; | |
var nBody = $('tbody', s.nTable)[0]; | |
var bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0")); | |
/* Remove any children the cloned table has */ | |
while (nTable.childNodes.length > 0) { | |
nTable.removeChild(nTable.childNodes[0]); | |
} | |
/* Is this the most efficient way to do this - it looks horrible... */ | |
nTable.appendChild($("thead", s.nTable).clone(true)[0]); | |
/* Remove unneeded cells */ | |
$('thead tr', nTable).each(function (k) { | |
$('th:gt(0)', this).remove(); | |
}); | |
this.fnEqualiseHeights('thead', nBody.parentNode, nTable); | |
// Custom width counter | |
// var iWidth = $('thead tr th:eq(0)', s.nTable).outerWidth(); | |
// var iWidth = $('thead tr:eq(1) th:eq(0)', s.nTable).outerWidth(); | |
var iWidth = $('thead tr:eq(1) th:eq(0)', s.nTable).width(); | |
nTable.style.width = iWidth + "px"; | |
oCache.nWrapper.style.width = iWidth + "px"; | |
}, | |
/** | |
* Equalise the heights of the rows in a given table node in a cross browser way. Note that this | |
* is more or less lifted as is from FixedColumns | |
* @method fnEqualiseHeights | |
* @returns void | |
* @param {string} parent Node type - thead, tbody or tfoot | |
* @param {element} original Original node to take the heights from | |
* @param {element} clone Copy the heights to | |
* @private | |
*/ | |
"fnEqualiseHeights":function (parent, original, clone) { | |
var that = this, | |
jqBoxHack = $(parent + ' tr:eq(0)', original).children(':eq(0)'), | |
iBoxHack = jqBoxHack.outerHeight() - jqBoxHack.height(), | |
bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0")); | |
/* Remove cells which are not needed and copy the height from the original table */ | |
$(parent + ' tr', clone).each(function (k) { | |
/* Can we use some kind of object detection here?! This is very nasty - damn browsers */ | |
if ($.browser.mozilla && parseFloat($.browser.version) < 16 || $.browser.opera) { | |
$(this).children().height($(parent + ' tr:eq(' + k + ') td:eq(0)', original).outerHeight()); | |
} | |
else { | |
// Custom hack for different outer table border width | |
if (parent == "tbody" && k == ($(parent + ' tr', clone).length -1)) { | |
iBoxHack++ | |
} | |
$(this).children().height($(parent + ' tr:eq(' + k + ') td:eq(0)', original).outerHeight() - iBoxHack); | |
} | |
if (!bRubbishOldIE) { | |
$(parent + ' tr:eq(' + k + ')', original).height($(parent + ' tr:eq(' + k + ')', original).outerHeight()); | |
} | |
}); | |
} | |
}; | |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
* Static properties and methods | |
* We use these for speed! This information is common to all instances of FixedHeader, so no | |
* point if having them calculated and stored for each different instance. | |
*/ | |
/* | |
* Variable: oWin | |
* Purpose: Store information about the window positioning | |
* Scope: FixedHeader | |
*/ | |
FixedHeader.oWin = { | |
"iScrollTop":0, | |
"iScrollRight":0, | |
"iScrollBottom":0, | |
"iScrollLeft":0, | |
"iHeight":0, | |
"iWidth":0 | |
}; | |
/* | |
* Variable: oDoc | |
* Purpose: Store information about the document size | |
* Scope: FixedHeader | |
*/ | |
FixedHeader.oDoc = { | |
"iHeight":0, | |
"iWidth":0 | |
}; | |
/* | |
* Variable: afnScroll | |
* Purpose: Array of functions that are to be used for the scrolling components | |
* Scope: FixedHeader | |
*/ | |
FixedHeader.afnScroll = []; | |
/* | |
* Function: fnMeasure | |
* Purpose: Update the measurements for the window and document | |
* Returns: - | |
* Inputs: - | |
*/ | |
FixedHeader.fnMeasure = function () { | |
var | |
jqWin = $(window), | |
jqDoc = $(document), | |
oWin = FixedHeader.oWin, | |
oDoc = FixedHeader.oDoc; | |
oDoc.iHeight = jqDoc.height(); | |
oDoc.iWidth = jqDoc.width(); | |
oWin.iHeight = jqWin.height(); | |
oWin.iWidth = jqWin.width(); | |
oWin.iScrollTop = jqWin.scrollTop(); | |
oWin.iScrollLeft = jqWin.scrollLeft(); | |
oWin.iScrollRight = oDoc.iWidth - oWin.iScrollLeft - oWin.iWidth; | |
oWin.iScrollBottom = oDoc.iHeight - oWin.iScrollTop - oWin.iHeight; | |
}; | |
FixedHeader.VERSION = "2.1.0.dev"; | |
FixedHeader.prototype.VERSION = FixedHeader.VERSION; | |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
* Global processing | |
*/ | |
/* | |
* Just one 'scroll' event handler in FixedHeader, which calls the required components. This is | |
* done as an optimisation, to reduce calculation and proagation time | |
*/ | |
$(window).scroll(function () { | |
FixedHeader.fnMeasure(); | |
for (var i = 0, iLen = FixedHeader.afnScroll.length; i < iLen; i++) { | |
FixedHeader.afnScroll[i](); | |
} | |
}); | |
}(window, document, jQuery)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment