diff options
| -rw-r--r-- | src/ng/compile.js | 141 | ||||
| -rw-r--r-- | src/ng/directive/ngIf.js | 6 | ||||
| -rw-r--r-- | src/ng/directive/ngInclude.js | 6 | ||||
| -rw-r--r-- | src/ng/directive/ngRepeat.js | 6 | ||||
| -rw-r--r-- | src/ng/directive/ngSwitch.js | 16 | ||||
| -rw-r--r-- | src/ngRoute/directive/ngView.js | 6 | ||||
| -rwxr-xr-x | test/ng/compileSpec.js | 203 | ||||
| -rwxr-xr-x | test/ng/directive/ngIfSpec.js | 28 | ||||
| -rw-r--r-- | test/ng/directive/ngIncludeSpec.js | 30 | ||||
| -rw-r--r-- | test/ng/directive/ngRepeatSpec.js | 27 | ||||
| -rw-r--r-- | test/ngRoute/directive/ngViewSpec.js | 38 | 
11 files changed, 434 insertions, 73 deletions
| diff --git a/src/ng/compile.js b/src/ng/compile.js index 039c211c..4d83f379 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -178,8 +178,9 @@   * * `$scope` - Current scope associated with the element   * * `$element` - Current element   * * `$attrs` - Current attributes object for the element - * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: - *   `function(cloneLinkingFn)`. + * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. + *    The scope can be overridden by an optional first argument. + *   `function([scope], cloneLinkingFn)`.   *   *   * #### `require` @@ -272,7 +273,7 @@   *   * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared   *     between all directive compile functions.   * - *   * `transclude` - A transclude linking function: `function(scope, cloneLinkingFn)`. + *   * `transclude` -  [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`   *   * <div class="alert alert-warning">   * **Note:** The template instance and the link instance may be different objects if the template has @@ -281,6 +282,12 @@   * should be done in a linking function rather than in a compile function.   * </div>   * + * <div class="alert alert-error"> + * **Note:** The `transclude` function that is passed to the compile function is deperecated, as it + *   e.g. does not know about the right outer scope. Please use the transclude function that is passed + *   to the link function instead. + * </div> +   * A compile function can have a return value which can be either a function or an object.   *   * * returning a (post-link) function - is equivalent to registering the linking function via the @@ -295,7 +302,7 @@   * This property is used only if the `compile` property is not defined.   *   * <pre> - *   function link(scope, iElement, iAttrs, controller) { ... } + *   function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }   * </pre>   *   * The link function is responsible for registering DOM listeners as well as updating the DOM. It is @@ -316,6 +323,10 @@   *     element defines a controller. The controller is shared among all the directives, which allows   *     the directives to use the controllers as a communication channel.   * + *   * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. + *     The scope can be overridden by an optional first argument. This is the same as the `$transclude` + *     parameter of directive controllers. + *     `function([scope], cloneLinkingFn)`.   *   *   * #### Pre-linking function @@ -821,7 +832,7 @@ function $CompileProvider($provide) {        var compositeLinkFn =                compileNodes($compileNodes, transcludeFn, $compileNodes,                             maxPriority, ignoreDirective, previousCompileContext); -      return function publicLinkFn(scope, cloneConnectFn){ +      return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){          assertArg(scope, 'scope');          // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart          // and sometimes changes the structure of the DOM. @@ -829,6 +840,10 @@ function $CompileProvider($provide) {            ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!            : $compileNodes; +        forEach(transcludeControllers, function(instance, name) { +          $linkNode.data('$' + name + 'Controller', instance); +        }); +          // Attach scope only to non-text nodes.          for(var i = 0, ii = $linkNode.length; i<ii; i++) {            var node = $linkNode[i]; @@ -940,12 +955,19 @@ function $CompileProvider($provide) {      }      function createBoundTranscludeFn(scope, transcludeFn) { -      return function boundTranscludeFn(cloneFn) { -        var transcludedScope = scope.$new(), -          clone; -        transcludedScope.$$transcluded = true; -        clone = transcludeFn(transcludedScope, cloneFn); -        clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy)); +      return function boundTranscludeFn(transcludedScope, cloneFn, controllers) { +        var scopeCreated = false; + +        if (!transcludedScope) { +          transcludedScope = scope.$new(); +          transcludedScope.$$transcluded = true; +          scopeCreated = true; +        } + +        var clone = transcludeFn(transcludedScope, cloneFn, controllers); +        if (scopeCreated) { +          clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy)); +        }          return clone;        };      } @@ -1086,9 +1108,9 @@ function $CompileProvider($provide) {       * @returns {Function}       */      function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { -      return function(scope, element, attrs, controllers) { +      return function(scope, element, attrs, controllers, transcludeFn) {          element = groupScan(element[0], attrStart, attrEnd); -        return linkFn(scope, element, attrs, controllers); +        return linkFn(scope, element, attrs, controllers, transcludeFn);        };      } @@ -1125,7 +1147,9 @@ function $CompileProvider($provide) {            controllerDirectives = previousCompileContext.controllerDirectives,            newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,            templateDirective = previousCompileContext.templateDirective, -          transcludeDirective = previousCompileContext.transcludeDirective, +          nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, +          hasTranscludeDirective = false, +          hasElementTranscludeDirective = false,            $compileNode = templateAttrs.$$element = jqLite(compileNode),            directive,            directiveName, @@ -1176,15 +1200,18 @@ function $CompileProvider($provide) {          }          if (directiveValue = directive.transclude) { +          hasTranscludeDirective = true; +            // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.            // This option should only be used by directives that know how to how to safely handle element transclusion,            // where the transcluded nodes are added or replaced after linking.            if (!directive.$$tlb) { -            assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode); -            transcludeDirective = directive; +            assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); +            nonTlbTranscludeDirective = directive;            }            if (directiveValue == 'element') { +            hasElementTranscludeDirective = true;              terminalPriority = directive.priority;              $template = groupScan(compileNode, attrStart, attrEnd);              $compileNode = templateAttrs.$$element = @@ -1200,9 +1227,9 @@ function $CompileProvider($provide) {                                            // - newIsolateScopeDirective or templateDirective - combining templates with                                            //   element transclusion doesn't make sense.                                            // -                                          // We need only transcludeDirective so that we prevent putting transclusion +                                          // We need only nonTlbTranscludeDirective so that we prevent putting transclusion                                            // on the same element more than once. -                                          transcludeDirective: transcludeDirective +                                          nonTlbTranscludeDirective: nonTlbTranscludeDirective                                          });            } else {              $template = jqLite(jqLiteClone(compileNode)).contents(); @@ -1271,7 +1298,7 @@ function $CompileProvider($provide) {                  controllerDirectives: controllerDirectives,                  newIsolateScopeDirective: newIsolateScopeDirective,                  templateDirective: templateDirective, -                transcludeDirective: transcludeDirective +                nonTlbTranscludeDirective: nonTlbTranscludeDirective                });            ii = directives.length;          } else if (directive.compile) { @@ -1295,7 +1322,7 @@ function $CompileProvider($provide) {        }        nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; -      nodeLinkFn.transclude = transcludeDirective && childTranscludeFn; +      nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;        // might be normal or delayed nodeLinkFn depending on if templateUrl is present        return nodeLinkFn; @@ -1322,7 +1349,7 @@ function $CompileProvider($provide) {        } -      function getControllers(require, $element) { +      function getControllers(require, $element, elementControllers) {          var value, retrievalMethod = 'data', optional = false;          if (isString(require)) {            while((value = require.charAt(0)) == '^' || value == '?') { @@ -1332,13 +1359,12 @@ function $CompileProvider($provide) {              }              optional = optional || value == '?';            } +          value = null; -          value = $element[retrievalMethod]('$' + require + 'Controller'); - -          if ($element[0].nodeType == 8 && $element[0].$$controller) { // Transclusion comment node -            value = value || $element[0].$$controller; -            $element[0].$$controller = null; +          if (elementControllers && retrievalMethod === 'data') { +            value = elementControllers[require];            } +          value = value || $element[retrievalMethod]('$' + require + 'Controller');            if (!value && !optional) {              throw $compileMinErr('ctreq', @@ -1349,7 +1375,7 @@ function $CompileProvider($provide) {          } else if (isArray(require)) {            value = [];            forEach(require, function(require) { -            value.push(getControllers(require, $element)); +            value.push(getControllers(require, $element, elementControllers));            });          }          return value; @@ -1357,7 +1383,7 @@ function $CompileProvider($provide) {        function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { -        var attrs, $element, i, ii, linkFn, controller, isolateScope; +        var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn;          if (compileNode === linkNode) {            attrs = templateAttrs; @@ -1451,14 +1477,14 @@ function $CompileProvider($provide) {              }            });          } - +        transcludeFn = boundTranscludeFn && controllersBoundTransclude;          if (controllerDirectives) {            forEach(controllerDirectives, function(directive) {              var locals = {                $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,                $element: $element,                $attrs: attrs, -              $transclude: boundTranscludeFn +              $transclude: transcludeFn              }, controllerInstance;              controller = directive.controller; @@ -1467,16 +1493,16 @@ function $CompileProvider($provide) {              }              controllerInstance = $controller(controller, locals); - -            // Directives with element transclusion and a controller need to attach controller -            // to the comment node created by the compiler, but jQuery .data doesn't support -            // attaching data to comment nodes so instead we set it directly on the element and -            // remove it after we read it later. -            if ($element[0].nodeType == 8) { // Transclusion comment node -              $element[0].$$controller = controllerInstance; -            } else { +            // For directives with element transclusion the element is a comment, +            // but jQuery .data doesn't support attaching data to comment nodes as it's hard to +            // clean up (http://bugs.jquery.com/ticket/8335). +            // Instead, we save the controllers for the element in a local hash and attach to .data +            // later, once we have the actual element. +            elementControllers[directive.name] = controllerInstance; +            if (!hasElementTranscludeDirective) {                $element.data('$' + directive.name + 'Controller', controllerInstance);              } +              if (directive.controllerAs) {                locals.$scope[directive.controllerAs] = controllerInstance;              } @@ -1488,7 +1514,7 @@ function $CompileProvider($provide) {            try {              linkFn = preLinkFns[i];              linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, -                linkFn.require && getControllers(linkFn.require, $element)); +                linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);            } catch (e) {              $exceptionHandler(e, startingTag($element));            } @@ -1508,11 +1534,28 @@ function $CompileProvider($provide) {            try {              linkFn = postLinkFns[i];              linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, -                linkFn.require && getControllers(linkFn.require, $element)); +                linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);            } catch (e) {              $exceptionHandler(e, startingTag($element));            }          } + +        // This is the function that is injected as `$transclude`. +        function controllersBoundTransclude(scope, cloneAttachFn) { +          var transcludeControllers; + +          // no scope passed +          if (arguments.length < 2) { +            cloneAttachFn = scope; +            scope = undefined; +          } + +          if (hasElementTranscludeDirective) { +            transcludeControllers = elementControllers; +          } + +          return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); +        }        }      } @@ -1622,7 +1665,7 @@ function $CompileProvider($provide) {        $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).          success(function(content) { -          var compileNode, tempTemplateAttrs, $template; +          var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;            content = denormalizeTemplate(content); @@ -1667,7 +1710,7 @@ function $CompileProvider($provide) {              var scope = linkQueue.shift(),                  beforeTemplateLinkNode = linkQueue.shift(),                  linkRootElement = linkQueue.shift(), -                controller = linkQueue.shift(), +                boundTranscludeFn = linkQueue.shift(),                  linkNode = $compileNode[0];              if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { @@ -1675,9 +1718,13 @@ function $CompileProvider($provide) {                linkNode = jqLiteClone(compileNode);                replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);              } - +            if (afterTemplateNodeLinkFn.transclude) { +              childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); +            } else { +              childBoundTranscludeFn = boundTranscludeFn; +            }              afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, -                                    controller); +              childBoundTranscludeFn);            }            linkQueue = null;          }). @@ -1685,14 +1732,14 @@ function $CompileProvider($provide) {            throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url);          }); -      return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) { +      return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {          if (linkQueue) {            linkQueue.push(scope);            linkQueue.push(node);            linkQueue.push(rootElement); -          linkQueue.push(controller); +          linkQueue.push(boundTranscludeFn);          } else { -          afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller); +          afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn);          }        };      } diff --git a/src/ng/directive/ngIf.js b/src/ng/directive/ngIf.js index 6e845e28..dcb3825d 100644 --- a/src/ng/directive/ngIf.js +++ b/src/ng/directive/ngIf.js @@ -86,15 +86,14 @@ var ngIfDirective = ['$animate', function($animate) {      terminal: true,      restrict: 'A',      $$tlb: true, -    compile: function (element, attr, transclude) { -      return function ($scope, $element, $attr) { +    link: function ($scope, $element, $attr, ctrl, $transclude) {          var block, childScope;          $scope.$watch($attr.ngIf, function ngIfWatchAction(value) {            if (toBoolean(value)) {              if (!childScope) {                childScope = $scope.$new(); -              transclude(childScope, function (clone) { +              $transclude(childScope, function (clone) {                  block = {                    startNode: clone[0],                    endNode: clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ') @@ -115,7 +114,6 @@ var ngIfDirective = ['$animate', function($animate) {              }            }          }); -      };      }    };  }]; diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js index 7ca9a48b..7f395f96 100644 --- a/src/ng/directive/ngInclude.js +++ b/src/ng/directive/ngInclude.js @@ -154,12 +154,12 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'      priority: 400,      terminal: true,      transclude: 'element', -    compile: function(element, attr, transclusion) { +    compile: function(element, attr) {        var srcExp = attr.ngInclude || attr.src,            onloadExp = attr.onload || '',            autoScrollExp = attr.autoscroll; -      return function(scope, $element) { +      return function(scope, $element, $attr, ctrl, $transclude) {          var changeCounter = 0,              currentScope,              currentElement; @@ -188,7 +188,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'                if (thisChangeId !== changeCounter) return;                var newScope = scope.$new(); -              transclusion(newScope, function(clone) { +              $transclude(newScope, function(clone) {                  cleanupLastIncludeContent();                  currentScope = newScope; diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js index c7d0c005..a5614bf4 100644 --- a/src/ng/directive/ngRepeat.js +++ b/src/ng/directive/ngRepeat.js @@ -201,8 +201,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {      priority: 1000,      terminal: true,      $$tlb: true, -    compile: function(element, attr, linker) { -      return function($scope, $element, $attr){ +    link: function($scope, $element, $attr, ctrl, $transclude){          var expression = $attr.ngRepeat;          var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),            trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, @@ -364,7 +363,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {              // jshint bitwise: true              if (!block.startNode) { -              linker(childScope, function(clone) { +              $transclude(childScope, function(clone) {                  clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');                  $animate.enter(clone, null, jqLite(previousNode));                  previousNode = clone; @@ -377,7 +376,6 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {            }            lastBlockMap = nextBlockMap;          }); -      };      }    };  }]; diff --git a/src/ng/directive/ngSwitch.js b/src/ng/directive/ngSwitch.js index 11ef7b71..d4387ae2 100644 --- a/src/ng/directive/ngSwitch.js +++ b/src/ng/directive/ngSwitch.js @@ -160,10 +160,10 @@ var ngSwitchWhenDirective = ngDirective({    transclude: 'element',    priority: 800,    require: '^ngSwitch', -  compile: function(element, attrs, transclude) { -    return function(scope, element, attr, ctrl) { +  compile: function(element, attrs) { +    return function(scope, element, attr, ctrl, $transclude) {        ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); -      ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: transclude, element: element }); +      ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });      };    }  }); @@ -172,10 +172,8 @@ var ngSwitchDefaultDirective = ngDirective({    transclude: 'element',    priority: 800,    require: '^ngSwitch', -  compile: function(element, attrs, transclude) { -    return function(scope, element, attr, ctrl) { -      ctrl.cases['?'] = (ctrl.cases['?'] || []); -      ctrl.cases['?'].push({ transclude: transclude, element: element }); -    }; -  } +  link: function(scope, element, attr, ctrl, $transclude) { +    ctrl.cases['?'] = (ctrl.cases['?'] || []); +    ctrl.cases['?'].push({ transclude: $transclude, element: element }); +   }  }); diff --git a/src/ngRoute/directive/ngView.js b/src/ngRoute/directive/ngView.js index a8a136df..3271ac0c 100644 --- a/src/ngRoute/directive/ngView.js +++ b/src/ngRoute/directive/ngView.js @@ -173,8 +173,7 @@ function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,      terminal: true,      priority: 400,      transclude: 'element', -    compile: function(element, attr, linker) { -      return function(scope, $element, attr) { +    link: function(scope, $element, attr, ctrl, $transclude) {          var currentScope,              currentElement,              autoScrollExp = attr.autoscroll, @@ -200,7 +199,7 @@ function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,            if (template) {              var newScope = scope.$new(); -            linker(newScope, function(clone) { +            $transclude(newScope, function(clone) {                clone.html(template);                $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {                  if (angular.isDefined(autoScrollExp) @@ -235,7 +234,6 @@ function ngViewFactory(   $route,   $anchorScroll,   $compile,   $controller,              cleanupLastView();            }          } -      };      }    };  } diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 3e35fae7..85ee17d2 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -3438,6 +3438,113 @@ describe('$compile', function() {            expect(log).toEqual('pre(); post(unicorn!)');          });        }); + +      it('should copy the directive controller to all clones', function() { +        var transcludeCtrl, cloneCount = 2; +        module(function() { +          directive('transclude', valueFn({ +            transclude: 'content', +            controller: function($transclude) { +              transcludeCtrl = this; +            }, +            link: function(scope, el, attr, ctrl, $transclude) { +              var i; +              for (i=0; i<cloneCount; i++) { +                $transclude(cloneAttach); +              } + +              function cloneAttach(clone) { +                el.append(clone); +              } +            } +          })); +        }); +        inject(function($compile) { +          element = $compile('<div transclude><span></span></div>')($rootScope); +          var children = element.children(), i; +          expect(transcludeCtrl).toBeDefined(); + +          expect(element.data('$transcludeController')).toBe(transcludeCtrl); +          for (i=0; i<cloneCount; i++) { +            expect(children.eq(i).data('$transcludeController')).toBeUndefined(); +          } +        }); +      }); + +      it('should provide the $transclude controller local as 5th argument to the pre and post-link function', function() { +        var ctrlTransclude, preLinkTransclude, postLinkTransclude; +        module(function() { +          directive('transclude', valueFn({ +            transclude: 'content', +            controller: function($transclude) { +              ctrlTransclude = $transclude; +            }, +            compile: function() { +              return { +                pre: function(scope, el, attr, ctrl, $transclude) { +                  preLinkTransclude = $transclude; +                }, +                post: function(scope, el, attr, ctrl, $transclude) { +                  postLinkTransclude = $transclude; +                } +              }; +            } +          })); +        }); +        inject(function($compile) { +          element = $compile('<div transclude></div>')($rootScope); +          expect(ctrlTransclude).toBeDefined(); +          expect(ctrlTransclude).toBe(preLinkTransclude); +          expect(ctrlTransclude).toBe(postLinkTransclude); +        }); +      }); + +      it('should allow an optional scope argument in $transclude', function() { +        var capturedChildCtrl; +        module(function() { +          directive('transclude', valueFn({ +            transclude: 'content', +            link: function(scope, element, attr, ctrl, $transclude) { +              $transclude(scope, function(clone) { +                element.append(clone); +              }); +            } +          })); +        }); +        inject(function($compile) { +          element = $compile('<div transclude>{{$id}}</div>')($rootScope); +          $rootScope.$apply(); +          expect(element.text()).toBe($rootScope.$id); +        }); + +      }); + +      it('should expose the directive controller to transcluded children', function() { +        var capturedChildCtrl; +        module(function() { +          directive('transclude', valueFn({ +            transclude: 'content', +            controller: function() { +            }, +            link: function(scope, element, attr, ctrl, $transclude) { +              $transclude(function(clone) { +                element.append(clone); +              }); +            } +          })); +          directive('child', valueFn({ +            require: '^transclude', +            link: function(scope, element, attr, ctrl) { +              capturedChildCtrl = ctrl; +            } +          })); +        }); +        inject(function($compile) { +          element = $compile('<div transclude><div child></div></div>')($rootScope); +          expect(capturedChildCtrl).toBeTruthy(); +        }); + +      });      }); @@ -3471,7 +3578,6 @@ describe('$compile', function() {          });        }); -        it('should only allow one element transclusion per element', function() {          module(function() {            directive('first', valueFn({ @@ -3620,8 +3726,101 @@ describe('$compile', function() {            ]);          });        }); -    }); +      it('should allow to access $transclude in the same directive', function() { +        var _$transclude; +        module(function() { +          directive('transclude', valueFn({ +            transclude: 'element', +            controller: function($transclude) { +              _$transclude = $transclude; +            } +          })); +        }); +        inject(function($compile) { +          element = $compile('<div transclude></div>')($rootScope); +          expect(_$transclude).toBeDefined() +        }); +      }); + +      it('should copy the directive controller to all clones', function() { +        var transcludeCtrl, cloneCount = 2; +        module(function() { +          directive('transclude', valueFn({ +            transclude: 'element', +            controller: function() { +              transcludeCtrl = this; +            }, +            link: function(scope, el, attr, ctrl, $transclude) { +              var i; +              for (i=0; i<cloneCount; i++) { +                $transclude(cloneAttach); +              } + +              function cloneAttach(clone) { +                el.after(clone); +              } +            } +          })); +        }); +        inject(function($compile) { +          element = $compile('<div><div transclude></div></div>')($rootScope); +          var children = element.children(), i; +          for (i=0; i<cloneCount; i++) { +            expect(children.eq(i).data('$transcludeController')).toBe(transcludeCtrl); +          } +        }); +      }); + +      it('should expose the directive controller to transcluded children', function() { +        var capturedTranscludeCtrl; +        module(function() { +          directive('transclude', valueFn({ +            transclude: 'element', +            controller: function() { +            }, +            link: function(scope, element, attr, ctrl, $transclude) { +              $transclude(scope, function(clone) { +                element.after(clone); +              }); +            } +          })); +          directive('child', valueFn({ +            require: '^transclude', +            link: function(scope, element, attr, ctrl) { +              capturedTranscludeCtrl = ctrl; +            } +          })); +        }); +        inject(function($compile) { +          element = $compile('<div transclude><div child></div></div>')($rootScope); +          expect(capturedTranscludeCtrl).toBeTruthy(); +        }); +      }); + +      it('should allow access to $transclude in a templateUrl directive', function() { +        var transclude; +        module(function() { +          directive('template', valueFn({ +            templateUrl: 'template.html', +            replace: true +          })); +          directive('transclude', valueFn({ +            transclude: 'content', +            controller: function($transclude) { +              transclude = $transclude; +            } +          })); +        }); +        inject(function($compile, $httpBackend) { +          $httpBackend.expectGET('template.html').respond('<div transclude></div>'); +          element = $compile('<div template></div>')($rootScope); +          $httpBackend.flush(); +          expect(transclude).toBeDefined(); +        }); +      }); + +    });      it('should safely create transclude comment node and not break with "-->"',          inject(function($rootScope) { diff --git a/test/ng/directive/ngIfSpec.js b/test/ng/directive/ngIfSpec.js index 3173f476..427bfd59 100755 --- a/test/ng/directive/ngIfSpec.js +++ b/test/ng/directive/ngIfSpec.js @@ -148,6 +148,34 @@ describe('ngIf', function () {  }); +describe('ngIf and transcludes', function() { +  it('should allow access to directive controller from children when used in a replace template', function() { +    var controller; +    module(function($compileProvider) { +      var directive = $compileProvider.directive; +      directive('template', valueFn({ +        template: '<div ng-if="true"><span test></span></div>', +        replace: true, +        controller: function() { +          this.flag = true; +        } +      })); +      directive('test', valueFn({ +        require: '^template', +        link: function(scope, el, attr, ctrl) { +          controller = ctrl; +        } +      })); +    }); +    inject(function($compile, $rootScope) { +      var element = $compile('<div><div template></div></div>')($rootScope); +      $rootScope.$apply(); +      expect(controller.flag).toBe(true); +      dealoc(element); +    }); +  }); +}); +  describe('ngIf animations', function () {    var body, element, $rootElement; diff --git a/test/ng/directive/ngIncludeSpec.js b/test/ng/directive/ngIncludeSpec.js index beb29da7..aba71e44 100644 --- a/test/ng/directive/ngIncludeSpec.js +++ b/test/ng/directive/ngIncludeSpec.js @@ -439,6 +439,36 @@ describe('ngInclude', function() {    });  }); +describe('ngInclude and transcludes', function() { +  it('should allow access to directive controller from children when used in a replace template', function() { +    var controller; +    module(function($compileProvider) { +      var directive = $compileProvider.directive; +      directive('template', valueFn({ +        template: '<div ng-include="\'include.html\'"></div>', +        replace: true, +        controller: function() { +          this.flag = true; +        } +      })); +      directive('test', valueFn({ +        require: '^template', +        link: function(scope, el, attr, ctrl) { +          controller = ctrl; +        } +      })); +    }); +    inject(function($compile, $rootScope, $httpBackend) { +      $httpBackend.expectGET('include.html').respond('<div><div test></div></div>'); +      var element = $compile('<div><div template></div></div>')($rootScope); +      $rootScope.$apply(); +      $httpBackend.flush(); +      expect(controller.flag).toBe(true); +      dealoc(element); +    }); +  }); +}); +  describe('ngInclude animations', function() {    var body, element, $rootElement; diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js index 9dde36e7..6584f31a 100644 --- a/test/ng/directive/ngRepeatSpec.js +++ b/test/ng/directive/ngRepeatSpec.js @@ -1058,6 +1058,33 @@ describe('ngRepeat', function() {    });  }); +describe('ngRepeat and transcludes', function() { +  it('should allow access to directive controller from children when used in a replace template', function() { +    var controller; +    module(function($compileProvider) { +      var directive = $compileProvider.directive; +      directive('template', valueFn({ +        template: '<div ng-repeat="l in [1]"><span test></span></div>', +        replace: true, +        controller: function() { +          this.flag = true; +        } +      })); +      directive('test', valueFn({ +        require: '^template', +        link: function(scope, el, attr, ctrl) { +          controller = ctrl; +        } +      })); +    }); +    inject(function($compile, $rootScope) { +      var element = $compile('<div><div template></div></div>')($rootScope); +      $rootScope.$apply(); +      expect(controller.flag).toBe(true); +      dealoc(element); +    }); +  }); +});  describe('ngRepeat animations', function() {    var body, element, $rootElement; diff --git a/test/ngRoute/directive/ngViewSpec.js b/test/ngRoute/directive/ngViewSpec.js index 1df19d6a..e96da022 100644 --- a/test/ngRoute/directive/ngViewSpec.js +++ b/test/ngRoute/directive/ngViewSpec.js @@ -514,6 +514,44 @@ describe('ngView', function() {    });  }); +describe('ngView and transcludes', function() { +  it('should allow access to directive controller from children when used in a replace template', function() { +    var controller; +    module('ngRoute'); +    module(function($compileProvider, $routeProvider) { +      $routeProvider.when('/view', {templateUrl: 'view.html'}); +      var directive = $compileProvider.directive; +      directive('template', function() { +        return { +          template: '<div ng-view></div>', +          replace: true, +          controller: function() { +            this.flag = true; +          } +        }; +      }); + +      directive('test', function() { +        return { +          require: '^template', +          link: function(scope, el, attr, ctrl) { +            controller = ctrl; +          } +        }; +      }); +    }); +    inject(function($compile, $rootScope, $httpBackend, $location) { +      $httpBackend.expectGET('view.html').respond('<div><div test></div></div>'); +      var element = $compile('<div><div template></div></div>')($rootScope); +      $location.url('/view'); +      $rootScope.$apply(); +      $httpBackend.flush(); +      expect(controller.flag).toBe(true); +      dealoc(element); +    }); +  }); +}); +  describe('ngView animations', function() {    var body, element, $rootElement; | 
