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