From ea09b1609ac17d6a84fdd0a4d4ad399ba9ba3bfd Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Mon, 23 Mar 2015 16:37:09 +1300 Subject: [PATCH] Fix(infiniteScroll): fix #2730, fix #2827, BREAKING changes Substantial refactor of infinite scroll. Provides new functionality, a much more detailed tutorial, and removes the code that crept into core in #2730 (which I merged when I shouldn't have). Clarifies the handling, removes a viewPort directive that was unnecessary, makes the handling for scroll up and down consistent. Strictly speaking the API hasn't changed, but there are some new settings and the default behaviour has subtly changed in that scrollUp isn't enabled by default. This should be more logical to users, but probably does require a review of the tutorial and API. --- misc/tutorial/212_infinite_scroll.ngdoc | 143 ++++++----- .../infinite-scroll/js/infinite-scroll.js | 240 ++++++++++++++---- .../test/infiniteScroll.spec.js | 138 ++++++---- src/js/core/directives/ui-grid.js | 35 +-- src/js/core/factories/GridRenderContainer.js | 37 +-- 5 files changed, 360 insertions(+), 233 deletions(-) diff --git a/misc/tutorial/212_infinite_scroll.ngdoc b/misc/tutorial/212_infinite_scroll.ngdoc index d8f184d9f4..44d26ed239 100644 --- a/misc/tutorial/212_infinite_scroll.ngdoc +++ b/misc/tutorial/212_infinite_scroll.ngdoc @@ -2,85 +2,104 @@ @name Tutorial: 212 Infinite scroll @description -The infinite scroll feature allows the user to lazy load their data to gridOptions.data +The infinite scroll feature allows the user to lazy load their data to gridOptions.data. -Specify percentage when lazy load should trigger: -
-  $scope.gridOptions.infiniteScroll = 20;
-
+Once you reach the top (or bottom) of your real data set, you can notify that no more pages exist +up (or down), and infinite scroll will stop triggering events in that direction. You can also +optionally tell us up-front that there are no more pages up through `infiniteScrollUp = true` or down through +`infiniteScrollDown = true`, and we will never trigger +pages in that direction. By default we assume you have pages down but not up. + +You can specify the percentage of the grid at which the infinite scroll will trigger a request for +more data `infiniteScrollPercentage = 20`. By default we trigger when you are 20% away from the end of +the grid (in either direction). + +We will raise a `needMoreData` or `needMoreDataTop` event, which you must listen to and respond to if +you have told us that you have more data available. Once you have retrieved the data and added it to your +data array (at the top if the event was `needMoreDataTop`), you need to call `dataLoaded` to tell us +that you have loaded your data. Optionally, you can tell us that there is no more data, and we won't trigger +further requests for more data in that direction. + +When you have loaded your data we will attempt to adjust the grid scroll to give the appearance of continuous +scrolling. This is a little jumpy at present, largely because we work of percentages instead of a number of +rows from the end. We basically assume that your user will have reached the end of the scroll (upwards or downwards) +by the time the data comes back, and scroll the user to the beginning of the newly added data to reflect that. If +your user has already scrolled a lot of pages, then they may not be at the end of the data (20% can be a long way). +Ideally the API would change to a number of rows. + +Finally, we suppress the normal grid behaviour of propagating the scroll to the parent container when you reach the end +if infinite scroll is enabled and there is still data in that direction. @example +In this example we have a data set that starts at page 2 of a 5 page data set. Each page is 100 records, so we start at +record 200, and we can scroll up 2 pages, and scroll down 2 pages. You should see smooth scrolling as you move up, when +you hit record zero a touchpad scroll should propagate to the parent page. You should also see smooth scrolling as you +move down, and when you hit record 499 a touchpad scroll should propagate to the parent page. + var app = angular.module('app', ['ngTouch', 'ui.grid', 'ui.grid.infiniteScroll']); - app.controller('MainCtrl', ['$scope', '$http', '$log', function ($scope, $http, $log) { - $scope.gridOptions = {}; - - /** - * @ngdoc property - * @name infiniteScrollPercentage - * @propertyOf ui.grid.class:GridOptions - * @description This setting controls at what percentage of the scroll more data - * is requested by the infinite scroll - */ - $scope.gridOptions.infiniteScrollPercentage = 15; - - $scope.gridOptions.columnDefs = [ - { name:'id'}, - { name:'name' }, - { name:'age' } - ]; - var page = 0; - var pageUp = 0; - var getData = function(data, page) { - var res = []; - for (var i = (page * 100); i < (page + 1) * 100 && i < data.length; ++i) { - res.push(data[i]); + app.controller('MainCtrl', ['$scope', '$http', function ($scope, $http) { + $scope.gridOptions = { + infiniteScrollPercentage: 15, + infiniteScrollUp: true, + infiniteScrollDown: true, + columnDefs: [ + { name:'id'}, + { name:'name' }, + { name:'age' } + ], + data: 'data', + onRegisterApi: function(gridApi){ + gridApi.infiniteScroll.on.needLoadMoreData($scope, $scope.getDataDown); + gridApi.infiniteScroll.on.needLoadMoreDataTop($scope, $scope.getDataUp); + $scope.gridApi = gridApi; } - return res; }; - var getDataUp = function(data, page) { - var res = []; - for (var i = data.length - (page * 100) - 1; (data.length - i) < ((page + 1) * 100) && (data.length - i) > 0; --i) { - data[i].id = -(data.length - data[i].id) - res.push(data[i]); - } - return res; + $scope.data = []; + + var firstPage = 2; + var lastPage = 1; + + $scope.getDataDown = function() { + $http.get('/data/10000_complex.json') + .success(function(data) { + lastPage++; + var newData = $scope.getPage(data, lastPage); + $scope.data = $scope.data.concat(newData); + $scope.gridApi.infiniteScroll.dataLoaded(null, lastPage === 4); + }) + .error(function(error) { + $scope.gridApi.infiniteScroll.dataLoaded(); + }); }; - $http.get('/data/10000_complex.json') + $scope.getDataUp = function() { + $http.get('/data/10000_complex.json') .success(function(data) { - $scope.gridOptions.data = getData(data, page); - ++page; + firstPage--; + var newData = $scope.getPage(data, firstPage); + $scope.data = newData.concat($scope.data); + $scope.gridApi.infiniteScroll.dataLoaded(firstPage === 0, null); + }) + .error(function(error) { + $scope.gridApi.infiniteScroll.dataLoaded(); }); + }; - $scope.gridOptions.onRegisterApi = function(gridApi){ - gridApi.infiniteScroll.on.needLoadMoreData($scope,function(){ - $http.get('/data/10000_complex.json') - .success(function(data) { - $scope.gridOptions.data = $scope.gridOptions.data.concat(getData(data, page)); - ++page; - gridApi.infiniteScroll.dataLoaded(); - }) - .error(function() { - gridApi.infiniteScroll.dataLoaded(); - }); - }); - gridApi.infiniteScroll.on.needLoadMoreDataTop($scope,function(){ - $http.get('/data/10000_complex.json') - .success(function(data) { - $scope.gridOptions.data = getDataUp(data, pageUp).reverse().concat($scope.gridOptions.data); - ++pageUp; - gridApi.infiniteScroll.dataLoaded(); - }) - .error(function() { - gridApi.infiniteScroll.dataLoaded(); - }); - }); + + $scope.getPage = function(data, page) { + var res = []; + for (var i = (page * 100); i < (page + 1) * 100 && i < data.length; ++i) { + res.push(data[i]); + } + return res; }; + + $scope.getDataDown(); }]); diff --git a/src/features/infinite-scroll/js/infinite-scroll.js b/src/features/infinite-scroll/js/infinite-scroll.js index bd90ae8747..06ba5b6e3a 100644 --- a/src/features/infinite-scroll/js/infinite-scroll.js +++ b/src/features/infinite-scroll/js/infinite-scroll.js @@ -17,7 +17,7 @@ * * @description Service for infinite scroll features */ - module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', 'uiGridConstants', function (gridUtil, $compile, $timeout, uiGridConstants) { + module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', 'uiGridConstants', 'ScrollEvent', function (gridUtil, $compile, $timeout, uiGridConstants, ScrollEvent) { var service = { @@ -28,8 +28,24 @@ * @description This method register events and methods into grid public API */ - initializeGrid: function(grid) { + initializeGrid: function(grid, $scope) { service.defaultGridOptions(grid.options); + grid.infiniteScroll = { dataLoading: false, scrollUp: grid.options.infiniteScrollUp, scrollDown: grid.options.infiniteScrollDown }; + + if ( grid.options.infiniteScrollUp){ + grid.suppressParentScrollUp = true; + } + + if ( grid.options.infiniteScrollDown){ + grid.suppressParentScrollDown = true; + } + + if (grid.options.enableInfiniteScroll) { + grid.api.core.on.scrollEvent($scope, service.handleScroll); + } + + // tweak the scroll for infinite scroll up (if enabled) + service.adjustScroll(grid); /** * @ngdoc object @@ -45,7 +61,7 @@ * @ngdoc event * @name needLoadMoreData * @eventOf ui.grid.infiniteScroll.api:PublicAPI - * @description This event fires when scroll reached bottom percentage of grid + * @description This event fires when scroll reaches bottom percentage of grid * and needs to load data */ @@ -56,7 +72,7 @@ * @ngdoc event * @name needLoadMoreDataTop * @eventOf ui.grid.infiniteScroll.api:PublicAPI - * @description This event fires when scroll reached top percentage of grid + * @description This event fires when scroll reaches top percentage of grid * and needs to load data */ @@ -71,20 +87,40 @@ * @ngdoc function * @name dataLoaded * @methodOf ui.grid.infiniteScroll.api:PublicAPI - * @description This function is used as a promise when data finished loading. - * See infinite_scroll ngdoc for example of usage + * @description Call this function when you have loaded the additional data + * requested. You can set noMoreDataTop or noMoreDataBottom to indicate + * that we've reached the end of your data set, we won't fire any more events + * for scroll in that direction. + * See infinite_scroll tutorial for example of usage + * @param {boolean} noMoreDataTop flag that there are no more pages upwards, so don't fire + * any more infinite scroll events upward + * @param {boolean} noMoreDataBottom flag that there are no more pages downwards, so don't + * fire any more infinite scroll events downward */ - dataLoaded: function() { - grid.options.loadTimout = false; + dataLoaded: function( noMoreDataTop, noMoreDataBottom ) { + grid.infiniteScroll.dataLoading = false; + + if ( noMoreDataTop === true ){ + grid.infiniteScroll.scrollUp = false; + grid.suppressParentScrollUp = false; + } + + if ( noMoreDataBottom === true ){ + grid.infiniteScroll.scrollDown = false; + grid.suppressParentScrollDown = false; + } + + service.adjustScroll(grid); } } } }; - grid.options.loadTimout = false; grid.api.registerEventsFromObject(publicApi.events); grid.api.registerMethodsFromObject(publicApi.methods); }, + + defaultGridOptions: function (gridOptions) { //default option to true unless it was explicitly set to false /** @@ -103,6 +139,73 @@ *
Defaults to true */ gridOptions.enableInfiniteScroll = gridOptions.enableInfiniteScroll !== false; + + /** + * @ngdoc property + * @name infiniteScrollPercentage + * @propertyOf ui.grid.class:GridOptions + * @description This setting controls at what percentage remaining more data + * is requested by the infinite scroll, whether scrolling up or down. + * + * TODO: it would be nice if this were percentage of a page, not percentage of the + * total scroll - as you get more and more data, the needMoreData event is triggered + * further and further away from the end (in terms of number of rows) + *
Defaults to 20 + */ + gridOptions.infiniteScrollPercentage = gridOptions.infiniteScrollPercentage || 20; + + /** + * @ngdoc property + * @name infiniteScrollUp + * @propertyOf ui.grid.class:GridOptions + * @description Whether you allow infinite scroll up, implying that the first page of data + * you have displayed is in the middle of your data set. If set to true then we trigger the + * needMoreDataTop event when the user hits the top of the scrollbar. + *
Defaults to false + */ + gridOptions.infiniteScrollUp = gridOptions.infiniteScrollUp === true; + + /** + * @ngdoc property + * @name infiniteScrollDown + * @propertyOf ui.grid.class:GridOptions + * @description Whether you allow infinite scroll down, implying that the first page of data + * you have displayed is in the middle of your data set. If set to true then we trigger the + * needMoreData event when the user hits the bottom of the scrollbar. + *
Defaults to true + */ + gridOptions.infiniteScrollDown = gridOptions.infiniteScrollDown !== false; + }, + + + /** + * @ngdoc function + * @name handleScroll + * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService + * @description Called whenever the grid scrolls, determines whether the scroll should + * trigger an infinite scroll request for more data + * @param {object} args the args from the event + */ + handleScroll: function (args) { + // don't request data if already waiting for data, or if source is coming from ui.grid.adjustInfiniteScrollPosition() function + if ( args.grid.infiniteScroll && args.grid.infiniteScroll.dataLoading || args.source === 'ui.grid.adjustInfiniteScrollPosition' ){ + return; + } + + if (args.y) { + var percentage; + if (args.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) { + percentage = args.y.percentage; + if (percentage <= args.grid.options.infiniteScrollPercentage / 100){ + service.loadData(args.grid); + } + } else if (args.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN) { + percentage = 1 - args.y.percentage; + if (percentage <= args.grid.options.infiniteScrollPercentage / 100){ + service.loadData(args.grid); + } + } + } }, @@ -111,43 +214,92 @@ * @name loadData * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection + * and whether there are more pages upwards or downwards + * @param {Grid} grid the grid we're working on */ - loadData: function (grid) { - grid.options.loadTimout = true; - if (grid.scrollDirection === uiGridConstants.scrollDirection.UP) { + // save number of currently visible rows to calculate new scroll position later - we know that we want + // to be at approximately the row we're currently at + grid.infiniteScroll.previousVisibleRows = grid.renderContainers.body.visibleRowCache.length; + grid.infiniteScroll.direction = grid.scrollDirection; + + if (grid.scrollDirection === uiGridConstants.scrollDirection.UP && grid.infiniteScroll.scrollUp ) { + grid.infiniteScroll.dataLoading = true; grid.api.infiniteScroll.raise.needLoadMoreDataTop(); - return; + } else if (grid.scrollDirection === uiGridConstants.scrollDirection.DOWN && grid.infiniteScroll.scrollDown ) { + grid.infiniteScroll.dataLoading = true; + grid.api.infiniteScroll.raise.needLoadMoreData(); } - grid.api.infiniteScroll.raise.needLoadMoreData(); }, - + + /** * @ngdoc function - * @name checkScroll + * @name adjustScroll * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService - * @description This function checks scroll position inside grid and - * calls 'loadData' function when scroll reaches 'infiniteScrollPercentage' + * @description Once we are informed that data has been loaded, adjust the scroll position to account for that + * addition and to make things look clean. + * + * If we're scrolling up we scroll to the first row of the old data set - + * so we're assuming that you would have gotten to the top of the grid (from the 20% need more data trigger) by + * the time the data comes back. If we're scrolling down we scoll to the last row of the old data set - so we're + * assuming that you would have gotten to the bottom of the grid (from the 80% need more data trigger) by the time + * the data comes back. + * + * Neither of these are good assumptions, but making this a smoother experience really requires + * 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 + * 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 + * somewhere else in the mean-time, in which case they'll get a jump back to the new data. Anyway, this will do for + * now, until someone wants to do better. + * @param {Grid} grid the grid we're working on */ + adjustScroll: function(grid){ + $timeout(function () { + var percentage; + + if ( grid.infiniteScroll.direction === undefined ){ + // called from initialize, tweak our scroll up a little + service.adjustInfiniteScrollPosition(grid, 0); + } - checkScroll: function(grid, scrollTop) { + var newVisibleRows = grid.renderContainers.body.visibleRowCache.length; + if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.UP ){ + percentage = ( newVisibleRows - grid.infiniteScroll.previousVisibleRows ) / newVisibleRows; + service.adjustInfiniteScrollPosition(grid, percentage); + } - /* Take infiniteScrollPercentage value or use 20% as default */ - var infiniteScrollPercentage = grid.options.infiniteScrollPercentage ? grid.options.infiniteScrollPercentage : 20; + if ( grid.infiniteScroll.direction === uiGridConstants.scrollDirection.DOWN ){ + percentage = grid.infiniteScroll.previousVisibleRows / newVisibleRows; + service.adjustInfiniteScrollPosition(grid, percentage); + } + }, 0); + }, + + + /** + * @ngdoc function + * @name adjustInfiniteScrollPosition + * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService + * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection + * @param {Grid} grid the grid we're working on + * @param {number} percentage the percentage through the grid that we want to scroll to + */ + adjustInfiniteScrollPosition: function (grid, percentage) { + var scrollEvent = new ScrollEvent(grid, null, null, 'ui.grid.adjustInfiniteScrollPosition'); - if (!grid.options.loadTimout && scrollTop <= infiniteScrollPercentage) { - this.loadData(grid); - return true; + //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 + if ( percentage === 0 && grid.infiniteScroll.scrollUp ) { + scrollEvent.y = {pixels: 1}; } - return false; + else { + scrollEvent.y = {percentage: percentage}; + } + scrollEvent.fireScrollingEvent(); } - /** - * @ngdoc property - * @name infiniteScrollPercentage - * @propertyOf ui.grid.class:GridOptions - * @description This setting controls at what percentage of the scroll more data - * is requested by the infinite scroll - */ + + + + }; return service; }]); @@ -193,7 +345,7 @@ compile: function($scope, $elm, $attr){ return { pre: function($scope, $elm, $attr, uiGridCtrl) { - uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid); + uiGridInfiniteScrollService.initializeGrid(uiGridCtrl.grid, $scope); }, post: function($scope, $elm, $attr) { } @@ -202,26 +354,4 @@ }; }]); - module.directive('uiGridViewport', - ['$compile', 'gridUtil', 'uiGridInfiniteScrollService', 'uiGridConstants', - function ($compile, gridUtil, uiGridInfiniteScrollService, uiGridConstants) { - return { - priority: -200, - scope: false, - link: function ($scope, $elm, $attr){ - if ($scope.grid.options.enableInfiniteScroll) { - $scope.grid.api.core.on.scrollEvent($scope, function (args) { - //Prevent circular scroll references, if source is coming from ui.grid.adjustInfiniteScrollPosition() function - if (args.y && (args.source !== 'ui.grid.adjustInfiniteScrollPosition')) { - var percentage = 100 - (args.y.percentage * 100); - if ($scope.grid.scrollDirection === uiGridConstants.scrollDirection.UP) { - percentage = (args.y.percentage * 100); - } - uiGridInfiniteScrollService.checkScroll($scope.grid, percentage); - } - }); - } - } - }; - }]); })(); \ No newline at end of file diff --git a/src/features/infinite-scroll/test/infiniteScroll.spec.js b/src/features/infinite-scroll/test/infiniteScroll.spec.js index dd9af5c09e..b978ad5f99 100644 --- a/src/features/infinite-scroll/test/infiniteScroll.spec.js +++ b/src/features/infinite-scroll/test/infiniteScroll.spec.js @@ -1,74 +1,112 @@ /* global _ */ (function () { - 'use strict'; - describe('ui.grid.infiniteScroll uiGridInfiniteScrollService', function () { + 'use strict'; + describe('ui.grid.infiniteScroll uiGridInfiniteScrollService', function () { - var uiGridInfiniteScrollService; - var grid; - var gridClassFactory; + var uiGridInfiniteScrollService; + var grid; + var gridClassFactory; var uiGridConstants; + var $rootScope; + var $scope; - beforeEach(module('ui.grid.infiniteScroll')); + beforeEach(module('ui.grid.infiniteScroll')); - beforeEach(inject(function (_uiGridInfiniteScrollService_, _gridClassFactory_, _uiGridConstants_) { - uiGridInfiniteScrollService = _uiGridInfiniteScrollService_; - gridClassFactory = _gridClassFactory_; + beforeEach(inject(function (_uiGridInfiniteScrollService_, _gridClassFactory_, _uiGridConstants_, _$rootScope_) { + uiGridInfiniteScrollService = _uiGridInfiniteScrollService_; + gridClassFactory = _gridClassFactory_; uiGridConstants = _uiGridConstants_; - - grid = gridClassFactory.createGrid({}); - - grid.options.columnDefs = [ - {field: 'col1'} - ]; - grid.options.infiniteScroll = 20; - - grid.options.onRegisterApi = function (gridApi) { - gridApi.infiniteScroll.on.needLoadMoreData(function(){ - return []; - }); - gridApi.infiniteScroll.on.needLoadMoreDataTop(function(){ - return []; - }); - - }; - - uiGridInfiniteScrollService.initializeGrid(grid); + $rootScope = _$rootScope_; + $scope = $rootScope.$new(); + + grid = gridClassFactory.createGrid({}); + + grid.options.columnDefs = [ + {field: 'col1'} + ]; + grid.options.infiniteScrollPercentage = 20; + + uiGridInfiniteScrollService.initializeGrid(grid, $scope); spyOn(grid.api.infiniteScroll.raise, 'needLoadMoreData'); spyOn(grid.api.infiniteScroll.raise, 'needLoadMoreDataTop'); - grid.options.data = [{col1:'a'},{col1:'b'}]; + grid.options.data = [{col1:'a'},{col1:'b'}]; - grid.buildColumns(); + grid.buildColumns(); - })); + })); - describe('event handling', function () { - it('should return false if scrollTop is positioned more than 20% of scrollHeight', function() { - var scrollHeight = 100; - var scrollTop = 80; - var callResult = uiGridInfiniteScrollService.checkScroll(grid, scrollTop); - expect(callResult).toBe(false); - }); + describe('event handling', function () { + beforeEach(function() { + spyOn(uiGridInfiniteScrollService, 'loadData').andCallFake(function() {}); + }); + + it('should not request more data if scroll up to 21%', function() { + grid.scrollDirection = uiGridConstants.scrollDirection.UP; + uiGridInfiniteScrollService.handleScroll( { grid: grid, y: { percentage: 0.21 }}); + expect(uiGridInfiniteScrollService.loadData).not.toHaveBeenCalled(); + }); - it('should return false if scrollTop is positioned less than 20% of scrollHeight', function() { - var scrollHeight = 100; - var scrollTop = 19; - var callResult = uiGridInfiniteScrollService.checkScroll(grid, scrollTop); - expect(callResult).toBe(true); - }); + it('should request more data if scroll up to 20%', function() { + grid.scrollDirection = uiGridConstants.scrollDirection.UP; + uiGridInfiniteScrollService.handleScroll( { grid: grid, y: { percentage: 0.20 }}); + expect(uiGridInfiniteScrollService.loadData).toHaveBeenCalled(); + }); - it('should call load data function on grid event raise', function () { - uiGridInfiniteScrollService.loadData(grid); - expect(grid.api.infiniteScroll.raise.needLoadMoreData).toHaveBeenCalled(); - }); + it('should not request more data if scroll down to 79%', function() { + grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; + uiGridInfiniteScrollService.handleScroll( {grid: grid, y: { percentage: 0.79 }}); + expect(uiGridInfiniteScrollService.loadData).not.toHaveBeenCalled(); + }); - it('should call load data top function on grid event raise', function () { + it('should request more data if scroll down to 80%', function() { + grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; + uiGridInfiniteScrollService.handleScroll( { grid: grid, y: { percentage: 0.80 }}); + expect(uiGridInfiniteScrollService.loadData).toHaveBeenCalled(); + }); + }); + + describe('loadData', function() { + it('scroll up and there is data up', function() { grid.scrollDirection = uiGridConstants.scrollDirection.UP; + grid.infiniteScroll.scrollUp = true; + uiGridInfiniteScrollService.loadData(grid); + expect(grid.api.infiniteScroll.raise.needLoadMoreDataTop).toHaveBeenCalled(); + expect(grid.infiniteScroll.previousVisibleRows).toEqual(0); + expect(grid.infiniteScroll.direction).toEqual(uiGridConstants.scrollDirection.UP); }); + it('scroll up and there isn\'t data up', function() { + grid.scrollDirection = uiGridConstants.scrollDirection.UP; + grid.infiniteScroll.scrollUp = false; + + uiGridInfiniteScrollService.loadData(grid); + + expect(grid.api.infiniteScroll.raise.needLoadMoreDataTop).not.toHaveBeenCalled(); + }); + + it('scroll down and there is data down', function() { + grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; + grid.infiniteScroll.scrollDown = true; + + uiGridInfiniteScrollService.loadData(grid); + + expect(grid.api.infiniteScroll.raise.needLoadMoreData).toHaveBeenCalled(); + expect(grid.infiniteScroll.previousVisibleRows).toEqual(0); + expect(grid.infiniteScroll.direction).toEqual(uiGridConstants.scrollDirection.DOWN); + }); + + it('scroll down and there isn\'t data down', function() { + grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; + grid.infiniteScroll.scrollDown = false; + + uiGridInfiniteScrollService.loadData(grid); + + expect(grid.api.infiniteScroll.raise.needLoadMoreData).not.toHaveBeenCalled(); + }); }); - }); + }); })(); \ No newline at end of file diff --git a/src/js/core/directives/ui-grid.js b/src/js/core/directives/ui-grid.js index 97ce1fe9b7..2e30f86169 100644 --- a/src/js/core/directives/ui-grid.js +++ b/src/js/core/directives/ui-grid.js @@ -2,9 +2,9 @@ 'use strict'; angular.module('ui.grid').controller('uiGridController', ['$scope', '$element', '$attrs', 'gridUtil', '$q', 'uiGridConstants', - '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile', 'ScrollEvent', + '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile', function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants, - $templateCache, gridClassFactory, $timeout, $parse, $compile, ScrollEvent) { + $templateCache, gridClassFactory, $timeout, $parse, $compile) { // gridUtil.logDebug('ui-grid controller'); var self = this; @@ -59,23 +59,6 @@ } } - function adjustInfiniteScrollPosition (scrollToRow) { - - var scrollEvent = new ScrollEvent(self.grid, null, null, 'ui.grid.adjustInfiniteScrollPosition'); - var totalRows = self.grid.renderContainers.body.visibleRowCache.length; - var percentage = ( scrollToRow + ( scrollToRow / ( totalRows - 1 ) ) ) / totalRows; - - //for infinite scroll, never allow it to be at the zero position so the up button can be active - if ( percentage === 0 ) { - scrollEvent.y = {pixels: 1}; - } - else { - scrollEvent.y = {percentage: percentage}; - } - scrollEvent.fireScrollingEvent(); - - } - function dataWatchFunction(newData) { // gridUtil.logDebug('dataWatch fired'); var promises = []; @@ -114,20 +97,6 @@ $scope.$evalAsync(function() { self.grid.refreshCanvas(true); self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW); - - $timeout(function () { - //Process post load scroll events if using infinite scroll - if ( self.grid.options.enableInfiniteScroll ) { - //If first load, seed the scrollbar down a little to activate the button - if ( self.grid.renderContainers.body.prevRowScrollIndex === 0 ) { - adjustInfiniteScrollPosition(0); - } - //If we are scrolling up, we need to reseed the grid. - if (self.grid.scrollDirection === uiGridConstants.scrollDirection.UP) { - adjustInfiniteScrollPosition(self.grid.renderContainers.body.prevRowScrollIndex + 1 + self.grid.options.excessRows); - } - } - }, 0); }); }); }); diff --git a/src/js/core/factories/GridRenderContainer.js b/src/js/core/factories/GridRenderContainer.js index 495b3eeaa4..7c39c73545 100644 --- a/src/js/core/factories/GridRenderContainer.js +++ b/src/js/core/factories/GridRenderContainer.js @@ -364,48 +364,19 @@ angular.module('ui.grid') if (rowCache.length > self.grid.options.virtualizationThreshold) { if (!(typeof(scrollTop) === 'undefined' || scrollTop === null)) { // Have we hit the threshold going down? - if (!self.grid.options.enableInfiniteScroll && self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) { + if ( !self.grid.suppressParentScrollDown && self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) { return; } //Have we hit the threshold going up? - if (!self.grid.options.enableInfiniteScroll && self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) { + if ( !self.grid.suppressParentScrollUp && self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) { return; } } var rangeStart = {}; var rangeEnd = {}; - //If infinite scroll is enabled, and we loaded more data coming from redrawInPlace, then recalculate the range and set rowIndex to proper place to scroll to - if ( self.grid.options.enableInfiniteScroll && self.grid.scrollDirection !== uiGridConstants.scrollDirection.NONE && postDataLoaded ) { - var findIndex = null; - var i = null; - if ( self.grid.scrollDirection === uiGridConstants.scrollDirection.UP ) { - findIndex = rowIndex > 0 ? self.grid.options.excessRows : 0; - for ( i = 0; i < rowCache.length; i++) { - if (self.grid.options.rowIdentity(rowCache[i].entity) === self.grid.options.rowIdentity(self.renderedRows[findIndex].entity)) { - rowIndex = i; - break; - } - } - rangeStart = Math.max(0, rowIndex); - rangeEnd = Math.min(rowCache.length, rangeStart + self.grid.options.excessRows + minRows); - } - else if ( self.grid.scrollDirection === uiGridConstants.scrollDirection.DOWN ) { - findIndex = minRows; - for ( i = 0; i < rowCache.length; i++) { - if (self.grid.options.rowIdentity(rowCache[i].entity) === self.grid.options.rowIdentity(self.renderedRows[findIndex].entity)) { - rowIndex = i; - break; - } - } - rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows - minRows); - rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows); - } - } - else { - rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows); - rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows); - } + rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows); + rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows); newRange = [rangeStart, rangeEnd]; }