Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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