]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
1 | /*! Scroller 1.2.2 |
2 | * ©2011-2014 SpryMedia Ltd - datatables.net/license | |
3 | */ | |
4 | ||
5 | /** | |
6 | * @summary Scroller | |
7 | * @description Virtual rendering for DataTables | |
8 | * @version 1.2.2 | |
9 | * @file dataTables.scroller.js | |
10 | * @author SpryMedia Ltd (www.sprymedia.co.uk) | |
11 | * @contact www.sprymedia.co.uk/contact | |
12 | * @copyright Copyright 2011-2014 SpryMedia Ltd. | |
13 | * | |
14 | * This source file is free software, available under the following license: | |
15 | * MIT license - http://datatables.net/license/mit | |
16 | * | |
17 | * This source file is distributed in the hope that it will be useful, but | |
18 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
19 | * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. | |
20 | * | |
21 | * For details please refer to: http://www.datatables.net | |
22 | */ | |
23 | ||
24 | (function(window, document, undefined){ | |
25 | ||
26 | ||
27 | var factory = function( $, DataTable ) { | |
28 | "use strict"; | |
29 | ||
30 | /** | |
31 | * Scroller is a virtual rendering plug-in for DataTables which allows large | |
32 | * datasets to be drawn on screen every quickly. What the virtual rendering means | |
33 | * is that only the visible portion of the table (and a bit to either side to make | |
34 | * the scrolling smooth) is drawn, while the scrolling container gives the | |
35 | * visual impression that the whole table is visible. This is done by making use | |
36 | * of the pagination abilities of DataTables and moving the table around in the | |
37 | * scrolling container DataTables adds to the page. The scrolling container is | |
38 | * forced to the height it would be for the full table display using an extra | |
39 | * element. | |
40 | * | |
41 | * Note that rows in the table MUST all be the same height. Information in a cell | |
42 | * which expands on to multiple lines will cause some odd behaviour in the scrolling. | |
43 | * | |
44 | * Scroller is initialised by simply including the letter 'S' in the sDom for the | |
45 | * table you want to have this feature enabled on. Note that the 'S' must come | |
46 | * AFTER the 't' parameter in `dom`. | |
47 | * | |
48 | * Key features include: | |
49 | * <ul class="limit_length"> | |
50 | * <li>Speed! The aim of Scroller for DataTables is to make rendering large data sets fast</li> | |
51 | * <li>Full compatibility with deferred rendering in DataTables 1.9 for maximum speed</li> | |
52 | * <li>Display millions of rows</li> | |
53 | * <li>Integration with state saving in DataTables (scrolling position is saved)</li> | |
54 | * <li>Easy to use</li> | |
55 | * </ul> | |
56 | * | |
57 | * @class | |
58 | * @constructor | |
59 | * @global | |
60 | * @param {object} oDT DataTables settings object | |
61 | * @param {object} [oOpts={}] Configuration object for FixedColumns. Options | |
62 | * are defined by {@link Scroller.defaults} | |
63 | * | |
64 | * @requires jQuery 1.7+ | |
65 | * @requires DataTables 1.9.0+ | |
66 | * | |
67 | * @example | |
68 | * $(document).ready(function() { | |
69 | * $('#example').dataTable( { | |
70 | * "sScrollY": "200px", | |
71 | * "sAjaxSource": "media/dataset/large.txt", | |
72 | * "sDom": "frtiS", | |
73 | * "bDeferRender": true | |
74 | * } ); | |
75 | * } ); | |
76 | */ | |
77 | var Scroller = function ( oDTSettings, oOpts ) { | |
78 | /* Sanity check - you just know it will happen */ | |
79 | if ( ! this instanceof Scroller ) | |
80 | { | |
81 | alert( "Scroller warning: Scroller must be initialised with the 'new' keyword." ); | |
82 | return; | |
83 | } | |
84 | ||
85 | if ( typeof oOpts == 'undefined' ) | |
86 | { | |
87 | oOpts = {}; | |
88 | } | |
89 | ||
90 | /** | |
91 | * Settings object which contains customisable information for the Scroller instance | |
92 | * @namespace | |
93 | * @private | |
94 | * @extends Scroller.defaults | |
95 | */ | |
96 | this.s = { | |
97 | /** | |
98 | * DataTables settings object | |
99 | * @type object | |
100 | * @default Passed in as first parameter to constructor | |
101 | */ | |
102 | "dt": oDTSettings, | |
103 | ||
104 | /** | |
105 | * Pixel location of the top of the drawn table in the viewport | |
106 | * @type int | |
107 | * @default 0 | |
108 | */ | |
109 | "tableTop": 0, | |
110 | ||
111 | /** | |
112 | * Pixel location of the bottom of the drawn table in the viewport | |
113 | * @type int | |
114 | * @default 0 | |
115 | */ | |
116 | "tableBottom": 0, | |
117 | ||
118 | /** | |
119 | * Pixel location of the boundary for when the next data set should be loaded and drawn | |
120 | * when scrolling up the way. | |
121 | * @type int | |
122 | * @default 0 | |
123 | * @private | |
124 | */ | |
125 | "redrawTop": 0, | |
126 | ||
127 | /** | |
128 | * Pixel location of the boundary for when the next data set should be loaded and drawn | |
129 | * when scrolling down the way. Note that this is actually calculated as the offset from | |
130 | * the top. | |
131 | * @type int | |
132 | * @default 0 | |
133 | * @private | |
134 | */ | |
135 | "redrawBottom": 0, | |
136 | ||
137 | /** | |
138 | * Auto row height or not indicator | |
139 | * @type bool | |
140 | * @default 0 | |
141 | */ | |
142 | "autoHeight": true, | |
143 | ||
144 | /** | |
145 | * Number of rows calculated as visible in the visible viewport | |
146 | * @type int | |
147 | * @default 0 | |
148 | */ | |
149 | "viewportRows": 0, | |
150 | ||
151 | /** | |
152 | * setTimeout reference for state saving, used when state saving is enabled in the DataTable | |
153 | * and when the user scrolls the viewport in order to stop the cookie set taking too much | |
154 | * CPU! | |
155 | * @type int | |
156 | * @default 0 | |
157 | */ | |
158 | "stateTO": null, | |
159 | ||
160 | /** | |
161 | * setTimeout reference for the redraw, used when server-side processing is enabled in the | |
162 | * DataTables in order to prevent DoSing the server | |
163 | * @type int | |
164 | * @default null | |
165 | */ | |
166 | "drawTO": null, | |
167 | ||
168 | heights: { | |
169 | jump: null, | |
170 | page: null, | |
171 | virtual: null, | |
172 | scroll: null, | |
173 | ||
174 | /** | |
175 | * Height of rows in the table | |
176 | * @type int | |
177 | * @default 0 | |
178 | */ | |
179 | row: null, | |
180 | ||
181 | /** | |
182 | * Pixel height of the viewport | |
183 | * @type int | |
184 | * @default 0 | |
185 | */ | |
186 | viewport: null | |
187 | }, | |
188 | ||
189 | topRowFloat: 0, | |
190 | scrollDrawDiff: null, | |
191 | loaderVisible: false | |
192 | }; | |
193 | ||
194 | // @todo The defaults should extend a `c` property and the internal settings | |
195 | // only held in the `s` property. At the moment they are mixed | |
196 | this.s = $.extend( this.s, Scroller.oDefaults, oOpts ); | |
197 | ||
198 | // Workaround for row height being read from height object (see above comment) | |
199 | this.s.heights.row = this.s.rowHeight; | |
200 | ||
201 | /** | |
202 | * DOM elements used by the class instance | |
203 | * @private | |
204 | * @namespace | |
205 | * | |
206 | */ | |
207 | this.dom = { | |
208 | "force": document.createElement('div'), | |
209 | "scroller": null, | |
210 | "table": null, | |
211 | "loader": null | |
212 | }; | |
213 | ||
214 | /* Attach the instance to the DataTables instance so it can be accessed */ | |
215 | this.s.dt.oScroller = this; | |
216 | ||
217 | /* Let's do it */ | |
218 | this._fnConstruct(); | |
219 | }; | |
220 | ||
221 | ||
222 | ||
223 | Scroller.prototype = /** @lends Scroller.prototype */{ | |
224 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
225 | * Public methods | |
226 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
227 | ||
228 | /** | |
229 | * Calculate the pixel position from the top of the scrolling container for | |
230 | * a given row | |
231 | * @param {int} iRow Row number to calculate the position of | |
232 | * @returns {int} Pixels | |
233 | * @example | |
234 | * $(document).ready(function() { | |
235 | * $('#example').dataTable( { | |
236 | * "sScrollY": "200px", | |
237 | * "sAjaxSource": "media/dataset/large.txt", | |
238 | * "sDom": "frtiS", | |
239 | * "bDeferRender": true, | |
240 | * "fnInitComplete": function (o) { | |
241 | * // Find where row 25 is | |
242 | * alert( o.oScroller.fnRowToPixels( 25 ) ); | |
243 | * } | |
244 | * } ); | |
245 | * } ); | |
246 | */ | |
247 | "fnRowToPixels": function ( rowIdx, intParse, virtual ) | |
248 | { | |
249 | var pixels; | |
250 | ||
251 | if ( virtual ) { | |
252 | pixels = this._domain( 'virtualToPhysical', rowIdx * this.s.heights.row ); | |
253 | } | |
254 | else { | |
255 | var diff = rowIdx - this.s.baseRowTop; | |
256 | pixels = this.s.baseScrollTop + (diff * this.s.heights.row); | |
257 | } | |
258 | ||
259 | return intParse || intParse === undefined ? | |
260 | parseInt( pixels, 10 ) : | |
261 | pixels; | |
262 | }, | |
263 | ||
264 | ||
265 | /** | |
266 | * Calculate the row number that will be found at the given pixel position | |
267 | * (y-scroll). | |
268 | * | |
269 | * Please note that when the height of the full table exceeds 1 million | |
270 | * pixels, Scroller switches into a non-linear mode for the scrollbar to fit | |
271 | * all of the records into a finite area, but this function returns a linear | |
272 | * value (relative to the last non-linear positioning). | |
273 | * @param {int} iPixels Offset from top to calculate the row number of | |
274 | * @param {int} [intParse=true] If an integer value should be returned | |
275 | * @param {int} [virtual=false] Perform the calculations in the virtual domain | |
276 | * @returns {int} Row index | |
277 | * @example | |
278 | * $(document).ready(function() { | |
279 | * $('#example').dataTable( { | |
280 | * "sScrollY": "200px", | |
281 | * "sAjaxSource": "media/dataset/large.txt", | |
282 | * "sDom": "frtiS", | |
283 | * "bDeferRender": true, | |
284 | * "fnInitComplete": function (o) { | |
285 | * // Find what row number is at 500px | |
286 | * alert( o.oScroller.fnPixelsToRow( 500 ) ); | |
287 | * } | |
288 | * } ); | |
289 | * } ); | |
290 | */ | |
291 | "fnPixelsToRow": function ( pixels, intParse, virtual ) | |
292 | { | |
293 | var diff = pixels - this.s.baseScrollTop; | |
294 | var row = virtual ? | |
295 | this._domain( 'physicalToVirtual', pixels ) / this.s.heights.row : | |
296 | ( diff / this.s.heights.row ) + this.s.baseRowTop; | |
297 | ||
298 | return intParse || intParse === undefined ? | |
299 | parseInt( row, 10 ) : | |
300 | row; | |
301 | }, | |
302 | ||
303 | ||
304 | /** | |
305 | * Calculate the row number that will be found at the given pixel position (y-scroll) | |
306 | * @param {int} iRow Row index to scroll to | |
307 | * @param {bool} [bAnimate=true] Animate the transition or not | |
308 | * @returns {void} | |
309 | * @example | |
310 | * $(document).ready(function() { | |
311 | * $('#example').dataTable( { | |
312 | * "sScrollY": "200px", | |
313 | * "sAjaxSource": "media/dataset/large.txt", | |
314 | * "sDom": "frtiS", | |
315 | * "bDeferRender": true, | |
316 | * "fnInitComplete": function (o) { | |
317 | * // Immediately scroll to row 1000 | |
318 | * o.oScroller.fnScrollToRow( 1000 ); | |
319 | * } | |
320 | * } ); | |
321 | * | |
322 | * // Sometime later on use the following to scroll to row 500... | |
323 | * var oSettings = $('#example').dataTable().fnSettings(); | |
324 | * oSettings.oScroller.fnScrollToRow( 500 ); | |
325 | * } ); | |
326 | */ | |
327 | "fnScrollToRow": function ( iRow, bAnimate ) | |
328 | { | |
329 | var that = this; | |
330 | var ani = false; | |
331 | var px = this.fnRowToPixels( iRow ); | |
332 | ||
333 | // We need to know if the table will redraw or not before doing the | |
334 | // scroll. If it will not redraw, then we need to use the currently | |
335 | // displayed table, and scroll with the physical pixels. Otherwise, we | |
336 | // need to calculate the table's new position from the virtual | |
337 | // transform. | |
338 | var preRows = ((this.s.displayBuffer-1)/2) * this.s.viewportRows; | |
339 | var drawRow = iRow - preRows; | |
340 | if ( drawRow < 0 ) { | |
341 | drawRow = 0; | |
342 | } | |
343 | ||
344 | if ( (px > this.s.redrawBottom || px < this.s.redrawTop) && this.s.dt._iDisplayStart !== drawRow ) { | |
345 | ani = true; | |
346 | px = this.fnRowToPixels( iRow, false, true ); | |
347 | } | |
348 | ||
349 | if ( typeof bAnimate == 'undefined' || bAnimate ) | |
350 | { | |
351 | this.s.ani = ani; | |
352 | $(this.dom.scroller).animate( { | |
353 | "scrollTop": px | |
354 | }, function () { | |
355 | // This needs to happen after the animation has completed and | |
356 | // the final scroll event fired | |
357 | setTimeout( function () { | |
358 | that.s.ani = false; | |
359 | }, 25 ); | |
360 | } ); | |
361 | } | |
362 | else | |
363 | { | |
364 | $(this.dom.scroller).scrollTop( px ); | |
365 | } | |
366 | }, | |
367 | ||
368 | ||
369 | /** | |
370 | * Calculate and store information about how many rows are to be displayed | |
371 | * in the scrolling viewport, based on current dimensions in the browser's | |
372 | * rendering. This can be particularly useful if the table is initially | |
373 | * drawn in a hidden element - for example in a tab. | |
374 | * @param {bool} [bRedraw=true] Redraw the table automatically after the recalculation, with | |
375 | * the new dimensions forming the basis for the draw. | |
376 | * @returns {void} | |
377 | * @example | |
378 | * $(document).ready(function() { | |
379 | * // Make the example container hidden to throw off the browser's sizing | |
380 | * document.getElementById('container').style.display = "none"; | |
381 | * var oTable = $('#example').dataTable( { | |
382 | * "sScrollY": "200px", | |
383 | * "sAjaxSource": "media/dataset/large.txt", | |
384 | * "sDom": "frtiS", | |
385 | * "bDeferRender": true, | |
386 | * "fnInitComplete": function (o) { | |
387 | * // Immediately scroll to row 1000 | |
388 | * o.oScroller.fnScrollToRow( 1000 ); | |
389 | * } | |
390 | * } ); | |
391 | * | |
392 | * setTimeout( function () { | |
393 | * // Make the example container visible and recalculate the scroller sizes | |
394 | * document.getElementById('container').style.display = "block"; | |
395 | * oTable.fnSettings().oScroller.fnMeasure(); | |
396 | * }, 3000 ); | |
397 | */ | |
398 | "fnMeasure": function ( bRedraw ) | |
399 | { | |
400 | if ( this.s.autoHeight ) | |
401 | { | |
402 | this._fnCalcRowHeight(); | |
403 | } | |
404 | ||
405 | var heights = this.s.heights; | |
406 | ||
407 | heights.viewport = $(this.dom.scroller).height(); | |
408 | this.s.viewportRows = parseInt( heights.viewport / heights.row, 10 )+1; | |
409 | this.s.dt._iDisplayLength = this.s.viewportRows * this.s.displayBuffer; | |
410 | ||
411 | if ( bRedraw === undefined || bRedraw ) | |
412 | { | |
413 | this.s.dt.oInstance.fnDraw(); | |
414 | } | |
415 | }, | |
416 | ||
417 | ||
418 | ||
419 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
420 | * Private methods (they are of course public in JS, but recommended as private) | |
421 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
422 | ||
423 | /** | |
424 | * Initialisation for Scroller | |
425 | * @returns {void} | |
426 | * @private | |
427 | */ | |
428 | "_fnConstruct": function () | |
429 | { | |
430 | var that = this; | |
431 | ||
432 | /* Sanity check */ | |
433 | if ( !this.s.dt.oFeatures.bPaginate ) { | |
434 | this.s.dt.oApi._fnLog( this.s.dt, 0, 'Pagination must be enabled for Scroller' ); | |
435 | return; | |
436 | } | |
437 | ||
438 | /* Insert a div element that we can use to force the DT scrolling container to | |
439 | * the height that would be required if the whole table was being displayed | |
440 | */ | |
441 | this.dom.force.style.position = "absolute"; | |
442 | this.dom.force.style.top = "0px"; | |
443 | this.dom.force.style.left = "0px"; | |
444 | this.dom.force.style.width = "1px"; | |
445 | ||
446 | this.dom.scroller = $('div.'+this.s.dt.oClasses.sScrollBody, this.s.dt.nTableWrapper)[0]; | |
447 | this.dom.scroller.appendChild( this.dom.force ); | |
448 | this.dom.scroller.style.position = "relative"; | |
449 | ||
450 | this.dom.table = $('>table', this.dom.scroller)[0]; | |
451 | this.dom.table.style.position = "absolute"; | |
452 | this.dom.table.style.top = "0px"; | |
453 | this.dom.table.style.left = "0px"; | |
454 | ||
455 | // Add class to 'announce' that we are a Scroller table | |
456 | $(this.s.dt.nTableWrapper).addClass('DTS'); | |
457 | ||
458 | // Add a 'loading' indicator | |
459 | if ( this.s.loadingIndicator ) | |
460 | { | |
461 | this.dom.loader = $('<div class="DTS_Loading">'+this.s.dt.oLanguage.sLoadingRecords+'</div>') | |
462 | .css('display', 'none'); | |
463 | ||
464 | $(this.dom.scroller.parentNode) | |
465 | .css('position', 'relative') | |
466 | .append( this.dom.loader ); | |
467 | } | |
468 | ||
469 | /* Initial size calculations */ | |
470 | if ( this.s.heights.row && this.s.heights.row != 'auto' ) | |
471 | { | |
472 | this.s.autoHeight = false; | |
473 | } | |
474 | this.fnMeasure( false ); | |
475 | ||
476 | /* Scrolling callback to see if a page change is needed - use a throttled | |
477 | * function for the save save callback so we aren't hitting it on every | |
478 | * scroll | |
479 | */ | |
480 | this.s.ingnoreScroll = true; | |
481 | this.s.stateSaveThrottle = this.s.dt.oApi._fnThrottle( function () { | |
482 | that.s.dt.oApi._fnSaveState( that.s.dt ); | |
483 | }, 500 ); | |
484 | $(this.dom.scroller).on( 'scroll.DTS', function (e) { | |
485 | that._fnScroll.call( that ); | |
486 | } ); | |
487 | ||
488 | /* In iOS we catch the touchstart event in case the user tries to scroll | |
489 | * while the display is already scrolling | |
490 | */ | |
491 | $(this.dom.scroller).on('touchstart.DTS', function () { | |
492 | that._fnScroll.call( that ); | |
493 | } ); | |
494 | ||
495 | /* Update the scroller when the DataTable is redrawn */ | |
496 | this.s.dt.aoDrawCallback.push( { | |
497 | "fn": function () { | |
498 | if ( that.s.dt.bInitialised ) { | |
499 | that._fnDrawCallback.call( that ); | |
500 | } | |
501 | }, | |
502 | "sName": "Scroller" | |
503 | } ); | |
504 | ||
505 | /* On resize, update the information element, since the number of rows shown might change */ | |
506 | $(window).on( 'resize.DTS', function () { | |
507 | that.fnMeasure( false ); | |
508 | that._fnInfo(); | |
509 | } ); | |
510 | ||
511 | /* Add a state saving parameter to the DT state saving so we can restore the exact | |
512 | * position of the scrolling | |
513 | */ | |
514 | var initialStateSave = true; | |
515 | this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) { | |
516 | /* Set iScroller to saved scroll position on initialization. | |
517 | */ | |
518 | if(initialStateSave && that.s.dt.oLoadedState){ | |
519 | oData.iScroller = that.s.dt.oLoadedState.iScroller; | |
520 | oData.iScrollerTopRow = that.s.dt.oLoadedState.iScrollerTopRow; | |
521 | initialStateSave = false; | |
522 | } else { | |
523 | oData.iScroller = that.dom.scroller.scrollTop; | |
524 | oData.iScrollerTopRow = that.s.topRowFloat; | |
525 | } | |
526 | }, "Scroller_State" ); | |
527 | ||
528 | if ( this.s.dt.oLoadedState ) { | |
529 | this.s.topRowFloat = this.s.dt.oLoadedState.iScrollerTopRow || 0; | |
530 | } | |
531 | ||
532 | /* Destructor */ | |
533 | this.s.dt.aoDestroyCallback.push( { | |
534 | "sName": "Scroller", | |
535 | "fn": function () { | |
536 | $(window).off( 'resize.DTS' ); | |
537 | $(that.dom.scroller).off('touchstart.DTS scroll.DTS'); | |
538 | $(that.s.dt.nTableWrapper).removeClass('DTS'); | |
539 | $('div.DTS_Loading', that.dom.scroller.parentNode).remove(); | |
540 | ||
541 | that.dom.table.style.position = ""; | |
542 | that.dom.table.style.top = ""; | |
543 | that.dom.table.style.left = ""; | |
544 | } | |
545 | } ); | |
546 | }, | |
547 | ||
548 | ||
549 | /** | |
550 | * Scrolling function - fired whenever the scrolling position is changed. | |
551 | * This method needs to use the stored values to see if the table should be | |
552 | * redrawn as we are moving towards the end of the information that is | |
553 | * currently drawn or not. If needed, then it will redraw the table based on | |
554 | * the new position. | |
555 | * @returns {void} | |
556 | * @private | |
557 | */ | |
558 | "_fnScroll": function () | |
559 | { | |
560 | var | |
561 | that = this, | |
562 | heights = this.s.heights, | |
563 | iScrollTop = this.dom.scroller.scrollTop, | |
564 | iTopRow; | |
565 | ||
566 | if ( this.s.skip ) { | |
567 | return; | |
568 | } | |
569 | ||
570 | if ( this.s.ingnoreScroll ) { | |
571 | return; | |
572 | } | |
573 | ||
574 | /* If the table has been sorted or filtered, then we use the redraw that | |
575 | * DataTables as done, rather than performing our own | |
576 | */ | |
577 | if ( this.s.dt.bFiltered || this.s.dt.bSorted ) { | |
578 | this.s.lastScrollTop = 0; | |
579 | return; | |
580 | } | |
581 | ||
582 | /* Update the table's information display for what is now in the viewport */ | |
583 | this._fnInfo(); | |
584 | ||
585 | /* We don't want to state save on every scroll event - that's heavy | |
586 | * handed, so use a timeout to update the state saving only when the | |
587 | * scrolling has finished | |
588 | */ | |
589 | clearTimeout( this.s.stateTO ); | |
590 | this.s.stateTO = setTimeout( function () { | |
591 | that.s.dt.oApi._fnSaveState( that.s.dt ); | |
592 | }, 250 ); | |
593 | ||
594 | /* Check if the scroll point is outside the trigger boundary which would required | |
595 | * a DataTables redraw | |
596 | */ | |
597 | if ( iScrollTop < this.s.redrawTop || iScrollTop > this.s.redrawBottom ) { | |
598 | var preRows = Math.ceil( ((this.s.displayBuffer-1)/2) * this.s.viewportRows ); | |
599 | ||
600 | if ( Math.abs( iScrollTop - this.s.lastScrollTop ) > heights.viewport || this.s.ani ) { | |
601 | iTopRow = parseInt(this._domain( 'physicalToVirtual', iScrollTop ) / heights.row, 10) - preRows; | |
602 | this.s.topRowFloat = (this._domain( 'physicalToVirtual', iScrollTop ) / heights.row); | |
603 | } | |
604 | else { | |
605 | iTopRow = this.fnPixelsToRow( iScrollTop ) - preRows; | |
606 | this.s.topRowFloat = this.fnPixelsToRow( iScrollTop, false ); | |
607 | } | |
608 | ||
609 | if ( iTopRow <= 0 ) { | |
610 | /* At the start of the table */ | |
611 | iTopRow = 0; | |
612 | } | |
613 | else if ( iTopRow + this.s.dt._iDisplayLength > this.s.dt.fnRecordsDisplay() ) { | |
614 | /* At the end of the table */ | |
615 | iTopRow = this.s.dt.fnRecordsDisplay() - this.s.dt._iDisplayLength; | |
616 | if ( iTopRow < 0 ) { | |
617 | iTopRow = 0; | |
618 | } | |
619 | } | |
620 | else if ( iTopRow % 2 !== 0 ) { | |
621 | // For the row-striping classes (odd/even) we want only to start | |
622 | // on evens otherwise the stripes will change between draws and | |
623 | // look rubbish | |
624 | iTopRow++; | |
625 | } | |
626 | ||
627 | if ( iTopRow != this.s.dt._iDisplayStart ) { | |
628 | /* Cache the new table position for quick lookups */ | |
629 | this.s.tableTop = $(this.s.dt.nTable).offset().top; | |
630 | this.s.tableBottom = $(this.s.dt.nTable).height() + this.s.tableTop; | |
631 | ||
632 | var draw = function () { | |
633 | if ( that.s.scrollDrawReq === null ) { | |
634 | that.s.scrollDrawReq = iScrollTop; | |
635 | } | |
636 | ||
637 | that.s.dt._iDisplayStart = iTopRow; | |
638 | if ( that.s.dt.oApi._fnCalculateEnd ) { // Removed in 1.10 | |
639 | that.s.dt.oApi._fnCalculateEnd( that.s.dt ); | |
640 | } | |
641 | that.s.dt.oApi._fnDraw( that.s.dt ); | |
642 | }; | |
643 | ||
644 | /* Do the DataTables redraw based on the calculated start point - note that when | |
645 | * using server-side processing we introduce a small delay to not DoS the server... | |
646 | */ | |
647 | if ( this.s.dt.oFeatures.bServerSide ) { | |
648 | clearTimeout( this.s.drawTO ); | |
649 | this.s.drawTO = setTimeout( draw, this.s.serverWait ); | |
650 | } | |
651 | else { | |
652 | draw(); | |
653 | } | |
654 | ||
655 | if ( this.dom.loader && ! this.s.loaderVisible ) { | |
656 | this.dom.loader.css( 'display', 'block' ); | |
657 | this.s.loaderVisible = true; | |
658 | } | |
659 | } | |
660 | } | |
661 | ||
662 | this.s.lastScrollTop = iScrollTop; | |
663 | this.s.stateSaveThrottle(); | |
664 | }, | |
665 | ||
666 | ||
667 | /** | |
668 | * Convert from one domain to another. The physical domain is the actual | |
669 | * pixel count on the screen, while the virtual is if we had browsers which | |
670 | * had scrolling containers of infinite height (i.e. the absolute value) | |
671 | * | |
672 | * @param {string} dir Domain transform direction, `virtualToPhysical` or | |
673 | * `physicalToVirtual` | |
674 | * @returns {number} Calculated transform | |
675 | * @private | |
676 | */ | |
677 | _domain: function ( dir, val ) | |
678 | { | |
679 | var heights = this.s.heights; | |
680 | var coeff; | |
681 | ||
682 | // If the virtual and physical height match, then we use a linear | |
683 | // transform between the two, allowing the scrollbar to be linear | |
684 | if ( heights.virtual === heights.scroll ) { | |
685 | coeff = (heights.virtual-heights.viewport) / (heights.scroll-heights.viewport); | |
686 | ||
687 | if ( dir === 'virtualToPhysical' ) { | |
688 | return val / coeff; | |
689 | } | |
690 | else if ( dir === 'physicalToVirtual' ) { | |
691 | return val * coeff; | |
692 | } | |
693 | } | |
694 | ||
695 | // Otherwise, we want a non-linear scrollbar to take account of the | |
696 | // redrawing regions at the start and end of the table, otherwise these | |
697 | // can stutter badly - on large tables 30px (for example) scroll might | |
698 | // be hundreds of rows, so the table would be redrawing every few px at | |
699 | // the start and end. Use a simple quadratic to stop this. It does mean | |
700 | // the scrollbar is non-linear, but with such massive data sets, the | |
701 | // scrollbar is going to be a best guess anyway | |
702 | var xMax = (heights.scroll - heights.viewport) / 2; | |
703 | var yMax = (heights.virtual - heights.viewport) / 2; | |
704 | ||
705 | coeff = yMax / ( xMax * xMax ); | |
706 | ||
707 | if ( dir === 'virtualToPhysical' ) { | |
708 | if ( val < yMax ) { | |
709 | return Math.pow(val / coeff, 0.5); | |
710 | } | |
711 | else { | |
712 | val = (yMax*2) - val; | |
713 | return val < 0 ? | |
714 | heights.scroll : | |
715 | (xMax*2) - Math.pow(val / coeff, 0.5); | |
716 | } | |
717 | } | |
718 | else if ( dir === 'physicalToVirtual' ) { | |
719 | if ( val < xMax ) { | |
720 | return val * val * coeff; | |
721 | } | |
722 | else { | |
723 | val = (xMax*2) - val; | |
724 | return val < 0 ? | |
725 | heights.virtual : | |
726 | (yMax*2) - (val * val * coeff); | |
727 | } | |
728 | } | |
729 | }, | |
730 | ||
731 | ||
732 | /** | |
733 | * Draw callback function which is fired when the DataTable is redrawn. The main function of | |
734 | * this method is to position the drawn table correctly the scrolling container for the rows | |
735 | * that is displays as a result of the scrolling position. | |
736 | * @returns {void} | |
737 | * @private | |
738 | */ | |
739 | "_fnDrawCallback": function () | |
740 | { | |
741 | var | |
742 | that = this, | |
743 | heights = this.s.heights, | |
744 | iScrollTop = this.dom.scroller.scrollTop, | |
745 | iActualScrollTop = iScrollTop, | |
746 | iScrollBottom = iScrollTop + heights.viewport, | |
747 | iTableHeight = $(this.s.dt.nTable).height(), | |
748 | displayStart = this.s.dt._iDisplayStart, | |
749 | displayLen = this.s.dt._iDisplayLength, | |
750 | displayEnd = this.s.dt.fnRecordsDisplay(); | |
751 | ||
752 | // Disable the scroll event listener while we are updating the DOM | |
753 | this.s.skip = true; | |
754 | ||
755 | // Resize the scroll forcing element | |
756 | this._fnScrollForce(); | |
757 | ||
758 | // Reposition the scrolling for the updated virtual position if needed | |
759 | if ( displayStart === 0 ) { | |
760 | // Linear calculation at the top of the table | |
761 | iScrollTop = this.s.topRowFloat * heights.row; | |
762 | } | |
763 | else if ( displayStart + displayLen >= displayEnd ) { | |
764 | // Linear calculation that the bottom as well | |
765 | iScrollTop = heights.scroll - ((displayEnd - this.s.topRowFloat) * heights.row); | |
766 | } | |
767 | else { | |
768 | // Domain scaled in the middle | |
769 | iScrollTop = this._domain( 'virtualToPhysical', this.s.topRowFloat * heights.row ); | |
770 | } | |
771 | ||
772 | this.dom.scroller.scrollTop = iScrollTop; | |
773 | ||
774 | // Store positional information so positional calculations can be based | |
775 | // upon the current table draw position | |
776 | this.s.baseScrollTop = iScrollTop; | |
777 | this.s.baseRowTop = this.s.topRowFloat; | |
778 | ||
779 | // Position the table in the virtual scroller | |
780 | var tableTop = iScrollTop - ((this.s.topRowFloat - displayStart) * heights.row); | |
781 | if ( displayStart === 0 ) { | |
782 | tableTop = 0; | |
783 | } | |
784 | else if ( displayStart + displayLen >= displayEnd ) { | |
785 | tableTop = heights.scroll - iTableHeight; | |
786 | } | |
787 | ||
788 | this.dom.table.style.top = tableTop+'px'; | |
789 | ||
790 | /* Cache some information for the scroller */ | |
791 | this.s.tableTop = tableTop; | |
792 | this.s.tableBottom = iTableHeight + this.s.tableTop; | |
793 | ||
794 | // Calculate the boundaries for where a redraw will be triggered by the | |
795 | // scroll event listener | |
796 | var boundaryPx = (iScrollTop - this.s.tableTop) * this.s.boundaryScale; | |
797 | this.s.redrawTop = iScrollTop - boundaryPx; | |
798 | this.s.redrawBottom = iScrollTop + boundaryPx; | |
799 | ||
800 | this.s.skip = false; | |
801 | ||
802 | // Restore the scrolling position that was saved by DataTable's state | |
803 | // saving Note that this is done on the second draw when data is Ajax | |
804 | // sourced, and the first draw when DOM soured | |
805 | if ( this.s.dt.oFeatures.bStateSave && this.s.dt.oLoadedState !== null && | |
806 | typeof this.s.dt.oLoadedState.iScroller != 'undefined' ) | |
807 | { | |
808 | // A quirk of DataTables is that the draw callback will occur on an | |
809 | // empty set if Ajax sourced, but not if server-side processing. | |
810 | var ajaxSourced = (this.s.dt.sAjaxSource || that.s.dt.ajax) && ! this.s.dt.oFeatures.bServerSide ? | |
811 | true : | |
812 | false; | |
813 | ||
814 | if ( ( ajaxSourced && this.s.dt.iDraw == 2) || | |
815 | (!ajaxSourced && this.s.dt.iDraw == 1) ) | |
816 | { | |
817 | setTimeout( function () { | |
818 | $(that.dom.scroller).scrollTop( that.s.dt.oLoadedState.iScroller ); | |
819 | that.s.redrawTop = that.s.dt.oLoadedState.iScroller - (heights.viewport/2); | |
820 | ||
821 | // In order to prevent layout thrashing we need another | |
822 | // small delay | |
823 | setTimeout( function () { | |
824 | that.s.ingnoreScroll = false; | |
825 | }, 0 ); | |
826 | }, 0 ); | |
827 | } | |
828 | } | |
829 | else { | |
830 | that.s.ingnoreScroll = false; | |
831 | } | |
832 | ||
833 | // Because of the order of the DT callbacks, the info update will | |
834 | // take precedence over the one we want here. So a 'thread' break is | |
835 | // needed | |
836 | setTimeout( function () { | |
837 | that._fnInfo.call( that ); | |
838 | }, 0 ); | |
839 | ||
840 | // Hide the loading indicator | |
841 | if ( this.dom.loader && this.s.loaderVisible ) { | |
842 | this.dom.loader.css( 'display', 'none' ); | |
843 | this.s.loaderVisible = false; | |
844 | } | |
845 | }, | |
846 | ||
847 | ||
848 | /** | |
849 | * Force the scrolling container to have height beyond that of just the | |
850 | * table that has been drawn so the user can scroll the whole data set. | |
851 | * | |
852 | * Note that if the calculated required scrolling height exceeds a maximum | |
853 | * value (1 million pixels - hard-coded) the forcing element will be set | |
854 | * only to that maximum value and virtual / physical domain transforms will | |
855 | * be used to allow Scroller to display tables of any number of records. | |
856 | * @returns {void} | |
857 | * @private | |
858 | */ | |
859 | _fnScrollForce: function () | |
860 | { | |
861 | var heights = this.s.heights; | |
862 | var max = 1000000; | |
863 | ||
864 | heights.virtual = heights.row * this.s.dt.fnRecordsDisplay(); | |
865 | heights.scroll = heights.virtual; | |
866 | ||
867 | if ( heights.scroll > max ) { | |
868 | heights.scroll = max; | |
869 | } | |
870 | ||
871 | this.dom.force.style.height = heights.scroll+"px"; | |
872 | }, | |
873 | ||
874 | ||
875 | /** | |
876 | * Automatic calculation of table row height. This is just a little tricky here as using | |
877 | * initialisation DataTables has tale the table out of the document, so we need to create | |
878 | * a new table and insert it into the document, calculate the row height and then whip the | |
879 | * table out. | |
880 | * @returns {void} | |
881 | * @private | |
882 | */ | |
883 | "_fnCalcRowHeight": function () | |
884 | { | |
885 | var dt = this.s.dt; | |
886 | var origTable = dt.nTable; | |
887 | var nTable = origTable.cloneNode( false ); | |
888 | var tbody = $('<tbody/>').appendTo( nTable ); | |
889 | var container = $( | |
890 | '<div class="'+dt.oClasses.sWrapper+' DTS">'+ | |
891 | '<div class="'+dt.oClasses.sScrollWrapper+'">'+ | |
892 | '<div class="'+dt.oClasses.sScrollBody+'"></div>'+ | |
893 | '</div>'+ | |
894 | '</div>' | |
895 | ); | |
896 | ||
897 | // Want 3 rows in the sizing table so :first-child and :last-child | |
898 | // CSS styles don't come into play - take the size of the middle row | |
899 | $('tbody tr:lt(4)', origTable).clone().appendTo( tbody ); | |
900 | while( $('tr', tbody).length < 3 ) { | |
901 | tbody.append( '<tr><td> </td></tr>' ); | |
902 | } | |
903 | ||
904 | $('div.'+dt.oClasses.sScrollBody, container).append( nTable ); | |
905 | ||
906 | var appendTo; | |
907 | if (dt._bInitComplete) { | |
908 | appendTo = origTable.parentNode; | |
909 | } else { | |
910 | if (!this.s.dt.nHolding) { | |
911 | this.s.dt.nHolding = $( '<div></div>' ).insertBefore( this.s.dt.nTable ); | |
912 | } | |
913 | appendTo = this.s.dt.nHolding; | |
914 | } | |
915 | ||
916 | container.appendTo( appendTo ); | |
917 | this.s.heights.row = $('tr', tbody).eq(1).outerHeight(); | |
918 | container.remove(); | |
919 | }, | |
920 | ||
921 | ||
922 | /** | |
923 | * Update any information elements that are controlled by the DataTable based on the scrolling | |
924 | * viewport and what rows are visible in it. This function basically acts in the same way as | |
925 | * _fnUpdateInfo in DataTables, and effectively replaces that function. | |
926 | * @returns {void} | |
927 | * @private | |
928 | */ | |
929 | "_fnInfo": function () | |
930 | { | |
931 | if ( !this.s.dt.oFeatures.bInfo ) | |
932 | { | |
933 | return; | |
934 | } | |
935 | ||
936 | var | |
937 | dt = this.s.dt, | |
938 | language = dt.oLanguage, | |
939 | iScrollTop = this.dom.scroller.scrollTop, | |
940 | iStart = Math.floor( this.fnPixelsToRow(iScrollTop, false, this.s.ani)+1 ), | |
941 | iMax = dt.fnRecordsTotal(), | |
942 | iTotal = dt.fnRecordsDisplay(), | |
943 | iPossibleEnd = Math.ceil( this.fnPixelsToRow(iScrollTop+this.s.heights.viewport, false, this.s.ani) ), | |
944 | iEnd = iTotal < iPossibleEnd ? iTotal : iPossibleEnd, | |
945 | sStart = dt.fnFormatNumber( iStart ), | |
946 | sEnd = dt.fnFormatNumber( iEnd ), | |
947 | sMax = dt.fnFormatNumber( iMax ), | |
948 | sTotal = dt.fnFormatNumber( iTotal ), | |
949 | sOut; | |
950 | ||
951 | if ( dt.fnRecordsDisplay() === 0 && | |
952 | dt.fnRecordsDisplay() == dt.fnRecordsTotal() ) | |
953 | { | |
954 | /* Empty record set */ | |
955 | sOut = language.sInfoEmpty+ language.sInfoPostFix; | |
956 | } | |
957 | else if ( dt.fnRecordsDisplay() === 0 ) | |
958 | { | |
959 | /* Empty record set after filtering */ | |
960 | sOut = language.sInfoEmpty +' '+ | |
961 | language.sInfoFiltered.replace('_MAX_', sMax)+ | |
962 | language.sInfoPostFix; | |
963 | } | |
964 | else if ( dt.fnRecordsDisplay() == dt.fnRecordsTotal() ) | |
965 | { | |
966 | /* Normal record set */ | |
967 | sOut = language.sInfo. | |
968 | replace('_START_', sStart). | |
969 | replace('_END_', sEnd). | |
970 | replace('_MAX_', sMax). | |
971 | replace('_TOTAL_', sTotal)+ | |
972 | language.sInfoPostFix; | |
973 | } | |
974 | else | |
975 | { | |
976 | /* Record set after filtering */ | |
977 | sOut = language.sInfo. | |
978 | replace('_START_', sStart). | |
979 | replace('_END_', sEnd). | |
980 | replace('_MAX_', sMax). | |
981 | replace('_TOTAL_', sTotal) +' '+ | |
982 | language.sInfoFiltered.replace( | |
983 | '_MAX_', | |
984 | dt.fnFormatNumber(dt.fnRecordsTotal()) | |
985 | )+ | |
986 | language.sInfoPostFix; | |
987 | } | |
988 | ||
989 | var callback = language.fnInfoCallback; | |
990 | if ( callback ) { | |
991 | sOut = callback.call( dt.oInstance, | |
992 | dt, iStart, iEnd, iMax, iTotal, sOut | |
993 | ); | |
994 | } | |
995 | ||
996 | var n = dt.aanFeatures.i; | |
997 | if ( typeof n != 'undefined' ) | |
998 | { | |
999 | for ( var i=0, iLen=n.length ; i<iLen ; i++ ) | |
1000 | { | |
1001 | $(n[i]).html( sOut ); | |
1002 | } | |
1003 | } | |
1004 | } | |
1005 | }; | |
1006 | ||
1007 | ||
1008 | ||
1009 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
1010 | * Statics | |
1011 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
1012 | ||
1013 | ||
1014 | /** | |
1015 | * Scroller default settings for initialisation | |
1016 | * @namespace | |
1017 | * @name Scroller.defaults | |
1018 | * @static | |
1019 | */ | |
1020 | Scroller.defaults = /** @lends Scroller.defaults */{ | |
1021 | /** | |
1022 | * Indicate if Scroller show show trace information on the console or not. This can be | |
1023 | * useful when debugging Scroller or if just curious as to what it is doing, but should | |
1024 | * be turned off for production. | |
1025 | * @type bool | |
1026 | * @default false | |
1027 | * @static | |
1028 | * @example | |
1029 | * var oTable = $('#example').dataTable( { | |
1030 | * "sScrollY": "200px", | |
1031 | * "sDom": "frtiS", | |
1032 | * "bDeferRender": true, | |
1033 | * "oScroller": { | |
1034 | * "trace": true | |
1035 | * } | |
1036 | * } ); | |
1037 | */ | |
1038 | "trace": false, | |
1039 | ||
1040 | /** | |
1041 | * Scroller will attempt to automatically calculate the height of rows for it's internal | |
1042 | * calculations. However the height that is used can be overridden using this parameter. | |
1043 | * @type int|string | |
1044 | * @default auto | |
1045 | * @static | |
1046 | * @example | |
1047 | * var oTable = $('#example').dataTable( { | |
1048 | * "sScrollY": "200px", | |
1049 | * "sDom": "frtiS", | |
1050 | * "bDeferRender": true, | |
1051 | * "oScroller": { | |
1052 | * "rowHeight": 30 | |
1053 | * } | |
1054 | * } ); | |
1055 | */ | |
1056 | "rowHeight": "auto", | |
1057 | ||
1058 | /** | |
1059 | * When using server-side processing, Scroller will wait a small amount of time to allow | |
1060 | * the scrolling to finish before requesting more data from the server. This prevents | |
1061 | * you from DoSing your own server! The wait time can be configured by this parameter. | |
1062 | * @type int | |
1063 | * @default 200 | |
1064 | * @static | |
1065 | * @example | |
1066 | * var oTable = $('#example').dataTable( { | |
1067 | * "sScrollY": "200px", | |
1068 | * "sDom": "frtiS", | |
1069 | * "bDeferRender": true, | |
1070 | * "oScroller": { | |
1071 | * "serverWait": 100 | |
1072 | * } | |
1073 | * } ); | |
1074 | */ | |
1075 | "serverWait": 200, | |
1076 | ||
1077 | /** | |
1078 | * The display buffer is what Scroller uses to calculate how many rows it should pre-fetch | |
1079 | * for scrolling. Scroller automatically adjusts DataTables' display length to pre-fetch | |
1080 | * rows that will be shown in "near scrolling" (i.e. just beyond the current display area). | |
1081 | * The value is based upon the number of rows that can be displayed in the viewport (i.e. | |
1082 | * a value of 1), and will apply the display range to records before before and after the | |
1083 | * current viewport - i.e. a factor of 3 will allow Scroller to pre-fetch 1 viewport's worth | |
1084 | * of rows before the current viewport, the current viewport's rows and 1 viewport's worth | |
1085 | * of rows after the current viewport. Adjusting this value can be useful for ensuring | |
1086 | * smooth scrolling based on your data set. | |
1087 | * @type int | |
1088 | * @default 7 | |
1089 | * @static | |
1090 | * @example | |
1091 | * var oTable = $('#example').dataTable( { | |
1092 | * "sScrollY": "200px", | |
1093 | * "sDom": "frtiS", | |
1094 | * "bDeferRender": true, | |
1095 | * "oScroller": { | |
1096 | * "displayBuffer": 10 | |
1097 | * } | |
1098 | * } ); | |
1099 | */ | |
1100 | "displayBuffer": 9, | |
1101 | ||
1102 | /** | |
1103 | * Scroller uses the boundary scaling factor to decide when to redraw the table - which it | |
1104 | * typically does before you reach the end of the currently loaded data set (in order to | |
1105 | * allow the data to look continuous to a user scrolling through the data). If given as 0 | |
1106 | * then the table will be redrawn whenever the viewport is scrolled, while 1 would not | |
1107 | * redraw the table until the currently loaded data has all been shown. You will want | |
1108 | * something in the middle - the default factor of 0.5 is usually suitable. | |
1109 | * @type float | |
1110 | * @default 0.5 | |
1111 | * @static | |
1112 | * @example | |
1113 | * var oTable = $('#example').dataTable( { | |
1114 | * "sScrollY": "200px", | |
1115 | * "sDom": "frtiS", | |
1116 | * "bDeferRender": true, | |
1117 | * "oScroller": { | |
1118 | * "boundaryScale": 0.75 | |
1119 | * } | |
1120 | * } ); | |
1121 | */ | |
1122 | "boundaryScale": 0.5, | |
1123 | ||
1124 | /** | |
1125 | * Show (or not) the loading element in the background of the table. Note that you should | |
1126 | * include the dataTables.scroller.css file for this to be displayed correctly. | |
1127 | * @type boolean | |
1128 | * @default false | |
1129 | * @static | |
1130 | * @example | |
1131 | * var oTable = $('#example').dataTable( { | |
1132 | * "sScrollY": "200px", | |
1133 | * "sDom": "frtiS", | |
1134 | * "bDeferRender": true, | |
1135 | * "oScroller": { | |
1136 | * "loadingIndicator": true | |
1137 | * } | |
1138 | * } ); | |
1139 | */ | |
1140 | "loadingIndicator": false | |
1141 | }; | |
1142 | ||
1143 | Scroller.oDefaults = Scroller.defaults; | |
1144 | ||
1145 | ||
1146 | ||
1147 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
1148 | * Constants | |
1149 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
1150 | ||
1151 | /** | |
1152 | * Scroller version | |
1153 | * @type String | |
1154 | * @default See code | |
1155 | * @name Scroller.version | |
1156 | * @static | |
1157 | */ | |
1158 | Scroller.version = "1.2.2"; | |
1159 | ||
1160 | ||
1161 | ||
1162 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
1163 | * Initialisation | |
1164 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
1165 | ||
1166 | /* | |
1167 | * Register a new feature with DataTables | |
1168 | */ | |
1169 | if ( typeof $.fn.dataTable == "function" && | |
1170 | typeof $.fn.dataTableExt.fnVersionCheck == "function" && | |
1171 | $.fn.dataTableExt.fnVersionCheck('1.9.0') ) | |
1172 | { | |
1173 | $.fn.dataTableExt.aoFeatures.push( { | |
1174 | "fnInit": function( oDTSettings ) { | |
1175 | var init = oDTSettings.oInit; | |
1176 | var opts = init.scroller || init.oScroller || {}; | |
1177 | var oScroller = new Scroller( oDTSettings, opts ); | |
1178 | return oScroller.dom.wrapper; | |
1179 | }, | |
1180 | "cFeature": "S", | |
1181 | "sFeature": "Scroller" | |
1182 | } ); | |
1183 | } | |
1184 | else | |
1185 | { | |
1186 | alert( "Warning: Scroller requires DataTables 1.9.0 or greater - www.datatables.net/download"); | |
1187 | } | |
1188 | ||
1189 | ||
1190 | // Attach Scroller to DataTables so it can be accessed as an 'extra' | |
1191 | $.fn.dataTable.Scroller = Scroller; | |
1192 | $.fn.DataTable.Scroller = Scroller; | |
1193 | ||
1194 | ||
1195 | // DataTables 1.10 API method aliases | |
1196 | if ( $.fn.dataTable.Api ) { | |
1197 | var Api = $.fn.dataTable.Api; | |
1198 | ||
1199 | Api.register( 'scroller()', function () { | |
1200 | return this; | |
1201 | } ); | |
1202 | ||
1203 | Api.register( 'scroller().rowToPixels()', function ( rowIdx, intParse, virtual ) { | |
1204 | var ctx = this.context; | |
1205 | ||
1206 | if ( ctx.length && ctx[0].oScroller ) { | |
1207 | return ctx[0].oScroller.fnRowToPixels( rowIdx, intParse, virtual ); | |
1208 | } | |
1209 | // undefined | |
1210 | } ); | |
1211 | ||
1212 | Api.register( 'scroller().pixelsToRow()', function ( pixels, intParse, virtual ) { | |
1213 | var ctx = this.context; | |
1214 | ||
1215 | if ( ctx.length && ctx[0].oScroller ) { | |
1216 | return ctx[0].oScroller.fnPixelsToRow( pixels, intParse, virtual ); | |
1217 | } | |
1218 | // undefined | |
1219 | } ); | |
1220 | ||
1221 | Api.register( 'scroller().scrollToRow()', function ( row, ani ) { | |
1222 | this.iterator( 'table', function ( ctx ) { | |
1223 | if ( ctx.oScroller ) { | |
1224 | ctx.oScroller.fnScrollToRow( row, ani ); | |
1225 | } | |
1226 | } ); | |
1227 | ||
1228 | return this; | |
1229 | } ); | |
1230 | ||
1231 | Api.register( 'scroller().measure()', function ( redraw ) { | |
1232 | this.iterator( 'table', function ( ctx ) { | |
1233 | if ( ctx.oScroller ) { | |
1234 | ctx.oScroller.fnMeasure( redraw ); | |
1235 | } | |
1236 | } ); | |
1237 | ||
1238 | return this; | |
1239 | } ); | |
1240 | } | |
1241 | ||
1242 | ||
1243 | return Scroller; | |
1244 | }; // /factory | |
1245 | ||
1246 | ||
1247 | // Define as an AMD module if possible | |
1248 | if ( typeof define === 'function' && define.amd ) { | |
1249 | define( ['jquery', 'datatables'], factory ); | |
1250 | } | |
1251 | else if ( typeof exports === 'object' ) { | |
1252 | // Node/CommonJS | |
1253 | factory( require('jquery'), require('datatables') ); | |
1254 | } | |
1255 | else if ( jQuery && !jQuery.fn.dataTable.Scroller ) { | |
1256 | // Otherwise simply initialise as normal, stopping multiple evaluation | |
1257 | factory( jQuery, jQuery.fn.dataTable ); | |
1258 | } | |
1259 | ||
1260 | ||
1261 | })(window, document); | |
1262 |