diff options
| author | Matias Niemelàˆ | 2013-07-06 00:48:54 -0400 |
|---|---|---|
| committer | Misko Hevery | 2013-07-26 23:49:54 -0700 |
| commit | aa2133ad818d2e5c27cbd3933061797096356c8a (patch) | |
| tree | b14901a1cab572dca6fab79e370cc89210360bbb | |
| parent | 8ed0d5b6aa2e29ebc8d2026cb04380ed3343baef (diff) | |
| download | angular.js-aa2133ad818d2e5c27cbd3933061797096356c8a.tar.bz2 | |
fix(ngInclude): $animate refactoring + use transclusion
BREAKING CHANGE: previously ngInclude only updated its content, after this change
ngInclude will recreate itself every time a new content is included. This ensures
that a single rootElement for all the included contents always exists, which makes
definition of css styles for animations much easier.
| -rw-r--r-- | docs/src/templates/css/animations.css | 4 | ||||
| -rw-r--r-- | src/ng/directive/ngInclude.js | 82 | ||||
| -rw-r--r-- | src/ngAnimate/animate.js | 8 | ||||
| -rw-r--r-- | test/ng/directive/ngIncludeSpec.js | 81 |
4 files changed, 105 insertions, 70 deletions
diff --git a/docs/src/templates/css/animations.css b/docs/src/templates/css/animations.css index 7324a8a1..cc531ccb 100644 --- a/docs/src/templates/css/animations.css +++ b/docs/src/templates/css/animations.css @@ -15,7 +15,7 @@ overflow:hidden; } -.slide-reveal > .ng-enter { +.slide-reveal.ng-enter { -webkit-transition:0.5s linear all; -moz-transition:0.5s linear all; -o-transition:0.5s linear all; @@ -26,7 +26,7 @@ opacity:0; top:10px; } -.slide-reveal > .ng-enter.ng-enter-active { +.slide-reveal.ng-enter.ng-enter-active { top:0; opacity:1; } diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js index d5ed1fc5..2ab9f847 100644 --- a/src/ng/directive/ngInclude.js +++ b/src/ng/directive/ngInclude.js @@ -24,8 +24,10 @@ * access on some browsers) * * @animations - * enter - happens just after the ngInclude contents change and a new DOM element is created and injected into the ngInclude container - * leave - happens just after the ngInclude 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 * @@ -49,9 +51,9 @@ </select> url of the template: <tt>{{template.url}}</tt> <hr/> - <div class="example-animate-container" - ng-include="template.url" - ng-animate="{enter: 'example-enter', leave: 'example-leave'}"></div> + <div class="example-animate-container"> + <div class="include-example" ng-include="template.url"></div> + </div> </div> </file> <file name="script.js"> @@ -63,14 +65,13 @@ } </file> <file name="template1.html"> - <div>Content of template1.html</div> + Content of template1.html </file> <file name="template2.html"> - <div>Content of template2.html</div> + Content of template2.html </file> <file name="animations.css"> - .example-leave, - .example-enter { + .include-example.ng-enter, .include-example.ng-leave { -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; @@ -82,24 +83,21 @@ left:0; right:0; bottom:0; - } - - .example-animate-container > * { display:block; padding:10px; } - .example-enter { + .include-example.ng-enter { top:-50px; } - .example-enter.example-enter-active { + .include-example.ng-enter.ng-enter-active { top:0; } - .example-leave { + .include-example.ng-leave { top:0; } - .example-leave.example-leave-active { + .include-example.ng-leave.ng-leave-active { top:50px; } </file> @@ -115,7 +113,7 @@ }); it('should change to blank', function() { select('template').option(''); - expect(element('.doc-example-live [ng-include]').text()).toEqual(''); + expect(element('.doc-example-live [ng-include]')).toBe(undefined); }); </file> </example> @@ -145,21 +143,26 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile' return { restrict: 'ECA', terminal: true, - compile: function(element, attr) { + transclude: 'element', + compile: function(element, attr, transclusion) { var srcExp = attr.ngInclude || attr.src, onloadExp = attr.onload || '', autoScrollExp = attr.autoscroll; - return function(scope, element, attr) { + return function(scope, $element) { var changeCounter = 0, - childScope; + currentScope, + currentElement; - var clearContent = function() { - if (childScope) { - childScope.$destroy(); - childScope = null; + var cleanupLastIncludeContent = function() { + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if(currentElement) { + $animate.leave(currentElement); + currentElement = null; } - $animate.leave(element.contents()); }; scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { @@ -168,28 +171,31 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile' if (src) { $http.get(src, {cache: $templateCache}).success(function(response) { if (thisChangeId !== changeCounter) return; + var newScope = scope.$new(); - if (childScope) childScope.$destroy(); - childScope = scope.$new(); - $animate.leave(element.contents()); + transclusion(newScope, function(clone) { + cleanupLastIncludeContent(); - var contents = jqLite('<div/>').html(response).contents(); + currentScope = newScope; + currentElement = clone; - $animate.enter(contents, element); - $compile(contents)(childScope); + currentElement.html(response); + $animate.enter(currentElement, null, $element); + $compile(currentElement.contents())(currentScope); - if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } + if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } - childScope.$emit('$includeContentLoaded'); - scope.$eval(onloadExp); + currentScope.$emit('$includeContentLoaded'); + scope.$eval(onloadExp); + }); }).error(function() { - if (thisChangeId === changeCounter) clearContent(); + if (thisChangeId === changeCounter) cleanupLastIncludeContent(); }); scope.$emit('$includeContentRequested'); } else { - clearContent(); + cleanupLastIncludeContent(); } }); }; diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js index 93ff9d8c..201d4aab 100644 --- a/src/ngAnimate/animate.js +++ b/src/ngAnimate/animate.js @@ -47,10 +47,10 @@ * transition:0.5s linear all; * } * - * .slide > .ng-enter { } /* starting animations for enter */ - * .slide > .ng-enter-active { } /* terminal animations for enter */ - * .slide > .ng-leave { } /* starting animations for leave */ - * .slide > .ng-leave-active { } /* terminal animations for leave */ + * .slide.ng-enter { } /* starting animations for enter */ + * .slide.ng-enter-active { } /* terminal animations for enter */ + * .slide.ng-leave { } /* starting animations for leave */ + * .slide.ng-leave-active { } /* terminal animations for leave */ * </style> * * <!-- diff --git a/test/ng/directive/ngIncludeSpec.js b/test/ng/directive/ngIncludeSpec.js index 286ee5af..c55fb0b3 100644 --- a/test/ng/directive/ngIncludeSpec.js +++ b/test/ng/directive/ngIncludeSpec.js @@ -17,7 +17,7 @@ describe('ngInclude', function() { it('should trust and use literal urls', inject(function( $rootScope, $httpBackend, $compile) { - element = $compile('<div ng-include="\'url\'"></div>')($rootScope); + element = $compile('<div><div ng-include="\'url\'"></div></div>')($rootScope); $httpBackend.expect('GET', 'url').respond('template text'); $rootScope.$digest(); $httpBackend.flush(); @@ -27,7 +27,7 @@ describe('ngInclude', function() { it('should trust and use trusted urls', inject(function($rootScope, $httpBackend, $compile, $sce) { - element = $compile('<div ng-include="fooUrl"></div>')($rootScope); + element = $compile('<div><div ng-include="fooUrl"></div></div>')($rootScope); $httpBackend.expect('GET', 'http://foo.bar/url').respond('template text'); $rootScope.fooUrl = $sce.trustAsResourceUrl('http://foo.bar/url'); $rootScope.$digest(); @@ -39,20 +39,21 @@ describe('ngInclude', function() { it('should include an external file', inject(putIntoCache('myUrl', '{{name}}'), function($rootScope, $compile) { - element = jqLite('<ng:include src="url"></ng:include>'); - jqLite(document.body).append(element); + element = jqLite('<div><ng:include src="url"></ng:include></div>'); + var body = jqLite(document.body); + body.append(element); element = $compile(element)($rootScope); $rootScope.name = 'misko'; $rootScope.url = 'myUrl'; $rootScope.$digest(); - expect(element.text()).toEqual('misko'); - jqLite(document.body).html(''); + expect(body.text()).toEqual('misko'); + body.html(''); })); it('should support ng-include="src" syntax', inject(putIntoCache('myUrl', '{{name}}'), function($rootScope, $compile) { - element = jqLite('<div ng-include="url"></div>'); + element = jqLite('<div><div ng-include="url"></div></div>'); jqLite(document.body).append(element); element = $compile(element)($rootScope); $rootScope.name = 'Alibaba'; @@ -89,7 +90,7 @@ describe('ngInclude', function() { it('should remove previously included text if a falsy value is bound to src', inject( putIntoCache('myUrl', '{{name}}'), function($rootScope, $compile) { - element = jqLite('<ng:include src="url"></ng:include>'); + element = jqLite('<div><ng:include src="url"></ng:include></div>'); element = $compile(element)($rootScope); $rootScope.name = 'igor'; $rootScope.url = 'myUrl'; @@ -112,7 +113,7 @@ describe('ngInclude', function() { $httpBackend.whenGET('url').respond('my partial'); $rootScope.$on('$includeContentRequested', contentRequestedSpy); - element = $compile('<ng:include src="\'url\'"></ng:include>')($rootScope); + element = $compile('<div><div><ng:include src="\'url\'"></ng:include></div></div>')($rootScope); $rootScope.$digest(); expect(contentRequestedSpy).toHaveBeenCalledOnce(); @@ -130,7 +131,7 @@ describe('ngInclude', function() { $templateCache.put('url', [200, 'partial content', {}]); $rootScope.$on('$includeContentLoaded', contentLoadedSpy); - element = $compile('<ng:include src="\'url\'"></ng:include>')($rootScope); + element = $compile('<div><div><ng:include src="\'url\'"></ng:include></div></div>')($rootScope); $rootScope.$digest(); expect(contentLoadedSpy).toHaveBeenCalledOnce(); @@ -140,7 +141,7 @@ describe('ngInclude', function() { it('should evaluate onload expression when a partial is loaded', inject( putIntoCache('myUrl', 'my partial'), function($rootScope, $compile) { - element = jqLite('<ng:include src="url" onload="loaded = true"></ng:include>'); + element = jqLite('<div><div><ng:include src="url" onload="loaded = true"></ng:include></div></div>'); element = $compile(element)($rootScope); expect($rootScope.loaded).not.toBeDefined(); @@ -158,7 +159,7 @@ describe('ngInclude', function() { $httpBackend.whenGET('url1').respond('partial {{$parent.url}}'); $httpBackend.whenGET('url2').respond(404); - element = $compile('<ng:include src="url"></ng:include>')($rootScope); + element = $compile('<div><ng:include src="url"></ng:include></div>')($rootScope); expect(element.children().scope()).toBeFalsy(); $rootScope.url = 'url1'; @@ -185,7 +186,7 @@ describe('ngInclude', function() { it('should do xhr request and cache it', inject(function($rootScope, $httpBackend, $compile) { - element = $compile('<ng:include src="url"></ng:include>')($rootScope); + element = $compile('<div><ng:include src="url"></ng:include></div>')($rootScope); $httpBackend.expect('GET', 'myUrl').respond('my partial'); $rootScope.url = 'myUrl'; @@ -206,7 +207,7 @@ describe('ngInclude', function() { it('should clear content when error during xhr request', inject(function($httpBackend, $compile, $rootScope) { - element = $compile('<ng:include src="url">content</ng:include>')($rootScope); + element = $compile('<div><ng:include src="url">content</ng:include></div>')($rootScope); $httpBackend.expect('GET', 'myUrl').respond(404, ''); $rootScope.url = 'myUrl'; @@ -220,7 +221,7 @@ describe('ngInclude', function() { it('should be async even if served from cache', inject( putIntoCache('myUrl', 'my partial'), function($rootScope, $compile) { - element = $compile('<ng:include src="url"></ng:include>')($rootScope); + element = $compile('<div><ng:include src="url"></ng:include></div>')($rootScope); $rootScope.url = 'myUrl'; @@ -237,7 +238,7 @@ describe('ngInclude', function() { it('should discard pending xhr callbacks if a new template is requested before the current ' + 'finished loading', inject(function($rootScope, $compile, $httpBackend) { - element = jqLite("<ng:include src='templateUrl'></ng:include>"); + element = jqLite("<div><ng:include src='templateUrl'></ng:include></div>"); var log = {}; $rootScope.templateUrl = 'myUrl1'; @@ -273,6 +274,10 @@ describe('ngInclude', function() { $rootScope.tpl = 'tpl.html'; }); expect(onload).toHaveBeenCalledOnce(); + + $rootScope.tpl = ''; + $rootScope.$digest(); + dealoc(element); })); @@ -308,14 +313,14 @@ describe('ngInclude', function() { it('should call $anchorScroll if autoscroll attribute is present', inject( - compileAndLink('<ng:include src="tpl" autoscroll></ng:include>'), + compileAndLink('<div><ng:include src="tpl" autoscroll></ng:include></div>'), changeTplAndValueTo('template.html'), function() { expect(autoScrollSpy).toHaveBeenCalledOnce(); })); it('should call $anchorScroll if autoscroll evaluates to true', inject( - compileAndLink('<ng:include src="tpl" autoscroll="value"></ng:include>'), + compileAndLink('<div><ng:include src="tpl" autoscroll="value"></ng:include></div>'), changeTplAndValueTo('template.html', true), changeTplAndValueTo('another.html', 'some-string'), changeTplAndValueTo('template.html', 100), function() { @@ -325,14 +330,14 @@ describe('ngInclude', function() { it('should not call $anchorScroll if autoscroll attribute is not present', inject( - compileAndLink('<ng:include src="tpl"></ng:include>'), + compileAndLink('<div><ng:include src="tpl"></ng:include></div>'), changeTplAndValueTo('template.html'), function() { expect(autoScrollSpy).not.toHaveBeenCalled(); })); it('should not call $anchorScroll if autoscroll evaluates to false', inject( - compileAndLink('<ng:include src="tpl" autoscroll="value"></ng:include>'), + compileAndLink('<div><ng:include src="tpl" autoscroll="value"></ng:include></div>'), changeTplAndValueTo('template.html', false), changeTplAndValueTo('template.html', undefined), changeTplAndValueTo('template.html', null), function() { @@ -377,13 +382,12 @@ describe('ngInclude animations', function() { $templateCache.put('enter', [200, '<div>data</div>', {}]); $rootScope.tpl = 'enter'; element = $compile(html( - '<div ' + + '<div><div ' + 'ng-include="tpl">' + - '</div>' + '</div></div>' ))($rootScope); $rootScope.$digest(); - item = $animate.process('leave').element; item = $animate.process('enter').element; expect(item.text()).toBe('data'); })); @@ -394,13 +398,12 @@ describe('ngInclude animations', function() { $templateCache.put('enter', [200, '<div>data</div>', {}]); $rootScope.tpl = 'enter'; element = $compile(html( - '<div ' + + '<div><div ' + 'ng-include="tpl">' + - '</div>' + '</div></div>' ))($rootScope); $rootScope.$digest(); - item = $animate.process('leave').element; item = $animate.process('enter').element; expect(item.text()).toBe('data'); @@ -411,4 +414,30 @@ describe('ngInclude animations', function() { expect(item.text()).toBe('data'); })); + it('should animate two separate ngInclude elements', + inject(function($compile, $rootScope, $templateCache, $animate) { + var item; + $templateCache.put('one', [200, 'one', {}]); + $templateCache.put('two', [200, 'two', {}]); + $rootScope.tpl = 'one'; + element = $compile(html( + '<div><div ' + + 'ng-include="tpl">' + + '</div></div>' + ))($rootScope); + $rootScope.$digest(); + + item = $animate.process('enter').element; + expect(item.text()).toBe('one'); + + $rootScope.tpl = 'two'; + $rootScope.$digest(); + + var itemA = $animate.process('leave').element; + var itemB = $animate.process('enter').element; + expect(itemA.attr('ng-include')).toBe('tpl'); + expect(itemB.attr('ng-include')).toBe('tpl'); + expect(itemA).not.toEqual(itemB); + })); + }); |
