Subversion-Projekte lars-tiefland.webanos.zeldi.de

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
4 lars 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