app/assets/javascripts/dataTables/extras/dataTables.scroller.js in jquery-datatables-rails-2.2.1 vs app/assets/javascripts/dataTables/extras/dataTables.scroller.js in jquery-datatables-rails-2.2.2

- old
+ new

@@ -1,13 +1,13 @@ -/*! Scroller 1.2.1 - * 2011-2014 SpryMedia Ltd - datatables.net/license +/*! Scroller 1.2.2 + * ©2011-2014 SpryMedia Ltd - datatables.net/license */ /** * @summary Scroller * @description Virtual rendering for DataTables - * @version 1.2.1 + * @version 1.2.2 * @file dataTables.scroller.js * @author SpryMedia Ltd (www.sprymedia.co.uk) * @contact www.sprymedia.co.uk/contact * @copyright Copyright 2011-2014 SpryMedia Ltd. * @@ -124,11 +124,11 @@ */ "redrawTop": 0, /** * Pixel location of the boundary for when the next data set should be loaded and drawn - * when scrolling down the way. Note that this is actually caluated as the offset from + * when scrolling down the way. Note that this is actually calculated as the offset from * the top. * @type int * @default 0 * @private */ @@ -185,11 +185,12 @@ */ viewport: null }, topRowFloat: 0, - scrollDrawDiff: null + scrollDrawDiff: null, + loaderVisible: false }; // @todo The defaults should extend a `c` property and the internal settings // only held in the `s` property. At the moment they are mixed this.s = $.extend( this.s, Scroller.oDefaults, oOpts ); @@ -202,13 +203,14 @@ * @private * @namespace * */ this.dom = { - "force": document.createElement('div'), + "force": document.createElement('div'), "scroller": null, - "table": null + "table": null, + "loader": null }; /* Attach the instance to the DataTables instance so it can be accessed */ this.s.dt.oScroller = this; @@ -300,11 +302,11 @@ /** * Calculate the row number that will be found at the given pixel position (y-scroll) * @param {int} iRow Row index to scroll to - * @param {bool} [bAnimate=true] Animate the transision or not + * @param {bool} [bAnimate=true] Animate the transition or not * @returns {void} * @example * $(document).ready(function() { * $('#example').dataTable( { * "sScrollY": "200px", @@ -352,11 +354,11 @@ }, function () { // This needs to happen after the animation has completed and // the final scroll event fired setTimeout( function () { that.s.ani = false; - }, 0 ); + }, 25 ); } ); } else { $(this.dom.scroller).scrollTop( px ); @@ -368,11 +370,11 @@ * Calculate and store information about how many rows are to be displayed * in the scrolling viewport, based on current dimensions in the browser's * rendering. This can be particularly useful if the table is initially * drawn in a hidden element - for example in a tab. * @param {bool} [bRedraw=true] Redraw the table automatically after the recalculation, with - * the new dimentions forming the basis for the draw. + * the new dimensions forming the basis for the draw. * @returns {void} * @example * $(document).ready(function() { * // Make the example container hidden to throw off the browser's sizing * document.getElementById('container').style.display = "none"; @@ -404,11 +406,11 @@ heights.viewport = $(this.dom.scroller).height(); this.s.viewportRows = parseInt( heights.viewport / heights.row, 10 )+1; this.s.dt._iDisplayLength = this.s.viewportRows * this.s.displayBuffer; - if ( typeof bRedraw == 'undefined' || bRedraw ) + if ( bRedraw === undefined || bRedraw ) { this.s.dt.oInstance.fnDraw(); } }, @@ -454,28 +456,38 @@ $(this.s.dt.nTableWrapper).addClass('DTS'); // Add a 'loading' indicator if ( this.s.loadingIndicator ) { + this.dom.loader = $('<div class="DTS_Loading">'+this.s.dt.oLanguage.sLoadingRecords+'</div>') + .css('display', 'none'); + $(this.dom.scroller.parentNode) .css('position', 'relative') - .append('<div class="DTS_Loading">'+this.s.dt.oLanguage.sLoadingRecords+'</div>'); + .append( this.dom.loader ); } /* Initial size calculations */ if ( this.s.heights.row && this.s.heights.row != 'auto' ) { this.s.autoHeight = false; } this.fnMeasure( false ); - /* Scrolling callback to see if a page change is needed */ - $(this.dom.scroller).on( 'scroll.DTS', function () { + /* Scrolling callback to see if a page change is needed - use a throttled + * function for the save save callback so we aren't hitting it on every + * scroll + */ + this.s.ingnoreScroll = true; + this.s.stateSaveThrottle = this.s.dt.oApi._fnThrottle( function () { + that.s.dt.oApi._fnSaveState( that.s.dt ); + }, 500 ); + $(this.dom.scroller).on( 'scroll.DTS', function (e) { that._fnScroll.call( that ); } ); - /* In iOS we catch the touchstart event incase the user tries to scroll + /* In iOS we catch the touchstart event in case the user tries to scroll * while the display is already scrolling */ $(this.dom.scroller).on('touchstart.DTS', function () { that._fnScroll.call( that ); } ); @@ -490,10 +502,11 @@ "sName": "Scroller" } ); /* On resize, update the information element, since the number of rows shown might change */ $(window).on( 'resize.DTS', function () { + that.fnMeasure( false ); that._fnInfo(); } ); /* Add a state saving parameter to the DT state saving so we can restore the exact * position of the scrolling @@ -502,16 +515,22 @@ this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) { /* Set iScroller to saved scroll position on initialization. */ if(initialStateSave && that.s.dt.oLoadedState){ oData.iScroller = that.s.dt.oLoadedState.iScroller; + oData.iScrollerTopRow = that.s.dt.oLoadedState.iScrollerTopRow; initialStateSave = false; } else { oData.iScroller = that.dom.scroller.scrollTop; + oData.iScrollerTopRow = that.s.topRowFloat; } }, "Scroller_State" ); + if ( this.s.dt.oLoadedState ) { + this.s.topRowFloat = this.s.dt.oLoadedState.iScrollerTopRow || 0; + } + /* Destructor */ this.s.dt.aoDestroyCallback.push( { "sName": "Scroller", "fn": function () { $(window).off( 'resize.DTS' ); @@ -546,10 +565,14 @@ if ( this.s.skip ) { return; } + if ( this.s.ingnoreScroll ) { + return; + } + /* If the table has been sorted or filtered, then we use the redraw that * DataTables as done, rather than performing our own */ if ( this.s.dt.bFiltered || this.s.dt.bSorted ) { this.s.lastScrollTop = 0; @@ -626,14 +649,20 @@ this.s.drawTO = setTimeout( draw, this.s.serverWait ); } else { draw(); } + + if ( this.dom.loader && ! this.s.loaderVisible ) { + this.dom.loader.css( 'display', 'block' ); + this.s.loaderVisible = true; + } } } this.s.lastScrollTop = iScrollTop; + this.s.stateSaveThrottle(); }, /** * Convert from one domain to another. The physical domain is the actual @@ -768,36 +797,53 @@ this.s.redrawTop = iScrollTop - boundaryPx; this.s.redrawBottom = iScrollTop + boundaryPx; this.s.skip = false; - // Because of the order of the DT callbacks, the info update will - // take precidence over the one we want here. So a 'thread' break is - // needed - setTimeout( function () { - that._fnInfo.call( that ); - }, 0 ); - // Restore the scrolling position that was saved by DataTable's state // saving Note that this is done on the second draw when data is Ajax // sourced, and the first draw when DOM soured if ( this.s.dt.oFeatures.bStateSave && this.s.dt.oLoadedState !== null && typeof this.s.dt.oLoadedState.iScroller != 'undefined' ) { - var ajaxSourced = this.s.dt.sAjaxSource || that.s.dt.ajax ? + // A quirk of DataTables is that the draw callback will occur on an + // empty set if Ajax sourced, but not if server-side processing. + var ajaxSourced = (this.s.dt.sAjaxSource || that.s.dt.ajax) && ! this.s.dt.oFeatures.bServerSide ? true : false; if ( ( ajaxSourced && this.s.dt.iDraw == 2) || (!ajaxSourced && this.s.dt.iDraw == 1) ) { setTimeout( function () { $(that.dom.scroller).scrollTop( that.s.dt.oLoadedState.iScroller ); that.s.redrawTop = that.s.dt.oLoadedState.iScroller - (heights.viewport/2); + + // In order to prevent layout thrashing we need another + // small delay + setTimeout( function () { + that.s.ingnoreScroll = false; + }, 0 ); }, 0 ); } } + else { + that.s.ingnoreScroll = false; + } + + // Because of the order of the DT callbacks, the info update will + // take precedence over the one we want here. So a 'thread' break is + // needed + setTimeout( function () { + that._fnInfo.call( that ); + }, 0 ); + + // Hide the loading indicator + if ( this.dom.loader && this.s.loaderVisible ) { + this.dom.loader.css( 'display', 'none' ); + this.s.loaderVisible = false; + } }, /** * Force the scrolling container to have height beyond that of just the @@ -834,17 +880,18 @@ * @returns {void} * @private */ "_fnCalcRowHeight": function () { - var origTable = this.s.dt.nTable; + var dt = this.s.dt; + var origTable = dt.nTable; var nTable = origTable.cloneNode( false ); var tbody = $('<tbody/>').appendTo( nTable ); var container = $( - '<div class="'+this.s.dt.oClasses.sWrapper+' DTS">'+ - '<div class="'+this.s.dt.oClasses.sScrollWrapper+'">'+ - '<div class="'+this.s.dt.oClasses.sScrollBody+'"></div>'+ + '<div class="'+dt.oClasses.sWrapper+' DTS">'+ + '<div class="'+dt.oClasses.sScrollWrapper+'">'+ + '<div class="'+dt.oClasses.sScrollBody+'"></div>'+ '</div>'+ '</div>' ); // Want 3 rows in the sizing table so :first-child and :last-child @@ -852,13 +899,23 @@ $('tbody tr:lt(4)', origTable).clone().appendTo( tbody ); while( $('tr', tbody).length < 3 ) { tbody.append( '<tr><td>&nbsp;</td></tr>' ); } - $('div.'+this.s.dt.oClasses.sScrollBody, container).append( nTable ); + $('div.'+dt.oClasses.sScrollBody, container).append( nTable ); - container.appendTo( this.s.dt.nHolding ); + var appendTo; + if (dt._bInitComplete) { + appendTo = origTable.parentNode; + } else { + if (!this.s.dt.nHolding) { + this.s.dt.nHolding = $( '<div></div>' ).insertBefore( this.s.dt.nTable ); + } + appendTo = this.s.dt.nHolding; + } + + container.appendTo( appendTo ); this.s.heights.row = $('tr', tbody).eq(1).outerHeight(); container.remove(); }, @@ -876,10 +933,11 @@ return; } var dt = this.s.dt, + language = dt.oLanguage, iScrollTop = this.dom.scroller.scrollTop, iStart = Math.floor( this.fnPixelsToRow(iScrollTop, false, this.s.ani)+1 ), iMax = dt.fnRecordsTotal(), iTotal = dt.fnRecordsDisplay(), iPossibleEnd = Math.ceil( this.fnPixelsToRow(iScrollTop+this.s.heights.viewport, false, this.s.ani) ), @@ -892,40 +950,51 @@ if ( dt.fnRecordsDisplay() === 0 && dt.fnRecordsDisplay() == dt.fnRecordsTotal() ) { /* Empty record set */ - sOut = dt.oLanguage.sInfoEmpty+ dt.oLanguage.sInfoPostFix; + sOut = language.sInfoEmpty+ language.sInfoPostFix; } else if ( dt.fnRecordsDisplay() === 0 ) { - /* Rmpty record set after filtering */ - sOut = dt.oLanguage.sInfoEmpty +' '+ - dt.oLanguage.sInfoFiltered.replace('_MAX_', sMax)+ - dt.oLanguage.sInfoPostFix; + /* Empty record set after filtering */ + sOut = language.sInfoEmpty +' '+ + language.sInfoFiltered.replace('_MAX_', sMax)+ + language.sInfoPostFix; } else if ( dt.fnRecordsDisplay() == dt.fnRecordsTotal() ) { /* Normal record set */ - sOut = dt.oLanguage.sInfo. + sOut = language.sInfo. replace('_START_', sStart). replace('_END_', sEnd). + replace('_MAX_', sMax). replace('_TOTAL_', sTotal)+ - dt.oLanguage.sInfoPostFix; + language.sInfoPostFix; } else { /* Record set after filtering */ - sOut = dt.oLanguage.sInfo. + sOut = language.sInfo. replace('_START_', sStart). replace('_END_', sEnd). + replace('_MAX_', sMax). replace('_TOTAL_', sTotal) +' '+ - dt.oLanguage.sInfoFiltered.replace('_MAX_', - dt.fnFormatNumber(dt.fnRecordsTotal()))+ - dt.oLanguage.sInfoPostFix; + language.sInfoFiltered.replace( + '_MAX_', + dt.fnFormatNumber(dt.fnRecordsTotal()) + )+ + language.sInfoPostFix; } + var callback = language.fnInfoCallback; + if ( callback ) { + sOut = callback.call( dt.oInstance, + dt, iStart, iEnd, iMax, iTotal, sOut + ); + } + var n = dt.aanFeatures.i; if ( typeof n != 'undefined' ) { for ( var i=0, iLen=n.length ; i<iLen ; i++ ) { @@ -1084,11 +1153,11 @@ * @type String * @default See code * @name Scroller.version * @static */ -Scroller.version = "1.2.1"; +Scroller.version = "1.2.2"; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Initialisation @@ -1125,10 +1194,14 @@ // DataTables 1.10 API method aliases if ( $.fn.dataTable.Api ) { var Api = $.fn.dataTable.Api; + Api.register( 'scroller()', function () { + return this; + } ); + Api.register( 'scroller().rowToPixels()', function ( rowIdx, intParse, virtual ) { var ctx = this.context; if ( ctx.length && ctx[0].oScroller ) { return ctx[0].oScroller.fnRowToPixels( rowIdx, intParse, virtual ); @@ -1171,10 +1244,14 @@ }; // /factory // Define as an AMD module if possible if ( typeof define === 'function' && define.amd ) { - define( 'datatables-scroller', ['jquery', 'datatables'], factory ); + define( ['jquery', 'datatables'], factory ); +} +else if ( typeof exports === 'object' ) { + // Node/CommonJS + factory( require('jquery'), require('datatables') ); } else if ( jQuery && !jQuery.fn.dataTable.Scroller ) { // Otherwise simply initialise as normal, stopping multiple evaluation factory( jQuery, jQuery.fn.dataTable ); }