1 |
/** |
2 |
* Add any control elements for the table - specifically scrolling |
3 |
* @param {object} oSettings dataTables settings object |
4 |
* @returns {node} Node to add to the DOM |
5 |
* @memberof DataTable#oApi |
6 |
*/ |
7 |
function _fnFeatureHtmlTable ( oSettings ) |
8 |
{ |
9 |
/* Check if scrolling is enabled or not - if not then leave the DOM unaltered */ |
10 |
if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" ) |
11 |
{ |
12 |
return oSettings.nTable; |
13 |
} |
14 |
|
15 |
/* |
16 |
* The HTML structure that we want to generate in this function is: |
17 |
* div - nScroller |
18 |
* div - nScrollHead |
19 |
* div - nScrollHeadInner |
20 |
* table - nScrollHeadTable |
21 |
* thead - nThead |
22 |
* div - nScrollBody |
23 |
* table - oSettings.nTable |
24 |
* thead - nTheadSize |
25 |
* tbody - nTbody |
26 |
* div - nScrollFoot |
27 |
* div - nScrollFootInner |
28 |
* table - nScrollFootTable |
29 |
* tfoot - nTfoot |
30 |
*/ |
31 |
var |
32 |
nScroller = document.createElement('div'), |
33 |
nScrollHead = document.createElement('div'), |
34 |
nScrollHeadInner = document.createElement('div'), |
35 |
nScrollBody = document.createElement('div'), |
36 |
nScrollFoot = document.createElement('div'), |
37 |
nScrollFootInner = document.createElement('div'), |
38 |
nScrollHeadTable = oSettings.nTable.cloneNode(false), |
39 |
nScrollFootTable = oSettings.nTable.cloneNode(false), |
40 |
nThead = oSettings.nTable.getElementsByTagName('thead')[0], |
41 |
nTfoot = oSettings.nTable.getElementsByTagName('tfoot').length === 0 ? null : |
42 |
oSettings.nTable.getElementsByTagName('tfoot')[0], |
43 |
oClasses = oSettings.oClasses; |
44 |
|
45 |
nScrollHead.appendChild( nScrollHeadInner ); |
46 |
nScrollFoot.appendChild( nScrollFootInner ); |
47 |
nScrollBody.appendChild( oSettings.nTable ); |
48 |
nScroller.appendChild( nScrollHead ); |
49 |
nScroller.appendChild( nScrollBody ); |
50 |
nScrollHeadInner.appendChild( nScrollHeadTable ); |
51 |
nScrollHeadTable.appendChild( nThead ); |
52 |
if ( nTfoot !== null ) |
53 |
{ |
54 |
nScroller.appendChild( nScrollFoot ); |
55 |
nScrollFootInner.appendChild( nScrollFootTable ); |
56 |
nScrollFootTable.appendChild( nTfoot ); |
57 |
} |
58 |
|
59 |
nScroller.className = oClasses.sScrollWrapper; |
60 |
nScrollHead.className = oClasses.sScrollHead; |
61 |
nScrollHeadInner.className = oClasses.sScrollHeadInner; |
62 |
nScrollBody.className = oClasses.sScrollBody; |
63 |
nScrollFoot.className = oClasses.sScrollFoot; |
64 |
nScrollFootInner.className = oClasses.sScrollFootInner; |
65 |
|
66 |
if ( oSettings.oScroll.bAutoCss ) |
67 |
{ |
68 |
nScrollHead.style.overflow = "hidden"; |
69 |
nScrollHead.style.position = "relative"; |
70 |
nScrollFoot.style.overflow = "hidden"; |
71 |
nScrollBody.style.overflow = "auto"; |
72 |
} |
73 |
|
74 |
nScrollHead.style.border = "0"; |
75 |
nScrollHead.style.width = "100%"; |
76 |
nScrollFoot.style.border = "0"; |
77 |
nScrollHeadInner.style.width = oSettings.oScroll.sXInner !== "" ? |
78 |
oSettings.oScroll.sXInner : "100%"; /* will be overwritten */ |
79 |
|
80 |
/* Modify attributes to respect the clones */ |
81 |
nScrollHeadTable.removeAttribute('id'); |
82 |
nScrollHeadTable.style.marginLeft = "0"; |
83 |
oSettings.nTable.style.marginLeft = "0"; |
84 |
if ( nTfoot !== null ) |
85 |
{ |
86 |
nScrollFootTable.removeAttribute('id'); |
87 |
nScrollFootTable.style.marginLeft = "0"; |
88 |
} |
89 |
|
90 |
/* Move caption elements from the body to the header, footer or leave where it is |
91 |
* depending on the configuration. Note that the DTD says there can be only one caption */ |
92 |
var nCaption = $(oSettings.nTable).children('caption'); |
93 |
if ( nCaption.length > 0 ) |
94 |
{ |
95 |
nCaption = nCaption[0]; |
96 |
if ( nCaption._captionSide === "top" ) |
97 |
{ |
98 |
nScrollHeadTable.appendChild( nCaption ); |
99 |
} |
100 |
else if ( nCaption._captionSide === "bottom" && nTfoot ) |
101 |
{ |
102 |
nScrollFootTable.appendChild( nCaption ); |
103 |
} |
104 |
} |
105 |
|
106 |
/* |
107 |
* Sizing |
108 |
*/ |
109 |
/* When x-scrolling add the width and a scroller to move the header with the body */ |
110 |
if ( oSettings.oScroll.sX !== "" ) |
111 |
{ |
112 |
nScrollHead.style.width = _fnStringToCss( oSettings.oScroll.sX ); |
113 |
nScrollBody.style.width = _fnStringToCss( oSettings.oScroll.sX ); |
114 |
|
115 |
if ( nTfoot !== null ) |
116 |
{ |
117 |
nScrollFoot.style.width = _fnStringToCss( oSettings.oScroll.sX ); |
118 |
} |
119 |
|
120 |
/* When the body is scrolled, then we also want to scroll the headers */ |
121 |
$(nScrollBody).scroll( function (e) { |
122 |
nScrollHead.scrollLeft = this.scrollLeft; |
123 |
|
124 |
if ( nTfoot !== null ) |
125 |
{ |
126 |
nScrollFoot.scrollLeft = this.scrollLeft; |
127 |
} |
128 |
} ); |
129 |
} |
130 |
|
131 |
/* When yscrolling, add the height */ |
132 |
if ( oSettings.oScroll.sY !== "" ) |
133 |
{ |
134 |
nScrollBody.style.height = _fnStringToCss( oSettings.oScroll.sY ); |
135 |
} |
136 |
|
137 |
/* Redraw - align columns across the tables */ |
138 |
oSettings.aoDrawCallback.push( { |
139 |
"fn": _fnScrollDraw, |
140 |
"sName": "scrolling" |
141 |
} ); |
142 |
|
143 |
/* Infinite scrolling event handlers */ |
144 |
if ( oSettings.oScroll.bInfinite ) |
145 |
{ |
146 |
$(nScrollBody).scroll( function() { |
147 |
/* Use a blocker to stop scrolling from loading more data while other data is still loading */ |
148 |
if ( !oSettings.bDrawing && $(this).scrollTop() !== 0 ) |
149 |
{ |
150 |
/* Check if we should load the next data set */ |
151 |
if ( $(this).scrollTop() + $(this).height() > |
152 |
$(oSettings.nTable).height() - oSettings.oScroll.iLoadGap ) |
153 |
{ |
154 |
/* Only do the redraw if we have to - we might be at the end of the data */ |
155 |
if ( oSettings.fnDisplayEnd() < oSettings.fnRecordsDisplay() ) |
156 |
{ |
157 |
_fnPageChange( oSettings, 'next' ); |
158 |
_fnCalculateEnd( oSettings ); |
159 |
_fnDraw( oSettings ); |
160 |
} |
161 |
} |
162 |
} |
163 |
} ); |
164 |
} |
165 |
|
166 |
oSettings.nScrollHead = nScrollHead; |
167 |
oSettings.nScrollFoot = nScrollFoot; |
168 |
|
169 |
return nScroller; |
170 |
} |
171 |
|
172 |
|
173 |
/** |
174 |
* Update the various tables for resizing. It's a bit of a pig this function, but |
175 |
* basically the idea to: |
176 |
* 1. Re-create the table inside the scrolling div |
177 |
* 2. Take live measurements from the DOM |
178 |
* 3. Apply the measurements |
179 |
* 4. Clean up |
180 |
* @param {object} o dataTables settings object |
181 |
* @returns {node} Node to add to the DOM |
182 |
* @memberof DataTable#oApi |
183 |
*/ |
184 |
function _fnScrollDraw ( o ) |
185 |
{ |
186 |
var |
187 |
nScrollHeadInner = o.nScrollHead.getElementsByTagName('div')[0], |
188 |
nScrollHeadTable = nScrollHeadInner.getElementsByTagName('table')[0], |
189 |
nScrollBody = o.nTable.parentNode, |
190 |
i, iLen, j, jLen, anHeadToSize, anHeadSizers, anFootSizers, anFootToSize, oStyle, iVis, |
191 |
nTheadSize, nTfootSize, |
192 |
iWidth, aApplied=[], aAppliedFooter=[], iSanityWidth, |
193 |
nScrollFootInner = (o.nTFoot !== null) ? o.nScrollFoot.getElementsByTagName('div')[0] : null, |
194 |
nScrollFootTable = (o.nTFoot !== null) ? nScrollFootInner.getElementsByTagName('table')[0] : null, |
195 |
ie67 = o.oBrowser.bScrollOversize, |
196 |
zeroOut = function(nSizer) { |
197 |
oStyle = nSizer.style; |
198 |
oStyle.paddingTop = "0"; |
199 |
oStyle.paddingBottom = "0"; |
200 |
oStyle.borderTopWidth = "0"; |
201 |
oStyle.borderBottomWidth = "0"; |
202 |
oStyle.height = 0; |
203 |
}; |
204 |
|
205 |
/* |
206 |
* 1. Re-create the table inside the scrolling div |
207 |
*/ |
208 |
|
209 |
/* Remove the old minimised thead and tfoot elements in the inner table */ |
210 |
$(o.nTable).children('thead, tfoot').remove(); |
211 |
|
212 |
/* Clone the current header and footer elements and then place it into the inner table */ |
213 |
nTheadSize = $(o.nTHead).clone()[0]; |
214 |
o.nTable.insertBefore( nTheadSize, o.nTable.childNodes[0] ); |
215 |
anHeadToSize = o.nTHead.getElementsByTagName('tr'); |
216 |
anHeadSizers = nTheadSize.getElementsByTagName('tr'); |
217 |
|
218 |
if ( o.nTFoot !== null ) |
219 |
{ |
220 |
nTfootSize = $(o.nTFoot).clone()[0]; |
221 |
o.nTable.insertBefore( nTfootSize, o.nTable.childNodes[1] ); |
222 |
anFootToSize = o.nTFoot.getElementsByTagName('tr'); |
223 |
anFootSizers = nTfootSize.getElementsByTagName('tr'); |
224 |
} |
225 |
|
226 |
/* |
227 |
* 2. Take live measurements from the DOM - do not alter the DOM itself! |
228 |
*/ |
229 |
|
230 |
/* Remove old sizing and apply the calculated column widths |
231 |
* Get the unique column headers in the newly created (cloned) header. We want to apply the |
232 |
* calculated sizes to this header |
233 |
*/ |
234 |
if ( o.oScroll.sX === "" ) |
235 |
{ |
236 |
nScrollBody.style.width = '100%'; |
237 |
nScrollHeadInner.parentNode.style.width = '100%'; |
238 |
} |
239 |
|
240 |
var nThs = _fnGetUniqueThs( o, nTheadSize ); |
241 |
for ( i=0, iLen=nThs.length ; i<iLen ; i++ ) |
242 |
{ |
243 |
iVis = _fnVisibleToColumnIndex( o, i ); |
244 |
nThs[i].style.width = o.aoColumns[iVis].sWidth; |
245 |
} |
246 |
|
247 |
if ( o.nTFoot !== null ) |
248 |
{ |
249 |
_fnApplyToChildren( function(n) { |
250 |
n.style.width = ""; |
251 |
}, anFootSizers ); |
252 |
} |
253 |
|
254 |
// If scroll collapse is enabled, when we put the headers back into the body for sizing, we |
255 |
// will end up forcing the scrollbar to appear, making our measurements wrong for when we |
256 |
// then hide it (end of this function), so add the header height to the body scroller. |
257 |
if ( o.oScroll.bCollapse && o.oScroll.sY !== "" ) |
258 |
{ |
259 |
nScrollBody.style.height = (nScrollBody.offsetHeight + o.nTHead.offsetHeight)+"px"; |
260 |
} |
261 |
|
262 |
/* Size the table as a whole */ |
263 |
iSanityWidth = $(o.nTable).outerWidth(); |
264 |
if ( o.oScroll.sX === "" ) |
265 |
{ |
266 |
/* No x scrolling */ |
267 |
o.nTable.style.width = "100%"; |
268 |
|
269 |
/* I know this is rubbish - but IE7 will make the width of the table when 100% include |
270 |
* the scrollbar - which is shouldn't. When there is a scrollbar we need to take this |
271 |
* into account. |
272 |
*/ |
273 |
if ( ie67 && ($('tbody', nScrollBody).height() > nScrollBody.offsetHeight || |
274 |
$(nScrollBody).css('overflow-y') == "scroll") ) |
275 |
{ |
276 |
o.nTable.style.width = _fnStringToCss( $(o.nTable).outerWidth() - o.oScroll.iBarWidth); |
277 |
} |
278 |
} |
279 |
else |
280 |
{ |
281 |
if ( o.oScroll.sXInner !== "" ) |
282 |
{ |
283 |
/* x scroll inner has been given - use it */ |
284 |
o.nTable.style.width = _fnStringToCss(o.oScroll.sXInner); |
285 |
} |
286 |
else if ( iSanityWidth == $(nScrollBody).width() && |
287 |
$(nScrollBody).height() < $(o.nTable).height() ) |
288 |
{ |
289 |
/* There is y-scrolling - try to take account of the y scroll bar */ |
290 |
o.nTable.style.width = _fnStringToCss( iSanityWidth-o.oScroll.iBarWidth ); |
291 |
if ( $(o.nTable).outerWidth() > iSanityWidth-o.oScroll.iBarWidth ) |
292 |
{ |
293 |
/* Not possible to take account of it */ |
294 |
o.nTable.style.width = _fnStringToCss( iSanityWidth ); |
295 |
} |
296 |
} |
297 |
else |
298 |
{ |
299 |
/* All else fails */ |
300 |
o.nTable.style.width = _fnStringToCss( iSanityWidth ); |
301 |
} |
302 |
} |
303 |
|
304 |
/* Recalculate the sanity width - now that we've applied the required width, before it was |
305 |
* a temporary variable. This is required because the column width calculation is done |
306 |
* before this table DOM is created. |
307 |
*/ |
308 |
iSanityWidth = $(o.nTable).outerWidth(); |
309 |
|
310 |
/* We want the hidden header to have zero height, so remove padding and borders. Then |
311 |
* set the width based on the real headers |
312 |
*/ |
313 |
|
314 |
// Apply all styles in one pass. Invalidates layout only once because we don't read any |
315 |
// DOM properties. |
316 |
_fnApplyToChildren( zeroOut, anHeadSizers ); |
317 |
|
318 |
// Read all widths in next pass. Forces layout only once because we do not change |
319 |
// any DOM properties. |
320 |
_fnApplyToChildren( function(nSizer) { |
321 |
aApplied.push( _fnStringToCss( $(nSizer).width() ) ); |
322 |
}, anHeadSizers ); |
323 |
|
324 |
// Apply all widths in final pass. Invalidates layout only once because we do not |
325 |
// read any DOM properties. |
326 |
_fnApplyToChildren( function(nToSize, i) { |
327 |
nToSize.style.width = aApplied[i]; |
328 |
}, anHeadToSize ); |
329 |
|
330 |
$(anHeadSizers).height(0); |
331 |
|
332 |
/* Same again with the footer if we have one */ |
333 |
if ( o.nTFoot !== null ) |
334 |
{ |
335 |
_fnApplyToChildren( zeroOut, anFootSizers ); |
336 |
|
337 |
_fnApplyToChildren( function(nSizer) { |
338 |
aAppliedFooter.push( _fnStringToCss( $(nSizer).width() ) ); |
339 |
}, anFootSizers ); |
340 |
|
341 |
_fnApplyToChildren( function(nToSize, i) { |
342 |
nToSize.style.width = aAppliedFooter[i]; |
343 |
}, anFootToSize ); |
344 |
|
345 |
$(anFootSizers).height(0); |
346 |
} |
347 |
|
348 |
/* |
349 |
* 3. Apply the measurements |
350 |
*/ |
351 |
|
352 |
/* "Hide" the header and footer that we used for the sizing. We want to also fix their width |
353 |
* to what they currently are |
354 |
*/ |
355 |
_fnApplyToChildren( function(nSizer, i) { |
356 |
nSizer.innerHTML = ""; |
357 |
nSizer.style.width = aApplied[i]; |
358 |
}, anHeadSizers ); |
359 |
|
360 |
if ( o.nTFoot !== null ) |
361 |
{ |
362 |
_fnApplyToChildren( function(nSizer, i) { |
363 |
nSizer.innerHTML = ""; |
364 |
nSizer.style.width = aAppliedFooter[i]; |
365 |
}, anFootSizers ); |
366 |
} |
367 |
|
368 |
/* Sanity check that the table is of a sensible width. If not then we are going to get |
369 |
* misalignment - try to prevent this by not allowing the table to shrink below its min width |
370 |
*/ |
371 |
if ( $(o.nTable).outerWidth() < iSanityWidth ) |
372 |
{ |
373 |
/* The min width depends upon if we have a vertical scrollbar visible or not */ |
374 |
var iCorrection = ((nScrollBody.scrollHeight > nScrollBody.offsetHeight || |
375 |
$(nScrollBody).css('overflow-y') == "scroll")) ? |
376 |
iSanityWidth+o.oScroll.iBarWidth : iSanityWidth; |
377 |
|
378 |
/* IE6/7 are a law unto themselves... */ |
379 |
if ( ie67 && (nScrollBody.scrollHeight > |
380 |
nScrollBody.offsetHeight || $(nScrollBody).css('overflow-y') == "scroll") ) |
381 |
{ |
382 |
o.nTable.style.width = _fnStringToCss( iCorrection-o.oScroll.iBarWidth ); |
383 |
} |
384 |
|
385 |
/* Apply the calculated minimum width to the table wrappers */ |
386 |
nScrollBody.style.width = _fnStringToCss( iCorrection ); |
387 |
o.nScrollHead.style.width = _fnStringToCss( iCorrection ); |
388 |
|
389 |
if ( o.nTFoot !== null ) |
390 |
{ |
391 |
o.nScrollFoot.style.width = _fnStringToCss( iCorrection ); |
392 |
} |
393 |
|
394 |
/* And give the user a warning that we've stopped the table getting too small */ |
395 |
if ( o.oScroll.sX === "" ) |
396 |
{ |
397 |
_fnLog( o, 1, "The table cannot fit into the current element which will cause column"+ |
398 |
" misalignment. The table has been drawn at its minimum possible width." ); |
399 |
} |
400 |
else if ( o.oScroll.sXInner !== "" ) |
401 |
{ |
402 |
_fnLog( o, 1, "The table cannot fit into the current element which will cause column"+ |
403 |
" misalignment. Increase the sScrollXInner value or remove it to allow automatic"+ |
404 |
" calculation" ); |
405 |
} |
406 |
} |
407 |
else |
408 |
{ |
409 |
nScrollBody.style.width = _fnStringToCss( '100%' ); |
410 |
o.nScrollHead.style.width = _fnStringToCss( '100%' ); |
411 |
|
412 |
if ( o.nTFoot !== null ) |
413 |
{ |
414 |
o.nScrollFoot.style.width = _fnStringToCss( '100%' ); |
415 |
} |
416 |
} |
417 |
|
418 |
|
419 |
/* |
420 |
* 4. Clean up |
421 |
*/ |
422 |
if ( o.oScroll.sY === "" ) |
423 |
{ |
424 |
/* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting |
425 |
* the scrollbar height from the visible display, rather than adding it on. We need to |
426 |
* set the height in order to sort this. Don't want to do it in any other browsers. |
427 |
*/ |
428 |
if ( ie67 ) |
429 |
{ |
430 |
nScrollBody.style.height = _fnStringToCss( o.nTable.offsetHeight+o.oScroll.iBarWidth ); |
431 |
} |
432 |
} |
433 |
|
434 |
if ( o.oScroll.sY !== "" && o.oScroll.bCollapse ) |
435 |
{ |
436 |
nScrollBody.style.height = _fnStringToCss( o.oScroll.sY ); |
437 |
|
438 |
var iExtra = (o.oScroll.sX !== "" && o.nTable.offsetWidth > nScrollBody.offsetWidth) ? |
439 |
o.oScroll.iBarWidth : 0; |
440 |
if ( o.nTable.offsetHeight < nScrollBody.offsetHeight ) |
441 |
{ |
442 |
nScrollBody.style.height = _fnStringToCss( o.nTable.offsetHeight+iExtra ); |
443 |
} |
444 |
} |
445 |
|
446 |
/* Finally set the width's of the header and footer tables */ |
447 |
var iOuterWidth = $(o.nTable).outerWidth(); |
448 |
nScrollHeadTable.style.width = _fnStringToCss( iOuterWidth ); |
449 |
nScrollHeadInner.style.width = _fnStringToCss( iOuterWidth ); |
450 |
|
451 |
// Figure out if there are scrollbar present - if so then we need a the header and footer to |
452 |
// provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) |
453 |
var bScrolling = $(o.nTable).height() > nScrollBody.clientHeight || $(nScrollBody).css('overflow-y') == "scroll"; |
454 |
nScrollHeadInner.style.paddingRight = bScrolling ? o.oScroll.iBarWidth+"px" : "0px"; |
455 |
|
456 |
if ( o.nTFoot !== null ) |
457 |
{ |
458 |
nScrollFootTable.style.width = _fnStringToCss( iOuterWidth ); |
459 |
nScrollFootInner.style.width = _fnStringToCss( iOuterWidth ); |
460 |
nScrollFootInner.style.paddingRight = bScrolling ? o.oScroll.iBarWidth+"px" : "0px"; |
461 |
} |
462 |
|
463 |
/* Adjust the position of the header in case we loose the y-scrollbar */ |
464 |
$(nScrollBody).scroll(); |
465 |
|
466 |
/* If sorting or filtering has occurred, jump the scrolling back to the top */ |
467 |
if ( o.bSorted || o.bFiltered ) |
468 |
{ |
469 |
nScrollBody.scrollTop = 0; |
470 |
} |
471 |
} |
472 |
|
473 |
|
474 |
/** |
475 |
* Apply a given function to the display child nodes of an element array (typically |
476 |
* TD children of TR rows |
477 |
* @param {function} fn Method to apply to the objects |
478 |
* @param array {nodes} an1 List of elements to look through for display children |
479 |
* @param array {nodes} an2 Another list (identical structure to the first) - optional |
480 |
* @memberof DataTable#oApi |
481 |
*/ |
482 |
function _fnApplyToChildren( fn, an1, an2 ) |
483 |
{ |
484 |
var index=0, i=0, iLen=an1.length; |
485 |
var nNode1, nNode2; |
486 |
|
487 |
while ( i < iLen ) |
488 |
{ |
489 |
nNode1 = an1[i].firstChild; |
490 |
nNode2 = an2 ? an2[i].firstChild : null; |
491 |
while ( nNode1 ) |
492 |
{ |
493 |
if ( nNode1.nodeType === 1 ) |
494 |
{ |
495 |
if ( an2 ) |
496 |
{ |
497 |
fn( nNode1, nNode2, index ); |
498 |
} |
499 |
else |
500 |
{ |
501 |
fn( nNode1, index ); |
502 |
} |
503 |
index++; |
504 |
} |
505 |
nNode1 = nNode1.nextSibling; |
506 |
nNode2 = an2 ? nNode2.nextSibling : null; |
507 |
} |
508 |
i++; |
509 |
} |
510 |
} |