ui-grid.infinite-scroll.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. /*!
  2. * ui-grid - v4.4.6 - 2018-04-06
  3. * Copyright (c) 2018 ; License: MIT
  4. */
  5. (function() {
  6. 'use strict';
  7. /**
  8. * @ngdoc overview
  9. * @name ui.grid.infiniteScroll
  10. *
  11. * @description
  12. *
  13. * #ui.grid.infiniteScroll
  14. *
  15. * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
  16. *
  17. * This module provides infinite scroll functionality to ui-grid
  18. *
  19. */
  20. var module = angular.module('ui.grid.infiniteScroll', ['ui.grid']);
  21. /**
  22. * @ngdoc service
  23. * @name ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
  24. *
  25. * @description Service for infinite scroll features
  26. */
  27. module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$rootScope', 'uiGridConstants', 'ScrollEvent', '$q', function (gridUtil, $compile, $rootScope, uiGridConstants, ScrollEvent, $q) {
  28. var service = {
  29. /**
  30. * @ngdoc function
  31. * @name initializeGrid
  32. * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
  33. * @description This method register events and methods into grid public API
  34. */
  35. initializeGrid: function(grid, $scope) {
  36. service.defaultGridOptions(grid.options);
  37. if (!grid.options.enableInfiniteScroll){
  38. return;
  39. }
  40. grid.infiniteScroll = { dataLoading: false };
  41. service.setScrollDirections( grid, grid.options.infiniteScrollUp, grid.options.infiniteScrollDown );
  42. grid.api.core.on.scrollEnd($scope, service.handleScroll);
  43. /**
  44. * @ngdoc object
  45. * @name ui.grid.infiniteScroll.api:PublicAPI
  46. *
  47. * @description Public API for infinite scroll feature
  48. */
  49. var publicApi = {
  50. events: {
  51. infiniteScroll: {
  52. /**
  53. * @ngdoc event
  54. * @name needLoadMoreData
  55. * @eventOf ui.grid.infiniteScroll.api:PublicAPI
  56. * @description This event fires when scroll reaches bottom percentage of grid
  57. * and needs to load data
  58. */
  59. needLoadMoreData: function ($scope, fn) {
  60. },
  61. /**
  62. * @ngdoc event
  63. * @name needLoadMoreDataTop
  64. * @eventOf ui.grid.infiniteScroll.api:PublicAPI
  65. * @description This event fires when scroll reaches top percentage of grid
  66. * and needs to load data
  67. */
  68. needLoadMoreDataTop: function ($scope, fn) {
  69. }
  70. }
  71. },
  72. methods: {
  73. infiniteScroll: {
  74. /**
  75. * @ngdoc function
  76. * @name dataLoaded
  77. * @methodOf ui.grid.infiniteScroll.api:PublicAPI
  78. * @description Call this function when you have loaded the additional data
  79. * requested. You should set scrollUp and scrollDown to indicate
  80. * whether there are still more pages in each direction.
  81. *
  82. * If you call dataLoaded without first calling `saveScrollPercentage` then we will
  83. * scroll the user to the start of the newly loaded data, which usually gives a smooth scroll
  84. * experience, but can give a jumpy experience with large `infiniteScrollRowsFromEnd` values, and
  85. * on variable speed internet connections. Using `saveScrollPercentage` as demonstrated in the tutorial
  86. * should give a smoother scrolling experience for users.
  87. *
  88. * See infinite_scroll tutorial for example of usage
  89. * @param {boolean} scrollUp if set to false flags that there are no more pages upwards, so don't fire
  90. * any more infinite scroll events upward
  91. * @param {boolean} scrollDown if set to false flags that there are no more pages downwards, so don't
  92. * fire any more infinite scroll events downward
  93. * @returns {promise} a promise that is resolved when the grid scrolling is fully adjusted. If you're
  94. * planning to remove pages, you should wait on this promise first, or you'll break the scroll positioning
  95. */
  96. dataLoaded: function( scrollUp, scrollDown ) {
  97. service.setScrollDirections(grid, scrollUp, scrollDown);
  98. var promise = service.adjustScroll(grid).then(function() {
  99. grid.infiniteScroll.dataLoading = false;
  100. });
  101. return promise;
  102. },
  103. /**
  104. * @ngdoc function
  105. * @name resetScroll
  106. * @methodOf ui.grid.infiniteScroll.api:PublicAPI
  107. * @description Call this function when you have taken some action that makes the current
  108. * scroll position invalid. For example, if you're using external sorting and you've resorted
  109. * then you might reset the scroll, or if you've otherwise substantially changed the data, perhaps
  110. * you've reused an existing grid for a new data set
  111. *
  112. * You must tell us whether there is data upwards or downwards after the reset
  113. *
  114. * @param {boolean} scrollUp flag that there are pages upwards, fire
  115. * infinite scroll events upward
  116. * @param {boolean} scrollDown flag that there are pages downwards, so
  117. * fire infinite scroll events downward
  118. */
  119. resetScroll: function( scrollUp, scrollDown ) {
  120. service.setScrollDirections( grid, scrollUp, scrollDown);
  121. service.adjustInfiniteScrollPosition(grid, 0);
  122. },
  123. /**
  124. * @ngdoc function
  125. * @name saveScrollPercentage
  126. * @methodOf ui.grid.infiniteScroll.api:PublicAPI
  127. * @description Saves the scroll percentage and number of visible rows before you adjust the data,
  128. * used if you're subsequently going to call `dataRemovedTop` or `dataRemovedBottom`
  129. */
  130. saveScrollPercentage: function() {
  131. grid.infiniteScroll.prevScrollTop = grid.renderContainers.body.prevScrollTop;
  132. grid.infiniteScroll.previousVisibleRows = grid.getVisibleRowCount();
  133. },
  134. /**
  135. * @ngdoc function
  136. * @name dataRemovedTop
  137. * @methodOf ui.grid.infiniteScroll.api:PublicAPI
  138. * @description Adjusts the scroll position after you've removed data at the top
  139. * @param {boolean} scrollUp flag that there are pages upwards, fire
  140. * infinite scroll events upward
  141. * @param {boolean} scrollDown flag that there are pages downwards, so
  142. * fire infinite scroll events downward
  143. */
  144. dataRemovedTop: function( scrollUp, scrollDown ) {
  145. service.dataRemovedTop( grid, scrollUp, scrollDown );
  146. },
  147. /**
  148. * @ngdoc function
  149. * @name dataRemovedBottom
  150. * @methodOf ui.grid.infiniteScroll.api:PublicAPI
  151. * @description Adjusts the scroll position after you've removed data at the bottom
  152. * @param {boolean} scrollUp flag that there are pages upwards, fire
  153. * infinite scroll events upward
  154. * @param {boolean} scrollDown flag that there are pages downwards, so
  155. * fire infinite scroll events downward
  156. */
  157. dataRemovedBottom: function( scrollUp, scrollDown ) {
  158. service.dataRemovedBottom( grid, scrollUp, scrollDown );
  159. },
  160. /**
  161. * @ngdoc function
  162. * @name setScrollDirections
  163. * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
  164. * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
  165. * and also sets the grid.suppressParentScroll
  166. * @param {boolean} scrollUp whether there are pages available up - defaults to false
  167. * @param {boolean} scrollDown whether there are pages available down - defaults to true
  168. */
  169. setScrollDirections: function ( scrollUp, scrollDown ) {
  170. service.setScrollDirections( grid, scrollUp, scrollDown );
  171. }
  172. }
  173. }
  174. };
  175. grid.api.registerEventsFromObject(publicApi.events);
  176. grid.api.registerMethodsFromObject(publicApi.methods);
  177. },
  178. defaultGridOptions: function (gridOptions) {
  179. //default option to true unless it was explicitly set to false
  180. /**
  181. * @ngdoc object
  182. * @name ui.grid.infiniteScroll.api:GridOptions
  183. *
  184. * @description GridOptions for infinite scroll feature, these are available to be
  185. * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
  186. */
  187. /**
  188. * @ngdoc object
  189. * @name enableInfiniteScroll
  190. * @propertyOf ui.grid.infiniteScroll.api:GridOptions
  191. * @description Enable infinite scrolling for this grid
  192. * <br/>Defaults to true
  193. */
  194. gridOptions.enableInfiniteScroll = gridOptions.enableInfiniteScroll !== false;
  195. /**
  196. * @ngdoc property
  197. * @name infiniteScrollRowsFromEnd
  198. * @propertyOf ui.grid.class:GridOptions
  199. * @description This setting controls how close to the end of the dataset a user gets before
  200. * more data is requested by the infinite scroll, whether scrolling up or down. This allows you to
  201. * 'prefetch' rows before the user actually runs out of scrolling.
  202. *
  203. * Note that if you set this value too high it may give jumpy scrolling behaviour, if you're getting
  204. * this behaviour you could use the `saveScrollPercentageMethod` right before loading your data, and we'll
  205. * preserve that scroll position
  206. *
  207. * <br> Defaults to 20
  208. */
  209. gridOptions.infiniteScrollRowsFromEnd = gridOptions.infiniteScrollRowsFromEnd || 20;
  210. /**
  211. * @ngdoc property
  212. * @name infiniteScrollUp
  213. * @propertyOf ui.grid.class:GridOptions
  214. * @description Whether you allow infinite scroll up, implying that the first page of data
  215. * you have displayed is in the middle of your data set. If set to true then we trigger the
  216. * needMoreDataTop event when the user hits the top of the scrollbar.
  217. * <br> Defaults to false
  218. */
  219. gridOptions.infiniteScrollUp = gridOptions.infiniteScrollUp === true;
  220. /**
  221. * @ngdoc property
  222. * @name infiniteScrollDown
  223. * @propertyOf ui.grid.class:GridOptions
  224. * @description Whether you allow infinite scroll down, implying that the first page of data
  225. * you have displayed is in the middle of your data set. If set to true then we trigger the
  226. * needMoreData event when the user hits the bottom of the scrollbar.
  227. * <br> Defaults to true
  228. */
  229. gridOptions.infiniteScrollDown = gridOptions.infiniteScrollDown !== false;
  230. },
  231. /**
  232. * @ngdoc function
  233. * @name setScrollDirections
  234. * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
  235. * @description Sets the scrollUp and scrollDown flags, handling nulls and undefined,
  236. * and also sets the grid.suppressParentScroll
  237. * @param {grid} grid the grid we're operating on
  238. * @param {boolean} scrollUp whether there are pages available up - defaults to false
  239. * @param {boolean} scrollDown whether there are pages available down - defaults to true
  240. */
  241. setScrollDirections: function ( grid, scrollUp, scrollDown ) {
  242. grid.infiniteScroll.scrollUp = ( scrollUp === true );
  243. grid.suppressParentScrollUp = ( scrollUp === true );
  244. grid.infiniteScroll.scrollDown = ( scrollDown !== false);
  245. grid.suppressParentScrollDown = ( scrollDown !== false);
  246. },
  247. /**
  248. * @ngdoc function
  249. * @name handleScroll
  250. * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
  251. * @description Called whenever the grid scrolls, determines whether the scroll should
  252. * trigger an infinite scroll request for more data
  253. * @param {object} args the args from the event
  254. */
  255. handleScroll: function (args) {
  256. // don't request data if already waiting for data, or if source is coming from ui.grid.adjustInfiniteScrollPosition() function
  257. if ( args.grid.infiniteScroll && args.grid.infiniteScroll.dataLoading || args.source === 'ui.grid.adjustInfiniteScrollPosition' ){
  258. return;
  259. }
  260. if (args.y) {
  261. // If the user is scrolling very quickly all the way to the top/bottom, the scroll handler can get confused
  262. // about the direction. First we check if they've gone all the way, and data always is loaded in this case.
  263. if (args.y.percentage === 0) {
  264. args.grid.scrollDirection = uiGridConstants.scrollDirection.UP;
  265. service.loadData(args.grid);
  266. } else if (args.y.percentage === 1) {
  267. args.grid.scrollDirection = uiGridConstants.scrollDirection.DOWN;
  268. service.loadData(args.grid);
  269. } else { // Scroll position is somewhere in between top/bottom, so determine whether it's far enough to load more data.
  270. var percentage;
  271. var targetPercentage = args.grid.options.infiniteScrollRowsFromEnd / args.grid.renderContainers.body.visibleRowCache.length;
  272. if (args.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) {
  273. percentage = args.y.percentage;
  274. if (percentage <= targetPercentage){
  275. service.loadData(args.grid);
  276. }
  277. } else if (args.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN) {
  278. percentage = 1 - args.y.percentage;
  279. if (percentage <= targetPercentage){
  280. service.loadData(args.grid);
  281. }
  282. }
  283. }
  284. }
  285. },
  286. /**
  287. * @ngdoc function
  288. * @name loadData
  289. * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
  290. * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
  291. * and whether there are more pages upwards or downwards. It also stores the number of rows that we had previously,
  292. * and clears out any saved scroll position so that we know whether or not the user calls `saveScrollPercentage`
  293. * @param {Grid} grid the grid we're working on
  294. */
  295. loadData: function (grid) {
  296. // save number of currently visible rows to calculate new scroll position later - we know that we want
  297. // to be at approximately the row we're currently at
  298. grid.infiniteScroll.previousVisibleRows = grid.renderContainers.body.visibleRowCache.length;
  299. grid.infiniteScroll.direction = grid.scrollDirection;
  300. delete grid.infiniteScroll.prevScrollTop;
  301. if (grid.scrollDirection === uiGridConstants.scrollDirection.UP && grid.infiniteScroll.scrollUp ) {
  302. grid.infiniteScroll.dataLoading = true;
  303. grid.api.infiniteScroll.raise.needLoadMoreDataTop();
  304. } else if (grid.scrollDirection === uiGridConstants.scrollDirection.DOWN && grid.infiniteScroll.scrollDown ) {
  305. grid.infiniteScroll.dataLoading = true;
  306. grid.api.infiniteScroll.raise.needLoadMoreData();
  307. }
  308. },
  309. /**
  310. * @ngdoc function
  311. * @name adjustScroll
  312. * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
  313. * @description Once we are informed that data has been loaded, adjust the scroll position to account for that
  314. * addition and to make things look clean.
  315. *
  316. * If we're scrolling up we scroll to the first row of the old data set -
  317. * so we're assuming that you would have gotten to the top of the grid (from the 20% need more data trigger) by
  318. * the time the data comes back. If we're scrolling down we scroll to the last row of the old data set - so we're
  319. * assuming that you would have gotten to the bottom of the grid (from the 80% need more data trigger) by the time
  320. * the data comes back.
  321. *
  322. * Neither of these are good assumptions, but making this a smoother experience really requires
  323. * that trigger to not be a percentage, and to be much closer to the end of the data (say, 5 rows off the end). Even then
  324. * it'd be better still to actually run into the end. But if the data takes a while to come back, they may have scrolled
  325. * somewhere else in the mean-time, in which case they'll get a jump back to the new data. Anyway, this will do for
  326. * now, until someone wants to do better.
  327. * @param {Grid} grid the grid we're working on
  328. * @returns {promise} a promise that is resolved when scrolling has finished
  329. */
  330. adjustScroll: function(grid){
  331. var promise = $q.defer();
  332. $rootScope.$applyAsync(function () {
  333. var newPercentage, viewportHeight, rowHeight, newVisibleRows, oldTop, newTop;
  334. viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight;
  335. rowHeight = grid.options.rowHeight;
  336. if ( grid.infiniteScroll.direction === undefined ){
  337. // called from initialize, tweak our scroll up a little
  338. service.adjustInfiniteScrollPosition(grid, 0);
  339. }
  340. newVisibleRows = grid.getVisibleRowCount();
  341. // in case not enough data is loaded to enable scroller - load more data
  342. var canvasHeight = rowHeight * newVisibleRows;
  343. if (grid.infiniteScroll.scrollDown && (viewportHeight > canvasHeight)) {
  344. grid.api.infiniteScroll.raise.needLoadMoreData();
  345. }
  346. if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.UP ){
  347. oldTop = grid.infiniteScroll.prevScrollTop || 0;
  348. newTop = oldTop + (newVisibleRows - grid.infiniteScroll.previousVisibleRows)*rowHeight;
  349. service.adjustInfiniteScrollPosition(grid, newTop);
  350. $rootScope.$applyAsync( function() {
  351. promise.resolve();
  352. });
  353. }
  354. if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.DOWN ){
  355. newTop = grid.infiniteScroll.prevScrollTop || (grid.infiniteScroll.previousVisibleRows*rowHeight - viewportHeight);
  356. service.adjustInfiniteScrollPosition(grid, newTop);
  357. $rootScope.$applyAsync( function() {
  358. promise.resolve();
  359. });
  360. }
  361. }, 0);
  362. return promise.promise;
  363. },
  364. /**
  365. * @ngdoc function
  366. * @name adjustInfiniteScrollPosition
  367. * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService
  368. * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection
  369. * @param {Grid} grid the grid we're working on
  370. * @param {number} scrollTop the position through the grid that we want to scroll to
  371. */
  372. adjustInfiniteScrollPosition: function (grid, scrollTop) {
  373. var scrollEvent = new ScrollEvent(grid, null, null, 'ui.grid.adjustInfiniteScrollPosition'),
  374. visibleRows = grid.getVisibleRowCount(),
  375. viewportHeight = grid.getViewportHeight() + grid.headerHeight - grid.renderContainers.body.headerHeight - grid.scrollbarHeight,
  376. rowHeight = grid.options.rowHeight,
  377. scrollHeight = visibleRows*rowHeight-viewportHeight;
  378. //for infinite scroll, if there are pages upwards then never allow it to be at the zero position so the up button can be active
  379. if (scrollTop === 0 && grid.infiniteScroll.scrollUp) {
  380. // using pixels results in a relative scroll, hence we have to use percentage
  381. scrollEvent.y = {percentage: 1/scrollHeight};
  382. }
  383. else {
  384. scrollEvent.y = {percentage: scrollTop/scrollHeight};
  385. }
  386. grid.scrollContainers('', scrollEvent);
  387. },
  388. /**
  389. * @ngdoc function
  390. * @name dataRemovedTop
  391. * @methodOf ui.grid.infiniteScroll.api:PublicAPI
  392. * @description Adjusts the scroll position after you've removed data at the top. You should
  393. * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
  394. * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
  395. * before you start removing data
  396. * @param {Grid} grid the grid we're working on
  397. * @param {boolean} scrollUp flag that there are pages upwards, fire
  398. * infinite scroll events upward
  399. * @param {boolean} scrollDown flag that there are pages downwards, so
  400. * fire infinite scroll events downward
  401. */
  402. dataRemovedTop: function( grid, scrollUp, scrollDown ) {
  403. var newVisibleRows, oldTop, newTop, rowHeight;
  404. service.setScrollDirections( grid, scrollUp, scrollDown );
  405. newVisibleRows = grid.renderContainers.body.visibleRowCache.length;
  406. oldTop = grid.infiniteScroll.prevScrollTop;
  407. rowHeight = grid.options.rowHeight;
  408. // since we removed from the top, our new scroll row will be the old scroll row less the number
  409. // of rows removed
  410. newTop = oldTop - ( grid.infiniteScroll.previousVisibleRows - newVisibleRows )*rowHeight;
  411. service.adjustInfiniteScrollPosition( grid, newTop );
  412. },
  413. /**
  414. * @ngdoc function
  415. * @name dataRemovedBottom
  416. * @methodOf ui.grid.infiniteScroll.api:PublicAPI
  417. * @description Adjusts the scroll position after you've removed data at the bottom. You should
  418. * have called `saveScrollPercentage` before you remove the data, and if you're doing this in
  419. * response to a `needMoreData` you should wait until the promise from `loadData` has resolved
  420. * before you start removing data
  421. * @param {Grid} grid the grid we're working on
  422. * @param {boolean} scrollUp flag that there are pages upwards, fire
  423. * infinite scroll events upward
  424. * @param {boolean} scrollDown flag that there are pages downwards, so
  425. * fire infinite scroll events downward
  426. */
  427. dataRemovedBottom: function( grid, scrollUp, scrollDown ) {
  428. var newTop;
  429. service.setScrollDirections( grid, scrollUp, scrollDown );
  430. newTop = grid.infiniteScroll.prevScrollTop;
  431. service.adjustInfiniteScrollPosition( grid, newTop );
  432. }
  433. };
  434. return service;
  435. }]);
  436. /**
  437. * @ngdoc directive
  438. * @name ui.grid.infiniteScroll.directive:uiGridInfiniteScroll
  439. * @element div
  440. * @restrict A
  441. *
  442. * @description Adds infinite scroll features to grid
  443. *
  444. * @example
  445. <example module="app">
  446. <file name="app.js">
  447. var app = angular.module('app', ['ui.grid', 'ui.grid.infiniteScroll']);
  448. app.controller('MainCtrl', ['$scope', function ($scope) {
  449. $scope.data = [
  450. { name: 'Alex', car: 'Toyota' },
  451. { name: 'Sam', car: 'Lexus' }
  452. ];
  453. $scope.columnDefs = [
  454. {name: 'name'},
  455. {name: 'car'}
  456. ];
  457. }]);
  458. </file>
  459. <file name="index.html">
  460. <div ng-controller="MainCtrl">
  461. <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-infinite-scroll="20"></div>
  462. </div>
  463. </file>
  464. </example>
  465. */
  466. module.directive('uiGridInfiniteScroll', ['uiGridInfiniteScrollService',
  467. function (uiGridInfiniteScrollService) {
  468. return {
  469. priority: -200,
  470. scope: false,
  471. require: '^uiGrid',
  472. compile: function($scope, $elm, $attr){
  473. return {
  474. pre: function($scope, $elm, $attr, uiGridCtrl) {
  475. uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid, $scope);
  476. },
  477. post: function($scope, $elm, $attr) {
  478. }
  479. };
  480. }
  481. };
  482. }]);
  483. })();