diff options
| -rw-r--r-- | src/ng/compile.js | 50 | ||||
| -rwxr-xr-x | test/ng/compileSpec.js | 84 | ||||
| -rw-r--r-- | test/ng/directive/ngRepeatSpec.js | 37 |
3 files changed, 142 insertions, 29 deletions
diff --git a/src/ng/compile.js b/src/ng/compile.js index a13be3b8..c44985fd 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -469,7 +469,7 @@ function $CompileProvider($provide) { //================================ - function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective) { + function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { if (!($compileNodes instanceof jqLite)) { // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it. $compileNodes = jqLite($compileNodes); @@ -481,7 +481,7 @@ function $CompileProvider($provide) { $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; } }); - var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective); + var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); return function publicLinkFn(scope, cloneConnectFn){ assertArg(scope, 'scope'); // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart @@ -528,7 +528,7 @@ function $CompileProvider($provide) { * @param {number=} max directive priority * @returns {?function} A composite linking function of all of the matched directives or null. */ - function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective) { + function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) { var linkFns = [], nodeLinkFn, childLinkFn, directives, attrs, linkFnFound; @@ -539,7 +539,7 @@ function $CompileProvider($provide) { directives = collectDirectives(nodeList[i], [], attrs, i == 0 ? maxPriority : undefined, ignoreDirective); nodeLinkFn = (directives.length) - ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], []) + ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [], previousCompileContext) : null; childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length) @@ -550,6 +550,7 @@ function $CompileProvider($provide) { linkFns.push(nodeLinkFn); linkFns.push(childLinkFn); linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn); + previousCompileContext = null; //use the previous context only for the first element in the virtual group } // return a linking function if we have found anything, null otherwise @@ -750,23 +751,26 @@ function $CompileProvider($provide) { * scope argument is auto-generated to the new child of the transcluded parent scope. * @param {JQLite} jqCollection If we are working on the root of the compile tree then this * argument has the root jqLite array so that we can replace nodes on it. - * @param {Object=} ignoreDirective An optional directive that will be ignored when compiling + * @param {Object=} originalReplaceDirective An optional directive that will be ignored when compiling * the transclusion. * @param {Array.<Function>} preLinkFns * @param {Array.<Function>} postLinkFns + * @param {Object} previousCompileContext Context used for previous compilation of the current node * @returns linkFn */ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, - originalReplaceDirective, preLinkFns, postLinkFns) { + originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) { + previousCompileContext = previousCompileContext || {}; + var terminalPriority = -Number.MAX_VALUE, - newScopeDirective = null, - newIsolateScopeDirective = null, - templateDirective = null, + newScopeDirective, + newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, + templateDirective = previousCompileContext.templateDirective, $compileNode = templateAttrs.$$element = jqLite(compileNode), directive, directiveName, $template, - transcludeDirective, + transcludeDirective = previousCompileContext.transcludeDirective, replaceDirective = originalReplaceDirective, childTranscludeFn = transcludeFn, controllerDirectives, @@ -815,19 +819,27 @@ function $CompileProvider($provide) { } if (directiveValue = directive.transclude) { - assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode); - transcludeDirective = directive; + // Special case ngRepeat so that we don't complain about duplicate transclusion, ngRepeat knows how to handle + // this on its own. + if (directiveName !== 'ngRepeat') { + assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode); + transcludeDirective = directive; + } if (directiveValue == 'element') { terminalPriority = directive.priority; - $template = groupScan(compileNode, attrStart, attrEnd) + $template = groupScan(compileNode, attrStart, attrEnd); $compileNode = templateAttrs.$$element = jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); compileNode = $compileNode[0]; replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); childTranscludeFn = compile($template, transcludeFn, terminalPriority, - replaceDirective && replaceDirective.name); + replaceDirective && replaceDirective.name, { + newIsolateScopeDirective: newIsolateScopeDirective, + transcludeDirective: transcludeDirective, + templateDirective: templateDirective + }); } else { $template = jqLite(JQLiteClone(compileNode)).contents(); $compileNode.html(''); // clear contents @@ -889,7 +901,11 @@ function $CompileProvider($provide) { } nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, - templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns); + templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { + newIsolateScopeDirective: newIsolateScopeDirective, + transcludeDirective: transcludeDirective, + templateDirective: templateDirective + }); ii = directives.length; } else if (directive.compile) { try { @@ -1188,7 +1204,7 @@ function $CompileProvider($provide) { function compileTemplateUrl(directives, $compileNode, tAttrs, - $rootElement, childTranscludeFn, preLinkFns, postLinkFns) { + $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { var linkQueue = [], afterTemplateNodeLinkFn, afterTemplateChildLinkFn, @@ -1231,7 +1247,7 @@ function $CompileProvider($provide) { directives.unshift(derivedSyncDirective); afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, - childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns); + childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, previousCompileContext); forEach($rootElement, function(node, i) { if (node == compileNode) { $rootElement[i] = $compileNode[0]; diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 1e6f6e26..9a69824a 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -1042,10 +1042,12 @@ describe('$compile', function() { templateUrl: 'template.html' })); }); - inject(function($compile){ + inject(function($compile, $httpBackend){ + $httpBackend.whenGET('template.html').respond('<p>template.html</p>'); expect(function() { $compile('<div><div class="sync async"></div></div>'); - }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [sync, async] asking for template on: '+ + $httpBackend.flush(); + }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [async, sync] asking for template on: '+ '<div class="sync async">'); }); }); @@ -1205,7 +1207,7 @@ describe('$compile', function() { )); - it('should work when directive is a repeater', inject( + it('should work when directive is in a repeater', inject( function($compile, $httpBackend, $rootScope) { $httpBackend.expect('GET', 'hello.html'). respond('<span>i=<span ng-transclude></span>;</span>'); @@ -1317,7 +1319,7 @@ describe('$compile', function() { }); - describe('template as function', function() { + describe('templateUrl as function', function() { beforeEach(module(function() { directive('myDirective', valueFn({ @@ -2745,23 +2747,81 @@ describe('$compile', function() { }); - it('should only allow one transclude per element', function() { + it('should only allow one content transclusion per element', function() { module(function() { directive('first', valueFn({ - scope: {}, - restrict: 'CA', - transclude: 'content' + transclude: true })); directive('second', valueFn({ - restrict: 'CA', - transclude: 'content' + transclude: true })); }); inject(function($compile) { expect(function() { - $compile('<div class="first second"></div>'); + $compile('<div first="" second=""></div>'); + }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: <div .+/); + }); + }); + + + it('should only allow one element transclusion per element', function() { + module(function() { + directive('first', valueFn({ + transclude: 'element' + })); + directive('second', valueFn({ + transclude: 'element' + })); + }); + inject(function($compile) { + expect(function() { + $compile('<div first second></div>'); }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [first, second] asking for transclusion on: ' + - '<div class="first second ng-isolate-scope ng-scope">'); + '<!-- first: -->'); + }); + }); + + + it('should only allow one element transclusion per element when directives have different priorities', function() { + // we restart compilation in this case and we need to remember the duplicates during the second compile + // regression #3893 + module(function() { + directive('first', valueFn({ + transclude: 'element', + priority: 100 + })); + directive('second', valueFn({ + transclude: 'element' + })); + }); + inject(function($compile) { + expect(function() { + $compile('<div first second></div>'); + }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: <div .+/); + }); + }); + + + it('should only allow one element transclusion per element when async replace directive is in the mix', function() { + module(function() { + directive('template', valueFn({ + templateUrl: 'template.html', + replace: true + })); + directive('first', valueFn({ + transclude: 'element', + priority: 100 + })); + directive('second', valueFn({ + transclude: 'element' + })); + }); + inject(function($compile, $httpBackend) { + $httpBackend.expectGET('template.html').respond('<p second>template.html</p>'); + $compile('<div template first></div>'); + expect(function() { + $httpBackend.flush(); + }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: <p .+/); }); }); diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js index 8dba13bf..9dde36e7 100644 --- a/test/ng/directive/ngRepeatSpec.js +++ b/test/ng/directive/ngRepeatSpec.js @@ -976,6 +976,43 @@ describe('ngRepeat', function() { }); + describe('compatibility', function() { + + it('should allow mixing ngRepeat and another element transclusion directive', function() { + $compileProvider.directive('elmTrans', valueFn({ + transclude: 'element', + controller: function($transclude, $scope, $element) { + $transclude(function(transcludedNodes) { + $element.after(']]').after(transcludedNodes).after('[['); + }); + } + })); + + inject(function($compile, $rootScope) { + element = $compile('<div><div ng-repeat="i in [1,2]" elm-trans>{{i}}</div></div>')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('[[1]][[2]]') + }); + }); + + + it('should allow mixing ngRepeat with ngInclude', inject(function($compile, $rootScope, $httpBackend) { + $httpBackend.whenGET('someTemplate.html').respond('<p>some template; </p>'); + element = $compile('<div><div ng-repeat="i in [1,2]" ng-include="\'someTemplate.html\'"></div></div>')($rootScope); + $rootScope.$digest(); + $httpBackend.flush(); + expect(element.text()).toBe('some template; some template; '); + })); + + + it('should allow mixing ngRepeat with ngIf', inject(function($compile, $rootScope) { + element = $compile('<div><div ng-repeat="i in [1,2,3,4]" ng-if="i % 2 == 0">{{i}};</div></div>')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('2;4;'); + })); + }); + + describe('ngRepeatStart', function () { it('should grow multi-node repeater', inject(function($compile, $rootScope) { $rootScope.show = false; |
