From 15fd735793cffe89fdf9662275409cdcdb3e801a Mon Sep 17 00:00:00 2001
From: Vojta Jina
Date: Thu, 12 Jan 2012 03:00:34 -0800
Subject: refactor($autoScroll): rename to $anchorScroll and allow disabling
auto scrolling (links)
Now, that we have autoscroll attribute on ng:include, there is no reason to disable the service completely, so $anchorScrollProvider.disableAutoScrolling() means it won't be scrolling when $location.hash() changes.
And then, it's not $autoScroll at all, it actually scrolls to anchor when it's called, so I renamed
it to $anchorScroll.
---
angularFiles.js | 2 +-
src/AngularPublic.js | 2 +-
src/service/anchorScroll.js | 66 +++++++++++++
src/service/autoScroll.js | 64 -------------
src/widgets.js | 16 ++--
test/service/anchorScrollSpec.js | 186 +++++++++++++++++++++++++++++++++++++
test/service/autoScrollSpec.js | 196 ---------------------------------------
test/widgetsSpec.js | 16 ++--
8 files changed, 270 insertions(+), 278 deletions(-)
create mode 100644 src/service/anchorScroll.js
delete mode 100644 src/service/autoScroll.js
create mode 100644 test/service/anchorScrollSpec.js
delete mode 100644 test/service/autoScrollSpec.js
diff --git a/angularFiles.js b/angularFiles.js
index 889d7f52..e2a37bb5 100644
--- a/angularFiles.js
+++ b/angularFiles.js
@@ -9,7 +9,7 @@ angularFiles = {
'src/sanitizer.js',
'src/jqLite.js',
'src/apis.js',
- 'src/service/autoScroll.js',
+ 'src/service/anchorScroll.js',
'src/service/browser.js',
'src/service/cacheFactory.js',
'src/service/compiler.js',
diff --git a/src/AngularPublic.js b/src/AngularPublic.js
index 4973f574..3614eb9a 100644
--- a/src/AngularPublic.js
+++ b/src/AngularPublic.js
@@ -66,7 +66,7 @@ function publishExternalAPI(angular){
$provide.value('$directive', angularDirective);
$provide.value('$widget', angularWidget);
- $provide.service('$autoScroll', $AutoScrollProvider);
+ $provide.service('$anchorScroll', $AnchorScrollProvider);
$provide.service('$browser', $BrowserProvider);
$provide.service('$cacheFactory', $CacheFactoryProvider);
$provide.service('$compile', $CompileProvider);
diff --git a/src/service/anchorScroll.js b/src/service/anchorScroll.js
new file mode 100644
index 00000000..19a09498
--- /dev/null
+++ b/src/service/anchorScroll.js
@@ -0,0 +1,66 @@
+/**
+ * @ngdoc function
+ * @name angular.module.ng.$anchorScroll
+ * @requires $window
+ * @requires $location
+ * @requires $rootScope
+ *
+ * @description
+ * When called, it checks current value of `$location.hash()` and scroll to related element,
+ * according to rules specified in
+ * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}.
+ *
+ * It also watches the `$location.hash()` and scroll whenever it changes to match any anchor.
+ * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
+ */
+function $AnchorScrollProvider() {
+
+ var autoScrollingEnabled = true;
+
+ this.disableAutoScrolling = function() {
+ autoScrollingEnabled = false;
+ };
+
+ this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
+ var document = $window.document;
+
+ // helper function to get first anchor from a NodeList
+ // can't use filter.filter, as it accepts only instances of Array
+ // and IE can't convert NodeList to an array using [].slice
+ // TODO(vojta): use filter if we change it to accept lists as well
+ function getFirstAnchor(list) {
+ var result = null;
+ forEach(list, function(element) {
+ if (!result && lowercase(element.nodeName) === 'a') result = element;
+ });
+ return result;
+ }
+
+ function scroll() {
+ var hash = $location.hash(), elm;
+
+ // empty hash, scroll to the top of the page
+ if (!hash) $window.scrollTo(0, 0);
+
+ // element with given id
+ else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
+
+ // first anchor with given name :-D
+ else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
+
+ // no element and hash == 'top', scroll to the top of the page
+ else if (hash === 'top') $window.scrollTo(0, 0);
+ }
+
+ // does not scroll when user clicks on anchor link that is currently on
+ // (no url change, no $locaiton.hash() change), browser native does scroll
+ if (autoScrollingEnabled) {
+ $rootScope.$watch(function() {return $location.hash();}, function() {
+ $rootScope.$evalAsync(scroll);
+ });
+ }
+
+ return scroll;
+ }];
+}
+
diff --git a/src/service/autoScroll.js b/src/service/autoScroll.js
deleted file mode 100644
index 223400f4..00000000
--- a/src/service/autoScroll.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * @ngdoc function
- * @name angular.module.ng.$autoScroll
- * @requires $window
- * @requires $location
- * @requires $rootScope
- *
- * @description
- * When called, it checks current value of `$location.hash()` and scroll to related element,
- * according to rules specified in
- * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}.
- *
- * It also watches the `$location.hash()` and scroll whenever it changes to match any anchor.
- *
- * You can disable `$autoScroll` service by calling `disable()` on `$autoScrollProvider`.
- * Note: disabling is only possible before the service is instantiated !
- */
-function $AutoScrollProvider() {
-
- this.disable = function() {
- this.$get = function() {return noop;};
- };
-
- this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
- var document = $window.document;
-
- // helper function to get first anchor from a NodeList
- // can't use filter.filter, as it accepts only instances of Array
- // and IE can't convert NodeList to an array using [].slice
- // TODO(vojta): use filter if we change it to accept lists as well
- function getFirstAnchor(list) {
- var result = null;
- forEach(list, function(element) {
- if (!result && lowercase(element.nodeName) === 'a') result = element;
- });
- return result;
- }
-
- function scroll() {
- var hash = $location.hash(), elm;
-
- // empty hash, scroll to the top of the page
- if (!hash) $window.scrollTo(0, 0);
-
- // element with given id
- else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
-
- // first anchor with given name :-D
- else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
-
- // no element and hash == 'top', scroll to the top of the page
- else if (hash === 'top') $window.scrollTo(0, 0);
- }
-
- // does not scroll when user clicks on anchor link that is currently on
- // (no url change, no $locaiton.hash() change), browser native does scroll
- $rootScope.$watch(function() {return $location.hash();}, function() {
- $rootScope.$evalAsync(scroll);
- });
-
- return scroll;
- }];
-}
-
diff --git a/src/widgets.js b/src/widgets.js
index 63ddaf36..09a800de 100644
--- a/src/widgets.js
+++ b/src/widgets.js
@@ -43,8 +43,8 @@
* instance of angular.module.ng.$rootScope.Scope to set the HTML fragment to.
* @param {string=} onload Expression to evaluate when a new partial is loaded.
*
- * @param {string=} autoscroll Whether `ng:include` should call {@link angular.module.ng.$autoScroll
- * $autoScroll} to scroll the viewport after the content is loaded.
+ * @param {string=} autoscroll Whether `ng:include` should call {@link angular.module.ng.$anchorScroll
+ * $anchorScroll} to scroll the viewport after the content is loaded.
*
* - If the attribute is not set, disable scrolling.
* - If the attribute is set without value, enable scrolling.
@@ -99,8 +99,8 @@ angularWidget('ng:include', function(element){
this.directives(true);
} else {
element[0]['ng:compiled'] = true;
- return ['$http', '$templateCache', '$autoScroll', '$element',
- function($http, $templateCache, $autoScroll, element) {
+ return ['$http', '$templateCache', '$anchorScroll', '$element',
+ function($http, $templateCache, $anchorScroll, element) {
var scope = this,
changeCounter = 0,
childScope;
@@ -133,7 +133,7 @@ angularWidget('ng:include', function(element){
childScope = useScope ? useScope : scope.$new();
compiler.compile(element)(childScope);
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
- $autoScroll();
+ $anchorScroll();
}
scope.$eval(onloadExp);
}
@@ -568,8 +568,8 @@ angularWidget('ng:view', function(element) {
if (!element[0]['ng:compiled']) {
element[0]['ng:compiled'] = true;
- return ['$http', '$templateCache', '$route', '$autoScroll', '$element',
- function($http, $templateCache, $route, $autoScroll, element) {
+ return ['$http', '$templateCache', '$route', '$anchorScroll', '$element',
+ function($http, $templateCache, $route, $anchorScroll, element) {
var template;
var changeCounter = 0;
@@ -593,7 +593,7 @@ angularWidget('ng:view', function(element) {
if (newChangeCounter == changeCounter) {
element.html(response);
compiler.compile(element)($route.current.scope);
- $autoScroll();
+ $anchorScroll();
}
}).error(clearContent);
} else {
diff --git a/test/service/anchorScrollSpec.js b/test/service/anchorScrollSpec.js
new file mode 100644
index 00000000..7e4b3aa3
--- /dev/null
+++ b/test/service/anchorScrollSpec.js
@@ -0,0 +1,186 @@
+describe('$anchorScroll', function() {
+
+ var elmSpy;
+
+ function addElements() {
+ var elements = sliceArgs(arguments);
+
+ return function() {
+ forEach(elements, function(identifier) {
+ var match = identifier.match(/(\w* )?(\w*)=(\w*)/),
+ jqElm = jqLite('<' + (match[1] || 'a ') + match[2] + '="' + match[3] + '"/>'),
+ elm = jqElm[0];
+
+ elmSpy[identifier] = spyOn(elm, 'scrollIntoView');
+ jqLite(document.body).append(jqElm);
+ });
+ };
+ }
+
+ function changeHashAndScroll(hash) {
+ return function($location, $anchorScroll) {
+ $location.hash(hash);
+ $anchorScroll();
+ };
+ }
+
+ function expectScrollingToTop($window) {
+ forEach(elmSpy, function(spy, id) {
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ expect($window.scrollTo).toHaveBeenCalledWith(0, 0);
+ }
+
+ function expectScrollingTo(identifier) {
+ return function($window) {
+ forEach(elmSpy, function(spy, id) {
+ if (identifier === id) expect(spy).toHaveBeenCalledOnce();
+ else expect(spy).not.toHaveBeenCalled();
+ });
+ expect($window.scrollTo).not.toHaveBeenCalled();
+ };
+ }
+
+ function expectNoScrolling() {
+ return expectScrollingTo(NaN);
+ }
+
+
+ beforeEach(module(function($provide) {
+ elmSpy = {};
+ $provide.value('$window', {
+ scrollTo: jasmine.createSpy('$window.scrollTo'),
+ document: document
+ });
+ }));
+
+
+ it('should scroll to top of the window if empty hash', inject(
+ changeHashAndScroll(''),
+ expectScrollingToTop));
+
+
+ it('should not scroll if hash does not match any element', inject(
+ addElements('id=one', 'id=two'),
+ changeHashAndScroll('non-existing'),
+ expectNoScrolling()));
+
+
+ it('should scroll to anchor element with name', inject(
+ addElements('a name=abc'),
+ changeHashAndScroll('abc'),
+ expectScrollingTo('a name=abc')));
+
+
+ it('should not scroll to other than anchor element with name', inject(
+ addElements('input name=xxl', 'select name=xxl', 'form name=xxl'),
+ changeHashAndScroll('xxl'),
+ expectNoScrolling()));
+
+
+ it('should scroll to anchor even if other element with given name exist', inject(
+ addElements('input name=some', 'a name=some'),
+ changeHashAndScroll('some'),
+ expectScrollingTo('a name=some')));
+
+
+ it('should scroll to element with id with precedence over name', inject(
+ addElements('name=abc', 'id=abc'),
+ changeHashAndScroll('abc'),
+ expectScrollingTo('id=abc')));
+
+
+ it('should scroll to top if hash == "top" and no matching element', inject(
+ changeHashAndScroll('top'),
+ expectScrollingToTop));
+
+
+ it('should scroll to element with id "top" if present', inject(
+ addElements('id=top'),
+ changeHashAndScroll('top'),
+ expectScrollingTo('id=top')));
+
+
+ describe('watcher', function() {
+
+ function initLocation(config) {
+ return function($provide, $locationProvider) {
+ $provide.value('$sniffer', {history: config.historyApi});
+ $locationProvider.html5Mode(config.html5Mode);
+ };
+ }
+
+ function changeHashTo(hash) {
+ return function ($location, $rootScope, $anchorScroll) {
+ $rootScope.$apply(function() {
+ $location.hash(hash);
+ });
+ };
+ }
+
+ function disableAutoScrolling() {
+ return function($anchorScrollProvider) {
+ $anchorScrollProvider.disableAutoScrolling();
+ };
+ }
+
+ afterEach(inject(function($document) {
+ dealoc($document);
+ }));
+
+
+ it('should scroll to element when hash change in hashbang mode', function() {
+ module(initLocation({html5Mode: false, historyApi: true}));
+ inject(
+ addElements('id=some'),
+ changeHashTo('some'),
+ expectScrollingTo('id=some')
+ );
+ });
+
+
+ it('should scroll to element when hash change in html5 mode with no history api', function() {
+ module(initLocation({html5Mode: true, historyApi: false}));
+ inject(
+ addElements('id=some'),
+ changeHashTo('some'),
+ expectScrollingTo('id=some')
+ );
+ });
+
+
+ it('should not scroll when element does not exist', function() {
+ module(initLocation({html5Mode: false, historyApi: false}));
+ inject(
+ addElements('id=some'),
+ changeHashTo('other'),
+ expectNoScrolling()
+ );
+ });
+
+
+ it('should scroll when html5 mode with history api', function() {
+ module(initLocation({html5Mode: true, historyApi: true}));
+ inject(
+ addElements('id=some'),
+ changeHashTo('some'),
+ expectScrollingTo('id=some')
+ );
+ });
+
+
+ it('should not scroll when disabled', function() {
+ module(
+ disableAutoScrolling(),
+ initLocation({html5Mode: false, historyApi: false})
+ );
+ inject(
+ addElements('id=fake'),
+ changeHashTo('fake'),
+ expectNoScrolling()
+ );
+ });
+ });
+});
+
diff --git a/test/service/autoScrollSpec.js b/test/service/autoScrollSpec.js
deleted file mode 100644
index 72fc3424..00000000
--- a/test/service/autoScrollSpec.js
+++ /dev/null
@@ -1,196 +0,0 @@
-describe('$autoScroll', function() {
-
- var elmSpy;
-
- function addElements() {
- var elements = sliceArgs(arguments);
-
- return function() {
- forEach(elements, function(identifier) {
- var match = identifier.match(/(\w* )?(\w*)=(\w*)/),
- jqElm = jqLite('<' + (match[1] || 'a ') + match[2] + '="' + match[3] + '"/>'),
- elm = jqElm[0];
-
- elmSpy[identifier] = spyOn(elm, 'scrollIntoView');
- jqLite(document.body).append(jqElm);
- });
- };
- }
-
- function changeHashAndScroll(hash) {
- return function($location, $autoScroll) {
- $location.hash(hash);
- $autoScroll();
- };
- }
-
- function expectScrollingToTop($window) {
- forEach(elmSpy, function(spy, id) {
- expect(spy).not.toHaveBeenCalled();
- });
-
- expect($window.scrollTo).toHaveBeenCalledWith(0, 0);
- }
-
- function expectScrollingTo(identifier) {
- return function($window) {
- forEach(elmSpy, function(spy, id) {
- if (identifier === id) expect(spy).toHaveBeenCalledOnce();
- else expect(spy).not.toHaveBeenCalled();
- });
- expect($window.scrollTo).not.toHaveBeenCalled();
- };
- }
-
- function expectNoScrolling() {
- return expectScrollingTo(NaN);
- }
-
- function disableScroller() {
- return function($autoScrollProvider) {
- $autoScrollProvider.disable();
- };
- }
-
-
- beforeEach(module(function($provide) {
- elmSpy = {};
- $provide.value('$window', {
- scrollTo: jasmine.createSpy('$window.scrollTo'),
- document: document
- });
- }));
-
-
- it('should scroll to top of the window if empty hash', inject(
- changeHashAndScroll(''),
- expectScrollingToTop));
-
-
- it('should not scroll if hash does not match any element', inject(
- addElements('id=one', 'id=two'),
- changeHashAndScroll('non-existing'),
- expectNoScrolling()));
-
-
- it('should scroll to anchor element with name', inject(
- addElements('a name=abc'),
- changeHashAndScroll('abc'),
- expectScrollingTo('a name=abc')));
-
-
- it('should not scroll to other than anchor element with name', inject(
- addElements('input name=xxl', 'select name=xxl', 'form name=xxl'),
- changeHashAndScroll('xxl'),
- expectNoScrolling()));
-
-
- it('should scroll to anchor even if other element with given name exist', inject(
- addElements('input name=some', 'a name=some'),
- changeHashAndScroll('some'),
- expectScrollingTo('a name=some')));
-
-
- it('should scroll to element with id with precedence over name', inject(
- addElements('name=abc', 'id=abc'),
- changeHashAndScroll('abc'),
- expectScrollingTo('id=abc')));
-
-
- it('should scroll to top if hash == "top" and no matching element', inject(
- changeHashAndScroll('top'),
- expectScrollingToTop));
-
-
- it('should scroll to element with id "top" if present', inject(
- addElements('id=top'),
- changeHashAndScroll('top'),
- expectScrollingTo('id=top')));
-
-
- it('should not scroll when disabled', function() {
- module(disableScroller());
- inject(
- addElements('id=fake', 'a name=fake', 'input name=fake'),
- changeHashAndScroll('fake'),
- expectNoScrolling()
- );
- });
-
-
- describe('watcher', function() {
-
- function initLocation(config) {
- return function($provide, $locationProvider) {
- $provide.value('$sniffer', {history: config.historyApi});
- $locationProvider.html5Mode(config.html5Mode);
- };
- }
-
- function changeHashTo(hash) {
- return function ($location, $rootScope, $autoScroll) {
- $rootScope.$apply(function() {
- $location.hash(hash);
- });
- };
- }
-
- afterEach(inject(function($document) {
- dealoc($document);
- }));
-
-
- it('should scroll to element when hash change in hashbang mode', function() {
- module(initLocation({html5Mode: false, historyApi: true}));
- inject(
- addElements('id=some'),
- changeHashTo('some'),
- expectScrollingTo('id=some')
- );
- });
-
-
- it('should scroll to element when hash change in html5 mode with no history api', function() {
- module(initLocation({html5Mode: true, historyApi: false}));
- inject(
- addElements('id=some'),
- changeHashTo('some'),
- expectScrollingTo('id=some')
- );
- });
-
-
- it('should not scroll when element does not exist', function() {
- module(initLocation({html5Mode: false, historyApi: false}));
- inject(
- addElements('id=some'),
- changeHashTo('other'),
- expectNoScrolling()
- );
- });
-
-
- it('should scroll when html5 mode with history api', function() {
- module(initLocation({html5Mode: true, historyApi: true}));
- inject(
- addElements('id=some'),
- changeHashTo('some'),
- expectScrollingTo('id=some')
- );
- });
-
-
- it('should not scroll when disabled', function() {
- module(
- disableScroller(),
- initLocation({html5Mode: false, historyApi: false})
- );
- inject(
- addElements('id=fake'),
- changeHashTo('fake'),
- expectNoScrolling()
- );
- });
- });
-});
-
diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js
index 753a36b4..f119174f 100644
--- a/test/widgetsSpec.js
+++ b/test/widgetsSpec.js
@@ -225,10 +225,10 @@ describe('widget', function() {
describe('autoscoll', function() {
var autoScrollSpy;
- function spyOnAutoScroll() {
+ function spyOnAnchorScroll() {
return function($provide) {
- autoScrollSpy = jasmine.createSpy('$autoScroll');
- $provide.value('$autoScroll', autoScrollSpy);
+ autoScrollSpy = jasmine.createSpy('$anchorScroll');
+ $provide.value('$anchorScroll', autoScrollSpy);
};
}
@@ -247,20 +247,20 @@ describe('widget', function() {
};
}
- beforeEach(module(spyOnAutoScroll()));
+ beforeEach(module(spyOnAnchorScroll()));
beforeEach(inject(
putIntoCache('template.html', 'CONTENT'),
putIntoCache('another.html', 'CONTENT')));
- it('should call $autoScroll if autoscroll attribute is present', inject(
+ it('should call $anchorScroll if autoscroll attribute is present', inject(
compileAndLink(''),
changeTplAndValueTo('template.html'), function() {
expect(autoScrollSpy).toHaveBeenCalledOnce();
}));
- it('should call $autoScroll if autoscroll evaluates to true', inject(
+ it('should call $anchorScroll if autoscroll evaluates to true', inject(
compileAndLink(''),
changeTplAndValueTo('template.html', true),
changeTplAndValueTo('another.html', 'some-string'),
@@ -270,14 +270,14 @@ describe('widget', function() {
}));
- it('should not call $autoScroll if autoscroll attribute is not present', inject(
+ it('should not call $anchorScroll if autoscroll attribute is not present', inject(
compileAndLink(''),
changeTplAndValueTo('template.html'), function() {
expect(autoScrollSpy).not.toHaveBeenCalled();
}));
- it('should not call $autoScroll if autoscroll evaluates to false', inject(
+ it('should not call $anchorScroll if autoscroll evaluates to false', inject(
compileAndLink(''),
changeTplAndValueTo('template.html', false),
changeTplAndValueTo('template.html', undefined),
--
cgit v1.2.3