1 |
/* |
2 |
* File: FixedHeader.js |
3 |
* Version: 2.0.6 |
4 |
* Description: "Fix" a header at the top of the table, so it scrolls with the table |
5 |
* Author: Allan Jardine (www.sprymedia.co.uk) |
6 |
* Created: Wed 16 Sep 2009 19:46:30 BST |
7 |
* Language: Javascript |
8 |
* License: GPL v2 or BSD 3 point style |
9 |
* Project: Just a little bit of fun - enjoy :-) |
10 |
* Contact: www.sprymedia.co.uk/contact |
11 |
* |
12 |
* Copyright 2009-2012 Allan Jardine, all rights reserved. |
13 |
* |
14 |
* This source file is free software, under either the GPL v2 license or a |
15 |
* BSD style license, available at: |
16 |
* http://datatables.net/license_gpl2 |
17 |
* http://datatables.net/license_bsd |
18 |
*/ |
19 |
|
20 |
/* |
21 |
* Function: FixedHeader |
22 |
* Purpose: Provide 'fixed' header, footer and columns on an HTML table |
23 |
* Returns: object:FixedHeader - must be called with 'new' |
24 |
* Inputs: mixed:mTable - target table |
25 |
* 1. DataTable object - when using FixedHeader with DataTables, or |
26 |
* 2. HTML table node - when using FixedHeader without DataTables |
27 |
* object:oInit - initialisation settings, with the following properties (each optional) |
28 |
* bool:top - fix the header (default true) |
29 |
* bool:bottom - fix the footer (default false) |
30 |
* bool:left - fix the left most column (default false) |
31 |
* bool:right - fix the right most column (default false) |
32 |
* int:zTop - fixed header zIndex |
33 |
* int:zBottom - fixed footer zIndex |
34 |
* int:zLeft - fixed left zIndex |
35 |
* int:zRight - fixed right zIndex |
36 |
*/ |
37 |
var FixedHeader = function ( mTable, oInit ) { |
38 |
/* Sanity check - you just know it will happen */ |
39 |
if ( typeof this.fnInit != 'function' ) |
40 |
{ |
41 |
alert( "FixedHeader warning: FixedHeader must be initialised with the 'new' keyword." ); |
42 |
return; |
43 |
} |
44 |
|
45 |
var that = this; |
46 |
var oSettings = { |
47 |
"aoCache": [], |
48 |
"oSides": { |
49 |
"top": true, |
50 |
"bottom": false, |
51 |
"left": false, |
52 |
"right": false |
53 |
}, |
54 |
"oZIndexes": { |
55 |
"top": 104, |
56 |
"bottom": 103, |
57 |
"left": 102, |
58 |
"right": 101 |
59 |
}, |
60 |
"oMes": { |
61 |
"iTableWidth": 0, |
62 |
"iTableHeight": 0, |
63 |
"iTableLeft": 0, |
64 |
"iTableRight": 0, /* note this is left+width, not actually "right" */ |
65 |
"iTableTop": 0, |
66 |
"iTableBottom": 0 /* note this is top+height, not actually "bottom" */ |
67 |
}, |
68 |
"oOffset": { |
69 |
"top": 0 |
70 |
}, |
71 |
"nTable": null, |
72 |
"bUseAbsPos": false, |
73 |
"bFooter": false |
74 |
}; |
75 |
|
76 |
/* |
77 |
* Function: fnGetSettings |
78 |
* Purpose: Get the settings for this object |
79 |
* Returns: object: - settings object |
80 |
* Inputs: - |
81 |
*/ |
82 |
this.fnGetSettings = function () { |
83 |
return oSettings; |
84 |
}; |
85 |
|
86 |
/* |
87 |
* Function: fnUpdate |
88 |
* Purpose: Update the positioning and copies of the fixed elements |
89 |
* Returns: - |
90 |
* Inputs: - |
91 |
*/ |
92 |
this.fnUpdate = function () { |
93 |
this._fnUpdateClones(); |
94 |
this._fnUpdatePositions(); |
95 |
}; |
96 |
|
97 |
/* |
98 |
* Function: fnPosition |
99 |
* Purpose: Update the positioning of the fixed elements |
100 |
* Returns: - |
101 |
* Inputs: - |
102 |
*/ |
103 |
this.fnPosition = function () { |
104 |
this._fnUpdatePositions(); |
105 |
}; |
106 |
|
107 |
/* Let's do it */ |
108 |
this.fnInit( mTable, oInit ); |
109 |
|
110 |
/* Store the instance on the DataTables object for easy access */ |
111 |
if ( typeof mTable.fnSettings == 'function' ) |
112 |
{ |
113 |
mTable._oPluginFixedHeader = this; |
114 |
} |
115 |
}; |
116 |
|
117 |
|
118 |
/* |
119 |
* Variable: FixedHeader |
120 |
* Purpose: Prototype for FixedHeader |
121 |
* Scope: global |
122 |
*/ |
123 |
FixedHeader.prototype = { |
124 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
125 |
* Initialisation |
126 |
*/ |
127 |
|
128 |
/* |
129 |
* Function: fnInit |
130 |
* Purpose: The "constructor" |
131 |
* Returns: - |
132 |
* Inputs: {as FixedHeader function} |
133 |
*/ |
134 |
fnInit: function ( oTable, oInit ) |
135 |
{ |
136 |
var s = this.fnGetSettings(); |
137 |
var that = this; |
138 |
|
139 |
/* Record the user definable settings */ |
140 |
this.fnInitSettings( s, oInit ); |
141 |
|
142 |
/* DataTables specific stuff */ |
143 |
if ( typeof oTable.fnSettings == 'function' ) |
144 |
{ |
145 |
if ( typeof oTable.fnVersionCheck == 'functon' && |
146 |
oTable.fnVersionCheck( '1.6.0' ) !== true ) |
147 |
{ |
148 |
alert( "FixedHeader 2 required DataTables 1.6.0 or later. "+ |
149 |
"Please upgrade your DataTables installation" ); |
150 |
return; |
151 |
} |
152 |
|
153 |
var oDtSettings = oTable.fnSettings(); |
154 |
|
155 |
if ( oDtSettings.oScroll.sX != "" || oDtSettings.oScroll.sY != "" ) |
156 |
{ |
157 |
alert( "FixedHeader 2 is not supported with DataTables' scrolling mode at this time" ); |
158 |
return; |
159 |
} |
160 |
|
161 |
s.nTable = oDtSettings.nTable; |
162 |
oDtSettings.aoDrawCallback.push( { |
163 |
"fn": function () { |
164 |
FixedHeader.fnMeasure(); |
165 |
that._fnUpdateClones.call(that); |
166 |
that._fnUpdatePositions.call(that); |
167 |
}, |
168 |
"sName": "FixedHeader" |
169 |
} ); |
170 |
} |
171 |
else |
172 |
{ |
173 |
s.nTable = oTable; |
174 |
} |
175 |
|
176 |
s.bFooter = ($('>tfoot', s.nTable).length > 0) ? true : false; |
177 |
|
178 |
/* "Detect" browsers that don't support absolute positioing - or have bugs */ |
179 |
s.bUseAbsPos = (jQuery.browser.msie && (jQuery.browser.version=="6.0"||jQuery.browser.version=="7.0")); |
180 |
|
181 |
/* Add the 'sides' that are fixed */ |
182 |
if ( s.oSides.top ) |
183 |
{ |
184 |
s.aoCache.push( that._fnCloneTable( "fixedHeader", "FixedHeader_Header", that._fnCloneThead ) ); |
185 |
} |
186 |
if ( s.oSides.bottom ) |
187 |
{ |
188 |
s.aoCache.push( that._fnCloneTable( "fixedFooter", "FixedHeader_Footer", that._fnCloneTfoot ) ); |
189 |
} |
190 |
if ( s.oSides.left ) |
191 |
{ |
192 |
s.aoCache.push( that._fnCloneTable( "fixedLeft", "FixedHeader_Left", that._fnCloneTLeft ) ); |
193 |
} |
194 |
if ( s.oSides.right ) |
195 |
{ |
196 |
s.aoCache.push( that._fnCloneTable( "fixedRight", "FixedHeader_Right", that._fnCloneTRight ) ); |
197 |
} |
198 |
|
199 |
/* Event listeners for window movement */ |
200 |
FixedHeader.afnScroll.push( function () { |
201 |
that._fnUpdatePositions.call(that); |
202 |
} ); |
203 |
|
204 |
jQuery(window).resize( function () { |
205 |
FixedHeader.fnMeasure(); |
206 |
that._fnUpdateClones.call(that); |
207 |
that._fnUpdatePositions.call(that); |
208 |
} ); |
209 |
|
210 |
/* Get things right to start with */ |
211 |
FixedHeader.fnMeasure(); |
212 |
that._fnUpdateClones(); |
213 |
that._fnUpdatePositions(); |
214 |
}, |
215 |
|
216 |
|
217 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
218 |
* Support functions |
219 |
*/ |
220 |
|
221 |
/* |
222 |
* Function: fnInitSettings |
223 |
* Purpose: Take the user's settings and copy them to our local store |
224 |
* Returns: - |
225 |
* Inputs: object:s - the local settings object |
226 |
* object:oInit - the user's settings object |
227 |
*/ |
228 |
fnInitSettings: function ( s, oInit ) |
229 |
{ |
230 |
if ( typeof oInit != 'undefined' ) |
231 |
{ |
232 |
if ( typeof oInit.top != 'undefined' ) { |
233 |
s.oSides.top = oInit.top; |
234 |
} |
235 |
if ( typeof oInit.bottom != 'undefined' ) { |
236 |
s.oSides.bottom = oInit.bottom; |
237 |
} |
238 |
if ( typeof oInit.left != 'undefined' ) { |
239 |
s.oSides.left = oInit.left; |
240 |
} |
241 |
if ( typeof oInit.right != 'undefined' ) { |
242 |
s.oSides.right = oInit.right; |
243 |
} |
244 |
|
245 |
if ( typeof oInit.zTop != 'undefined' ) { |
246 |
s.oZIndexes.top = oInit.zTop; |
247 |
} |
248 |
if ( typeof oInit.zBottom != 'undefined' ) { |
249 |
s.oZIndexes.bottom = oInit.zBottom; |
250 |
} |
251 |
if ( typeof oInit.zLeft != 'undefined' ) { |
252 |
s.oZIndexes.left = oInit.zLeft; |
253 |
} |
254 |
if ( typeof oInit.zRight != 'undefined' ) { |
255 |
s.oZIndexes.right = oInit.zRight; |
256 |
} |
257 |
|
258 |
if ( typeof oInit.offsetTop != 'undefined' ) { |
259 |
s.oOffset.top = oInit.offsetTop; |
260 |
} |
261 |
} |
262 |
|
263 |
/* Detect browsers which have poor position:fixed support so we can use absolute positions. |
264 |
* This is much slower since the position must be updated for each scroll, but widens |
265 |
* compatibility |
266 |
*/ |
267 |
s.bUseAbsPos = (jQuery.browser.msie && |
268 |
(jQuery.browser.version=="6.0"||jQuery.browser.version=="7.0")); |
269 |
}, |
270 |
|
271 |
/* |
272 |
* Function: _fnCloneTable |
273 |
* Purpose: Clone the table node and do basic initialisation |
274 |
* Returns: - |
275 |
* Inputs: - |
276 |
*/ |
277 |
_fnCloneTable: function ( sType, sClass, fnClone ) |
278 |
{ |
279 |
var s = this.fnGetSettings(); |
280 |
var nCTable; |
281 |
|
282 |
/* We know that the table _MUST_ has a DIV wrapped around it, because this is simply how |
283 |
* DataTables works. Therefore, we can set this to be relatively position (if it is not |
284 |
* alreadu absolute, and use this as the base point for the cloned header |
285 |
*/ |
286 |
if ( jQuery(s.nTable.parentNode).css('position') != "absolute" ) |
287 |
{ |
288 |
s.nTable.parentNode.style.position = "relative"; |
289 |
} |
290 |
|
291 |
/* Just a shallow clone will do - we only want the table node */ |
292 |
nCTable = s.nTable.cloneNode( false ); |
293 |
nCTable.removeAttribute( 'id' ); |
294 |
|
295 |
var nDiv = document.createElement( 'div' ); |
296 |
nDiv.style.position = "absolute"; |
297 |
nDiv.style.top = "0px"; |
298 |
nDiv.style.left = "0px"; |
299 |
nDiv.className += " FixedHeader_Cloned "+sType+" "+sClass; |
300 |
|
301 |
/* Set the zIndexes */ |
302 |
if ( sType == "fixedHeader" ) |
303 |
{ |
304 |
nDiv.style.zIndex = s.oZIndexes.top; |
305 |
} |
306 |
if ( sType == "fixedFooter" ) |
307 |
{ |
308 |
nDiv.style.zIndex = s.oZIndexes.bottom; |
309 |
} |
310 |
if ( sType == "fixedLeft" ) |
311 |
{ |
312 |
nDiv.style.zIndex = s.oZIndexes.left; |
313 |
} |
314 |
else if ( sType == "fixedRight" ) |
315 |
{ |
316 |
nDiv.style.zIndex = s.oZIndexes.right; |
317 |
} |
318 |
|
319 |
/* remove margins since we are going to poistion it absolutely */ |
320 |
nCTable.style.margin = "0"; |
321 |
|
322 |
/* Insert the newly cloned table into the DOM, on top of the "real" header */ |
323 |
nDiv.appendChild( nCTable ); |
324 |
document.body.appendChild( nDiv ); |
325 |
|
326 |
return { |
327 |
"nNode": nCTable, |
328 |
"nWrapper": nDiv, |
329 |
"sType": sType, |
330 |
"sPosition": "", |
331 |
"sTop": "", |
332 |
"sLeft": "", |
333 |
"fnClone": fnClone |
334 |
}; |
335 |
}, |
336 |
|
337 |
/* |
338 |
* Function: _fnUpdatePositions |
339 |
* Purpose: Get the current positioning of the table in the DOM |
340 |
* Returns: - |
341 |
* Inputs: - |
342 |
*/ |
343 |
_fnMeasure: function () |
344 |
{ |
345 |
var |
346 |
s = this.fnGetSettings(), |
347 |
m = s.oMes, |
348 |
jqTable = jQuery(s.nTable), |
349 |
oOffset = jqTable.offset(), |
350 |
iParentScrollTop = this._fnSumScroll( s.nTable.parentNode, 'scrollTop' ), |
351 |
iParentScrollLeft = this._fnSumScroll( s.nTable.parentNode, 'scrollLeft' ); |
352 |
|
353 |
m.iTableWidth = jqTable.outerWidth(); |
354 |
m.iTableHeight = jqTable.outerHeight(); |
355 |
m.iTableLeft = oOffset.left + s.nTable.parentNode.scrollLeft; |
356 |
m.iTableTop = oOffset.top + iParentScrollTop; |
357 |
m.iTableRight = m.iTableLeft + m.iTableWidth; |
358 |
m.iTableRight = FixedHeader.oDoc.iWidth - m.iTableLeft - m.iTableWidth; |
359 |
m.iTableBottom = FixedHeader.oDoc.iHeight - m.iTableTop - m.iTableHeight; |
360 |
}, |
361 |
|
362 |
/* |
363 |
* Function: _fnSumScroll |
364 |
* Purpose: Sum node parameters all the way to the top |
365 |
* Returns: int: sum |
366 |
* Inputs: node:n - node to consider |
367 |
* string:side - scrollTop or scrollLeft |
368 |
*/ |
369 |
_fnSumScroll: function ( n, side ) |
370 |
{ |
371 |
var i = n[side]; |
372 |
while ( n = n.parentNode ) |
373 |
{ |
374 |
if ( n.nodeName == 'HTML' || n.nodeName == 'BODY' ) |
375 |
{ |
376 |
break; |
377 |
} |
378 |
i = n[side]; |
379 |
} |
380 |
return i; |
381 |
}, |
382 |
|
383 |
/* |
384 |
* Function: _fnUpdatePositions |
385 |
* Purpose: Loop over the fixed elements for this table and update their positions |
386 |
* Returns: - |
387 |
* Inputs: - |
388 |
*/ |
389 |
_fnUpdatePositions: function () |
390 |
{ |
391 |
var s = this.fnGetSettings(); |
392 |
this._fnMeasure(); |
393 |
|
394 |
for ( var i=0, iLen=s.aoCache.length ; i<iLen ; i++ ) |
395 |
{ |
396 |
if ( s.aoCache[i].sType == "fixedHeader" ) |
397 |
{ |
398 |
this._fnScrollFixedHeader( s.aoCache[i] ); |
399 |
} |
400 |
else if ( s.aoCache[i].sType == "fixedFooter" ) |
401 |
{ |
402 |
this._fnScrollFixedFooter( s.aoCache[i] ); |
403 |
} |
404 |
else if ( s.aoCache[i].sType == "fixedLeft" ) |
405 |
{ |
406 |
this._fnScrollHorizontalLeft( s.aoCache[i] ); |
407 |
} |
408 |
else |
409 |
{ |
410 |
this._fnScrollHorizontalRight( s.aoCache[i] ); |
411 |
} |
412 |
} |
413 |
}, |
414 |
|
415 |
/* |
416 |
* Function: _fnUpdateClones |
417 |
* Purpose: Loop over the fixed elements for this table and call their cloning functions |
418 |
* Returns: - |
419 |
* Inputs: - |
420 |
*/ |
421 |
_fnUpdateClones: function () |
422 |
{ |
423 |
var s = this.fnGetSettings(); |
424 |
for ( var i=0, iLen=s.aoCache.length ; i<iLen ; i++ ) |
425 |
{ |
426 |
s.aoCache[i].fnClone.call( this, s.aoCache[i] ); |
427 |
} |
428 |
}, |
429 |
|
430 |
|
431 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
432 |
* Scrolling functions |
433 |
*/ |
434 |
|
435 |
/* |
436 |
* Function: _fnScrollHorizontalLeft |
437 |
* Purpose: Update the positioning of the scrolling elements |
438 |
* Returns: - |
439 |
* Inputs: object:oCache - the cahced values for this fixed element |
440 |
*/ |
441 |
_fnScrollHorizontalRight: function ( oCache ) |
442 |
{ |
443 |
var |
444 |
s = this.fnGetSettings(), |
445 |
oMes = s.oMes, |
446 |
oWin = FixedHeader.oWin, |
447 |
oDoc = FixedHeader.oDoc, |
448 |
nTable = oCache.nWrapper, |
449 |
iFixedWidth = jQuery(nTable).outerWidth(); |
450 |
|
451 |
if ( oWin.iScrollRight < oMes.iTableRight ) |
452 |
{ |
453 |
/* Fully right aligned */ |
454 |
this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style ); |
455 |
this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style ); |
456 |
this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft+oMes.iTableWidth-iFixedWidth)+"px", 'left', nTable.style ); |
457 |
} |
458 |
else if ( oMes.iTableLeft < oDoc.iWidth-oWin.iScrollRight-iFixedWidth ) |
459 |
{ |
460 |
/* Middle */ |
461 |
if ( s.bUseAbsPos ) |
462 |
{ |
463 |
this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style ); |
464 |
this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style ); |
465 |
this._fnUpdateCache( oCache, 'sLeft', (oDoc.iWidth-oWin.iScrollRight-iFixedWidth)+"px", 'left', nTable.style ); |
466 |
} |
467 |
else |
468 |
{ |
469 |
this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style ); |
470 |
this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop-oWin.iScrollTop)+"px", 'top', nTable.style ); |
471 |
this._fnUpdateCache( oCache, 'sLeft', (oWin.iWidth-iFixedWidth)+"px", 'left', nTable.style ); |
472 |
} |
473 |
} |
474 |
else |
475 |
{ |
476 |
/* Fully left aligned */ |
477 |
this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style ); |
478 |
this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style ); |
479 |
this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style ); |
480 |
} |
481 |
}, |
482 |
|
483 |
/* |
484 |
* Function: _fnScrollHorizontalLeft |
485 |
* Purpose: Update the positioning of the scrolling elements |
486 |
* Returns: - |
487 |
* Inputs: object:oCache - the cahced values for this fixed element |
488 |
*/ |
489 |
_fnScrollHorizontalLeft: function ( oCache ) |
490 |
{ |
491 |
var |
492 |
s = this.fnGetSettings(), |
493 |
oMes = s.oMes, |
494 |
oWin = FixedHeader.oWin, |
495 |
oDoc = FixedHeader.oDoc, |
496 |
nTable = oCache.nWrapper, |
497 |
iCellWidth = jQuery(nTable).outerWidth(); |
498 |
|
499 |
if ( oWin.iScrollLeft < oMes.iTableLeft ) |
500 |
{ |
501 |
/* Fully left align */ |
502 |
this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style ); |
503 |
this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style ); |
504 |
this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style ); |
505 |
} |
506 |
else if ( oWin.iScrollLeft < oMes.iTableLeft+oMes.iTableWidth-iCellWidth ) |
507 |
{ |
508 |
/* Middle */ |
509 |
if ( s.bUseAbsPos ) |
510 |
{ |
511 |
this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style ); |
512 |
this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style ); |
513 |
this._fnUpdateCache( oCache, 'sLeft', oWin.iScrollLeft+"px", 'left', nTable.style ); |
514 |
} |
515 |
else |
516 |
{ |
517 |
this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style ); |
518 |
this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop-oWin.iScrollTop)+"px", 'top', nTable.style ); |
519 |
this._fnUpdateCache( oCache, 'sLeft', "0px", 'left', nTable.style ); |
520 |
} |
521 |
} |
522 |
else |
523 |
{ |
524 |
/* Fully right align */ |
525 |
this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style ); |
526 |
this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style ); |
527 |
this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft+oMes.iTableWidth-iCellWidth)+"px", 'left', nTable.style ); |
528 |
} |
529 |
}, |
530 |
|
531 |
/* |
532 |
* Function: _fnScrollFixedFooter |
533 |
* Purpose: Update the positioning of the scrolling elements |
534 |
* Returns: - |
535 |
* Inputs: object:oCache - the cahced values for this fixed element |
536 |
*/ |
537 |
_fnScrollFixedFooter: function ( oCache ) |
538 |
{ |
539 |
var |
540 |
s = this.fnGetSettings(), |
541 |
oMes = s.oMes, |
542 |
oWin = FixedHeader.oWin, |
543 |
oDoc = FixedHeader.oDoc, |
544 |
nTable = oCache.nWrapper, |
545 |
iTheadHeight = jQuery("thead", s.nTable).outerHeight(), |
546 |
iCellHeight = jQuery(nTable).outerHeight(); |
547 |
|
548 |
if ( oWin.iScrollBottom < oMes.iTableBottom ) |
549 |
{ |
550 |
/* Below */ |
551 |
this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style ); |
552 |
this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop+oMes.iTableHeight-iCellHeight)+"px", 'top', nTable.style ); |
553 |
this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style ); |
554 |
} |
555 |
else if ( oWin.iScrollBottom < oMes.iTableBottom+oMes.iTableHeight-iCellHeight-iTheadHeight ) |
556 |
{ |
557 |
/* Middle */ |
558 |
if ( s.bUseAbsPos ) |
559 |
{ |
560 |
this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style ); |
561 |
this._fnUpdateCache( oCache, 'sTop', (oDoc.iHeight-oWin.iScrollBottom-iCellHeight)+"px", 'top', nTable.style ); |
562 |
this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style ); |
563 |
} |
564 |
else |
565 |
{ |
566 |
this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style ); |
567 |
this._fnUpdateCache( oCache, 'sTop', (oWin.iHeight-iCellHeight)+"px", 'top', nTable.style ); |
568 |
this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft-oWin.iScrollLeft)+"px", 'left', nTable.style ); |
569 |
} |
570 |
} |
571 |
else |
572 |
{ |
573 |
/* Above */ |
574 |
this._fnUpdateCache( oCache, 'sPosition', 'absolute', 'position', nTable.style ); |
575 |
this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop+iCellHeight)+"px", 'top', nTable.style ); |
576 |
this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style ); |
577 |
} |
578 |
}, |
579 |
|
580 |
/* |
581 |
* Function: _fnScrollFixedHeader |
582 |
* Purpose: Update the positioning of the scrolling elements |
583 |
* Returns: - |
584 |
* Inputs: object:oCache - the cahced values for this fixed element |
585 |
*/ |
586 |
_fnScrollFixedHeader: function ( oCache ) |
587 |
{ |
588 |
var |
589 |
s = this.fnGetSettings(), |
590 |
oMes = s.oMes, |
591 |
oWin = FixedHeader.oWin, |
592 |
oDoc = FixedHeader.oDoc, |
593 |
nTable = oCache.nWrapper, |
594 |
iTbodyHeight = 0, |
595 |
anTbodies = s.nTable.getElementsByTagName('tbody'); |
596 |
|
597 |
for (var i = 0; i < anTbodies.length; ++i) { |
598 |
iTbodyHeight += anTbodies[i].offsetHeight; |
599 |
} |
600 |
|
601 |
if ( oMes.iTableTop > oWin.iScrollTop + s.oOffset.top ) |
602 |
{ |
603 |
/* Above the table */ |
604 |
this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style ); |
605 |
this._fnUpdateCache( oCache, 'sTop', oMes.iTableTop+"px", 'top', nTable.style ); |
606 |
this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style ); |
607 |
} |
608 |
else if ( oWin.iScrollTop + s.oOffset.top > oMes.iTableTop+iTbodyHeight ) |
609 |
{ |
610 |
/* At the bottom of the table */ |
611 |
this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style ); |
612 |
this._fnUpdateCache( oCache, 'sTop', (oMes.iTableTop+iTbodyHeight)+"px", 'top', nTable.style ); |
613 |
this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style ); |
614 |
} |
615 |
else |
616 |
{ |
617 |
/* In the middle of the table */ |
618 |
if ( s.bUseAbsPos ) |
619 |
{ |
620 |
this._fnUpdateCache( oCache, 'sPosition', "absolute", 'position', nTable.style ); |
621 |
this._fnUpdateCache( oCache, 'sTop', oWin.iScrollTop+"px", 'top', nTable.style ); |
622 |
this._fnUpdateCache( oCache, 'sLeft', oMes.iTableLeft+"px", 'left', nTable.style ); |
623 |
} |
624 |
else |
625 |
{ |
626 |
this._fnUpdateCache( oCache, 'sPosition', 'fixed', 'position', nTable.style ); |
627 |
this._fnUpdateCache( oCache, 'sTop', s.oOffset.top+"px", 'top', nTable.style ); |
628 |
this._fnUpdateCache( oCache, 'sLeft', (oMes.iTableLeft-oWin.iScrollLeft)+"px", 'left', nTable.style ); |
629 |
} |
630 |
} |
631 |
}, |
632 |
|
633 |
/* |
634 |
* Function: _fnUpdateCache |
635 |
* Purpose: Check the cache and update cache and value if needed |
636 |
* Returns: - |
637 |
* Inputs: object:oCache - local cache object |
638 |
* string:sCache - cache property |
639 |
* string:sSet - value to set |
640 |
* string:sProperty - object property to set |
641 |
* object:oObj - object to update |
642 |
*/ |
643 |
_fnUpdateCache: function ( oCache, sCache, sSet, sProperty, oObj ) |
644 |
{ |
645 |
if ( oCache[sCache] != sSet ) |
646 |
{ |
647 |
oObj[sProperty] = sSet; |
648 |
oCache[sCache] = sSet; |
649 |
} |
650 |
}, |
651 |
|
652 |
|
653 |
|
654 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
655 |
* Cloning functions |
656 |
*/ |
657 |
|
658 |
/* |
659 |
* Function: _fnCloneThead |
660 |
* Purpose: Clone the thead element |
661 |
* Returns: - |
662 |
* Inputs: object:oCache - the cahced values for this fixed element |
663 |
*/ |
664 |
_fnCloneThead: function ( oCache ) |
665 |
{ |
666 |
var s = this.fnGetSettings(); |
667 |
var nTable = oCache.nNode; |
668 |
|
669 |
/* Set the wrapper width to match that of the cloned table */ |
670 |
oCache.nWrapper.style.width = jQuery(s.nTable).outerWidth()+"px"; |
671 |
|
672 |
/* Remove any children the cloned table has */ |
673 |
while ( nTable.childNodes.length > 0 ) |
674 |
{ |
675 |
jQuery('thead th', nTable).unbind( 'click' ); |
676 |
nTable.removeChild( nTable.childNodes[0] ); |
677 |
} |
678 |
|
679 |
/* Clone the DataTables header */ |
680 |
var nThead = jQuery('thead', s.nTable).clone(true)[0]; |
681 |
nTable.appendChild( nThead ); |
682 |
|
683 |
/* Copy the widths across - apparently a clone isn't good enough for this */ |
684 |
jQuery("thead>tr th", s.nTable).each( function (i) { |
685 |
jQuery("thead>tr th:eq("+i+")", nTable).width( jQuery(this).width() ); |
686 |
} ); |
687 |
|
688 |
jQuery("thead>tr td", s.nTable).each( function (i) { |
689 |
jQuery("thead>tr td:eq("+i+")", nTable).width( jQuery(this).width() ); |
690 |
} ); |
691 |
}, |
692 |
|
693 |
/* |
694 |
* Function: _fnCloneTfoot |
695 |
* Purpose: Clone the tfoot element |
696 |
* Returns: - |
697 |
* Inputs: object:oCache - the cahced values for this fixed element |
698 |
*/ |
699 |
_fnCloneTfoot: function ( oCache ) |
700 |
{ |
701 |
var s = this.fnGetSettings(); |
702 |
var nTable = oCache.nNode; |
703 |
|
704 |
/* Set the wrapper width to match that of the cloned table */ |
705 |
oCache.nWrapper.style.width = jQuery(s.nTable).outerWidth()+"px"; |
706 |
|
707 |
/* Remove any children the cloned table has */ |
708 |
while ( nTable.childNodes.length > 0 ) |
709 |
{ |
710 |
nTable.removeChild( nTable.childNodes[0] ); |
711 |
} |
712 |
|
713 |
/* Clone the DataTables footer */ |
714 |
var nTfoot = jQuery('tfoot', s.nTable).clone(true)[0]; |
715 |
nTable.appendChild( nTfoot ); |
716 |
|
717 |
/* Copy the widths across - apparently a clone isn't good enough for this */ |
718 |
jQuery("tfoot:eq(0)>tr th", s.nTable).each( function (i) { |
719 |
jQuery("tfoot:eq(0)>tr th:eq("+i+")", nTable).width( jQuery(this).width() ); |
720 |
} ); |
721 |
|
722 |
jQuery("tfoot:eq(0)>tr td", s.nTable).each( function (i) { |
723 |
jQuery("tfoot:eq(0)>tr th:eq("+i+")", nTable)[0].style.width( jQuery(this).width() ); |
724 |
} ); |
725 |
}, |
726 |
|
727 |
/* |
728 |
* Function: _fnCloneTLeft |
729 |
* Purpose: Clone the left column |
730 |
* Returns: - |
731 |
* Inputs: object:oCache - the cached values for this fixed element |
732 |
*/ |
733 |
_fnCloneTLeft: function ( oCache ) |
734 |
{ |
735 |
var s = this.fnGetSettings(); |
736 |
var nTable = oCache.nNode; |
737 |
var nBody = $('tbody', s.nTable)[0]; |
738 |
var iCols = $('tbody tr:eq(0) td', s.nTable).length; |
739 |
var bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0")); |
740 |
|
741 |
/* Remove any children the cloned table has */ |
742 |
while ( nTable.childNodes.length > 0 ) |
743 |
{ |
744 |
nTable.removeChild( nTable.childNodes[0] ); |
745 |
} |
746 |
|
747 |
/* Is this the most efficient way to do this - it looks horrible... */ |
748 |
nTable.appendChild( jQuery("thead", s.nTable).clone(true)[0] ); |
749 |
nTable.appendChild( jQuery("tbody", s.nTable).clone(true)[0] ); |
750 |
if ( s.bFooter ) |
751 |
{ |
752 |
nTable.appendChild( jQuery("tfoot", s.nTable).clone(true)[0] ); |
753 |
} |
754 |
|
755 |
/* Remove unneeded cells */ |
756 |
$('thead tr', nTable).each( function (k) { |
757 |
$('th:gt(0)', this).remove(); |
758 |
} ); |
759 |
|
760 |
$('tfoot tr', nTable).each( function (k) { |
761 |
$('th:gt(0)', this).remove(); |
762 |
} ); |
763 |
|
764 |
$('tbody tr', nTable).each( function (k) { |
765 |
$('td:gt(0)', this).remove(); |
766 |
} ); |
767 |
|
768 |
this.fnEqualiseHeights( 'tbody', nBody.parentNode, nTable ); |
769 |
|
770 |
var iWidth = jQuery('thead tr th:eq(0)', s.nTable).outerWidth(); |
771 |
nTable.style.width = iWidth+"px"; |
772 |
oCache.nWrapper.style.width = iWidth+"px"; |
773 |
}, |
774 |
|
775 |
/* |
776 |
* Function: _fnCloneTRight |
777 |
* Purpose: Clone the right most colun |
778 |
* Returns: - |
779 |
* Inputs: object:oCache - the cahced values for this fixed element |
780 |
*/ |
781 |
_fnCloneTRight: function ( oCache ) |
782 |
{ |
783 |
var s = this.fnGetSettings(); |
784 |
var nBody = $('tbody', s.nTable)[0]; |
785 |
var nTable = oCache.nNode; |
786 |
var iCols = jQuery('tbody tr:eq(0) td', s.nTable).length; |
787 |
var bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0")); |
788 |
|
789 |
/* Remove any children the cloned table has */ |
790 |
while ( nTable.childNodes.length > 0 ) |
791 |
{ |
792 |
nTable.removeChild( nTable.childNodes[0] ); |
793 |
} |
794 |
|
795 |
/* Is this the most efficient way to do this - it looks horrible... */ |
796 |
nTable.appendChild( jQuery("thead", s.nTable).clone(true)[0] ); |
797 |
nTable.appendChild( jQuery("tbody", s.nTable).clone(true)[0] ); |
798 |
if ( s.bFooter ) |
799 |
{ |
800 |
nTable.appendChild( jQuery("tfoot", s.nTable).clone(true)[0] ); |
801 |
} |
802 |
jQuery('thead tr th:not(:nth-child('+iCols+'n))', nTable).remove(); |
803 |
jQuery('tfoot tr th:not(:nth-child('+iCols+'n))', nTable).remove(); |
804 |
|
805 |
/* Remove unneeded cells */ |
806 |
$('tbody tr', nTable).each( function (k) { |
807 |
$('td:lt('+(iCols-1)+')', this).remove(); |
808 |
} ); |
809 |
|
810 |
this.fnEqualiseHeights( 'tbody', nBody.parentNode, nTable ); |
811 |
|
812 |
var iWidth = jQuery('thead tr th:eq('+(iCols-1)+')', s.nTable).outerWidth(); |
813 |
nTable.style.width = iWidth+"px"; |
814 |
oCache.nWrapper.style.width = iWidth+"px"; |
815 |
}, |
816 |
|
817 |
|
818 |
/** |
819 |
* Equalise the heights of the rows in a given table node in a cross browser way. Note that this |
820 |
* is more or less lifted as is from FixedColumns |
821 |
* @method fnEqualiseHeights |
822 |
* @returns void |
823 |
* @param {string} parent Node type - thead, tbody or tfoot |
824 |
* @param {element} original Original node to take the heights from |
825 |
* @param {element} clone Copy the heights to |
826 |
* @private |
827 |
*/ |
828 |
"fnEqualiseHeights": function ( parent, original, clone ) |
829 |
{ |
830 |
var that = this, |
831 |
jqBoxHack = $(parent+' tr:eq(0)', original).children(':eq(0)'), |
832 |
iBoxHack = jqBoxHack.outerHeight() - jqBoxHack.height(), |
833 |
bRubbishOldIE = ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0")); |
834 |
|
835 |
/* Remove cells which are not needed and copy the height from the original table */ |
836 |
$(parent+' tr', clone).each( function (k) { |
837 |
/* Can we use some kind of object detection here?! This is very nasty - damn browsers */ |
838 |
if ( $.browser.mozilla || $.browser.opera ) |
839 |
{ |
840 |
$(this).children().height( $(parent+' tr:eq('+k+')', original).outerHeight() ); |
841 |
} |
842 |
else |
843 |
{ |
844 |
$(this).children().height( $(parent+' tr:eq('+k+')', original).outerHeight() - iBoxHack ); |
845 |
} |
846 |
|
847 |
if ( !bRubbishOldIE ) |
848 |
{ |
849 |
$(parent+' tr:eq('+k+')', original).height( $(parent+' tr:eq('+k+')', original).outerHeight() ); |
850 |
} |
851 |
} ); |
852 |
} |
853 |
}; |
854 |
|
855 |
|
856 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
857 |
* Static properties and methods |
858 |
* We use these for speed! This information is common to all instances of FixedHeader, so no |
859 |
* point if having them calculated and stored for each different instance. |
860 |
*/ |
861 |
|
862 |
/* |
863 |
* Variable: oWin |
864 |
* Purpose: Store information about the window positioning |
865 |
* Scope: FixedHeader |
866 |
*/ |
867 |
FixedHeader.oWin = { |
868 |
"iScrollTop": 0, |
869 |
"iScrollRight": 0, |
870 |
"iScrollBottom": 0, |
871 |
"iScrollLeft": 0, |
872 |
"iHeight": 0, |
873 |
"iWidth": 0 |
874 |
}; |
875 |
|
876 |
/* |
877 |
* Variable: oDoc |
878 |
* Purpose: Store information about the document size |
879 |
* Scope: FixedHeader |
880 |
*/ |
881 |
FixedHeader.oDoc = { |
882 |
"iHeight": 0, |
883 |
"iWidth": 0 |
884 |
}; |
885 |
|
886 |
/* |
887 |
* Variable: afnScroll |
888 |
* Purpose: Array of functions that are to be used for the scrolling components |
889 |
* Scope: FixedHeader |
890 |
*/ |
891 |
FixedHeader.afnScroll = []; |
892 |
|
893 |
/* |
894 |
* Function: fnMeasure |
895 |
* Purpose: Update the measurements for the window and document |
896 |
* Returns: - |
897 |
* Inputs: - |
898 |
*/ |
899 |
FixedHeader.fnMeasure = function () |
900 |
{ |
901 |
var |
902 |
jqWin = jQuery(window), |
903 |
jqDoc = jQuery(document), |
904 |
oWin = FixedHeader.oWin, |
905 |
oDoc = FixedHeader.oDoc; |
906 |
|
907 |
oDoc.iHeight = jqDoc.height(); |
908 |
oDoc.iWidth = jqDoc.width(); |
909 |
|
910 |
oWin.iHeight = jqWin.height(); |
911 |
oWin.iWidth = jqWin.width(); |
912 |
oWin.iScrollTop = jqWin.scrollTop(); |
913 |
oWin.iScrollLeft = jqWin.scrollLeft(); |
914 |
oWin.iScrollRight = oDoc.iWidth - oWin.iScrollLeft - oWin.iWidth; |
915 |
oWin.iScrollBottom = oDoc.iHeight - oWin.iScrollTop - oWin.iHeight; |
916 |
}; |
917 |
|
918 |
|
919 |
FixedHeader.VERSION = "2.0.6"; |
920 |
FixedHeader.prototype.VERSION = FixedHeader.VERSION; |
921 |
|
922 |
|
923 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
924 |
* Global processing |
925 |
*/ |
926 |
|
927 |
/* |
928 |
* Just one 'scroll' event handler in FixedHeader, which calls the required components. This is |
929 |
* done as an optimisation, to reduce calculation and proagation time |
930 |
*/ |
931 |
jQuery(window).scroll( function () { |
932 |
FixedHeader.fnMeasure(); |
933 |
for ( var i=0, iLen=FixedHeader.afnScroll.length ; i<iLen ; i++ ) |
934 |
{ |
935 |
FixedHeader.afnScroll[i](); |
936 |
} |
937 |
} ); |