jquery-datatables-rails 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,904 @@
1
+ /**
2
+ * @summary Scroller
3
+ * @description Virtual rendering for DataTables
4
+ * @file Scroller.js
5
+ * @version 1.1.0
6
+ * @author Allan Jardine (www.sprymedia.co.uk)
7
+ * @license GPL v2 or BSD 3 point style
8
+ * @contact www.sprymedia.co.uk/contact
9
+ *
10
+ * @copyright Copyright 2011-2012 Allan Jardine, all rights reserved.
11
+ *
12
+ * This source file is free software, under either the GPL v2 license or a
13
+ * BSD style license, available at:
14
+ * http://datatables.net/license_gpl2
15
+ * http://datatables.net/license_bsd
16
+ */
17
+
18
+ (/** @lends <global> */function($, window, document) {
19
+
20
+
21
+ /**
22
+ * Scroller is a virtual rendering plug-in for DataTables which allows large
23
+ * datasets to be drawn on screen every quickly. What the virtual rendering means
24
+ * is that only the visible portion of the table (and a bit to either side to make
25
+ * the scrolling smooth) is drawn, while the scrolling container gives the
26
+ * visual impression that the whole table is visible. This is done by making use
27
+ * of the pagination abilities of DataTables and moving the table around in the
28
+ * scrolling container DataTables adds to the page. The scrolling container is
29
+ * forced to the height it would be for the full table display using an extra
30
+ * element.
31
+ *
32
+ * Note that rows in the table MUST all be the same height. Information in a cell
33
+ * which expands on to multiple lines will cause some odd behaviour in the scrolling.
34
+ *
35
+ * Scroller is initialised by simply including the letter 'S' in the sDom for the
36
+ * table you want to have this feature enabled on. Note that the 'S' must come
37
+ * AFTER the 't' parameter in sDom.
38
+ *
39
+ * Key features include:
40
+ * <ul class="limit_length">
41
+ * <li>Speed! The aim of Scroller for DataTables is to make rendering large data sets fast</li>
42
+ * <li>Full compatibility with deferred rendering in DataTables 1.9 for maximum speed</li>
43
+ * <li>Correct visual scrolling implementation, similar to "infinite scrolling" in DataTable core</li>
44
+ * <li>Integration with state saving in DataTables (scrolling position is saved)</li>
45
+ * <li>Easy to use</li>
46
+ * </ul>
47
+ *
48
+ * @class
49
+ * @constructor
50
+ * @param {object} oDT DataTables settings object
51
+ * @param {object} [oOpts={}] Configuration object for FixedColumns. Options are defined by {@link Scroller.oDefaults}
52
+ *
53
+ * @requires jQuery 1.4+
54
+ * @requires DataTables 1.9.0+
55
+ *
56
+ * @example
57
+ * $(document).ready(function() {
58
+ * $('#example').dataTable( {
59
+ * "sScrollY": "200px",
60
+ * "sAjaxSource": "media/dataset/large.txt",
61
+ * "sDom": "frtiS",
62
+ * "bDeferRender": true
63
+ * } );
64
+ * } );
65
+ */
66
+ var Scroller = function ( oDTSettings, oOpts ) {
67
+ /* Sanity check - you just know it will happen */
68
+ if ( ! this instanceof Scroller )
69
+ {
70
+ alert( "Scroller warning: Scroller must be initialised with the 'new' keyword." );
71
+ return;
72
+ }
73
+
74
+ if ( typeof oOpts == 'undefined' )
75
+ {
76
+ oOpts = {};
77
+ }
78
+
79
+ /**
80
+ * Settings object which contains customisable information for the Scroller instance
81
+ * @namespace
82
+ * @extends Scroller.DEFAULTS
83
+ */
84
+ this.s = {
85
+ /**
86
+ * DataTables settings object
87
+ * @type object
88
+ * @default Passed in as first parameter to constructor
89
+ */
90
+ "dt": oDTSettings,
91
+
92
+ /**
93
+ * Pixel location of the top of the drawn table in the viewport
94
+ * @type int
95
+ * @default 0
96
+ */
97
+ "tableTop": 0,
98
+
99
+ /**
100
+ * Pixel location of the bottom of the drawn table in the viewport
101
+ * @type int
102
+ * @default 0
103
+ */
104
+ "tableBottom": 0,
105
+
106
+ /**
107
+ * Pixel location of the boundary for when the next data set should be loaded and drawn
108
+ * when scrolling up the way.
109
+ * @type int
110
+ * @default 0
111
+ * @private
112
+ */
113
+ "redrawTop": 0,
114
+
115
+ /**
116
+ * Pixel location of the boundary for when the next data set should be loaded and drawn
117
+ * when scrolling down the way. Note that this is actually caluated as the offset from
118
+ * the top.
119
+ * @type int
120
+ * @default 0
121
+ * @private
122
+ */
123
+ "redrawBottom": 0,
124
+
125
+ /**
126
+ * Height of rows in the table
127
+ * @type int
128
+ * @default 0
129
+ */
130
+ "rowHeight": null,
131
+
132
+ /**
133
+ * Auto row height or not indicator
134
+ * @type bool
135
+ * @default 0
136
+ */
137
+ "autoHeight": true,
138
+
139
+ /**
140
+ * Pixel height of the viewport
141
+ * @type int
142
+ * @default 0
143
+ */
144
+ "viewportHeight": 0,
145
+
146
+ /**
147
+ * Number of rows calculated as visible in the visible viewport
148
+ * @type int
149
+ * @default 0
150
+ */
151
+ "viewportRows": 0,
152
+
153
+ /**
154
+ * setTimeout reference for state saving, used when state saving is enabled in the DataTable
155
+ * and when the user scrolls the viewport in order to stop the cookie set taking too much
156
+ * CPU!
157
+ * @type int
158
+ * @default 0
159
+ */
160
+ "stateTO": null,
161
+
162
+ /**
163
+ * setTimeout reference for the redraw, used when server-side processing is enabled in the
164
+ * DataTables in order to prevent DoSing the server
165
+ * @type int
166
+ * @default null
167
+ */
168
+ "drawTO": null
169
+ };
170
+ this.s = $.extend( this.s, Scroller.oDefaults, oOpts );
171
+
172
+ /**
173
+ * DOM elements used by the class instance
174
+ * @namespace
175
+ *
176
+ */
177
+ this.dom = {
178
+ "force": document.createElement('div'),
179
+ "scroller": null,
180
+ "table": null
181
+ };
182
+
183
+ /* Attach the instance to the DataTables instance so it can be accessed */
184
+ this.s.dt.oScroller = this;
185
+
186
+ /* Let's do it */
187
+ this._fnConstruct();
188
+ };
189
+
190
+
191
+
192
+ Scroller.prototype = {
193
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
194
+ * Public methods
195
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
196
+
197
+ /**
198
+ * Calculate the pixel position from the top of the scrolling container for a given row
199
+ * @param {int} iRow Row number to calculate the position of
200
+ * @returns {int} Pixels
201
+ * @example
202
+ * $(document).ready(function() {
203
+ * $('#example').dataTable( {
204
+ * "sScrollY": "200px",
205
+ * "sAjaxSource": "media/dataset/large.txt",
206
+ * "sDom": "frtiS",
207
+ * "bDeferRender": true,
208
+ * "fnInitComplete": function (o) {
209
+ * // Find where row 25 is
210
+ * alert( o.oScroller.fnRowToPixels( 25 ) );
211
+ * }
212
+ * } );
213
+ * } );
214
+ */
215
+ "fnRowToPixels": function ( iRow )
216
+ {
217
+ return iRow * this.s.rowHeight;
218
+ },
219
+
220
+
221
+ /**
222
+ * Calculate the row number that will be found at the given pixel position (y-scroll)
223
+ * @param {int} iPixels Offset from top to caluclate the row number of
224
+ * @returns {int} Row index
225
+ * @example
226
+ * $(document).ready(function() {
227
+ * $('#example').dataTable( {
228
+ * "sScrollY": "200px",
229
+ * "sAjaxSource": "media/dataset/large.txt",
230
+ * "sDom": "frtiS",
231
+ * "bDeferRender": true,
232
+ * "fnInitComplete": function (o) {
233
+ * // Find what row number is at 500px
234
+ * alert( o.oScroller.fnPixelsToRow( 500 ) );
235
+ * }
236
+ * } );
237
+ * } );
238
+ */
239
+ "fnPixelsToRow": function ( iPixels )
240
+ {
241
+ return parseInt( iPixels / this.s.rowHeight, 10 );
242
+ },
243
+
244
+
245
+ /**
246
+ * Calculate the row number that will be found at the given pixel position (y-scroll)
247
+ * @param {int} iRow Row index to scroll to
248
+ * @param {bool} [bAnimate=true] Animate the transision or not
249
+ * @returns {void}
250
+ * @example
251
+ * $(document).ready(function() {
252
+ * $('#example').dataTable( {
253
+ * "sScrollY": "200px",
254
+ * "sAjaxSource": "media/dataset/large.txt",
255
+ * "sDom": "frtiS",
256
+ * "bDeferRender": true,
257
+ * "fnInitComplete": function (o) {
258
+ * // Immediately scroll to row 1000
259
+ * o.oScroller.fnScrollToRow( 1000 );
260
+ * }
261
+ * } );
262
+ *
263
+ * // Sometime later on use the following to scroll to row 500...
264
+ * var oSettings = $('#example').dataTable().fnSettings();
265
+ * oSettings.oScroller.fnScrollToRow( 500 );
266
+ * } );
267
+ */
268
+ "fnScrollToRow": function ( iRow, bAnimate )
269
+ {
270
+ var px = this.fnRowToPixels( iRow );
271
+ if ( typeof bAnimate == 'undefined' || bAnimate )
272
+ {
273
+ $(this.dom.scroller).animate( {
274
+ "scrollTop": px
275
+ } );
276
+ }
277
+ else
278
+ {
279
+ $(this.dom.scroller).scrollTop( px );
280
+ }
281
+ },
282
+
283
+
284
+ /**
285
+ * Calculate and store information about how many rows are to be displayed in the scrolling
286
+ * viewport, based on current dimensions in the browser's rendering. This can be particularly
287
+ * useful if the table is initially drawn in a hidden element - for example in a tab.
288
+ * @param {bool} [bRedraw=true] Redraw the table automatically after the recalculation, with
289
+ * the new dimentions forming the basis for the draw.
290
+ * @returns {void}
291
+ * @example
292
+ * $(document).ready(function() {
293
+ * // Make the example container hidden to throw off the browser's sizing
294
+ * document.getElementById('container').style.display = "none";
295
+ * var oTable = $('#example').dataTable( {
296
+ * "sScrollY": "200px",
297
+ * "sAjaxSource": "media/dataset/large.txt",
298
+ * "sDom": "frtiS",
299
+ * "bDeferRender": true,
300
+ * "fnInitComplete": function (o) {
301
+ * // Immediately scroll to row 1000
302
+ * o.oScroller.fnScrollToRow( 1000 );
303
+ * }
304
+ * } );
305
+ *
306
+ * setTimeout( function () {
307
+ * // Make the example container visible and recalculate the scroller sizes
308
+ * document.getElementById('container').style.display = "block";
309
+ * oTable.fnSettings().oScroller.fnMeasure();
310
+ * }, 3000 );
311
+ */
312
+ "fnMeasure": function ( bRedraw )
313
+ {
314
+ if ( this.s.autoHeight )
315
+ {
316
+ this._fnCalcRowHeight();
317
+ }
318
+
319
+ this.s.viewportHeight = $(this.dom.scroller).height();
320
+ this.s.viewportRows = parseInt( this.s.viewportHeight/this.s.rowHeight, 10 )+1;
321
+ this.s.dt._iDisplayLength = this.s.viewportRows * this.s.displayBuffer;
322
+
323
+ if ( this.s.trace )
324
+ {
325
+ console.log(
326
+ 'Row height: '+this.s.rowHeight +' '+
327
+ 'Viewport height: '+this.s.viewportHeight +' '+
328
+ 'Viewport rows: '+ this.s.viewportRows +' '+
329
+ 'Display rows: '+ this.s.dt._iDisplayLength
330
+ );
331
+ }
332
+
333
+ if ( typeof bRedraw == 'undefined' || bRedraw )
334
+ {
335
+ this.s.dt.oInstance.fnDraw();
336
+ }
337
+ },
338
+
339
+
340
+
341
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
342
+ * Private methods (they are of course public in JS, but recommended as private)
343
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
344
+
345
+ /**
346
+ * Initialisation for Scroller
347
+ * @returns {void}
348
+ * @private
349
+ */
350
+ "_fnConstruct": function ()
351
+ {
352
+ var that = this;
353
+
354
+ /* Insert a div element that we can use to force the DT scrolling container to
355
+ * the height that would be required if the whole table was being displayed
356
+ */
357
+ this.dom.force.style.position = "absolute";
358
+ this.dom.force.style.top = "0px";
359
+ this.dom.force.style.left = "0px";
360
+ this.dom.force.style.width = "1px";
361
+
362
+ this.dom.scroller = $('div.'+this.s.dt.oClasses.sScrollBody, this.s.dt.nTableWrapper)[0];
363
+ this.dom.scroller.appendChild( this.dom.force );
364
+ this.dom.scroller.style.position = "relative";
365
+
366
+ this.dom.table = $('>table', this.dom.scroller)[0];
367
+ this.dom.table.style.position = "absolute";
368
+ this.dom.table.style.top = "0px";
369
+ this.dom.table.style.left = "0px";
370
+
371
+ // Add class to 'announce' that we are a Scroller table
372
+ $(this.s.dt.nTableWrapper).addClass('DTS');
373
+
374
+ // Add a 'loading' indicator
375
+ if ( this.s.loadingIndicator )
376
+ {
377
+ $(this.dom.scroller.parentNode)
378
+ .css('position', 'relative')
379
+ .append('<div class="DTS_Loading">'+this.s.dt.oLanguage.sLoadingRecords+'</div>');
380
+ }
381
+
382
+ /* Initial size calculations */
383
+ if ( this.s.rowHeight && this.s.rowHeight != 'auto' )
384
+ {
385
+ this.s.autoHeight = false;
386
+ }
387
+ this.fnMeasure( false );
388
+
389
+ /* Scrolling callback to see if a page change is needed */
390
+ $(this.dom.scroller).scroll( function () {
391
+ that._fnScroll.call( that );
392
+ } );
393
+
394
+ /* In iOS we catch the touchstart event incase the user tries to scroll
395
+ * while the display is already scrolling
396
+ */
397
+ $(this.dom.scroller).bind('touchstart', function () {
398
+ that._fnScroll.call( that );
399
+ } );
400
+
401
+ /* Update the scroller when the DataTable is redrawn */
402
+ this.s.dt.aoDrawCallback.push( {
403
+ "fn": function () {
404
+ if ( that.s.dt.bInitialised ) {
405
+ that._fnDrawCallback.call( that );
406
+ }
407
+ },
408
+ "sName": "Scroller"
409
+ } );
410
+
411
+ /* Add a state saving parameter to the DT state saving so we can restore the exact
412
+ * position of the scrolling
413
+ */
414
+ this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) {
415
+ oData.iScroller = that.dom.scroller.scrollTop;
416
+ }, "Scroller_State" );
417
+ },
418
+
419
+
420
+ /**
421
+ * Scrolling function - fired whenever the scrolling position is changed. This method needs
422
+ * to use the stored values to see if the table should be redrawn as we are moving towards
423
+ * the end of the information that is currently drawn or not. If needed, then it will redraw
424
+ * the table based on the new position.
425
+ * @returns {void}
426
+ * @private
427
+ */
428
+ "_fnScroll": function ()
429
+ {
430
+ var
431
+ that = this,
432
+ iScrollTop = this.dom.scroller.scrollTop,
433
+ iTopRow;
434
+
435
+ /* If the table has been sorted or filtered, then we use the redraw that
436
+ * DataTables as done, rather than performing our own
437
+ */
438
+ if ( this.s.dt.bFiltered || this.s.dt.bSorted )
439
+ {
440
+ return;
441
+ }
442
+
443
+ if ( this.s.trace )
444
+ {
445
+ console.log(
446
+ 'Scroll: '+iScrollTop+'px - boundaries: '+this.s.redrawTop+' / '+this.s.redrawBottom+'. '+
447
+ ' Showing rows '+this.fnPixelsToRow(iScrollTop)+
448
+ ' to '+this.fnPixelsToRow(iScrollTop+$(this.dom.scroller).height())+
449
+ ' in the viewport, with rows '+this.s.dt._iDisplayStart+
450
+ ' to '+(this.s.dt._iDisplayEnd)+' rendered by the DataTable'
451
+ );
452
+ }
453
+
454
+ /* Update the table's information display for what is now in the viewport */
455
+ this._fnInfo();
456
+
457
+ /* We dont' want to state save on every scroll event - that's heavy handed, so
458
+ * use a timeout to update the state saving only when the scrolling has finished
459
+ */
460
+ clearTimeout( this.s.stateTO );
461
+ this.s.stateTO = setTimeout( function () {
462
+ that.s.dt.oApi._fnSaveState( that.s.dt );
463
+ }, 250 );
464
+
465
+ /* Check if the scroll point is outside the trigger boundary which would required
466
+ * a DataTables redraw
467
+ */
468
+ if ( iScrollTop < this.s.redrawTop || iScrollTop > this.s.redrawBottom )
469
+ {
470
+ var preRows = ((this.s.displayBuffer-1)/2) * this.s.viewportRows;
471
+ iTopRow = parseInt( iScrollTop / this.s.rowHeight, 10 ) - preRows;
472
+ if ( iTopRow < 0 )
473
+ {
474
+ /* At the start of the table */
475
+ iTopRow = 0;
476
+ }
477
+ else if ( iTopRow + this.s.dt._iDisplayLength > this.s.dt.fnRecordsDisplay() )
478
+ {
479
+ /* At the end of the table */
480
+ iTopRow = this.s.dt.fnRecordsDisplay() - this.s.dt._iDisplayLength;
481
+ if ( iTopRow < 0 )
482
+ {
483
+ iTopRow = 0;
484
+ }
485
+ }
486
+ else if ( iTopRow % 2 !== 0 )
487
+ {
488
+ /* For the row-striping classes (odd/even) we want only to start on evens
489
+ * otherwise the stripes will change between draws and look rubbish
490
+ */
491
+ iTopRow++;
492
+ }
493
+
494
+ if ( iTopRow != this.s.dt._iDisplayStart )
495
+ {
496
+ /* Cache the new table position for quick lookups */
497
+ this.s.tableTop = $(this.s.dt.nTable).offset().top;
498
+ this.s.tableBottom = $(this.s.dt.nTable).height() + this.s.tableTop;
499
+
500
+ /* Do the DataTables redraw based on the calculated start point - note that when
501
+ * using server-side processing we introduce a small delay to not DoS the server...
502
+ */
503
+ if ( this.s.dt.oFeatures.bServerSide ) {
504
+ clearTimeout( this.s.drawTO );
505
+ this.s.drawTO = setTimeout( function () {
506
+ that.s.dt._iDisplayStart = iTopRow;
507
+ that.s.dt.oApi._fnCalculateEnd( that.s.dt );
508
+ that.s.dt.oApi._fnDraw( that.s.dt );
509
+ }, this.s.serverWait );
510
+ }
511
+ else
512
+ {
513
+ this.s.dt._iDisplayStart = iTopRow;
514
+ this.s.dt.oApi._fnCalculateEnd( this.s.dt );
515
+ this.s.dt.oApi._fnDraw( this.s.dt );
516
+ }
517
+
518
+ if ( this.s.trace )
519
+ {
520
+ console.log( 'Scroll forcing redraw - top DT render row: '+ iTopRow );
521
+ }
522
+ }
523
+ }
524
+ },
525
+
526
+
527
+ /**
528
+ * Draw callback function which is fired when the DataTable is redrawn. The main function of
529
+ * this method is to position the drawn table correctly the scrolling container for the rows
530
+ * that is displays as a result of the scrolling position.
531
+ * @returns {void}
532
+ * @private
533
+ */
534
+ "_fnDrawCallback": function ()
535
+ {
536
+ var
537
+ that = this,
538
+ iScrollTop = this.dom.scroller.scrollTop,
539
+ iScrollBottom = iScrollTop + this.s.viewportHeight;
540
+
541
+ /* Set the height of the scrolling forcer to be suitable for the number of rows
542
+ * in this draw
543
+ */
544
+ this.dom.force.style.height = (this.s.rowHeight * this.s.dt.fnRecordsDisplay())+"px";
545
+
546
+ /* Calculate the position that the top of the table should be at */
547
+ var iTableTop = (this.s.rowHeight*this.s.dt._iDisplayStart);
548
+ if ( this.s.dt._iDisplayStart === 0 )
549
+ {
550
+ iTableTop = 0;
551
+ }
552
+ else if ( this.s.dt._iDisplayStart === this.s.dt.fnRecordsDisplay() - this.s.dt._iDisplayLength )
553
+ {
554
+ iTableTop = this.s.rowHeight * this.s.dt._iDisplayStart;
555
+ }
556
+
557
+ this.dom.table.style.top = iTableTop+"px";
558
+
559
+ /* Cache some information for the scroller */
560
+ this.s.tableTop = iTableTop;
561
+ this.s.tableBottom = $(this.s.dt.nTable).height() + this.s.tableTop;
562
+
563
+ this.s.redrawTop = iScrollTop - ( (iScrollTop - this.s.tableTop) * this.s.boundaryScale );
564
+ this.s.redrawBottom = iScrollTop + ( (this.s.tableBottom - iScrollBottom) * this.s.boundaryScale );
565
+
566
+ if ( this.s.trace )
567
+ {
568
+ console.log(
569
+ "Table redraw. Table top: "+iTableTop+"px "+
570
+ "Table bottom: "+this.s.tableBottom+" "+
571
+ "Scroll boundary top: "+this.s.redrawTop+" "+
572
+ "Scroll boundary bottom: "+this.s.redrawBottom+" "+
573
+ "Rows drawn: "+this.s.dt._iDisplayLength);
574
+ }
575
+
576
+ /* Because of the order of the DT callbacks, the info update will
577
+ * take precidence over the one we want here. So a 'thread' break is
578
+ * needed
579
+ */
580
+ setTimeout( function () {
581
+ that._fnInfo.call( that );
582
+ }, 0 );
583
+
584
+ /* Restore the scrolling position that was saved by DataTable's state saving
585
+ * Note that this is done on the second draw when data is Ajax sourced, and the
586
+ * first draw when DOM soured
587
+ */
588
+ if ( this.s.dt.oFeatures.bStateSave && this.s.dt.oLoadedState !== null &&
589
+ typeof this.s.dt.oLoadedState.iScroller != 'undefined' )
590
+ {
591
+ if ( (this.s.dt.sAjaxSource !== null && this.s.dt.iDraw == 2) ||
592
+ (this.s.dt.sAjaxSource === null && this.s.dt.iDraw == 1) )
593
+ {
594
+ setTimeout( function () {
595
+ $(that.dom.scroller).scrollTop( that.s.dt.oLoadedState.iScroller );
596
+ that.s.redrawTop = that.s.dt.oLoadedState.iScroller - (that.s.viewportHeight/2);
597
+ }, 0 );
598
+ }
599
+ }
600
+ },
601
+
602
+
603
+ /**
604
+ * Automatic calculation of table row height. This is just a little tricky here as using
605
+ * initialisation DataTables has tale the table out of the document, so we need to create
606
+ * a new table and insert it into the document, calculate the row height and then whip the
607
+ * table out.
608
+ * @returns {void}
609
+ * @private
610
+ */
611
+ "_fnCalcRowHeight": function ()
612
+ {
613
+ var nTable = this.s.dt.nTable.cloneNode( false );
614
+ var nContainer = $(
615
+ '<div class="'+this.s.dt.oClasses.sWrapper+' DTS">'+
616
+ '<div class="'+this.s.dt.oClasses.sScrollWrapper+'">'+
617
+ '<div class="'+this.s.dt.oClasses.sScrollBody+'"></div>'+
618
+ '</div>'+
619
+ '</div>'
620
+ )[0];
621
+
622
+ $(nTable).append(
623
+ '<tbody>'+
624
+ '<tr>'+
625
+ '<td>&nbsp;</td>'+
626
+ '</tr>'+
627
+ '</tbody>'
628
+ );
629
+
630
+ $('div.'+this.s.dt.oClasses.sScrollBody, nContainer).append( nTable );
631
+
632
+ document.body.appendChild( nContainer );
633
+ this.s.rowHeight = $('tbody tr', nTable).outerHeight();
634
+ document.body.removeChild( nContainer );
635
+ },
636
+
637
+
638
+ /**
639
+ * Update any information elements that are controlled by the DataTable based on the scrolling
640
+ * viewport and what rows are visible in it. This function basically acts in the same way as
641
+ * _fnUpdateInfo in DataTables, and effectively replaces that function.
642
+ * @returns {void}
643
+ * @private
644
+ */
645
+ "_fnInfo": function ()
646
+ {
647
+ if ( !this.s.dt.oFeatures.bInfo )
648
+ {
649
+ return;
650
+ }
651
+
652
+ var
653
+ dt = this.s.dt,
654
+ iScrollTop = this.dom.scroller.scrollTop,
655
+ iStart = this.fnPixelsToRow(iScrollTop)+1,
656
+ iMax = dt.fnRecordsTotal(),
657
+ iTotal = dt.fnRecordsDisplay(),
658
+ iPossibleEnd = this.fnPixelsToRow(iScrollTop+$(this.dom.scroller).height()),
659
+ iEnd = iTotal < iPossibleEnd ? iTotal : iPossibleEnd,
660
+ sStart = dt.fnFormatNumber( iStart ),
661
+ sEnd = dt.fnFormatNumber( iEnd ),
662
+ sMax = dt.fnFormatNumber( iMax ),
663
+ sTotal = dt.fnFormatNumber( iTotal ),
664
+ sOut;
665
+
666
+ if ( dt.fnRecordsDisplay() === 0 &&
667
+ dt.fnRecordsDisplay() == dt.fnRecordsTotal() )
668
+ {
669
+ /* Empty record set */
670
+ sOut = dt.oLanguage.sInfoEmpty+ dt.oLanguage.sInfoPostFix;
671
+ }
672
+ else if ( dt.fnRecordsDisplay() === 0 )
673
+ {
674
+ /* Rmpty record set after filtering */
675
+ sOut = dt.oLanguage.sInfoEmpty +' '+
676
+ dt.oLanguage.sInfoFiltered.replace('_MAX_', sMax)+
677
+ dt.oLanguage.sInfoPostFix;
678
+ }
679
+ else if ( dt.fnRecordsDisplay() == dt.fnRecordsTotal() )
680
+ {
681
+ /* Normal record set */
682
+ sOut = dt.oLanguage.sInfo.
683
+ replace('_START_', sStart).
684
+ replace('_END_', sEnd).
685
+ replace('_TOTAL_', sTotal)+
686
+ dt.oLanguage.sInfoPostFix;
687
+ }
688
+ else
689
+ {
690
+ /* Record set after filtering */
691
+ sOut = dt.oLanguage.sInfo.
692
+ replace('_START_', sStart).
693
+ replace('_END_', sEnd).
694
+ replace('_TOTAL_', sTotal) +' '+
695
+ dt.oLanguage.sInfoFiltered.replace('_MAX_',
696
+ dt.fnFormatNumber(dt.fnRecordsTotal()))+
697
+ dt.oLanguage.sInfoPostFix;
698
+ }
699
+
700
+ var n = dt.aanFeatures.i;
701
+ if ( typeof n != 'undefined' )
702
+ {
703
+ for ( var i=0, iLen=n.length ; i<iLen ; i++ )
704
+ {
705
+ $(n[i]).html( sOut );
706
+ }
707
+ }
708
+ }
709
+ };
710
+
711
+
712
+
713
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
714
+ * Statics
715
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
716
+
717
+
718
+ /**
719
+ * Scroller default settings for initialisation
720
+ * @namespace
721
+ * @static
722
+ */
723
+ Scroller.oDefaults = {
724
+ /**
725
+ * Indicate if Scroller show show trace information on the console or not. This can be
726
+ * useful when debugging Scroller or if just curious as to what it is doing, but should
727
+ * be turned off for production.
728
+ * @type bool
729
+ * @default false
730
+ * @static
731
+ * @example
732
+ * var oTable = $('#example').dataTable( {
733
+ * "sScrollY": "200px",
734
+ * "sDom": "frtiS",
735
+ * "bDeferRender": true,
736
+ * "oScroller": {
737
+ * "trace": true
738
+ * }
739
+ * } );
740
+ */
741
+ "trace": false,
742
+
743
+ /**
744
+ * Scroller will attempt to automatically calculate the height of rows for it's internal
745
+ * calculations. However the height that is used can be overridden using this parameter.
746
+ * @type int|string
747
+ * @default auto
748
+ * @static
749
+ * @example
750
+ * var oTable = $('#example').dataTable( {
751
+ * "sScrollY": "200px",
752
+ * "sDom": "frtiS",
753
+ * "bDeferRender": true,
754
+ * "oScroller": {
755
+ * "rowHeight": 30
756
+ * }
757
+ * } );
758
+ */
759
+ "rowHeight": "auto",
760
+
761
+ /**
762
+ * When using server-side processing, Scroller will wait a small amount of time to allow
763
+ * the scrolling to finish before requesting more data from the server. This prevents
764
+ * you from DoSing your own server! The wait time can be configured by this parameter.
765
+ * @type int
766
+ * @default 200
767
+ * @static
768
+ * @example
769
+ * var oTable = $('#example').dataTable( {
770
+ * "sScrollY": "200px",
771
+ * "sDom": "frtiS",
772
+ * "bDeferRender": true,
773
+ * "oScroller": {
774
+ * "serverWait": 100
775
+ * }
776
+ * } );
777
+ */
778
+ "serverWait": 200,
779
+
780
+ /**
781
+ * The display buffer is what Scroller uses to calculate how many rows it should pre-fetch
782
+ * for scrolling. Scroller automatically adjusts DataTables' display length to pre-fetch
783
+ * rows that will be shown in "near scrolling" (i.e. just beyond the current display area).
784
+ * The value is based upon the number of rows that can be displayed in the viewport (i.e.
785
+ * a value of 1), and will apply the display range to records before before and after the
786
+ * current viewport - i.e. a factor of 3 will allow Scroller to pre-fetch 1 viewport's worth
787
+ * of rows before the current viewport, the current viewport's rows and 1 viewport's worth
788
+ * of rows after the current viewport. Adjusting this value can be useful for ensuring
789
+ * smooth scrolling based on your data set.
790
+ * @type int
791
+ * @default 7
792
+ * @static
793
+ * @example
794
+ * var oTable = $('#example').dataTable( {
795
+ * "sScrollY": "200px",
796
+ * "sDom": "frtiS",
797
+ * "bDeferRender": true,
798
+ * "oScroller": {
799
+ * "displayBuffer": 10
800
+ * }
801
+ * } );
802
+ */
803
+ "displayBuffer": 9,
804
+
805
+ /**
806
+ * Scroller uses the boundary scaling factor to decide when to redraw the table - which it
807
+ * typically does before you reach the end of the currently loaded data set (in order to
808
+ * allow the data to look continuous to a user scrolling through the data). If given as 0
809
+ * then the table will be redrawn whenever the viewport is scrolled, while 1 would not
810
+ * redraw the table until the currently loaded data has all been shown. You will want
811
+ * something in the middle - the default factor of 0.5 is usually suitable.
812
+ * @type float
813
+ * @default 0.5
814
+ * @static
815
+ * @example
816
+ * var oTable = $('#example').dataTable( {
817
+ * "sScrollY": "200px",
818
+ * "sDom": "frtiS",
819
+ * "bDeferRender": true,
820
+ * "oScroller": {
821
+ * "boundaryScale": 0.75
822
+ * }
823
+ * } );
824
+ */
825
+ "boundaryScale": 0.5,
826
+
827
+ /**
828
+ * Show (or not) the loading element in the background of the table. Note that you should
829
+ * include the dataTables.scroller.css file for this to be displayed correctly.
830
+ * @type boolean
831
+ * @default false
832
+ * @static
833
+ * @example
834
+ * var oTable = $('#example').dataTable( {
835
+ * "sScrollY": "200px",
836
+ * "sDom": "frtiS",
837
+ * "bDeferRender": true,
838
+ * "oScroller": {
839
+ * "loadingIndicator": true
840
+ * }
841
+ * } );
842
+ */
843
+ "loadingIndicator": false
844
+ };
845
+
846
+
847
+
848
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
849
+ * Constants
850
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
851
+
852
+
853
+ /**
854
+ * Name of this class
855
+ * @type String
856
+ * @default Scroller
857
+ * @static
858
+ */
859
+ Scroller.prototype.CLASS = "Scroller";
860
+
861
+
862
+ /**
863
+ * Scroller version
864
+ * @type String
865
+ * @default See code
866
+ * @static
867
+ */
868
+ Scroller.VERSION = "1.1.0";
869
+ Scroller.prototype.VERSION = Scroller.VERSION;
870
+
871
+
872
+
873
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
874
+ * Initialisation
875
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
876
+
877
+ /*
878
+ * Register a new feature with DataTables
879
+ */
880
+ if ( typeof $.fn.dataTable == "function" &&
881
+ typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
882
+ $.fn.dataTableExt.fnVersionCheck('1.9.0') )
883
+ {
884
+ $.fn.dataTableExt.aoFeatures.push( {
885
+ "fnInit": function( oDTSettings ) {
886
+ var init = (typeof oDTSettings.oInit.oScroller == 'undefined') ?
887
+ {} : oDTSettings.oInit.oScroller;
888
+ var oScroller = new Scroller( oDTSettings, init );
889
+ return oScroller.dom.wrapper;
890
+ },
891
+ "cFeature": "S",
892
+ "sFeature": "Scroller"
893
+ } );
894
+ }
895
+ else
896
+ {
897
+ alert( "Warning: Scroller requires DataTables 1.9.0 or greater - www.datatables.net/download");
898
+ }
899
+
900
+
901
+ // Attach Scroller to DataTables so it can be accessed as an 'extra'
902
+ $.fn.dataTable.Scroller = Scroller;
903
+
904
+ })(jQuery, window, document);