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