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