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;  | 
