diff --git a/Gruntfile.js b/Gruntfile.js index 74d80b9c7..c89f050a5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -36,6 +36,7 @@ module.exports = function (grunt) { 'src/urlRouter.js', 'src/state.js', 'src/view.js', + 'src/viewScroll.js', 'src/viewDirective.js', 'src/stateDirectives.js', 'src/compat.js' diff --git a/config/karma.js b/config/karma.js index bfd0dc94a..d17bedd29 100644 --- a/config/karma.js +++ b/config/karma.js @@ -19,6 +19,7 @@ module.exports = function (karma) { 'src/urlRouter.js', 'src/view.js', 'src/state.js', + 'src/viewScroll.js', 'src/viewDirective.js', 'src/stateDirectives.js', 'src/stateFilters.js', @@ -63,4 +64,4 @@ module.exports = function (karma) { // - PhantomJS browsers: [ 'PhantomJS' ] }) -}; \ No newline at end of file +}; diff --git a/src/viewDirective.js b/src/viewDirective.js index 891da4a6d..a2aedfc71 100644 --- a/src/viewDirective.js +++ b/src/viewDirective.js @@ -6,18 +6,17 @@ * @requires $compile * @requires $controller * @requires $injector - * @requires $anchorScroll * * @restrict ECA * * @description - * The ui-view directive tells $state where to place your templates. + * The ui-view directive tells $state where to place your templates. * A view can be unnamed or named. * * @param {string} ui-view A view name. */ -$ViewDirective.$inject = ['$state', '$compile', '$controller', '$injector', '$anchorScroll']; -function $ViewDirective( $state, $compile, $controller, $injector, $anchorScroll) { +$ViewDirective.$inject = ['$state', '$compile', '$controller', '$injector', '$uiViewScroll']; +function $ViewDirective( $state, $compile, $controller, $injector, $uiViewScroll) { var $animator = $injector.has('$animator') ? $injector.get('$animator') : false; var viewIsUpdating = false; @@ -31,6 +30,7 @@ function $ViewDirective( $state, $compile, $controller, $injector, $an var viewScope, viewLocals, name = attr[directive.name] || attr.name || '', onloadExp = attr.onload || '', + autoscrollExp = attr.autoscroll, animate = $animator && $animator(scope, attr), initialView = transclude(scope); @@ -121,9 +121,9 @@ function $ViewDirective( $state, $compile, $controller, $injector, $an viewScope.$emit('$viewContentLoaded'); if (onloadExp) viewScope.$eval(onloadExp); - // TODO: This seems strange, shouldn't $anchorScroll listen for $viewContentLoaded if necessary? - // $anchorScroll might listen on event... - $anchorScroll(); + if (!angular.isDefined(autoscrollExp) || !autoscrollExp || scope.$eval(autoscrollExp)) { + $uiViewScroll(element); + } } }; } diff --git a/src/viewScroll.js b/src/viewScroll.js new file mode 100644 index 000000000..b2ce2e02e --- /dev/null +++ b/src/viewScroll.js @@ -0,0 +1,36 @@ +/** + * @ngdoc provider + * @name ui.router.state.$uiViewScroll + * + * @requires $anchorScroll + * @requires $timeout + * + * @description + * When called with a jqLite element, it scrolls the element into view (after a + * `$timeout` so the DOM has time to refresh). + * + * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, + * this can be enabled by calling `$uiViewScrollProvider.useAnchorScroll()`. + */ +function $ViewScrollProvider() { + + var useAnchorScroll = false; + + this.useAnchorScroll = function () { + useAnchorScroll = true; + }; + + this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { + if (useAnchorScroll) { + return $anchorScroll; + } + + return function ($element) { + $timeout(function () { + $element[0].scrollIntoView(); + }, 0, false); + }; + }]; +} + +angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider); diff --git a/test/debug.js b/test/debug.js index 4e9b49bc0..c7ca201da 100644 --- a/test/debug.js +++ b/test/debug.js @@ -21,6 +21,7 @@ module.exports = function(config) { '../src/urlRouter.js', '../src/view.js', '../src/state.js', + '../src/viewScroll.js', '../src/viewDirective.js', '../src/stateDirectives.js', '../src/compat.js', diff --git a/test/viewDirectiveSpec.js b/test/viewDirectiveSpec.js index 2b182bc7e..de7e8d944 100644 --- a/test/viewDirectiveSpec.js +++ b/test/viewDirectiveSpec.js @@ -17,6 +17,12 @@ describe('uiView', function () { beforeEach(module('ui.router')); + beforeEach(module(function ($provide) { + $provide.decorator('$uiViewScroll', function ($delegate) { + return jasmine.createSpy('$uiViewScroll'); + }); + })); + var aState = { template: 'aState template' }, @@ -209,4 +215,34 @@ describe('uiView', function () { })); }); + describe('autoscroll attribute', function () { + it('should autoscroll when unspecified', inject(function ($state, $q, $uiViewScroll) { + elem.append($compile('
')(scope)); + $state.transitionTo(aState); + $q.flush(); + expect($uiViewScroll).toHaveBeenCalledWith(elem.find('div')); + })); + + it('should autoscroll when expression is missing', inject(function ($state, $q, $uiViewScroll) { + elem.append($compile('')(scope)); + $state.transitionTo(aState); + $q.flush(); + expect($uiViewScroll).toHaveBeenCalledWith(elem.find('div')); + })); + + it('should autoscroll based on expression', inject(function ($state, $q, $uiViewScroll) { + elem.append($compile('')(scope)); + + scope.doScroll = false; + $state.transitionTo(aState); + $q.flush(); + expect($uiViewScroll).not.toHaveBeenCalled(); + + scope.doScroll = true; + $state.transitionTo(bState); + $q.flush(); + expect($uiViewScroll).toHaveBeenCalledWith(elem.find('div')); + })); + }); + }); diff --git a/test/viewScrollSpec.js b/test/viewScrollSpec.js new file mode 100644 index 000000000..c46cae198 --- /dev/null +++ b/test/viewScrollSpec.js @@ -0,0 +1,35 @@ +describe('uiView', function () { + 'use strict'; + + beforeEach(module('ui.router')); + + describe('scrollIntoView', function () { + var elem; + + beforeEach(function () { + elem = [{ scrollIntoView: jasmine.createSpy('scrollIntoView') }]; + }); + + it('should scroll element into view after timeout', inject(function ($uiViewScroll, $timeout) { + $uiViewScroll(elem); + expect(elem[0].scrollIntoView).not.toHaveBeenCalled(); + + $timeout.flush(); + expect(elem[0].scrollIntoView).toHaveBeenCalled(); + })); + }); + + describe('useAnchorScroll', function () { + beforeEach(module(function ($provide, $uiViewScrollProvider) { + $provide.decorator('$anchorScroll', function ($delegate) { + return jasmine.createSpy('$anchorScroll'); + }); + $uiViewScrollProvider.useAnchorScroll(); + })); + + it('should call $anchorScroll', inject(function ($uiViewScroll, $anchorScroll) { + $uiViewScroll(); + expect($anchorScroll).toHaveBeenCalled(); + })); + }); +});