Skip to content

Commit c2bbd9e

Browse files
committed
feat($ionicScrollDelegate): add .anchorScroll() function
Scrolls to the location of element with id matching $location.hash(). If $location.hash() is blank or the id does not exist, it will scroll to the top.
1 parent a970f0b commit c2bbd9e

File tree

2 files changed

+103
-10
lines changed

2 files changed

+103
-10
lines changed

Diff for: js/ext/angular/src/service/delegates/ionicScrollDelegate.js

+29-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
angular.module('ionic.ui.service.scrollDelegate', [])
55

6-
.factory('$ionicScrollDelegate', ['$rootScope', '$timeout', '$q', function($rootScope, $timeout, $q) {
6+
.factory('$ionicScrollDelegate', ['$rootScope', '$timeout', '$q', '$anchorScroll', '$location', '$document', function($rootScope, $timeout, $q, $anchorScroll, $location, $document) {
77
return {
88
/**
99
* Trigger a scroll-to-top event on child scrollers.
@@ -17,6 +17,9 @@ angular.module('ionic.ui.service.scrollDelegate', [])
1717
resize: function() {
1818
$rootScope.$broadcast('scroll.resize');
1919
},
20+
anchorScroll: function() {
21+
$rootScope.$broadcast('scroll.anchorScroll');
22+
},
2023
tapScrollToTop: function(element) {
2124
var _this = this;
2225

@@ -47,29 +50,46 @@ angular.module('ionic.ui.service.scrollDelegate', [])
4750
* $scope {Scope} the scope to register and listen for events
4851
*/
4952
register: function($scope, $element) {
53+
//Get scroll controller from parent
54+
var scrollCtrl = $element.controller('$ionicScroll');
55+
if (!scrollCtrl) {
56+
return;
57+
}
58+
var scrollView = scrollCtrl.scrollView;
59+
var scrollEl = scrollCtrl.element;
5060

5161
function scrollViewResize() {
5262
// Run the resize after this digest
5363
return $timeout(function() {
54-
$scope.$parent.scrollView && $scope.$parent.scrollView.resize();
64+
scrollView.resize();
5565
});
5666
}
5767

5868
$element.bind('scroll', function(e) {
59-
$scope.onScroll({
69+
$scope.onScroll && $scope.onScroll({
6070
event: e,
6171
scrollTop: e.detail ? e.detail.scrollTop : e.originalEvent ? e.originalEvent.detail.scrollTop : 0,
6272
scrollLeft: e.detail ? e.detail.scrollLeft: e.originalEvent ? e.originalEvent.detail.scrollLeft : 0
6373
});
6474
});
6575

66-
$scope.$parent.$on('scroll.resize', function(e) {
67-
scrollViewResize();
68-
});
76+
$scope.$parent.$on('scroll.resize', scrollViewResize);
6977

7078
// Called to stop refreshing on the scroll view
7179
$scope.$parent.$on('scroll.refreshComplete', function(e) {
72-
$scope.$parent.scrollView && $scope.$parent.scrollView.finishPullToRefresh();
80+
scrollView.finishPullToRefresh();
81+
});
82+
83+
$scope.$parent.$on('scroll.anchorScroll', function() {
84+
var hash = $location.hash();
85+
var elm;
86+
//If there are multiple with this id, go to first one
87+
if (hash && (elm = $document.body.querySelectorAll('#' + hash)[0])) {
88+
var scroll = ionic.DomUtil.getPositionInParent(elm, scrollEl);
89+
scrollView.scrollTo(scroll.left, scroll.top);
90+
} else {
91+
scrollView.scrollTo(0,0);
92+
}
7393
});
7494

7595
/**
@@ -79,12 +99,12 @@ angular.module('ionic.ui.service.scrollDelegate', [])
7999
*/
80100
$scope.$parent.$on('scroll.scrollTop', function(e, animate) {
81101
scrollViewResize().then(function() {
82-
$scope.$parent.scrollView && $scope.$parent.scrollView.scrollTo(0, 0, animate === false ? false : true);
102+
scrollView.scrollTo(0, 0, animate === false ? false : true);
83103
});
84104
});
85105
$scope.$parent.$on('scroll.scrollBottom', function(e, animate) {
86106
scrollViewResize().then(function() {
87-
var sv = $scope.$parent.scrollView;
107+
var sv = scrollView;
88108
if (sv) {
89109
var max = sv.getScrollMax();
90110
sv.scrollTo(0, max.top, animate === false ? false : true);

Diff for: js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js

+74-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('Ionic ScrollDelegate Service', function() {
5555
expect(sv.getValues().top).toBe(0);
5656
});
5757

58-
it('Should resize & scroll top', function() {
58+
it('Should resize & scroll bottom', function() {
5959
var scope = rootScope.$new();
6060
var el = compile('<content start-y="100"></content>')(scope);
6161

@@ -83,3 +83,76 @@ describe('Ionic ScrollDelegate Service', function() {
8383
});
8484
});
8585

86+
describe('anchorScroll', function() {
87+
function setLocationHash(hash) {
88+
inject(function($location) {
89+
$location.hash = function() { return hash; };
90+
});
91+
}
92+
93+
beforeEach(module('ionic'));
94+
95+
var contentEl, scope, del;
96+
beforeEach(inject(function($rootScope, $compile, $timeout, $document, $ionicScrollDelegate) {
97+
scope = $rootScope.$new();
98+
contentEl = $compile('<content></content>')(scope);
99+
100+
mockBody = angular.element('<div>').append(contentEl);
101+
$document.body = mockBody[0];
102+
del = $ionicScrollDelegate
103+
}));
104+
105+
it('should anchorScroll to an element with id', function() {
106+
var anchorMe = angular.element('<div id="anchorMe">');
107+
var sv = del.getScrollView(scope);
108+
spyOn(sv, 'scrollTo');
109+
110+
setLocationHash('anchorMe');
111+
contentEl.append(anchorMe);
112+
113+
var pos = ionic.DomUtil.getPositionInParent(anchorMe[0], contentEl[0]);
114+
del.anchorScroll();
115+
expect(sv.scrollTo).toHaveBeenCalledWith(pos.left, pos.top);
116+
});
117+
118+
it('should anchorScroll to top if !$location.hash()', function() {
119+
var sv = del.getScrollView(scope);
120+
spyOn(sv, 'scrollTo');
121+
del.anchorScroll();
122+
expect(sv.scrollTo).toHaveBeenCalledWith(0, 0);
123+
});
124+
125+
it('should anchorScroll to top if element with hash id doesnt exist', function() {
126+
var sv = del.getScrollView(scope);
127+
spyOn(sv, 'scrollTo');
128+
129+
setLocationHash('doesnotexist');
130+
del.anchorScroll();
131+
132+
expect(sv.scrollTo).toHaveBeenCalledWith(0, 0);
133+
});
134+
135+
it('should anchorScroll to first element with id if multiple exist', function() {
136+
var foo1 = angular.element('<div id="foo">hello</div>');
137+
var foo2 = angular.element('<div id="foo">hola</div>');
138+
var sv = del.getScrollView(scope);
139+
140+
contentEl.append(foo1).append(foo2);
141+
142+
//Fake the top/left because dom doesn't have time to load in a test
143+
spyOn(ionic.DomUtil, 'getPositionInParent').andCallFake(function(el) {
144+
return el === foo1[0] ? {left: 20, top: 40} : {left: 30, top: 50};
145+
});
146+
var pos1 = ionic.DomUtil.getPositionInParent(foo1[0], contentEl[0]);
147+
var pos2 = ionic.DomUtil.getPositionInParent(foo2[0], contentEl[0]);
148+
149+
spyOn(sv, 'scrollTo');
150+
setLocationHash('foo');
151+
del.anchorScroll();
152+
expect(sv.scrollTo.callCount).toBe(1);
153+
expect(sv.scrollTo).toHaveBeenCalledWith(pos1.left, pos1.top);
154+
expect(sv.scrollTo).not.toHaveBeenCalledWith(pos2.left, pos2.top);
155+
});
156+
157+
});
158+

0 commit comments

Comments
 (0)