diff --git a/src/ng/compile.js b/src/ng/compile.js index 393ab6375a27..4be8ee82dc1d 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1486,6 +1486,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }); }; + var boundSlots = boundTranscludeFn.$$slots = createMap(); + for (var slotName in transcludeFn.$$slots) { + boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); + } + return boundTranscludeFn; } @@ -1646,6 +1651,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }; } + + function compileTemplate(eager, $templateNodes, parentTranscludeFn, terminalPriority, ignoreDirective, previousCompileContext) { + var defaultSlot = []; + var slots = createMap(); + forEach($templateNodes, function(node) { + var slotName = jqLite(node).attr('ng-transclude-slot'); + var slot = defaultSlot; + if (slotName) { + slot = slots[slotName] = (slots[slotName] || []); + } + slot.push(node); + }); + + var transcludeFn = compilationGenerator(eager, defaultSlot, parentTranscludeFn, terminalPriority, ignoreDirective, previousCompileContext); + forEach(Object.keys(slots), function(slotName) { + slots[slotName] = compilationGenerator(eager, slots[slotName], parentTranscludeFn, terminalPriority, ignoreDirective, previousCompileContext); + }); + transcludeFn.$$slots = slots; + return transcludeFn; + } + /** * A function generator that is used to support both eager and lazy compilation * linking function. @@ -1815,7 +1841,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { compileNode = $compileNode[0]; replaceWith(jqCollection, sliceArgs($template), compileNode); - childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, + childTranscludeFn = compileTemplate(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, replaceDirective && replaceDirective.name, { // Don't pass in: // - controllerDirectives - otherwise we'll create duplicates controllers @@ -1829,7 +1855,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } else { $template = jqLite(jqLiteClone(compileNode)).contents(); $compileNode.empty(); // clear contents - childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn); + childTranscludeFn = compileTemplate(mightHaveMultipleTransclusionError, $template, transcludeFn); } } diff --git a/src/ng/directive/ngTransclude.js b/src/ng/directive/ngTransclude.js index 36201a69ab73..8c189d3c75ab 100644 --- a/src/ng/directive/ngTransclude.js +++ b/src/ng/directive/ngTransclude.js @@ -65,9 +65,20 @@ var ngTranscludeDirective = ngDirective({ startingTag($element)); } - $transclude(function(clone) { - $element.empty(); - $element.append(clone); - }); + if ($attrs.ngTransclude) { + $transclude = $transclude.$$boundTransclude.$$slots[$attrs.ngTransclude]; + if (!$transclude) return; + + $transclude(undefined, function(clone) { + $element.empty(); + $element.append(clone); + }); + } else { + + $transclude(function(clone) { + $element.empty(); + $element.append(clone); + }); + } } }); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 54942dfdd1e7..e160d7c7dd51 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -7262,6 +7262,104 @@ describe('$compile', function() { }); + describe('multi-slot transclude', function() { + it('should only include elements without `ng-transclude-slot` attribute in default transclusion function', function() { + module(function() { + directive('trans', function() { + return { + transclude: true, + template: '
' + }; + }); + }); + inject(function($rootScope, $compile) { + element = $compile( + '
' + + 'stuart' + + 'bob' + + 'gru' + + 'kevin' + + '
')($rootScope); + $rootScope.$apply(); + expect(element.text()).toEqual('stuartbobkevin'); + }); + }); + + it('should transclude elements to an `ng-transclude` with a matching `ng-transclude-slot`', function() { + module(function() { + directive('trans', function() { + return { + transclude: true, + template: + '
' + + '
' + + '
' + }; + }); + }); + inject(function($rootScope, $compile) { + element = $compile( + '
' + + 'stuart' + + 'dorothy' + + 'gru' + + 'kevin' + + '
')($rootScope); + $rootScope.$apply(); + expect(element.children().eq(0).text()).toEqual('gru'); + expect(element.children().eq(1).text()).toEqual('stuartkevin'); + expect(element.children().eq(2).text()).toEqual('dorothy'); + }); + }); + + it('should provide the elements marked with `ng-transclude-slot` as additional transclude functions on the $$slots property', function() { + var capturedTranscludeFn; + module(function() { + directive('trans', function() { + return { + transclude: true, + link: function(scope, element, attrs, controller, transclude) { + capturedTranscludeFn = transclude; + } + }; + }); + }); + inject(function($rootScope, $compile, log) { + element = $compile( + '
' + + ' stuart' + + ' bob' + + ' dorothy' + + ' gru' + + '
')($rootScope); + $rootScope.$apply(); + + var minionTranscludeFn = capturedTranscludeFn.$$boundTransclude.$$slots['minion']; + var minions = minionTranscludeFn(); + expect(minions[0].outerHTML).toEqual('stuart'); + expect(minions[1].outerHTML).toEqual('bob'); + + var scope = element.scope(); + + var minionScope = jqLite(minions[0]).scope(); + expect(minionScope.$parent).toBe(scope); + + var bossTranscludeFn = capturedTranscludeFn.$$boundTransclude.$$slots['boss']; + var boss = bossTranscludeFn(); + expect(boss[0].outerHTML).toEqual('gru'); + + var bossScope = jqLite(boss[0]).scope(); + expect(bossScope.$parent).toBe(scope); + + expect(bossScope).not.toBe(minionScope); + + dealoc(boss); + dealoc(minions); + }); + }); + }); + + describe('img[src] sanitization', function() { it('should NOT require trusted values for img src', inject(function($rootScope, $compile, $sce) {