Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($compile): multiple transclusion via slots
Browse files Browse the repository at this point in the history
  • Loading branch information
petebacondarwin committed Sep 23, 2015
1 parent 3af71be commit 7af6e3a
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 6 deletions.
30 changes: 28 additions & 2 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}

Expand Down
19 changes: 15 additions & 4 deletions src/ng/directive/ngTransclude.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
}
});
98 changes: 98 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<div ng-transclude></div>'
};
});
});
inject(function($rootScope, $compile) {
element = $compile(
'<div trans>' +
'<span>stuart</span>' +
'<span>bob</span>' +
'<span ng-transclude-slot="boss">gru</span>' +
'<span>kevin</span>' +
'</div>')($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:
'<div class="boss" ng-transclude="boss"></div>' +
'<div class="minion" ng-transclude="minion"></div>' +
'<div class="other" ng-transclude></div>'
};
});
});
inject(function($rootScope, $compile) {
element = $compile(
'<div trans>' +
'<span ng-transclude-slot="minion">stuart</span>' +
'<span>dorothy</span>' +
'<span ng-transclude-slot="boss">gru</span>' +
'<span ng-transclude-slot="minion">kevin</span>' +
'</div>')($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(
'<div trans>' +
' <span ng-transclude-slot="minion">stuart</span>' +
' <span ng-transclude-slot="minion">bob</span>' +
' <span>dorothy</span>' +
' <span ng-transclude-slot="boss">gru</span>' +
'</div>')($rootScope);
$rootScope.$apply();

var minionTranscludeFn = capturedTranscludeFn.$$boundTransclude.$$slots['minion'];
var minions = minionTranscludeFn();
expect(minions[0].outerHTML).toEqual('<span ng-transclude-slot="minion" class="ng-scope">stuart</span>');
expect(minions[1].outerHTML).toEqual('<span ng-transclude-slot="minion" class="ng-scope">bob</span>');

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('<span ng-transclude-slot="boss" class="ng-scope">gru</span>');

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) {
Expand Down

0 comments on commit 7af6e3a

Please sign in to comment.