From 6b2549d794d3bb7e687c7ccafef1286b7c9ca9ca Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Mon, 13 Oct 2014 14:41:22 +0300 Subject: [PATCH] feat($anchorScroll): add support for scrolling independently of `$location.hash()` Add an optional argument to `$anchorScroll()` to enable scrolling to an anchor element different than that related to the current value of `$location.hash()`. If the argument is omitted, the value of `$location.hash()` will be used instead. Closes #4568 --- src/ng/anchorScroll.js | 15 ++-- test/ng/anchorScrollSpec.js | 136 +++++++++++++++++++++++++++--------- 2 files changed, 113 insertions(+), 38 deletions(-) diff --git a/src/ng/anchorScroll.js b/src/ng/anchorScroll.js index baaac2f3fda0..784b5a0b6520 100644 --- a/src/ng/anchorScroll.js +++ b/src/ng/anchorScroll.js @@ -38,9 +38,10 @@ function $AnchorScrollProvider() { * @requires $rootScope * * @description - * When called, it checks the current value of {@link ng.$location#hash $location.hash()} and - * scrolls to the related element, according to the rules specified in the - * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). + * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the + * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified + * in the + * [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). * * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to * match any anchor whenever it changes. This can be disabled by calling @@ -49,6 +50,9 @@ function $AnchorScrollProvider() { * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a * vertical scroll-offset (either fixed or dynamic). * + * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of + * {@link ng.$location#hash $location.hash()} will be used. + * * @property {(number|function|jqLite)} yOffset * If set, specifies a vertical scroll-offset. This is often useful when there are fixed * positioned elements at the top of the page, such as navbars, headers etc. @@ -232,8 +236,9 @@ function $AnchorScrollProvider() { } } - function scroll() { - var hash = $location.hash(), elm; + function scroll(hash) { + hash = (arguments.length && isString(hash)) ? hash : $location.hash(); + var elm; // empty hash, scroll to the top of the page if (!hash) scrollTo(null); diff --git a/test/ng/anchorScrollSpec.js b/test/ng/anchorScrollSpec.js index 63213458e55e..2d09b0bb46d4 100644 --- a/test/ng/anchorScrollSpec.js +++ b/test/ng/anchorScrollSpec.js @@ -23,7 +23,6 @@ describe('$anchorScroll', function() { }; } - function addElements() { var elements = sliceArgs(arguments); @@ -49,9 +48,10 @@ describe('$anchorScroll', function() { }; } - function callAnchorScroll() { + function callAnchorScroll(hash) { + var args = arguments; return function($anchorScroll) { - $anchorScroll(); + $anchorScroll.apply(null, args); }; } @@ -141,50 +141,120 @@ describe('$anchorScroll', function() { beforeEach(createMockWindow()); - it('should scroll to top of the window if empty hash', inject( - changeHashAndScroll(''), - expectScrollingToTop)); + describe('and implicitly using `$location.hash()`', function() { + + 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 not scroll if hash does not match any element', inject( - addElements('id=one', 'id=two'), - changeHashAndScroll('non-existing'), - expectNoScrolling())); + it('should scroll to top if hash == "top" and no matching element', inject( + changeHashAndScroll('top'), + expectScrollingToTop)); - it('should scroll to anchor element with name', inject( - addElements('a name=abc'), - changeHashAndScroll('abc'), - expectScrollingTo('a name=abc'))); + it('should scroll to element with id "top" if present', inject( + addElements('id=top'), + changeHashAndScroll('top'), + expectScrollingTo('id=top'))); + }); + + + describe('and specifying a hash', function() { + + it('should ignore the `hash` argument if not a string', inject( + spyOnJQLiteDocumentLoaded(), + addElements('id=one', 'id=two'), + changeHashTo('one'), // won't scroll since `jqLiteDocumentLoaded()` is spied upon + callAnchorScroll({}), + expectScrollingTo('id=one'), + unspyOnJQLiteDocumentLoaded())); + + + it('should ignore `$location.hash()` if `hash` is passed as argument', inject( + spyOnJQLiteDocumentLoaded(), + addElements('id=one', 'id=two'), + changeHashTo('one'), // won't scroll since `jqLiteDocumentLoaded()` is spied upon + callAnchorScroll('two'), + expectScrollingTo('id=two'), + unspyOnJQLiteDocumentLoaded())); + + it('should scroll to top of the window if empty hash', inject( + callAnchorScroll(''), + expectScrollingToTop)); - 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 not scroll if hash does not match any element', inject( + addElements('id=one', 'id=two'), + callAnchorScroll('non-existing'), + 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 anchor element with name', inject( + addElements('a name=abc'), + callAnchorScroll('abc'), + expectScrollingTo('a name=abc'))); - it('should scroll to element with id with precedence over name', inject( - addElements('name=abc', 'id=abc'), - changeHashAndScroll('abc'), - expectScrollingTo('id=abc'))); + it('should not scroll to other than anchor element with name', inject( + addElements('input name=xxl', 'select name=xxl', 'form name=xxl'), + callAnchorScroll('xxl'), + expectNoScrolling())); - it('should scroll to top if hash == "top" and no matching element', inject( - changeHashAndScroll('top'), - expectScrollingToTop)); + it('should scroll to anchor even if other element with given name exist', inject( + addElements('input name=some', 'a name=some'), + callAnchorScroll('some'), + expectScrollingTo('a name=some'))); - it('should scroll to element with id "top" if present', inject( - addElements('id=top'), - changeHashAndScroll('top'), - expectScrollingTo('id=top'))); + + it('should scroll to element with id with precedence over name', inject( + addElements('name=abc', 'id=abc'), + callAnchorScroll('abc'), + expectScrollingTo('id=abc'))); + + + it('should scroll to top if hash == "top" and no matching element', inject( + callAnchorScroll('top'), + expectScrollingToTop)); + + + it('should scroll to element with id "top" if present', inject( + addElements('id=top'), + callAnchorScroll('top'), + expectScrollingTo('id=top'))); + }); });