Subversion Repositories DevTools

Rev

Details | Last modification | View Log | RSS feed

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