diff options
| -rw-r--r-- | src/ngRoute/directive/ngView.js | 139 | ||||
| -rw-r--r-- | test/ngRoute/directive/ngViewSpec.js | 251 | 
2 files changed, 207 insertions, 183 deletions
| diff --git a/src/ngRoute/directive/ngView.js b/src/ngRoute/directive/ngView.js index 3074df49..c4add9ce 100644 --- a/src/ngRoute/directive/ngView.js +++ b/src/ngRoute/directive/ngView.js @@ -15,8 +15,10 @@ ngRouteModule.directive('ngView', ngViewFactory);   * configuration of the `$route` service.   *   * @animations - * enter - happens just after the ngView contents are changed (when the new view DOM element is inserted into the DOM) - * leave - happens just after the current ngView contents change and just before the former contents are removed from the DOM + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently.   *   * @scope   * @example @@ -30,10 +32,9 @@ ngRouteModule.directive('ngView', ngViewFactory);            <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |            <a href="Book/Scarlet">Scarlet Letter</a><br/> -          <div -            ng-view -            class="example-$animate-container" -            ng-$animate="{enter: 'example-enter', leave: 'example-leave'}"></div> +          <div class="example-animate-container"> +            <div ng-view class="view-example"></div> +          </div>            <hr />            <pre>$location.path() = {{main.$location.path()}}</pre> @@ -60,20 +61,13 @@ ngRouteModule.directive('ngView', ngViewFactory);        </file>        <file name="animations.css"> -        .example-leave, .example-enter { +        .view-example {            -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;            -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;            -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;            -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;            transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; -        } - -        .example-$animate-container { -          position:relative; -          height:100px; -        } -        .example-$animate-container > * {            display:block;            width:100%;            border-left:1px solid black; @@ -86,15 +80,20 @@ ngRouteModule.directive('ngView', ngViewFactory);            padding:10px;          } -        .example-enter { +        .example-animate-container { +          position:relative; +          height:100px; +        } + +        .view-example.ng-enter {            left:100%;          } -        .example-enter.example-enter-active { +        .view-example.ng-enter.ng-enter-active {            left:0;          } -        .example-leave { } -        .example-leave.example-leave-active { +        .view-example.ng-leave { } +        .view-example.ng-leave.ng-leave-active {            left:-100%;          }        </file> @@ -164,57 +163,65 @@ function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,    return {      restrict: 'ECA',      terminal: true, -    link: function(scope, element, attr) { -      var lastScope, -          onloadExp = attr.onload || ''; - -      scope.$on('$routeChangeSuccess', update); -      update(); - - -      function destroyLastScope() { -        if (lastScope) { -          lastScope.$destroy(); -          lastScope = null; +    transclude: 'element', +    compile: function(element, attr, linker) { +      return function(scope, $element, attr) { +        var currentScope, +            currentElement, +            onloadExp = attr.onload || ''; + +        scope.$on('$routeChangeSuccess', update); +        update(); + +        function cleanupLastView() { +          if (currentScope) { +            currentScope.$destroy(); +            currentScope = null; +          } +          if(currentElement) { +            $animate.leave(currentElement); +            currentElement = null; +          }          } -      } - -      function clearContent() { -        $animate.leave(element.contents()); -        destroyLastScope(); -      } -      function update() { -        var locals = $route.current && $route.current.locals, -            template = locals && locals.$template; - -        if (template) { -          clearContent(); -          var enterElements = jqLite('<div></div>').html(template).contents(); -          $animate.enter(enterElements, element); - -          var link = $compile(enterElements), -              current = $route.current, -              controller; - -          lastScope = current.scope = scope.$new(); -          if (current.controller) { -            locals.$scope = lastScope; -            controller = $controller(current.controller, locals); -            if (current.controllerAs) { -              lastScope[current.controllerAs] = controller; -            } -            element.children().data('$ngControllerController', controller); +        function update() { +          var locals = $route.current && $route.current.locals, +              template = locals && locals.$template; + +          if (template) { +            var newScope = scope.$new(); +            linker(newScope, function(clone) { +              cleanupLastView(); + +              clone.html(template); +              $animate.enter(clone, null, $element); + +              var link = $compile(clone.contents()), +                  current = $route.current; + +              currentScope = current.scope = newScope; +              currentElement = clone; + +              if (current.controller) { +                locals.$scope = currentScope; +                var controller = $controller(current.controller, locals); +                if (current.controllerAs) { +                  currentScope[current.controllerAs] = controller; +                } +                clone.data('$ngControllerController', controller); +                clone.contents().data('$ngControllerController', controller); +              } + +              link(currentScope); +              currentScope.$emit('$viewContentLoaded'); +              currentScope.$eval(onloadExp); + +              // $anchorScroll might listen on event... +              $anchorScroll(); +            }); +          } else { +            cleanupLastView();            } - -          link(lastScope); -          lastScope.$emit('$viewContentLoaded'); -          lastScope.$eval(onloadExp); - -          // $anchorScroll might listen on event... -          $anchorScroll(); -        } else { -          clearContent();          }        }      } diff --git a/test/ngRoute/directive/ngViewSpec.js b/test/ngRoute/directive/ngViewSpec.js index 5f021f2d..7d96a581 100644 --- a/test/ngRoute/directive/ngViewSpec.js +++ b/test/ngRoute/directive/ngViewSpec.js @@ -8,8 +8,7 @@ describe('ngView', function() {    beforeEach(module(function($provide) {      $provide.value('$window', angular.mock.createMockWindow());      return function($rootScope, $compile, $animate) { -      element = $compile('<ng:view onload="load()"></ng:view>')($rootScope); -      $animate.enabled(true); +      element = $compile('<div><ng:view onload="load()"></ng:view></div>')($rootScope);      };    })); @@ -93,7 +92,7 @@ describe('ngView', function() {        $rootScope.$digest();        expect($route.current.controller).toBe('MyCtrl'); -      expect(MyCtrl).toHaveBeenCalledWith(element.contents().scope()); +      expect(MyCtrl).toHaveBeenCalledWith(element.children().scope());      });    }); @@ -498,9 +497,12 @@ describe('ngView', function() {        $rootScope.$digest();        forEach(element.contents(), function(node) { -        if ( node.nodeType == 3 /* text node */) { +        if(node.nodeType == 3 /* text node */) {            expect(jqLite(node).scope()).not.toBe($route.current.scope);            expect(jqLite(node).controller()).not.toBeDefined(); +        } else if(node.nodeType == 8 /* comment node */) { +          expect(jqLite(node).scope()).toBe(element.scope()); +          expect(jqLite(node).controller()).toBe(element.controller());          } else {            expect(jqLite(node).scope()).toBe($route.current.scope);            expect(jqLite(node).controller()).toBeDefined(); @@ -508,163 +510,178 @@ describe('ngView', function() {        });      });    }); +}); -  describe('animations', function() { -    var body, element, $rootElement; +describe('ngView animations', function() { +  var body, element, $rootElement; -    function html(html) { -      $rootElement.html(html); -      body.append($rootElement); -      element = $rootElement.children().eq(0); -      return element; -    } +  beforeEach(module('ngRoute')); -    beforeEach(module(function() { -      // we need to run animation on attached elements; -      return function(_$rootElement_) { -        $rootElement = _$rootElement_; -        body = jqLite(document.body); -      }; -    })); +  function html(html) { +    $rootElement.html(html); +    body.append($rootElement); +    element = $rootElement.children().eq(0); +    return element; +  } + +  beforeEach(module(function() { +    // we need to run animation on attached elements; +    return function(_$rootElement_) { +      $rootElement = _$rootElement_; +      body = jqLite(document.body); +    }; +  })); -    afterEach(function(){ -      dealoc(body); -      dealoc(element); -    }); +  afterEach(function(){ +    dealoc(body); +    dealoc(element); +  }); -    beforeEach(module(function($provide, $routeProvider) { -      $routeProvider.when('/foo', {controller: noop, templateUrl: '/foo.html'}); -      return function($templateCache) { -        $templateCache.put('/foo.html', [200, '<div>data</div>', {}]); -      } -    })); +  beforeEach(module(function($provide, $routeProvider) { +    $provide.value('$window', angular.mock.createMockWindow()); +    $routeProvider.when('/foo', {controller: noop, templateUrl: '/foo.html'}); +    $routeProvider.when('/bar', {controller: noop, templateUrl: '/bar.html'}); +    return function($templateCache) { +      $templateCache.put('/foo.html', [200, '<div>data</div>', {}]); +      $templateCache.put('/bar.html', [200, '<div>data2</div>', {}]); +    } +  })); -    describe('hooks', function() { -      beforeEach(module('mock.animate')); +  describe('hooks', function() { +    beforeEach(module('mock.animate')); -      it('should fire off the enter animation', -          inject(function($compile, $rootScope, $location, $animate) { -            var item; -            element = $compile(html('<div ng-view></div>'))($rootScope); +    it('should fire off the enter animation', +        inject(function($compile, $rootScope, $location, $animate) { +          element = $compile(html('<div ng-view></div>'))($rootScope); -            $location.path('/foo'); -            $rootScope.$digest(); +          $location.path('/foo'); +          $rootScope.$digest(); -            item = $animate.process('leave').element; -            item = $animate.process('leave').element; -            item = $animate.process('leave').element; +          var item = $animate.process('enter').element; +          expect(item.text()).toBe('data'); +        })); -            item = $animate.process('enter').element; -            expect(item.text()).toBe('data'); +    it('should fire off the leave animation', +        inject(function($compile, $rootScope, $location, $templateCache, $animate) { -            item = $animate.process('leave').element; -            item = $animate.process('enter').element; -            expect(item.text()).toBe('data'); -          })); +      var item; +      $templateCache.put('/foo.html', [200, '<div>foo</div>', {}]); +      element = $compile(html('<div ng-view></div>'))($rootScope); -      it('should fire off the leave animation', -          inject(function($compile, $rootScope, $location, $templateCache, $animate) { +      $location.path('/foo'); +      $rootScope.$digest(); + +      item = $animate.process('enter').element; +      expect(item.text()).toBe('foo'); + +      $location.path('/'); +      $rootScope.$digest(); +      item = $animate.process('leave').element; +      expect(item.text()).toBe('foo'); +    })); + +    it('should animate two separate ngView elements', +      inject(function($compile, $rootScope, $templateCache, $animate, $location) {          var item; -        $templateCache.put('/foo.html', [200, '<div>foo</div>', {}]); -        element = $compile(html('<div ng-view></div>'))($rootScope); +        $rootScope.tpl = 'one'; +        element = $compile(html('<div><div ng-view></div></div>'))($rootScope); +        $rootScope.$digest();          $location.path('/foo');          $rootScope.$digest(); -        item = $animate.process('leave').element; -        item = $animate.process('leave').element; -        item = $animate.process('leave').element; -          item = $animate.process('enter').element; -        expect(item.text()).toBe('foo'); +        expect(item.text()).toBe('data'); -        item = $animate.process('leave').element; -        item = $animate.process('enter').element; -        expect(item.text()).toBe('foo'); - -        $location.path('/'); +        $location.path('/bar');          $rootScope.$digest(); -        item = $animate.process('leave').element; -        expect(item.text()).toBe('foo'); - -        item = $animate.process('leave').element; -        expect(item.text()).toBe('foo'); -      })); -    }); - -    it('should not double compile when route changes', function() { -      module('ngAnimate'); -      module('mock.animate'); -      module(function($routeProvider, $animateProvider, $provide) { -        $routeProvider.when('/foo', {template: '<div ng-repeat="i in [1,2]">{{i}}</div>'}); -        $routeProvider.when('/bar', {template: '<div ng-repeat="i in [3,4]">{{i}}</div>'}); -        $animateProvider.register('.my-animation', function() { -          return { -            leave: function(element, done) { -              dump('yes'); -              done(); -            } -          }; -        }); +        var itemA = $animate.process('leave').element; +        expect(itemA).not.toEqual(itemB); +        var itemB = $animate.process('enter').element; +    })); +  }); + +  it('should not double compile when the route changes', function() { + +    module('ngAnimate'); +    module('mock.animate'); + +    var window; +    module(function($routeProvider, $animateProvider, $provide) { +      $provide.value('$window', window = angular.mock.createMockWindow()); +      $routeProvider.when('/foo', {template: '<div ng-repeat="i in [1,2]">{{i}}</div>'}); +      $routeProvider.when('/bar', {template: '<div ng-repeat="i in [3,4]">{{i}}</div>'}); +      $animateProvider.register('.my-animation', function() { +        return { +          leave: function(element, done) { +            done(); +          } +        };        }); +    }); -      inject(function($rootScope, $compile, $location, $route, $window, $rootElement, $sniffer, $animate) { -        if (!$sniffer.transitions) return; +    inject(function($rootScope, $compile, $location, $route, $window, $rootElement, $sniffer, $animate) { +      element = $compile(html('<div><ng:view onload="load()" class="my-animation"></ng:view></div>'))($rootScope); +      $animate.enabled(true); -        element = $compile(html('<ng:view onload="load()"></ng:view>'))($rootScope); +      $location.path('/foo'); +      $rootScope.$digest(); -        $location.path('/foo'); -        $rootScope.$digest(); +      $animate.process('enter'); //ngView -        $animate.process('leave'); -        $animate.process('leave'); -        $animate.process('leave'); -        $animate.process('enter'); -        $animate.process('leave'); -        $animate.process('enter'); -        $animate.process('enter'); -        $animate.process('enter'); -        $animate.process('enter'); -        $animate.process('enter'); +      if($sniffer.transitions) {          $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(0).process(); +      } + +      $animate.process('enter'); //repeat 1 +      $animate.process('enter'); //repeat 2 + +      if($sniffer.transitions) {          $window.setTimeout.expect(1).process();          $window.setTimeout.expect(1).process();          $window.setTimeout.expect(0).process();          $window.setTimeout.expect(0).process(); -        $window.setTimeout.expect(0).process(); +      } -        expect(element.text()).toEqual('12'); +      expect(element.text()).toEqual('12'); -        $location.path('/bar'); -        $rootScope.$digest(); -        $animate.process('leave'); -        $animate.process('enter'); -        $animate.process('leave'); -        $animate.process('enter'); -        $animate.process('enter'); -        $animate.process('enter'); -        $animate.process('enter'); -        $animate.process('enter'); -        expect(n(element.text())).toEqual('1234'); +      $location.path('/bar'); +      $rootScope.$digest(); +      $animate.process('leave'); //ngView old +      if($sniffer.transitions) {          $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(0).process(); +      } + +      $animate.process('enter'); //ngView new +      if($sniffer.transitions) {          $window.setTimeout.expect(1).process(); +        $window.setTimeout.expect(0).process(); +      } + +      expect(n(element.text())).toEqual(''); //this is midway during the animation + +      $animate.process('enter'); //ngRepeat 3 +      $animate.process('enter'); //ngRepeat 4 + + +      if($sniffer.transitions) {          $window.setTimeout.expect(1).process();          $window.setTimeout.expect(1).process();          $window.setTimeout.expect(0).process();          $window.setTimeout.expect(0).process(); -        $window.setTimeout.expect(0).process(); -        $window.setTimeout.expect(0).process(); +      } -        expect(element.text()).toEqual('34'); +      expect(element.text()).toEqual('34'); -        function n(text) { -          return text.replace(/\r\n/m, '').replace(/\r\n/m, ''); -        } -      }); +      function n(text) { +        return text.replace(/\r\n/m, '').replace(/\r\n/m, ''); +      }      });    });  }); | 
