Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / pybind / mgr / dashboard / static / AdminLTE-2.3.7 / plugins / datatables / extensions / Scroller / js / dataTables.scroller.js
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>&nbsp;</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