dataTables.scroller.js 37 KB

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