diff --git a/src/viewDirective.js b/src/viewDirective.js index 8c91c86bd..4d8e66d41 100644 --- a/src/viewDirective.js +++ b/src/viewDirective.js @@ -1,5 +1,3 @@ -var ngMajorVer = angular.version.major; -var ngMinorVer = angular.version.minor; /** * @ngdoc directive * @name ui.router.state.directive:ui-view @@ -24,33 +22,30 @@ var ngMinorVer = angular.version.minor; * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you * scroll ui-view elements into view when they are populated during a state activation. * - * @param {string=} noanimation If truthy, the non-animated renderer will be selected (no animations - * will be applied to the ui-view) - * * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* * * @param {string=} onload Expression to evaluate whenever the view updates. - * + * * @example - * A view can be unnamed or named. + * A view can be unnamed or named. *
  * 
- * 
- * + *
+ * * *
*
* - * You can only have one unnamed view within any template (or root html). If you are only using a + * You can only have one unnamed view within any template (or root html). If you are only using a * single view and it is unnamed then you can populate it like so: *
- * 
+ *
* $stateProvider.state("home", { * template: "

HELLO!

" * }) *
- * + * * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#methods_state `views`} * config property, by name, in this case an empty name: *
@@ -62,13 +57,13 @@ var ngMinorVer = angular.version.minor;
  *   }    
  * })
  * 
- * - * But typically you'll only use the views property if you name your view or have more than one view - * in the same template. There's not really a compelling reason to name a view if its the only one, + * + * But typically you'll only use the views property if you name your view or have more than one view + * in the same template. There's not really a compelling reason to name a view if its the only one, * but you could if you wanted, like so: *
  * 
- *
+ * *
  * $stateProvider.state("home", {
  *   views: {
@@ -78,14 +73,14 @@ var ngMinorVer = angular.version.minor;
  *   }    
  * })
  * 
- * + * * Really though, you'll use views to set up multiple views: *
  * 
- *
- *
+ *
+ *
*
- * + * *
  * $stateProvider.state("home", {
  *   views: {
@@ -116,8 +111,8 @@ var ngMinorVer = angular.version.minor;
  * 
  * 
*/ -$ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate']; -function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) { +$ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate', '$q']; +function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate, $q) { function getService() { return ($injector.has) ? function(service) { @@ -138,35 +133,24 @@ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) // Returns a set of DOM manipulation functions based on which Angular version // it should use function getRenderer(attrs, scope) { - var statics = { - enter: function (element, target, cb) { target.after(element); cb(); }, - leave: function (element, cb) { element.remove(); cb(); } + var statics = function() { + return { + enter: function (element, target, cb) { target.after(element); cb(); }, + leave: function (element, cb) { element.remove(); cb(); } + }; }; - if (!!attrs.noanimation) return statics; - - function animEnabled(element) { - if (ngMajorVer === 1 && ngMinorVer >= 4) return !!$animate.enabled(element); - if (ngMajorVer === 1 && ngMinorVer >= 2) return !!$animate.enabled(); - return (!!$animator); - } - - // ng 1.2+ if ($animate) { return { enter: function(element, target, cb) { - if (!animEnabled(element)) { - statics.enter(element, target, cb); - } else if (angular.version.minor > 2) { + if (angular.version.minor > 2) { $animate.enter(element, null, target).then(cb); } else { $animate.enter(element, null, target, cb); } }, leave: function(element, cb) { - if (!animEnabled(element)) { - statics.leave(element, cb); - } else if (angular.version.minor > 2) { + if (angular.version.minor > 2) { $animate.leave(element).then(cb); } else { $animate.leave(element, cb); @@ -175,7 +159,6 @@ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) }; } - // ng 1.1.5 if ($animator) { var animate = $animator && $animator(scope, attrs); @@ -185,7 +168,7 @@ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) }; } - return statics; + return statics(); } var directive = { @@ -198,7 +181,8 @@ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) var previousEl, currentEl, currentScope, latestLocals, onloadExp = attrs.onload || '', autoScrollExp = attrs.autoscroll, - renderer = getRenderer(attrs, scope); + renderer = getRenderer(attrs, scope), + inherited = $element.inheritedData('$uiView'); scope.$on('$stateChangeSuccess', function() { updateView(false); @@ -207,45 +191,34 @@ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) updateView(true); function cleanupLastView() { - var _previousEl = previousEl; - var _currentScope = currentScope; - - if (_currentScope) { - _currentScope._willBeDestroyed = true; + if (previousEl) { + previousEl.remove(); + previousEl = null; } - function cleanOld() { - if (_previousEl) { - _previousEl.remove(); - } - - if (_currentScope) { - _currentScope.$destroy(); - } + if (currentScope) { + currentScope.$destroy(); + currentScope = null; } if (currentEl) { + var $uiViewData = currentEl.data('$uiView'); renderer.leave(currentEl, function() { - cleanOld(); + $uiViewData.$$animLeave.resolve(); previousEl = null; }); previousEl = currentEl; - } else { - cleanOld(); - previousEl = null; + currentEl = null; } - - currentEl = null; - currentScope = null; } function updateView(firstTime) { var newScope, - name = getUiViewName(scope, attrs, $element, $interpolate), + name = getUiViewName(scope, attrs, inherited, $interpolate), previousLocals = name && $state.$current && $state.$current.locals[name]; - if (!firstTime && previousLocals === latestLocals || scope._willBeDestroyed) return; // nothing to do + if (!firstTime && previousLocals === latestLocals) return; // nothing to do newScope = scope.$new(); latestLocals = $state.$current.locals[name]; @@ -264,7 +237,16 @@ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) newScope.$emit('$viewContentLoading', name); var clone = $transclude(newScope, function(clone) { - renderer.enter(clone, $element, function onUiViewEnter() { + var animEnter = $q.defer(), animLeave = $q.defer(); + var viewData = { + name: name, + $animEnter: animEnter.promise, + $animLeave: animLeave.promise, + $$animLeave: animLeave + }; + + renderer.enter(clone.data('$uiView', viewData), $element, function onUiViewEnter() { + animEnter.resolve(); if(currentScope) { currentScope.$emit('$viewContentAnimationEnded'); } @@ -308,14 +290,14 @@ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate var initial = tElement.html(); return function (scope, $element, attrs) { var current = $state.$current, - name = getUiViewName(scope, attrs, $element, $interpolate), - locals = current && current.locals[name]; + $uiViewData = $element.data('$uiView'), + locals = current && current.locals[$uiViewData.name]; if (! locals) { return; } - $element.data('$uiView', { name: name, state: locals.$$state }); + extend($uiViewData, { state: locals.$$state }); $element.html(locals.$template ? locals.$template : initial); var link = $compile($element.contents()); @@ -341,9 +323,8 @@ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate * Shared ui-view code for both directives: * Given scope, element, and its attributes, return the view's name */ -function getUiViewName(scope, attrs, element, $interpolate) { +function getUiViewName(scope, attrs, inherited, $interpolate) { var name = $interpolate(attrs.uiView || attrs.name || '')(scope); - var inherited = element.inheritedData('$uiView'); return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : '')); } diff --git a/test/viewDirectiveSpec.js b/test/viewDirectiveSpec.js index fd9566326..d9a27441f 100644 --- a/test/viewDirectiveSpec.js +++ b/test/viewDirectiveSpec.js @@ -9,10 +9,11 @@ function animateFlush($animate) { describe('uiView', function () { 'use strict'; - var log, scope, $compile, elem; + var scope, $compile, elem, log; beforeEach(function() { var depends = ['ui.router']; + log = ""; try { angular.module('ngAnimate'); @@ -32,10 +33,6 @@ describe('uiView', function () { }); })); - beforeEach(function() { - log = ''; - }); - var aState = { template: 'aState template' }, @@ -105,6 +102,16 @@ describe('uiView', function () { template: 'view3' } } + }, + nState = { + template: 'nState', + controller: function ($scope, $element) { + var data = $element.data('$uiView'); + $scope.$on("$destroy", function() { log += 'destroy;'}); + data.$animEnter.then(function() { log += "animEnter;"}); + data.$animLeave.then(function() { + log += "animLeave;"}); + } }; beforeEach(module(function ($stateProvider) { @@ -121,15 +128,7 @@ describe('uiView', function () { .state('j', jState) .state('k', kState) .state('l', lState) - .state('m', { - controller: function($scope) { - log += 'ctrl(m);'; - $scope.$on('$destroy', function() { log += '$destroy(m);'; }); - } - }) - .state('n', { - controller: function($scope) { log += 'ctrl(n);'; } - }); + .state('n', nState) })); beforeEach(inject(function ($rootScope, _$compile_) { @@ -574,54 +573,32 @@ describe('uiView', function () { expect($animate.queue.length).toBe(0); })); - it ('should disable animations if noanimation="true" is present', inject(function($state, $q, $compile, $animate) { - var content = 'Initial Content', animation; - elem.append($compile('
' + content + '
')(scope)); - - animation = $animate.queue.shift(); - expect(animation).toBeUndefined(); + it ('should expose animation promises to controllers', inject(function($state, $q, $compile, $animate, $rootScope) { + $rootScope.$on('$stateChangeStart', function(evt, toState) { + log += 'start:' + toState.name + ';'; + }); + $rootScope.$on('$stateChangeSuccess', function(evt, toState) { + log += 'success:' + toState.name + ';'; + }); - $state.transitionTo(aState); + var content = 'Initial Content'; + elem.append($compile('
' + content + '
')(scope)); + $state.transitionTo('n'); $q.flush(); - animation = $animate.queue.shift(); - expect(animation).toBeUndefined(); - expect(elem.text()).toBe(aState.template); - $state.transitionTo(bState); - $q.flush(); - animation = $animate.queue.shift(); - expect(animation).toBeUndefined(); - expect(elem.text()).toBe(bState.template); - })); + expect($state.current.name).toBe('n'); + expect(log).toBe('start:n;success:n;'); - it('$destroy event is triggered after animation ends', inject(function($state, $q, $animate, $rootScope) { - elem.append($compile('
')(scope)); - $rootScope.$on('$stateChangeSuccess', function(evt, toState) { log += 'success(' + toState.name + ');'; }); + animateFlush($animate); + expect(log).toBe('start:n;success:n;animEnter;'); - $state.transitionTo('m'); - $q.flush(); - expect(log).toBe('success(m);ctrl(m);'); - $state.transitionTo('n'); + $state.transitionTo('a'); $q.flush(); - if ($animate) { - expect(log).toBe('success(m);ctrl(m);success(n);ctrl(n);'); - animateFlush($animate); - expect(log).toBe('success(m);ctrl(m);success(n);ctrl(n);$destroy(m);'); - } else { - expect(log).toBe('success(m);ctrl(m);$destroy(m);success(n);ctrl(n);'); - } - })); + expect($state.current.name).toBe('a'); + expect(log).toBe('start:n;success:n;animEnter;start:a;success:a;destroy;'); - it('$destroy event is triggered before $stateChangeSuccess if noanimation is present', inject(function($state, $q, $animate, $rootScope) { - elem.append($compile('
')(scope)); - $rootScope.$on('$stateChangeSuccess', function(evt, toState) { log += 'success(' + toState.name + ');'; }); - - $state.transitionTo('m'); - $q.flush(); - expect(log).toBe('success(m);ctrl(m);'); - $state.transitionTo('n'); - $q.flush(); - expect(log).toBe('success(m);ctrl(m);success(n);$destroy(m);ctrl(n);'); + animateFlush($animate); + expect(log).toBe('start:n;success:n;animEnter;start:a;success:a;destroy;animLeave;'); })); });