diff --git a/js/ext/angular/src/service/delegates/ionicScrollDelegate.js b/js/ext/angular/src/service/delegates/ionicScrollDelegate.js index d4e6afad59d..22b0cb2abd3 100644 --- a/js/ext/angular/src/service/delegates/ionicScrollDelegate.js +++ b/js/ext/angular/src/service/delegates/ionicScrollDelegate.js @@ -3,7 +3,7 @@ angular.module('ionic.ui.service.scrollDelegate', []) -.factory('$ionicScrollDelegate', ['$rootScope', '$timeout', '$q', function($rootScope, $timeout, $q) { +.factory('$ionicScrollDelegate', ['$rootScope', '$timeout', '$q', '$anchorScroll', '$location', '$document', function($rootScope, $timeout, $q, $anchorScroll, $location, $document) { return { /** * Trigger a scroll-to-top event on child scrollers. @@ -17,6 +17,9 @@ angular.module('ionic.ui.service.scrollDelegate', []) resize: function() { $rootScope.$broadcast('scroll.resize'); }, + anchorScroll: function() { + $rootScope.$broadcast('scroll.anchorScroll'); + }, tapScrollToTop: function(element) { var _this = this; @@ -47,29 +50,46 @@ angular.module('ionic.ui.service.scrollDelegate', []) * $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; function scrollViewResize() { // Run the resize after this digest return $timeout(function() { - $scope.$parent.scrollView && $scope.$parent.scrollView.resize(); + scrollView.resize(); }); } $element.bind('scroll', function(e) { - $scope.onScroll({ + $scope.onScroll && $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) { - scrollViewResize(); - }); + $scope.$parent.$on('scroll.resize', scrollViewResize); // Called to stop refreshing on the scroll view $scope.$parent.$on('scroll.refreshComplete', function(e) { - $scope.$parent.scrollView && $scope.$parent.scrollView.finishPullToRefresh(); + scrollView.finishPullToRefresh(); + }); + + $scope.$parent.$on('scroll.anchorScroll', function() { + var hash = $location.hash(); + var elm; + //If there are multiple with this id, go to first one + if (hash && (elm = $document.body.querySelectorAll('#' + hash)[0])) { + var scroll = ionic.DomUtil.getPositionInParent(elm, scrollEl); + scrollView.scrollTo(scroll.left, scroll.top); + } else { + scrollView.scrollTo(0,0); + } }); /** @@ -79,12 +99,12 @@ angular.module('ionic.ui.service.scrollDelegate', []) */ $scope.$parent.$on('scroll.scrollTop', function(e, animate) { scrollViewResize().then(function() { - $scope.$parent.scrollView && $scope.$parent.scrollView.scrollTo(0, 0, animate === false ? false : true); + scrollView.scrollTo(0, 0, animate === false ? false : true); }); }); $scope.$parent.$on('scroll.scrollBottom', function(e, animate) { scrollViewResize().then(function() { - var sv = $scope.$parent.scrollView; + var sv = scrollView; if (sv) { var max = sv.getScrollMax(); sv.scrollTo(0, max.top, animate === false ? false : true); diff --git a/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js b/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js index 03ca1039ba9..67e00ee0b7a 100644 --- a/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js +++ b/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js @@ -55,7 +55,7 @@ describe('Ionic ScrollDelegate Service', function() { expect(sv.getValues().top).toBe(0); }); - it('Should resize & scroll top', function() { + it('Should resize & scroll bottom', function() { var scope = rootScope.$new(); var el = compile('')(scope); @@ -83,3 +83,76 @@ describe('Ionic ScrollDelegate Service', function() { }); }); +describe('anchorScroll', function() { + function setLocationHash(hash) { + inject(function($location) { + $location.hash = function() { return hash; }; + }); + } + + beforeEach(module('ionic')); + + var contentEl, scope, del; + beforeEach(inject(function($rootScope, $compile, $timeout, $document, $ionicScrollDelegate) { + scope = $rootScope.$new(); + contentEl = $compile('')(scope); + + mockBody = angular.element('
').append(contentEl); + $document.body = mockBody[0]; + del = $ionicScrollDelegate + })); + + it('should anchorScroll to an element with id', function() { + var anchorMe = angular.element('
'); + var sv = del.getScrollView(scope); + spyOn(sv, 'scrollTo'); + + setLocationHash('anchorMe'); + contentEl.append(anchorMe); + + var pos = ionic.DomUtil.getPositionInParent(anchorMe[0], contentEl[0]); + del.anchorScroll(); + expect(sv.scrollTo).toHaveBeenCalledWith(pos.left, pos.top); + }); + + it('should anchorScroll to top if !$location.hash()', function() { + var sv = del.getScrollView(scope); + spyOn(sv, 'scrollTo'); + del.anchorScroll(); + expect(sv.scrollTo).toHaveBeenCalledWith(0, 0); + }); + + it('should anchorScroll to top if element with hash id doesnt exist', function() { + var sv = del.getScrollView(scope); + spyOn(sv, 'scrollTo'); + + setLocationHash('doesnotexist'); + del.anchorScroll(); + + expect(sv.scrollTo).toHaveBeenCalledWith(0, 0); + }); + + it('should anchorScroll to first element with id if multiple exist', function() { + var foo1 = angular.element('
hello
'); + var foo2 = angular.element('
hola
'); + var sv = del.getScrollView(scope); + + contentEl.append(foo1).append(foo2); + + //Fake the top/left because dom doesn't have time to load in a test + spyOn(ionic.DomUtil, 'getPositionInParent').andCallFake(function(el) { + return el === foo1[0] ? {left: 20, top: 40} : {left: 30, top: 50}; + }); + var pos1 = ionic.DomUtil.getPositionInParent(foo1[0], contentEl[0]); + var pos2 = ionic.DomUtil.getPositionInParent(foo2[0], contentEl[0]); + + spyOn(sv, 'scrollTo'); + setLocationHash('foo'); + del.anchorScroll(); + expect(sv.scrollTo.callCount).toBe(1); + expect(sv.scrollTo).toHaveBeenCalledWith(pos1.left, pos1.top); + expect(sv.scrollTo).not.toHaveBeenCalledWith(pos2.left, pos2.top); + }); + +}); +