diff --git a/misc/tutorial/212_infinite_scroll.ngdoc b/misc/tutorial/212_infinite_scroll.ngdoc index 9b4e27205a..d8f184d9f4 100644 --- a/misc/tutorial/212_infinite_scroll.ngdoc +++ b/misc/tutorial/212_infinite_scroll.ngdoc @@ -32,10 +32,20 @@ Specify percentage when lazy load should trigger: { name:'name' }, { name:'age' } ]; - var page = 1; + var page = 0; + var pageUp = 0; var getData = function(data, page) { var res = []; - for (var i = 0; i < page * 100 && i < data.length; ++i) { + for (var i = (page * 100); i < (page + 1) * 100 && i < data.length; ++i) { + res.push(data[i]); + } + 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; @@ -51,7 +61,7 @@ Specify percentage when lazy load should trigger: gridApi.infiniteScroll.on.needLoadMoreData($scope,function(){ $http.get('/data/10000_complex.json') .success(function(data) { - $scope.gridOptions.data = getData(data, page); + $scope.gridOptions.data = $scope.gridOptions.data.concat(getData(data, page)); ++page; gridApi.infiniteScroll.dataLoaded(); }) @@ -59,6 +69,17 @@ Specify percentage when lazy load should trigger: 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(); + }); + }); }; }]); diff --git a/src/features/infinite-scroll/js/infinite-scroll.js b/src/features/infinite-scroll/js/infinite-scroll.js index 53900e86cf..bd90ae8747 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', function (gridUtil, $compile, $timeout) { + module.service('uiGridInfiniteScrollService', ['gridUtil', '$compile', '$timeout', 'uiGridConstants', function (gridUtil, $compile, $timeout, uiGridConstants) { var service = { @@ -50,6 +50,17 @@ */ needLoadMoreData: function ($scope, fn) { + }, + + /** + * @ngdoc event + * @name needLoadMoreDataTop + * @eventOf ui.grid.infiniteScroll.api:PublicAPI + * @description This event fires when scroll reached top percentage of grid + * and needs to load data + */ + + needLoadMoreDataTop: function ($scope, fn) { } } }, @@ -99,12 +110,16 @@ * @ngdoc function * @name loadData * @methodOf ui.grid.infiniteScroll.service:uiGridInfiniteScrollService - * @description This function fires 'needLoadMoreData' event + * @description This function fires 'needLoadMoreData' or 'needLoadMoreDataTop' event based on scrollDirection */ loadData: function (grid) { - grid.options.loadTimout = true; - grid.api.infiniteScroll.raise.needLoadMoreData(); + grid.options.loadTimout = true; + if (grid.scrollDirection === uiGridConstants.scrollDirection.UP) { + grid.api.infiniteScroll.raise.needLoadMoreDataTop(); + return; + } + grid.api.infiniteScroll.raise.needLoadMoreData(); }, /** @@ -196,10 +211,14 @@ link: function ($scope, $elm, $attr){ if ($scope.grid.options.enableInfiniteScroll) { $scope.grid.api.core.on.scrollEvent($scope, function (args) { - if (args.y) { - var percentage = 100 - (args.y.percentage * 100); - uiGridInfiniteScrollService.checkScroll($scope.grid, percentage); - } + //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); + } }); } } diff --git a/src/features/infinite-scroll/test/infiniteScroll.spec.js b/src/features/infinite-scroll/test/infiniteScroll.spec.js index e2f0c15be9..dd9af5c09e 100644 --- a/src/features/infinite-scroll/test/infiniteScroll.spec.js +++ b/src/features/infinite-scroll/test/infiniteScroll.spec.js @@ -6,12 +6,14 @@ var uiGridInfiniteScrollService; var grid; var gridClassFactory; + var uiGridConstants; beforeEach(module('ui.grid.infiniteScroll')); - beforeEach(inject(function (_uiGridInfiniteScrollService_, _gridClassFactory_) { + beforeEach(inject(function (_uiGridInfiniteScrollService_, _gridClassFactory_, _uiGridConstants_) { uiGridInfiniteScrollService = _uiGridInfiniteScrollService_; gridClassFactory = _gridClassFactory_; + uiGridConstants = _uiGridConstants_; grid = gridClassFactory.createGrid({}); @@ -24,11 +26,16 @@ gridApi.infiniteScroll.on.needLoadMoreData(function(){ return []; }); - }; + gridApi.infiniteScroll.on.needLoadMoreDataTop(function(){ + return []; + }); + + }; uiGridInfiniteScrollService.initializeGrid(grid); - spyOn(grid.api.infiniteScroll.raise, 'needLoadMoreData'); - + spyOn(grid.api.infiniteScroll.raise, 'needLoadMoreData'); + spyOn(grid.api.infiniteScroll.raise, 'needLoadMoreDataTop'); + grid.options.data = [{col1:'a'},{col1:'b'}]; grid.buildColumns(); @@ -54,7 +61,14 @@ uiGridInfiniteScrollService.loadData(grid); expect(grid.api.infiniteScroll.raise.needLoadMoreData).toHaveBeenCalled(); }); - }); + + it('should call load data top function on grid event raise', function () { + grid.scrollDirection = uiGridConstants.scrollDirection.UP; + uiGridInfiniteScrollService.loadData(grid); + expect(grid.api.infiniteScroll.raise.needLoadMoreDataTop).toHaveBeenCalled(); + }); + + }); }); })(); \ No newline at end of file diff --git a/src/js/core/constants.js b/src/js/core/constants.js index e92b0b1ad2..8905d33898 100644 --- a/src/js/core/constants.js +++ b/src/js/core/constants.js @@ -84,7 +84,16 @@ // TODO(c0bra): Create full list of these somehow. NOTE: do any allow a space before or after them? CURRENCY_SYMBOLS: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫'], - + + scrollDirection: { + UP: 'up', + DOWN: 'down', + LEFT: 'left', + RIGHT: 'right', + NONE: 'none' + + }, + dataChange: { ALL: 'all', EDIT: 'edit', diff --git a/src/js/core/directives/ui-grid-viewport.js b/src/js/core/directives/ui-grid-viewport.js index 62656637e0..fe19eec7fd 100644 --- a/src/js/core/directives/ui-grid-viewport.js +++ b/src/js/core/directives/ui-grid-viewport.js @@ -1,8 +1,8 @@ (function(){ 'use strict'; - angular.module('ui.grid').directive('uiGridViewport', ['gridUtil','ScrollEvent', - function(gridUtil, ScrollEvent) { + angular.module('ui.grid').directive('uiGridViewport', ['gridUtil','ScrollEvent','uiGridConstants', + function(gridUtil, ScrollEvent, uiGridConstants) { return { replace: true, scope: {}, @@ -43,6 +43,9 @@ grid.flagScrollingHorizontally(); var xDiff = newScrollLeft - colContainer.prevScrollLeft; + if (xDiff > 0) { grid.scrollDirection = uiGridConstants.scrollDirection.RIGHT; } + if (xDiff < 0) { grid.scrollDirection = uiGridConstants.scrollDirection.LEFT; } + var horizScrollLength = (colContainer.getCanvasWidth() - colContainer.getViewportWidth()); if (horizScrollLength !== 0) { horizScrollPercentage = newScrollLeft / horizScrollLength; @@ -58,6 +61,8 @@ grid.flagScrollingVertically(); var yDiff = newScrollTop - rowContainer.prevScrollTop; + if (yDiff > 0 ) { grid.scrollDirection = uiGridConstants.scrollDirection.DOWN; } + if (yDiff < 0 ) { grid.scrollDirection = uiGridConstants.scrollDirection.UP; } var vertScrollLength = rowContainer.getVerticalScrollLength(); diff --git a/src/js/core/directives/ui-grid.js b/src/js/core/directives/ui-grid.js index a4c38f659d..2a2d56ed91 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', + '$templateCache', 'gridClassFactory', '$timeout', '$parse', '$compile', 'ScrollEvent', function ($scope, $elm, $attrs, gridUtil, $q, uiGridConstants, - $templateCache, gridClassFactory, $timeout, $parse, $compile) { + $templateCache, gridClassFactory, $timeout, $parse, $compile, ScrollEvent) { // gridUtil.logDebug('ui-grid controller'); var self = this; @@ -59,6 +59,23 @@ } } + 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 = []; @@ -97,6 +114,20 @@ $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/Grid.js b/src/js/core/factories/Grid.js index a24b328a94..6794983213 100644 --- a/src/js/core/factories/Grid.js +++ b/src/js/core/factories/Grid.js @@ -99,14 +99,25 @@ angular.module('ui.grid') * @description set to true when Grid is scrolling horizontally. Set to false via debounced method */ self.isScrollingHorizontally = false; - + + /** + * @ngdoc property + * @name scrollDirection + * @propertyOf ui.grid.class:Grid + * @description set one of the uiGridConstants.scrollDirection values (UP, DOWN, LEFT, RIGHT, NONE), which tells + * us which direction we are scrolling. Set to NONE via debounced method + */ + self.scrollDirection = uiGridConstants.scrollDirection.NONE; + var debouncedVertical = gridUtil.debounce(function () { self.isScrollingVertically = false; - }, 300); + self.scrollDirection = uiGridConstants.scrollDirection.NONE; + }, 1000); var debouncedHorizontal = gridUtil.debounce(function () { self.isScrollingHorizontally = false; - }, 300); + self.scrollDirection = uiGridConstants.scrollDirection.NONE; + }, 1000); /** @@ -1853,7 +1864,7 @@ angular.module('ui.grid') */ Grid.prototype.refreshCanvas = function(buildStyles) { var self = this; - + if (buildStyles) { self.buildStyles(); } @@ -1969,7 +1980,7 @@ angular.module('ui.grid') // gridUtil.logDebug('redrawing container', i); - container.adjustRows(null, container.prevScrolltopPercentage); + container.adjustRows(null, container.prevScrolltopPercentage, true); container.adjustColumns(null, container.prevScrollleftPercentage); } }; diff --git a/src/js/core/factories/GridRenderContainer.js b/src/js/core/factories/GridRenderContainer.js index 62500b37b7..9eee283324 100644 --- a/src/js/core/factories/GridRenderContainer.js +++ b/src/js/core/factories/GridRenderContainer.js @@ -314,7 +314,7 @@ angular.module('ui.grid') scrollTop = (this.getCanvasHeight() - this.getCanvasWidth()) * scrollPercentage; } - this.adjustRows(scrollTop, scrollPercentage); + this.adjustRows(scrollTop, scrollPercentage, false); this.prevScrollTop = scrollTop; this.prevScrolltopPercentage = scrollPercentage; @@ -339,7 +339,7 @@ angular.module('ui.grid') this.grid.queueRefresh(); }; - GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage) { + GridRenderContainer.prototype.adjustRows = function adjustRows(scrollTop, scrollPercentage, postDataLoaded) { var self = this; var minRows = self.minRowsToRender(); @@ -359,22 +359,53 @@ angular.module('ui.grid') if (rowIndex > maxRowIndex) { rowIndex = maxRowIndex; } - + var newRange = []; if (rowCache.length > self.grid.options.virtualizationThreshold) { if (!(typeof(scrollTop) === 'undefined' || scrollTop === null)) { // Have we hit the threshold going down? - if (self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) { + if (!self.grid.options.enableInfiniteScroll && self.prevScrollTop < scrollTop && rowIndex < self.prevRowScrollIndex + self.grid.options.scrollThreshold && rowIndex < maxRowIndex) { return; } //Have we hit the threshold going up? - if (self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) { + if (!self.grid.options.enableInfiniteScroll && self.prevScrollTop > scrollTop && rowIndex > self.prevRowScrollIndex - self.grid.options.scrollThreshold && rowIndex < maxRowIndex) { return; } } - - var rangeStart = Math.max(0, rowIndex - self.grid.options.excessRows); - var rangeEnd = Math.min(rowCache.length, rowIndex + minRows + self.grid.options.excessRows); + 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 (rowCache[i].entity.$$hashKey === self.renderedRows[findIndex].entity.$$hashKey) { + 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 (rowCache[i].entity.$$hashKey === self.renderedRows[findIndex].entity.$$hashKey) { + 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); + } newRange = [rangeStart, rangeEnd]; }