1 |
/** |
2 |
* @summary Scroller |
3 |
* @description Virtual rendering for DataTables |
4 |
* @file Scroller.js |
5 |
* @version 1.1.0 |
6 |
* @author Allan Jardine (www.sprymedia.co.uk) |
7 |
* @license GPL v2 or BSD 3 point style |
8 |
* @contact www.sprymedia.co.uk/contact |
9 |
* |
10 |
* @copyright Copyright 2011-2012 Allan Jardine, all rights reserved. |
11 |
* |
12 |
* This source file is free software, under either the GPL v2 license or a |
13 |
* BSD style license, available at: |
14 |
* http://datatables.net/license_gpl2 |
15 |
* http://datatables.net/license_bsd |
16 |
*/ |
17 |
|
18 |
(/** @lends <global> */function($, window, document) { |
19 |
|
20 |
|
21 |
/** |
22 |
* Scroller is a virtual rendering plug-in for DataTables which allows large |
23 |
* datasets to be drawn on screen every quickly. What the virtual rendering means |
24 |
* is that only the visible portion of the table (and a bit to either side to make |
25 |
* the scrolling smooth) is drawn, while the scrolling container gives the |
26 |
* visual impression that the whole table is visible. This is done by making use |
27 |
* of the pagination abilities of DataTables and moving the table around in the |
28 |
* scrolling container DataTables adds to the page. The scrolling container is |
29 |
* forced to the height it would be for the full table display using an extra |
30 |
* element. |
31 |
* |
32 |
* Note that rows in the table MUST all be the same height. Information in a cell |
33 |
* which expands on to multiple lines will cause some odd behaviour in the scrolling. |
34 |
* |
35 |
* Scroller is initialised by simply including the letter 'S' in the sDom for the |
36 |
* table you want to have this feature enabled on. Note that the 'S' must come |
37 |
* AFTER the 't' parameter in sDom. |
38 |
* |
39 |
* Key features include: |
40 |
* <ul class="limit_length"> |
41 |
* <li>Speed! The aim of Scroller for DataTables is to make rendering large data sets fast</li> |
42 |
* <li>Full compatibility with deferred rendering in DataTables 1.9 for maximum speed</li> |
43 |
* <li>Correct visual scrolling implementation, similar to "infinite scrolling" in DataTable core</li> |
44 |
* <li>Integration with state saving in DataTables (scrolling position is saved)</li> |
45 |
* <li>Easy to use</li> |
46 |
* </ul> |
47 |
* |
48 |
* @class |
49 |
* @constructor |
50 |
* @param {object} oDT DataTables settings object |
51 |
* @param {object} [oOpts={}] Configuration object for FixedColumns. Options are defined by {@link Scroller.oDefaults} |
52 |
* |
53 |
* @requires jQuery 1.4+ |
54 |
* @requires DataTables 1.9.0+ |
55 |
* |
56 |
* @example |
57 |
* $(document).ready(function() { |
58 |
* $('#example').dataTable( { |
59 |
* "sScrollY": "200px", |
60 |
* "sAjaxSource": "media/dataset/large.txt", |
61 |
* "sDom": "frtiS", |
62 |
* "bDeferRender": true |
63 |
* } ); |
64 |
* } ); |
65 |
*/ |
66 |
var Scroller = function ( oDTSettings, oOpts ) { |
67 |
/* Sanity check - you just know it will happen */ |
68 |
if ( ! this instanceof Scroller ) |
69 |
{ |
70 |
alert( "Scroller warning: Scroller must be initialised with the 'new' keyword." ); |
71 |
return; |
72 |
} |
73 |
|
74 |
if ( typeof oOpts == 'undefined' ) |
75 |
{ |
76 |
oOpts = {}; |
77 |
} |
78 |
|
79 |
/** |
80 |
* Settings object which contains customisable information for the Scroller instance |
81 |
* @namespace |
82 |
* @extends Scroller.DEFAULTS |
83 |
*/ |
84 |
this.s = { |
85 |
/** |
86 |
* DataTables settings object |
87 |
* @type object |
88 |
* @default Passed in as first parameter to constructor |
89 |
*/ |
90 |
"dt": oDTSettings, |
91 |
|
92 |
/** |
93 |
* Pixel location of the top of the drawn table in the viewport |
94 |
* @type int |
95 |
* @default 0 |
96 |
*/ |
97 |
"tableTop": 0, |
98 |
|
99 |
/** |
100 |
* Pixel location of the bottom of the drawn table in the viewport |
101 |
* @type int |
102 |
* @default 0 |
103 |
*/ |
104 |
"tableBottom": 0, |
105 |
|
106 |
/** |
107 |
* Pixel location of the boundary for when the next data set should be loaded and drawn |
108 |
* when scrolling up the way. |
109 |
* @type int |
110 |
* @default 0 |
111 |
* @private |
112 |
*/ |
113 |
"redrawTop": 0, |
114 |
|
115 |
/** |
116 |
* Pixel location of the boundary for when the next data set should be loaded and drawn |
117 |
* when scrolling down the way. Note that this is actually caluated as the offset from |
118 |
* the top. |
119 |
* @type int |
120 |
* @default 0 |
121 |
* @private |
122 |
*/ |
123 |
"redrawBottom": 0, |
124 |
|
125 |
/** |
126 |
* Height of rows in the table |
127 |
* @type int |
128 |
* @default 0 |
129 |
*/ |
130 |
"rowHeight": null, |
131 |
|
132 |
/** |
133 |
* Auto row height or not indicator |
134 |
* @type bool |
135 |
* @default 0 |
136 |
*/ |
137 |
"autoHeight": true, |
138 |
|
139 |
/** |
140 |
* Pixel height of the viewport |
141 |
* @type int |
142 |
* @default 0 |
143 |
*/ |
144 |
"viewportHeight": 0, |
145 |
|
146 |
/** |
147 |
* Number of rows calculated as visible in the visible viewport |
148 |
* @type int |
149 |
* @default 0 |
150 |
*/ |
151 |
"viewportRows": 0, |
152 |
|
153 |
/** |
154 |
* setTimeout reference for state saving, used when state saving is enabled in the DataTable |
155 |
* and when the user scrolls the viewport in order to stop the cookie set taking too much |
156 |
* CPU! |
157 |
* @type int |
158 |
* @default 0 |
159 |
*/ |
160 |
"stateTO": null, |
161 |
|
162 |
/** |
163 |
* setTimeout reference for the redraw, used when server-side processing is enabled in the |
164 |
* DataTables in order to prevent DoSing the server |
165 |
* @type int |
166 |
* @default null |
167 |
*/ |
168 |
"drawTO": null |
169 |
}; |
170 |
this.s = $.extend( this.s, Scroller.oDefaults, oOpts ); |
171 |
|
172 |
/** |
173 |
* DOM elements used by the class instance |
174 |
* @namespace |
175 |
* |
176 |
*/ |
177 |
this.dom = { |
178 |
"force": document.createElement('div'), |
179 |
"scroller": null, |
180 |
"table": null |
181 |
}; |
182 |
|
183 |
/* Attach the instance to the DataTables instance so it can be accessed */ |
184 |
this.s.dt.oScroller = this; |
185 |
|
186 |
/* Let's do it */ |
187 |
this._fnConstruct(); |
188 |
}; |
189 |
|
190 |
|
191 |
|
192 |
Scroller.prototype = { |
193 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
194 |
* Public methods |
195 |
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
196 |
|
197 |
/** |
198 |
* Calculate the pixel position from the top of the scrolling container for a given row |
199 |
* @param {int} iRow Row number to calculate the position of |
200 |
* @returns {int} Pixels |
201 |
* @example |
202 |
* $(document).ready(function() { |
203 |
* $('#example').dataTable( { |
204 |
* "sScrollY": "200px", |
205 |
* "sAjaxSource": "media/dataset/large.txt", |
206 |
* "sDom": "frtiS", |
207 |
* "bDeferRender": true, |
208 |
* "fnInitComplete": function (o) { |
209 |
* // Find where row 25 is |
210 |
* alert( o.oScroller.fnRowToPixels( 25 ) ); |
211 |
* } |
212 |
* } ); |
213 |
* } ); |
214 |
*/ |
215 |
"fnRowToPixels": function ( iRow ) |
216 |
{ |
217 |
return iRow * this.s.rowHeight; |
218 |
}, |
219 |
|
220 |
|
221 |
/** |
222 |
* Calculate the row number that will be found at the given pixel position (y-scroll) |
223 |
* @param {int} iPixels Offset from top to caluclate the row number of |
224 |
* @returns {int} Row index |
225 |
* @example |
226 |
* $(document).ready(function() { |
227 |
* $('#example').dataTable( { |
228 |
* "sScrollY": "200px", |
229 |
* "sAjaxSource": "media/dataset/large.txt", |
230 |
* "sDom": "frtiS", |
231 |
* "bDeferRender": true, |
232 |
* "fnInitComplete": function (o) { |
233 |
* // Find what row number is at 500px |
234 |
* alert( o.oScroller.fnPixelsToRow( 500 ) ); |
235 |
* } |
236 |
* } ); |
237 |
* } ); |
238 |
*/ |
239 |
"fnPixelsToRow": function ( iPixels ) |
240 |
{ |
241 |
return parseInt( iPixels / this.s.rowHeight, 10 ); |
242 |
}, |
243 |
|
244 |
|
245 |
/** |
246 |
* Calculate the row number that will be found at the given pixel position (y-scroll) |
247 |
* @param {int} iRow Row index to scroll to |
248 |
* @param {bool} [bAnimate=true] Animate the transision or not |
249 |
* @returns {void} |
250 |
* @example |
251 |
* $(document).ready(function() { |
252 |
* $('#example').dataTable( { |
253 |
* "sScrollY": "200px", |
254 |
* "sAjaxSource": "media/dataset/large.txt", |
255 |
* "sDom": "frtiS", |
256 |
* "bDeferRender": true, |
257 |
* "fnInitComplete": function (o) { |
258 |
* // Immediately scroll to row 1000 |
259 |
* o.oScroller.fnScrollToRow( 1000 ); |
260 |
* } |
261 |
* } ); |
262 |
* |
263 |
* // Sometime later on use the following to scroll to row 500... |
264 |
* var oSettings = $('#example').dataTable().fnSettings(); |
265 |
* oSettings.oScroller.fnScrollToRow( 500 ); |
266 |
* } ); |
267 |
*/ |
268 |
"fnScrollToRow": function ( iRow, bAnimate ) |
269 |
{ |
270 |
var px = this.fnRowToPixels( iRow ); |
271 |
if ( typeof bAnimate == 'undefined' || bAnimate ) |
272 |
{ |
273 |
$(this.dom.scroller).animate( { |
274 |
"scrollTop": px |
275 |
} ); |
276 |
} |
277 |
else |
278 |
{ |
279 |
$(this.dom.scroller).scrollTop( px ); |
280 |
} |
281 |
}, |
282 |
|
283 |
|
284 |
/** |
285 |
* Calculate and store information about how many rows are to be displayed in the scrolling |
286 |
* viewport, based on current dimensions in the browser's rendering. This can be particularly |
287 |
* useful if the table is initially drawn in a hidden element - for example in a tab. |
288 |
* @param {bool} [bRedraw=true] Redraw the table automatically after the recalculation, with |
289 |
* the new dimentions forming the basis for the draw. |
290 |
* @returns {void} |
291 |
* @example |
292 |
* $(document).ready(function() { |
293 |
* // Make the example container hidden to throw off the browser's sizing |
294 |
* document.getElementById('container').style.display = "none"; |
295 |
* var oTable = $('#example').dataTable( { |
296 |
* "sScrollY": "200px", |
297 |
* "sAjaxSource": "media/dataset/large.txt", |
298 |
* "sDom": "frtiS", |
299 |
* "bDeferRender": true, |
300 |
* "fnInitComplete": function (o) { |
301 |
* // Immediately scroll to row 1000 |
302 |
* o.oScroller.fnScrollToRow( 1000 ); |
303 |
* } |
304 |
* } ); |
305 |
* |
306 |
* setTimeout( function () { |
307 |
* // Make the example container visible and recalculate the scroller sizes |
308 |
* document.getElementById('container').style.display = "block"; |
309 |
* oTable.fnSettings().oScroller.fnMeasure(); |
310 |
* }, 3000 ); |
311 |
*/ |
312 |
"fnMeasure": function ( bRedraw ) |
313 |
{ |
314 |
if ( this.s.autoHeight ) |
315 |
{ |
316 |
this._fnCalcRowHeight(); |
317 |
} |
318 |
|
319 |
this.s.viewportHeight = $(this.dom.scroller).height(); |
320 |
this.s.viewportRows = parseInt( this.s.viewportHeight/this.s.rowHeight, 10 )+1; |
321 |
this.s.dt._iDisplayLength = this.s.viewportRows * this.s.displayBuffer; |
322 |
|
323 |
if ( this.s.trace ) |
324 |
{ |
325 |
console.log( |
326 |
'Row height: '+this.s.rowHeight +' '+ |
327 |
'Viewport height: '+this.s.viewportHeight +' '+ |
328 |
'Viewport rows: '+ this.s.viewportRows +' '+ |
329 |
'Display rows: '+ this.s.dt._iDisplayLength |
330 |
); |
331 |
} |
332 |
|
333 |
if ( typeof bRedraw == 'undefined' || bRedraw ) |
334 |
{ |
335 |
this.s.dt.oInstance.fnDraw(); |
336 |
} |
337 |
}, |
338 |
|
339 |
|
340 |
|
341 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
342 |
* Private methods (they are of course public in JS, but recommended as private) |
343 |
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
344 |
|
345 |
/** |
346 |
* Initialisation for Scroller |
347 |
* @returns {void} |
348 |
* @private |
349 |
*/ |
350 |
"_fnConstruct": function () |
351 |
{ |
352 |
var that = this; |
353 |
|
354 |
/* Insert a div element that we can use to force the DT scrolling container to |
355 |
* the height that would be required if the whole table was being displayed |
356 |
*/ |
357 |
this.dom.force.style.position = "absolute"; |
358 |
this.dom.force.style.top = "0px"; |
359 |
this.dom.force.style.left = "0px"; |
360 |
this.dom.force.style.width = "1px"; |
361 |
|
362 |
this.dom.scroller = $('div.'+this.s.dt.oClasses.sScrollBody, this.s.dt.nTableWrapper)[0]; |
363 |
this.dom.scroller.appendChild( this.dom.force ); |
364 |
this.dom.scroller.style.position = "relative"; |
365 |
|
366 |
this.dom.table = $('>table', this.dom.scroller)[0]; |
367 |
this.dom.table.style.position = "absolute"; |
368 |
this.dom.table.style.top = "0px"; |
369 |
this.dom.table.style.left = "0px"; |
370 |
|
371 |
// Add class to 'announce' that we are a Scroller table |
372 |
$(this.s.dt.nTableWrapper).addClass('DTS'); |
373 |
|
374 |
// Add a 'loading' indicator |
375 |
if ( this.s.loadingIndicator ) |
376 |
{ |
377 |
$(this.dom.scroller.parentNode) |
378 |
.css('position', 'relative') |
379 |
.append('<div class="DTS_Loading">'+this.s.dt.oLanguage.sLoadingRecords+'</div>'); |
380 |
} |
381 |
|
382 |
/* Initial size calculations */ |
383 |
if ( this.s.rowHeight && this.s.rowHeight != 'auto' ) |
384 |
{ |
385 |
this.s.autoHeight = false; |
386 |
} |
387 |
this.fnMeasure( false ); |
388 |
|
389 |
/* Scrolling callback to see if a page change is needed */ |
390 |
$(this.dom.scroller).scroll( function () { |
391 |
that._fnScroll.call( that ); |
392 |
} ); |
393 |
|
394 |
/* In iOS we catch the touchstart event incase the user tries to scroll |
395 |
* while the display is already scrolling |
396 |
*/ |
397 |
$(this.dom.scroller).bind('touchstart', function () { |
398 |
that._fnScroll.call( that ); |
399 |
} ); |
400 |
|
401 |
/* Update the scroller when the DataTable is redrawn */ |
402 |
this.s.dt.aoDrawCallback.push( { |
403 |
"fn": function () { |
404 |
if ( that.s.dt.bInitialised ) { |
405 |
that._fnDrawCallback.call( that ); |
406 |
} |
407 |
}, |
408 |
"sName": "Scroller" |
409 |
} ); |
410 |
|
411 |
/* Add a state saving parameter to the DT state saving so we can restore the exact |
412 |
* position of the scrolling |
413 |
*/ |
414 |
this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) { |
415 |
oData.iScroller = that.dom.scroller.scrollTop; |
416 |
}, "Scroller_State" ); |
417 |
}, |
418 |
|
419 |
|
420 |
/** |
421 |
* Scrolling function - fired whenever the scrolling position is changed. This method needs |
422 |
* to use the stored values to see if the table should be redrawn as we are moving towards |
423 |
* the end of the information that is currently drawn or not. If needed, then it will redraw |
424 |
* the table based on the new position. |
425 |
* @returns {void} |
426 |
* @private |
427 |
*/ |
428 |
"_fnScroll": function () |
429 |
{ |
430 |
var |
431 |
that = this, |
432 |
iScrollTop = this.dom.scroller.scrollTop, |
433 |
iTopRow; |
434 |
|
435 |
/* If the table has been sorted or filtered, then we use the redraw that |
436 |
* DataTables as done, rather than performing our own |
437 |
*/ |
438 |
if ( this.s.dt.bFiltered || this.s.dt.bSorted ) |
439 |
{ |
440 |
return; |
441 |
} |
442 |
|
443 |
if ( this.s.trace ) |
444 |
{ |
445 |
console.log( |
446 |
'Scroll: '+iScrollTop+'px - boundaries: '+this.s.redrawTop+' / '+this.s.redrawBottom+'. '+ |
447 |
' Showing rows '+this.fnPixelsToRow(iScrollTop)+ |
448 |
' to '+this.fnPixelsToRow(iScrollTop+$(this.dom.scroller).height())+ |
449 |
' in the viewport, with rows '+this.s.dt._iDisplayStart+ |
450 |
' to '+(this.s.dt._iDisplayEnd)+' rendered by the DataTable' |
451 |
); |
452 |
} |
453 |
|
454 |
/* Update the table's information display for what is now in the viewport */ |
455 |
this._fnInfo(); |
456 |
|
457 |
/* We dont' want to state save on every scroll event - that's heavy handed, so |
458 |
* use a timeout to update the state saving only when the scrolling has finished |
459 |
*/ |
460 |
clearTimeout( this.s.stateTO ); |
461 |
this.s.stateTO = setTimeout( function () { |
462 |
that.s.dt.oApi._fnSaveState( that.s.dt ); |
463 |
}, 250 ); |
464 |
|
465 |
/* Check if the scroll point is outside the trigger boundary which would required |
466 |
* a DataTables redraw |
467 |
*/ |
468 |
if ( iScrollTop < this.s.redrawTop || iScrollTop > this.s.redrawBottom ) |
469 |
{ |
470 |
var preRows = ((this.s.displayBuffer-1)/2) * this.s.viewportRows; |
471 |
iTopRow = parseInt( iScrollTop / this.s.rowHeight, 10 ) - preRows; |
472 |
if ( iTopRow < 0 ) |
473 |
{ |
474 |
/* At the start of the table */ |
475 |
iTopRow = 0; |
476 |
} |
477 |
else if ( iTopRow + this.s.dt._iDisplayLength > this.s.dt.fnRecordsDisplay() ) |
478 |
{ |
479 |
/* At the end of the table */ |
480 |
iTopRow = this.s.dt.fnRecordsDisplay() - this.s.dt._iDisplayLength; |
481 |
if ( iTopRow < 0 ) |
482 |
{ |
483 |
iTopRow = 0; |
484 |
} |
485 |
} |
486 |
else if ( iTopRow % 2 !== 0 ) |
487 |
{ |
488 |
/* For the row-striping classes (odd/even) we want only to start on evens |
489 |
* otherwise the stripes will change between draws and look rubbish |
490 |
*/ |
491 |
iTopRow++; |
492 |
} |
493 |
|
494 |
if ( iTopRow != this.s.dt._iDisplayStart ) |
495 |
{ |
496 |
/* Cache the new table position for quick lookups */ |
497 |
this.s.tableTop = $(this.s.dt.nTable).offset().top; |
498 |
this.s.tableBottom = $(this.s.dt.nTable).height() + this.s.tableTop; |
499 |
|
500 |
/* Do the DataTables redraw based on the calculated start point - note that when |
501 |
* using server-side processing we introduce a small delay to not DoS the server... |
502 |
*/ |
503 |
if ( this.s.dt.oFeatures.bServerSide ) { |
504 |
clearTimeout( this.s.drawTO ); |
505 |
this.s.drawTO = setTimeout( function () { |
506 |
that.s.dt._iDisplayStart = iTopRow; |
507 |
that.s.dt.oApi._fnCalculateEnd( that.s.dt ); |
508 |
that.s.dt.oApi._fnDraw( that.s.dt ); |
509 |
}, this.s.serverWait ); |
510 |
} |
511 |
else |
512 |
{ |
513 |
this.s.dt._iDisplayStart = iTopRow; |
514 |
this.s.dt.oApi._fnCalculateEnd( this.s.dt ); |
515 |
this.s.dt.oApi._fnDraw( this.s.dt ); |
516 |
} |
517 |
|
518 |
if ( this.s.trace ) |
519 |
{ |
520 |
console.log( 'Scroll forcing redraw - top DT render row: '+ iTopRow ); |
521 |
} |
522 |
} |
523 |
} |
524 |
}, |
525 |
|
526 |
|
527 |
/** |
528 |
* Draw callback function which is fired when the DataTable is redrawn. The main function of |
529 |
* this method is to position the drawn table correctly the scrolling container for the rows |
530 |
* that is displays as a result of the scrolling position. |
531 |
* @returns {void} |
532 |
* @private |
533 |
*/ |
534 |
"_fnDrawCallback": function () |
535 |
{ |
536 |
var |
537 |
that = this, |
538 |
iScrollTop = this.dom.scroller.scrollTop, |
539 |
iScrollBottom = iScrollTop + this.s.viewportHeight; |
540 |
|
541 |
/* Set the height of the scrolling forcer to be suitable for the number of rows |
542 |
* in this draw |
543 |
*/ |
544 |
this.dom.force.style.height = (this.s.rowHeight * this.s.dt.fnRecordsDisplay())+"px"; |
545 |
|
546 |
/* Calculate the position that the top of the table should be at */ |
547 |
var iTableTop = (this.s.rowHeight*this.s.dt._iDisplayStart); |
548 |
if ( this.s.dt._iDisplayStart === 0 ) |
549 |
{ |
550 |
iTableTop = 0; |
551 |
} |
552 |
else if ( this.s.dt._iDisplayStart === this.s.dt.fnRecordsDisplay() - this.s.dt._iDisplayLength ) |
553 |
{ |
554 |
iTableTop = this.s.rowHeight * this.s.dt._iDisplayStart; |
555 |
} |
556 |
|
557 |
this.dom.table.style.top = iTableTop+"px"; |
558 |
|
559 |
/* Cache some information for the scroller */ |
560 |
this.s.tableTop = iTableTop; |
561 |
this.s.tableBottom = $(this.s.dt.nTable).height() + this.s.tableTop; |
562 |
|
563 |
this.s.redrawTop = iScrollTop - ( (iScrollTop - this.s.tableTop) * this.s.boundaryScale ); |
564 |
this.s.redrawBottom = iScrollTop + ( (this.s.tableBottom - iScrollBottom) * this.s.boundaryScale ); |
565 |
|
566 |
if ( this.s.trace ) |
567 |
{ |
568 |
console.log( |
569 |
"Table redraw. Table top: "+iTableTop+"px "+ |
570 |
"Table bottom: "+this.s.tableBottom+" "+ |
571 |
"Scroll boundary top: "+this.s.redrawTop+" "+ |
572 |
"Scroll boundary bottom: "+this.s.redrawBottom+" "+ |
573 |
"Rows drawn: "+this.s.dt._iDisplayLength); |
574 |
} |
575 |
|
576 |
/* Because of the order of the DT callbacks, the info update will |
577 |
* take precidence over the one we want here. So a 'thread' break is |
578 |
* needed |
579 |
*/ |
580 |
setTimeout( function () { |
581 |
that._fnInfo.call( that ); |
582 |
}, 0 ); |
583 |
|
584 |
/* Restore the scrolling position that was saved by DataTable's state saving |
585 |
* Note that this is done on the second draw when data is Ajax sourced, and the |
586 |
* first draw when DOM soured |
587 |
*/ |
588 |
if ( this.s.dt.oFeatures.bStateSave && this.s.dt.oLoadedState !== null && |
589 |
typeof this.s.dt.oLoadedState.iScroller != 'undefined' ) |
590 |
{ |
591 |
if ( (this.s.dt.sAjaxSource !== null && this.s.dt.iDraw == 2) || |
592 |
(this.s.dt.sAjaxSource === null && this.s.dt.iDraw == 1) ) |
593 |
{ |
594 |
setTimeout( function () { |
595 |
$(that.dom.scroller).scrollTop( that.s.dt.oLoadedState.iScroller ); |
596 |
that.s.redrawTop = that.s.dt.oLoadedState.iScroller - (that.s.viewportHeight/2); |
597 |
}, 0 ); |
598 |
} |
599 |
} |
600 |
}, |
601 |
|
602 |
|
603 |
/** |
604 |
* Automatic calculation of table row height. This is just a little tricky here as using |
605 |
* initialisation DataTables has tale the table out of the document, so we need to create |
606 |
* a new table and insert it into the document, calculate the row height and then whip the |
607 |
* table out. |
608 |
* @returns {void} |
609 |
* @private |
610 |
*/ |
611 |
"_fnCalcRowHeight": function () |
612 |
{ |
613 |
var nTable = this.s.dt.nTable.cloneNode( false ); |
614 |
var nContainer = $( |
615 |
'<div class="'+this.s.dt.oClasses.sWrapper+' DTS">'+ |
616 |
'<div class="'+this.s.dt.oClasses.sScrollWrapper+'">'+ |
617 |
'<div class="'+this.s.dt.oClasses.sScrollBody+'"></div>'+ |
618 |
'</div>'+ |
619 |
'</div>' |
620 |
)[0]; |
621 |
|
622 |
$(nTable).append( |
623 |
'<tbody>'+ |
624 |
'<tr>'+ |
625 |
'<td> </td>'+ |
626 |
'</tr>'+ |
627 |
'</tbody>' |
628 |
); |
629 |
|
630 |
$('div.'+this.s.dt.oClasses.sScrollBody, nContainer).append( nTable ); |
631 |
|
632 |
document.body.appendChild( nContainer ); |
633 |
this.s.rowHeight = $('tbody tr', nTable).outerHeight(); |
634 |
document.body.removeChild( nContainer ); |
635 |
}, |
636 |
|
637 |
|
638 |
/** |
639 |
* Update any information elements that are controlled by the DataTable based on the scrolling |
640 |
* viewport and what rows are visible in it. This function basically acts in the same way as |
641 |
* _fnUpdateInfo in DataTables, and effectively replaces that function. |
642 |
* @returns {void} |
643 |
* @private |
644 |
*/ |
645 |
"_fnInfo": function () |
646 |
{ |
647 |
if ( !this.s.dt.oFeatures.bInfo ) |
648 |
{ |
649 |
return; |
650 |
} |
651 |
|
652 |
var |
653 |
dt = this.s.dt, |
654 |
iScrollTop = this.dom.scroller.scrollTop, |
655 |
iStart = this.fnPixelsToRow(iScrollTop)+1, |
656 |
iMax = dt.fnRecordsTotal(), |
657 |
iTotal = dt.fnRecordsDisplay(), |
658 |
iPossibleEnd = this.fnPixelsToRow(iScrollTop+$(this.dom.scroller).height()), |
659 |
iEnd = iTotal < iPossibleEnd ? iTotal : iPossibleEnd, |
660 |
sStart = dt.fnFormatNumber( iStart ), |
661 |
sEnd = dt.fnFormatNumber( iEnd ), |
662 |
sMax = dt.fnFormatNumber( iMax ), |
663 |
sTotal = dt.fnFormatNumber( iTotal ), |
664 |
sOut; |
665 |
|
666 |
if ( dt.fnRecordsDisplay() === 0 && |
667 |
dt.fnRecordsDisplay() == dt.fnRecordsTotal() ) |
668 |
{ |
669 |
/* Empty record set */ |
670 |
sOut = dt.oLanguage.sInfoEmpty+ dt.oLanguage.sInfoPostFix; |
671 |
} |
672 |
else if ( dt.fnRecordsDisplay() === 0 ) |
673 |
{ |
674 |
/* Rmpty record set after filtering */ |
675 |
sOut = dt.oLanguage.sInfoEmpty +' '+ |
676 |
dt.oLanguage.sInfoFiltered.replace('_MAX_', sMax)+ |
677 |
dt.oLanguage.sInfoPostFix; |
678 |
} |
679 |
else if ( dt.fnRecordsDisplay() == dt.fnRecordsTotal() ) |
680 |
{ |
681 |
/* Normal record set */ |
682 |
sOut = dt.oLanguage.sInfo. |
683 |
replace('_START_', sStart). |
684 |
replace('_END_', sEnd). |
685 |
replace('_TOTAL_', sTotal)+ |
686 |
dt.oLanguage.sInfoPostFix; |
687 |
} |
688 |
else |
689 |
{ |
690 |
/* Record set after filtering */ |
691 |
sOut = dt.oLanguage.sInfo. |
692 |
replace('_START_', sStart). |
693 |
replace('_END_', sEnd). |
694 |
replace('_TOTAL_', sTotal) +' '+ |
695 |
dt.oLanguage.sInfoFiltered.replace('_MAX_', |
696 |
dt.fnFormatNumber(dt.fnRecordsTotal()))+ |
697 |
dt.oLanguage.sInfoPostFix; |
698 |
} |
699 |
|
700 |
var n = dt.aanFeatures.i; |
701 |
if ( typeof n != 'undefined' ) |
702 |
{ |
703 |
for ( var i=0, iLen=n.length ; i<iLen ; i++ ) |
704 |
{ |
705 |
$(n[i]).html( sOut ); |
706 |
} |
707 |
} |
708 |
} |
709 |
}; |
710 |
|
711 |
|
712 |
|
713 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
714 |
* Statics |
715 |
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
716 |
|
717 |
|
718 |
/** |
719 |
* Scroller default settings for initialisation |
720 |
* @namespace |
721 |
* @static |
722 |
*/ |
723 |
Scroller.oDefaults = { |
724 |
/** |
725 |
* Indicate if Scroller show show trace information on the console or not. This can be |
726 |
* useful when debugging Scroller or if just curious as to what it is doing, but should |
727 |
* be turned off for production. |
728 |
* @type bool |
729 |
* @default false |
730 |
* @static |
731 |
* @example |
732 |
* var oTable = $('#example').dataTable( { |
733 |
* "sScrollY": "200px", |
734 |
* "sDom": "frtiS", |
735 |
* "bDeferRender": true, |
736 |
* "oScroller": { |
737 |
* "trace": true |
738 |
* } |
739 |
* } ); |
740 |
*/ |
741 |
"trace": false, |
742 |
|
743 |
/** |
744 |
* Scroller will attempt to automatically calculate the height of rows for it's internal |
745 |
* calculations. However the height that is used can be overridden using this parameter. |
746 |
* @type int|string |
747 |
* @default auto |
748 |
* @static |
749 |
* @example |
750 |
* var oTable = $('#example').dataTable( { |
751 |
* "sScrollY": "200px", |
752 |
* "sDom": "frtiS", |
753 |
* "bDeferRender": true, |
754 |
* "oScroller": { |
755 |
* "rowHeight": 30 |
756 |
* } |
757 |
* } ); |
758 |
*/ |
759 |
"rowHeight": "auto", |
760 |
|
761 |
/** |
762 |
* When using server-side processing, Scroller will wait a small amount of time to allow |
763 |
* the scrolling to finish before requesting more data from the server. This prevents |
764 |
* you from DoSing your own server! The wait time can be configured by this parameter. |
765 |
* @type int |
766 |
* @default 200 |
767 |
* @static |
768 |
* @example |
769 |
* var oTable = $('#example').dataTable( { |
770 |
* "sScrollY": "200px", |
771 |
* "sDom": "frtiS", |
772 |
* "bDeferRender": true, |
773 |
* "oScroller": { |
774 |
* "serverWait": 100 |
775 |
* } |
776 |
* } ); |
777 |
*/ |
778 |
"serverWait": 200, |
779 |
|
780 |
/** |
781 |
* The display buffer is what Scroller uses to calculate how many rows it should pre-fetch |
782 |
* for scrolling. Scroller automatically adjusts DataTables' display length to pre-fetch |
783 |
* rows that will be shown in "near scrolling" (i.e. just beyond the current display area). |
784 |
* The value is based upon the number of rows that can be displayed in the viewport (i.e. |
785 |
* a value of 1), and will apply the display range to records before before and after the |
786 |
* current viewport - i.e. a factor of 3 will allow Scroller to pre-fetch 1 viewport's worth |
787 |
* of rows before the current viewport, the current viewport's rows and 1 viewport's worth |
788 |
* of rows after the current viewport. Adjusting this value can be useful for ensuring |
789 |
* smooth scrolling based on your data set. |
790 |
* @type int |
791 |
* @default 7 |
792 |
* @static |
793 |
* @example |
794 |
* var oTable = $('#example').dataTable( { |
795 |
* "sScrollY": "200px", |
796 |
* "sDom": "frtiS", |
797 |
* "bDeferRender": true, |
798 |
* "oScroller": { |
799 |
* "displayBuffer": 10 |
800 |
* } |
801 |
* } ); |
802 |
*/ |
803 |
"displayBuffer": 9, |
804 |
|
805 |
/** |
806 |
* Scroller uses the boundary scaling factor to decide when to redraw the table - which it |
807 |
* typically does before you reach the end of the currently loaded data set (in order to |
808 |
* allow the data to look continuous to a user scrolling through the data). If given as 0 |
809 |
* then the table will be redrawn whenever the viewport is scrolled, while 1 would not |
810 |
* redraw the table until the currently loaded data has all been shown. You will want |
811 |
* something in the middle - the default factor of 0.5 is usually suitable. |
812 |
* @type float |
813 |
* @default 0.5 |
814 |
* @static |
815 |
* @example |
816 |
* var oTable = $('#example').dataTable( { |
817 |
* "sScrollY": "200px", |
818 |
* "sDom": "frtiS", |
819 |
* "bDeferRender": true, |
820 |
* "oScroller": { |
821 |
* "boundaryScale": 0.75 |
822 |
* } |
823 |
* } ); |
824 |
*/ |
825 |
"boundaryScale": 0.5, |
826 |
|
827 |
/** |
828 |
* Show (or not) the loading element in the background of the table. Note that you should |
829 |
* include the dataTables.scroller.css file for this to be displayed correctly. |
830 |
* @type boolean |
831 |
* @default false |
832 |
* @static |
833 |
* @example |
834 |
* var oTable = $('#example').dataTable( { |
835 |
* "sScrollY": "200px", |
836 |
* "sDom": "frtiS", |
837 |
* "bDeferRender": true, |
838 |
* "oScroller": { |
839 |
* "loadingIndicator": true |
840 |
* } |
841 |
* } ); |
842 |
*/ |
843 |
"loadingIndicator": false |
844 |
}; |
845 |
|
846 |
|
847 |
|
848 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
849 |
* Constants |
850 |
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
851 |
|
852 |
|
853 |
/** |
854 |
* Name of this class |
855 |
* @type String |
856 |
* @default Scroller |
857 |
* @static |
858 |
*/ |
859 |
Scroller.prototype.CLASS = "Scroller"; |
860 |
|
861 |
|
862 |
/** |
863 |
* Scroller version |
864 |
* @type String |
865 |
* @default See code |
866 |
* @static |
867 |
*/ |
868 |
Scroller.VERSION = "1.1.0"; |
869 |
Scroller.prototype.VERSION = Scroller.VERSION; |
870 |
|
871 |
|
872 |
|
873 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
874 |
* Initialisation |
875 |
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
876 |
|
877 |
/* |
878 |
* Register a new feature with DataTables |
879 |
*/ |
880 |
if ( typeof $.fn.dataTable == "function" && |
881 |
typeof $.fn.dataTableExt.fnVersionCheck == "function" && |
882 |
$.fn.dataTableExt.fnVersionCheck('1.9.0') ) |
883 |
{ |
884 |
$.fn.dataTableExt.aoFeatures.push( { |
885 |
"fnInit": function( oDTSettings ) { |
886 |
var init = (typeof oDTSettings.oInit.oScroller == 'undefined') ? |
887 |
{} : oDTSettings.oInit.oScroller; |
888 |
var oScroller = new Scroller( oDTSettings, init ); |
889 |
return oScroller.dom.wrapper; |
890 |
}, |
891 |
"cFeature": "S", |
892 |
"sFeature": "Scroller" |
893 |
} ); |
894 |
} |
895 |
else |
896 |
{ |
897 |
alert( "Warning: Scroller requires DataTables 1.9.0 or greater - www.datatables.net/download"); |
898 |
} |
899 |
|
900 |
|
901 |
// Attach Scroller to DataTables so it can be accessed as an 'extra' |
902 |
$.fn.dataTable.Scroller = Scroller; |
903 |
|
904 |
})(jQuery, window, document); |