From c119498d1bdfe9c5ec0d7453fce8f9a18227977c Mon Sep 17 00:00:00 2001 From: Andy Joslin Date: Tue, 11 Feb 2014 13:32:03 -0500 Subject: [PATCH] feat($ionicScrollDelegate): add scrollTo(left,top,animate) to delegate Also moves $ionicScrollDelegate.register to $ionicScroll controller, and makes `` directive be registered with $ionicScrollDelegate. --- config/karma.conf.js | 2 +- .../src/controller/ionicScrollController.js | 11 ++- js/ext/angular/src/directive/ionicContent.js | 3 - js/ext/angular/src/directive/ionicScroll.js | 21 ----- .../service/delegates/ionicScrollDelegate.js | 31 ++++---- .../controller/ionicScrollController.unit.js | 11 ++- .../test/directive/ionicContent.unit.js | 7 +- .../test/directive/ionicScroll.unit.js | 67 ++++++++++++++++ js/ext/angular/test/scroll.html | 14 +++- .../delegates/ionicScrollDelegate.unit.js | 76 +++++++++++-------- 10 files changed, 158 insertions(+), 85 deletions(-) create mode 100644 js/ext/angular/test/directive/ionicScroll.unit.js diff --git a/config/karma.conf.js b/config/karma.conf.js index 95eb989cc91..81915fffa0b 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -18,7 +18,7 @@ module.exports = function(config) { 'config/lib/js/angular/angular.js', 'config/lib/js/angular/angular-animate.js', 'config/lib/js/angular/angular-mocks.js', - 'config/lib/js/angular-ui/angular-ui-router.js', + 'config/lib/js/angular-ui/angular-ui-router.js' ] .concat(buildConfig.ionicFiles) .concat(buildConfig.angularIonicFiles) diff --git a/js/ext/angular/src/controller/ionicScrollController.js b/js/ext/angular/src/controller/ionicScrollController.js index 928e334cf4f..8a5198de267 100644 --- a/js/ext/angular/src/controller/ionicScrollController.js +++ b/js/ext/angular/src/controller/ionicScrollController.js @@ -3,8 +3,8 @@ angular.module('ionic.ui.scroll') -.controller('$ionicScroll', ['$scope', 'scrollViewOptions', '$timeout', - function($scope, scrollViewOptions, $timeout) { +.controller('$ionicScroll', ['$scope', 'scrollViewOptions', '$timeout', '$ionicScrollDelegate', + function($scope, scrollViewOptions, $timeout, $ionicScrollDelegate) { scrollViewOptions.bouncing = angular.isDefined(scrollViewOptions.bouncing) ? scrollViewOptions.bouncing : @@ -15,11 +15,14 @@ angular.module('ionic.ui.scroll') var element = this.element = scrollViewOptions.el; var scrollView = this.scrollView = new ionic.views.Scroll(scrollViewOptions); - this.$element = angular.element(element); + var $element = this.$element = angular.element(element); //Attach self to element as a controller so other directives can require this controller //through `require: '$ionicScroll' - this.$element.data('$$ionicScrollController', this); + $element.data('$$ionicScrollController', this); + + //Register delegate for event handling + $ionicScrollDelegate.register($scope, $element, scrollView); $timeout(function() { scrollView.run(); diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index ce206736072..0012e6f8474 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -120,9 +120,6 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll']) }; } - // Register for scroll delegate event handling - $ionicScrollDelegate.register($scope, $element); - // Check if this supports infinite scrolling and listen for scroll events // to trigger the infinite scrolling // TODO(ajoslin): move functionality out of this function and make testable diff --git a/js/ext/angular/src/directive/ionicScroll.js b/js/ext/angular/src/directive/ionicScroll.js index 63513750783..1eb88e2f418 100644 --- a/js/ext/angular/src/directive/ionicScroll.js +++ b/js/ext/angular/src/directive/ionicScroll.js @@ -33,8 +33,6 @@ angular.module('ionic.ui.scroll', []) function prelink($scope, $element, $attr) { var scrollView, scrollCtrl, sc = $element[0].children[0]; - // Create the internal scroll div - sc.className = 'scroll'; if(attr.padding == "true") { sc.classList.add('padding'); } @@ -63,25 +61,6 @@ angular.module('ionic.ui.scroll', []) scrollViewOptions: scrollViewOptions }); scrollView = $scope.$parent.scrollView = scrollCtrl.scrollView; - - $element.bind('scroll', function(e) { - $scope.onScroll({ - event: e, - scrollTop: e.detail ? e.detail.scrollTop : e.originalEvent ? e.originalEvent.detail.scrollTop : 0, - scrollLeft: e.detail ? e.detail.scrollLeft: e.originalEvent ? e.originalEvent.detail.scrollLeft : 0 - }); - }); - - $scope.$parent.$on('scroll.resize', function(e) { - // Run the resize after this digest - $timeout(function() { - scrollView && scrollView.resize(); - }); - }); - - $scope.$parent.$on('scroll.refreshComplete', function(e) { - scrollView && scrollView.finishPullToRefresh(); - }); } } }; diff --git a/js/ext/angular/src/service/delegates/ionicScrollDelegate.js b/js/ext/angular/src/service/delegates/ionicScrollDelegate.js index d4bb6aa5797..9b2ea289583 100644 --- a/js/ext/angular/src/service/delegates/ionicScrollDelegate.js +++ b/js/ext/angular/src/service/delegates/ionicScrollDelegate.js @@ -14,6 +14,9 @@ angular.module('ionic.ui.service.scrollDelegate', []) scrollBottom: function(animate) { $rootScope.$broadcast('scroll.scrollBottom', animate); }, + scrollTo: function(left, top, animate) { + $rootScope.$broadcast('scroll.scrollTo', left, top, animate); + }, resize: function() { $rootScope.$broadcast('scroll.resize'); }, @@ -45,18 +48,14 @@ angular.module('ionic.ui.service.scrollDelegate', []) getScrollView: function($scope) { return $scope.scrollView; }, + /** - * Register a scope for scroll event handling. + * Register a scope and scroll view for scroll event handling. * $scope {Scope} the scope to register and listen for events */ - register: function($scope, $element) { - //Get scroll controller from parent - var scrollCtrl = $element.controller('$ionicScroll'); - if (!scrollCtrl) { - return; - } - var scrollView = scrollCtrl.scrollView; - var scrollEl = scrollCtrl.element; + register: function($scope, $element, scrollView) { + + var scrollEl = $element[0]; function scrollViewResize() { // Run the resize after this digest @@ -93,14 +92,14 @@ angular.module('ionic.ui.service.scrollDelegate', []) }); }); - /** - * Called to scroll to the top of the content - * - * @param animate {boolean} whether to animate or just snap - */ + $scope.$parent.$on('scroll.scrollTo', function(e, left, top, animate) { + scrollViewResize().then(function() { + scrollView.scrollTo(left, top, !!animate); + }); + }); $scope.$parent.$on('scroll.scrollTop', function(e, animate) { scrollViewResize().then(function() { - scrollView.scrollTo(0, 0, animate === false ? false : true); + scrollView.scrollTo(0, 0, !!animate); }); }); $scope.$parent.$on('scroll.scrollBottom', function(e, animate) { @@ -108,7 +107,7 @@ angular.module('ionic.ui.service.scrollDelegate', []) var sv = scrollView; if (sv) { var max = sv.getScrollMax(); - sv.scrollTo(0, max.top, animate === false ? false : true); + sv.scrollTo(max.left, max.top, !!animate); } }); }); diff --git a/js/ext/angular/test/controller/ionicScrollController.unit.js b/js/ext/angular/test/controller/ionicScrollController.unit.js index c8c93d030b9..ac11a8b789c 100644 --- a/js/ext/angular/test/controller/ionicScrollController.unit.js +++ b/js/ext/angular/test/controller/ionicScrollController.unit.js @@ -1,6 +1,6 @@ describe('$ionicScroll Controller', function() { - beforeEach(module('ionic.ui.scroll')); + beforeEach(module('ionic')); var scope, ctrl, timeout; function setup(options) { @@ -41,6 +41,12 @@ describe('$ionicScroll Controller', function() { expect(ctrl.scrollView.run).toHaveBeenCalled(); }); + it('should register with $ionicScrollDelegate', inject(function($ionicScrollDelegate) { + spyOn($ionicScrollDelegate, 'register'); + setup(); + expect($ionicScrollDelegate.register).toHaveBeenCalledWith(scope, ctrl.$element, ctrl.scrollView); + })); + it('should not setup if no child .scroll-refresher', function() { setup(); timeout.flush(); @@ -70,7 +76,6 @@ describe('$ionicScroll Controller', function() { }); scope.onRefresh = jasmine.createSpy('onRefresh'); - scope.$parent.$broadcast = jasmine.createSpy('$broadcast'); timeout.flush(); var refresher = ctrl.refresher; @@ -87,13 +92,11 @@ describe('$ionicScroll Controller', function() { expect(refresher.classList.contains('refreshing')).toBe(false); expect(scope.onRefresh).not.toHaveBeenCalled(); - expect(scope.$parent.$broadcast).not.toHaveBeenCalledWith('scroll.onRefresh'); doneCb(); expect(refresher.classList.contains('active')).toBe(false); expect(refresher.classList.contains('refreshing')).toBe(true); expect(scope.onRefresh).toHaveBeenCalled(); - expect(scope.$parent.$broadcast).toHaveBeenCalledWith('scroll.onRefresh'); }); }); diff --git a/js/ext/angular/test/directive/ionicContent.unit.js b/js/ext/angular/test/directive/ionicContent.unit.js index 6de09dd1eb1..a569679740c 100644 --- a/js/ext/angular/test/directive/ionicContent.unit.js +++ b/js/ext/angular/test/directive/ionicContent.unit.js @@ -1,7 +1,7 @@ describe('Ionic Content directive', function() { var compile, element, scope; - beforeEach(module('ionic.ui.content')); + beforeEach(module('ionic')); beforeEach(inject(function($compile, $rootScope, $timeout, $window) { compile = $compile; @@ -11,6 +11,11 @@ describe('Ionic Content directive', function() { ionic.Platform.setPlatform('Android'); })); + it('Has $ionicScroll controller', function() { + element = compile('')(scope); + expect(element.controller('$ionicScroll').element).toBe(element[0]); + }); + it('Has content class', function() { element = compile('')(scope); expect(element.hasClass('scroll-content')).toBe(true); diff --git a/js/ext/angular/test/directive/ionicScroll.unit.js b/js/ext/angular/test/directive/ionicScroll.unit.js new file mode 100644 index 00000000000..cff691b81d6 --- /dev/null +++ b/js/ext/angular/test/directive/ionicScroll.unit.js @@ -0,0 +1,67 @@ +describe('Ionic Scroll Directive', function() { + var compile, element, scope; + + beforeEach(module('ionic')); + + beforeEach(inject(function($compile, $rootScope, $timeout, $window) { + compile = $compile; + scope = $rootScope; + timeout = $timeout; + window = $window; + ionic.Platform.setPlatform('Android'); + })); + + it('Has $ionicScroll controller', function() { + element = compile('')(scope); + expect(element.controller('$ionicScroll').element).toBe(element[0]); + }); + + it('Has scroll-view class', function() { + element = compile('')(scope); + expect(element.hasClass('scroll-view')).toBe(true); + }); + + it('should add padding classname', function() { + element = compile('')(scope); + expect(element.children().eq(0).hasClass('padding')).toEqual(true); + var scrollElement = element.find('.scroll'); + expect(scrollElement.hasClass('padding')).toEqual(true); + }); + + it('Enables bouncing by default', function() { + ionic.Platform.setPlatform('iPhone'); + element = compile('')(scope); + scope.$apply(); + var newScope = element.isolateScope(); + var scrollView = scope.scrollView; + expect(scrollView.options.bouncing).toBe(true); + }); + + it('Disables bouncing when has-bouncing = false', function() { + ionic.Platform.setPlatform('iPhone'); + element = compile('')(scope); + scope.$apply(); + var newScope = element.isolateScope(); + var scrollView = scope.scrollView; + expect(scrollView.options.bouncing).toBe(false); + }); + + it('Disables bouncing by default on Android', function() { + ionic.Platform.setPlatform('Android'); + element = compile('')(scope); + scope.$apply(); + var newScope = element.isolateScope(); + var scrollView = scope.scrollView; + expect(scrollView.options.bouncing).toBe(false); + }); + + it('Should set start x and y', function() { + element = compile('')(scope); + scope.$apply(); + var newScope = element.isolateScope(); + var scrollView = scope.scrollView; + var vals = scrollView.getValues(); + expect(vals.left).toBe(100); + expect(vals.top).toBe(300); + }); +}); diff --git a/js/ext/angular/test/scroll.html b/js/ext/angular/test/scroll.html index a581b3a1e1c..3e4488a6feb 100644 --- a/js/ext/angular/test/scroll.html +++ b/js/ext/angular/test/scroll.html @@ -52,20 +52,24 @@

Hourly Forecast

- +
+
{{hour.temp}} °F
- + + Scroll to 100 + +
@@ -83,7 +87,7 @@

Hourly Forecast

} }) - .controller('ThisCtrl', function($scope) { + .controller('ThisCtrl', function($scope, $ionicScrollDelegate) { var header = document.getElementById('header'); var content = document.getElementById('container'); @@ -97,6 +101,10 @@

Hourly Forecast

$scope.onScrollComplete = function(event, scrollTop, scrollLeft) { console.log('Scroll complete', scrollTop, scrollLeft); } + $scope.scrollTo = function() { + console.log('scrollTo'); + $ionicScrollDelegate.scrollTo(0, 100, true); + }; $scope.onScroll = function(event, scrollTop, scrollLeft) { /* if(scrollTop > startTop) { diff --git a/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js b/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js index 17f7562f262..3bf027b0675 100644 --- a/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js +++ b/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js @@ -11,15 +11,6 @@ describe('Ionic ScrollDelegate Service', function() { compile = $compile; })); - it('Should register', function() { - spyOn(del, 'register'); - - var scope = rootScope.$new(); - var el = compile('')(scope); - - expect(del.register).toHaveBeenCalled(); - }); - it('Should get scroll view', function() { var scope = rootScope.$new(); var el = compile('')(scope); @@ -33,43 +24,64 @@ describe('Ionic ScrollDelegate Service', function() { var sv = del.getScrollView(scope); spyOn(sv, 'resize'); + spyOn(sv, 'scrollTo'); del.resize(); timeout.flush(); expect(sv.resize).toHaveBeenCalled(); }); - it('Should resize & scroll top', function() { - var scope = rootScope.$new(); - var el = compile('')(scope); + testWithAnimate(true); + testWithAnimate(false); + function testWithAnimate(animate) { + describe('with animate='+animate, function() { + it('should resize & scroll top', function() { + var scope = rootScope.$new(); + var el = compile('')(scope); - var sv = del.getScrollView(scope); - spyOn(sv, 'resize'); + var sv = del.getScrollView(scope); + spyOn(sv, 'resize'); + spyOn(sv, 'scrollTo'); + del.scrollTop(animate); - expect(sv.getValues().top).toBe(100); + timeout.flush(); + expect(sv.resize).toHaveBeenCalled(); + expect(sv.scrollTo.mostRecentCall.args).toEqual([0, 0, animate]); + }); - del.scrollTop(false); - timeout.flush(); - expect(sv.resize).toHaveBeenCalled(); + it('should resize & scroll bottom', function() { + var scope = rootScope.$new(); + var el = compile('

')(scope); - expect(sv.getValues().top).toBe(0); - }); - - it('Should resize & scroll bottom', function() { - var scope = rootScope.$new(); - var el = compile('')(scope); + var sv = del.getScrollView(scope); + spyOn(sv, 'getScrollMax').andCallFake(function() { + return { left: 10, top: 11 }; + }); + spyOn(sv, 'resize'); + spyOn(sv, 'scrollTo'); + var max = sv.getScrollMax(); + del.scrollBottom(animate); - var sv = del.getScrollView(scope); - spyOn(sv, 'resize'); + timeout.flush(); + expect(sv.resize).toHaveBeenCalled(); + expect(sv.scrollTo.mostRecentCall.args).toEqual([max.left, max.top, animate]); + }); - expect(sv.getValues().top).toBe(100); + it('should resize & scrollTo', function() { + var scope = rootScope.$new(); + var el = compile('

')(scope); - del.scrollBottom(false); - timeout.flush(); - expect(sv.resize).toHaveBeenCalled(); + var sv = del.getScrollView(scope); + spyOn(sv, 'scrollTo'); + spyOn(sv, 'resize'); + del.scrollTo(2, 3, animate); - expect(sv.getValues().top).toBe(sv.getScrollMax().top); - }); + timeout.flush(); + expect(sv.resize).toHaveBeenCalled(); + expect(sv.scrollTo.mostRecentCall.args).toEqual([2, 3, animate]); + }); + }); + } it('should finish refreshing', function() { var scope = rootScope.$new();