0
|
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 } );
|