Skip to content

Commit f86edbd

Browse files
committed
Full Element Transclusion
1 parent bf7eba7 commit f86edbd

File tree

2 files changed

+223
-19
lines changed

2 files changed

+223
-19
lines changed

src/compile.js

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ function $CompileProvider($provide) {
161161
}
162162
};
163163

164-
function compile($compileNodes) {
165-
var compositeLinkFn = compileNodes($compileNodes);
164+
function compile($compileNodes, maxPriority) {
165+
var compositeLinkFn = compileNodes($compileNodes, maxPriority);
166166

167167
return function publicLinkFn(scope, cloneAttachFn, options) {
168168
options = options || {};
@@ -183,19 +183,19 @@ function $CompileProvider($provide) {
183183
};
184184
}
185185

186-
function compileNodes($compileNodes) {
186+
function compileNodes($compileNodes, maxPriority) {
187187
var linkFns = [];
188-
_.forEach($compileNodes, function(node, i) {
189-
var attrs = new Attributes($(node));
190-
var directives = collectDirectives(node, attrs);
188+
_.times($compileNodes.length, function(i) {
189+
var attrs = new Attributes($($compileNodes[i]));
190+
var directives = collectDirectives($compileNodes[i], attrs, maxPriority);
191191
var nodeLinkFn;
192192
if (directives.length) {
193-
nodeLinkFn = applyDirectivesToNode(directives, node, attrs);
193+
nodeLinkFn = applyDirectivesToNode(directives, $compileNodes[i], attrs);
194194
}
195195
var childLinkFn;
196196
if ((!nodeLinkFn || !nodeLinkFn.terminal) &&
197-
node.childNodes && node.childNodes.length) {
198-
childLinkFn = compileNodes(node.childNodes);
197+
$compileNodes[i].childNodes && $compileNodes[i].childNodes.length) {
198+
childLinkFn = compileNodes($compileNodes[i].childNodes);
199199
}
200200
if (nodeLinkFn && nodeLinkFn.scope) {
201201
attrs.$$element.addClass('ng-scope');
@@ -279,12 +279,12 @@ function $CompileProvider($provide) {
279279
return false;
280280
}
281281

282-
function collectDirectives(node, attrs) {
282+
function collectDirectives(node, attrs, maxPriority) {
283283
var directives = [];
284284
var match;
285285
if (node.nodeType === Node.ELEMENT_NODE) {
286286
var normalizedNodeName = directiveNormalize(nodeName(node).toLowerCase());
287-
addDirective(directives, normalizedNodeName, 'E');
287+
addDirective(directives, normalizedNodeName, 'E', maxPriority);
288288
_.forEach(node.attributes, function(attr) {
289289
var attrStartName, attrEndName;
290290
var name = attr.name;
@@ -309,7 +309,7 @@ function $CompileProvider($provide) {
309309
}
310310
}
311311
normalizedAttrName = directiveNormalize(name.toLowerCase());
312-
addDirective(directives, normalizedAttrName, 'A', attrStartName, attrEndName);
312+
addDirective(directives, normalizedAttrName, 'A', maxPriority, attrStartName, attrEndName);
313313
if (isNgAttr || !attrs.hasOwnProperty(normalizedAttrName)) {
314314
attrs[normalizedAttrName] = attr.value.trim();
315315
if (isBooleanAttribute(node, normalizedAttrName)) {
@@ -322,7 +322,7 @@ function $CompileProvider($provide) {
322322
if (_.isString(className) && !_.isEmpty(className)) {
323323
while ((match = /([\d\w\-_]+)(?:\:([^;]+))?;?/.exec(className))) {
324324
var normalizedClassName = directiveNormalize(match[1]);
325-
if (addDirective(directives, normalizedClassName, 'C')) {
325+
if (addDirective(directives, normalizedClassName, 'C', maxPriority)) {
326326
attrs[normalizedClassName] = match[2] ? match[2].trim() : undefined;
327327
}
328328
className = className.substr(match.index + match[0].length);
@@ -332,7 +332,7 @@ function $CompileProvider($provide) {
332332
match = /^\s*directive\:\s*([\d\w\-_]+)\s*(.*)$/.exec(node.nodeValue);
333333
if (match) {
334334
var normalizedName = directiveNormalize(match[1]);
335-
if (addDirective(directives, normalizedName, 'M')) {
335+
if (addDirective(directives, normalizedName, 'M', maxPriority)) {
336336
attrs[normalizedName] = match[2] ? match[2].trim() : undefined;
337337
}
338338
}
@@ -341,12 +341,13 @@ function $CompileProvider($provide) {
341341
return directives;
342342
}
343343

344-
function addDirective(directives, name, mode, attrStartName, attrEndName) {
344+
function addDirective(directives, name, mode, maxPriority, attrStartName, attrEndName) {
345345
var match;
346346
if (hasDirectives.hasOwnProperty(name)) {
347347
var foundDirectives = $injector.get(name + 'Directive');
348348
var applicableDirectives = _.filter(foundDirectives, function(dir) {
349-
return dir.restrict.indexOf(mode) !== -1;
349+
return (maxPriority === undefined || maxPriority > dir.priority) &&
350+
dir.restrict.indexOf(mode) !== -1;
350351
});
351352
_.forEach(applicableDirectives, function(directive) {
352353
if (attrStartName) {
@@ -488,9 +489,17 @@ function $CompileProvider($provide) {
488489
throw 'Multiple directives asking for transclude';
489490
}
490491
hasTranscludeDirective = true;
491-
var $transcludedNodes = $compileNode.clone().contents();
492-
childTranscludeFn = compile($transcludedNodes);
493-
$compileNode.empty();
492+
if (directive.transclude === 'element') {
493+
var $originalCompileNode = $compileNode;
494+
$compileNode = attrs.$$element = $(document.createComment(' ' + directive.name + ': ' + attrs[directive.name] + ' '));
495+
$originalCompileNode.replaceWith($compileNode);
496+
terminalPriority = directive.priority;
497+
childTranscludeFn = compile($originalCompileNode, terminalPriority);
498+
} else {
499+
var $transcludedNodes = $compileNode.clone().contents();
500+
childTranscludeFn = compile($transcludedNodes);
501+
$compileNode.empty();
502+
}
494503
}
495504
if (directive.template) {
496505
if (templateDirective) {

test/compile_spec.js

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3277,4 +3277,199 @@ describe('$compile', function() {
32773277

32783278
});
32793279

3280+
describe('element transclusion', function() {
3281+
3282+
it('removes the element from the DOM', function() {
3283+
var injector = makeInjectorWithDirectives({
3284+
myTranscluder: function() {
3285+
return {
3286+
transclude: 'element'
3287+
};
3288+
}
3289+
});
3290+
injector.invoke(function($compile) {
3291+
var el = $('<div><div my-transcluder></div></div>');
3292+
3293+
$compile(el);
3294+
3295+
expect(el.is(':empty')).toBe(true);
3296+
});
3297+
});
3298+
3299+
it('replaces the element with a comment', function() {
3300+
var injector = makeInjectorWithDirectives({
3301+
myTranscluder: function() {
3302+
return {
3303+
transclude: 'element'
3304+
};
3305+
}
3306+
});
3307+
injector.invoke(function($compile) {
3308+
var el = $('<div><div my-transcluder></div></div>');
3309+
3310+
$compile(el);
3311+
3312+
expect(el.html()).toEqual('<!-- myTranscluder: -->');
3313+
});
3314+
});
3315+
3316+
it('includes directive attribute value in comment', function() {
3317+
var injector = makeInjectorWithDirectives({
3318+
myTranscluder: function() {
3319+
return {transclude: 'element'};
3320+
}
3321+
});
3322+
injector.invoke(function($compile) {
3323+
var el = $('<div><div my-transcluder=42></div></div>');
3324+
3325+
$compile(el);
3326+
3327+
expect(el.html()).toEqual('<!-- myTranscluder: 42 -->');
3328+
});
3329+
});
3330+
3331+
it('calls directive compile and link with comment', function() {
3332+
var gotCompiledEl, gotLinkedEl;
3333+
var injector = makeInjectorWithDirectives({
3334+
myTranscluder: function() {
3335+
return {
3336+
transclude: 'element',
3337+
compile: function(compiledEl) {
3338+
gotCompiledEl = compiledEl;
3339+
return function(scope, linkedEl) {
3340+
gotLinkedEl = linkedEl;
3341+
};
3342+
}
3343+
};
3344+
}
3345+
});
3346+
injector.invoke(function($compile, $rootScope) {
3347+
var el = $('<div><div my-transcluder></div></div>');
3348+
3349+
$compile(el)($rootScope);
3350+
3351+
expect(gotCompiledEl[0].nodeType).toBe(Node.COMMENT_NODE);
3352+
expect(gotLinkedEl[0].nodeType).toBe(Node.COMMENT_NODE);
3353+
});
3354+
});
3355+
3356+
it('calls lower priority compile with original', function() {
3357+
var gotCompiledEl, gotLinkedEl;
3358+
var injector = makeInjectorWithDirectives({
3359+
myTranscluder: function() {
3360+
return {
3361+
priority: 2,
3362+
transclude: 'element'
3363+
};
3364+
},
3365+
myOtherDirective: function() {
3366+
return {
3367+
priority: 1,
3368+
compile: function(compiledEl) {
3369+
gotCompiledEl = compiledEl;
3370+
}
3371+
};
3372+
}
3373+
});
3374+
injector.invoke(function($compile) {
3375+
var el = $('<div><div my-transcluder my-other-directive></div></div>');
3376+
3377+
$compile(el);
3378+
3379+
expect(gotCompiledEl[0].nodeType).toBe(Node.ELEMENT_NODE);
3380+
});
3381+
});
3382+
3383+
it('calls compile on child element directives', function() {
3384+
var compileSpy = jasmine.createSpy();
3385+
var injector = makeInjectorWithDirectives({
3386+
myTranscluder: function() {
3387+
return {
3388+
transclude: 'element'
3389+
};
3390+
},
3391+
myOtherDirective: function() {
3392+
return {
3393+
compile: compileSpy
3394+
};
3395+
}
3396+
});
3397+
injector.invoke(function($compile) {
3398+
var el = $('<div><div my-transcluder><div my-other-directive></div></div></div>');
3399+
3400+
$compile(el);
3401+
3402+
expect(compileSpy).toHaveBeenCalled();
3403+
});
3404+
});
3405+
3406+
it('compiles original element contents once', function() {
3407+
var compileSpy = jasmine.createSpy();
3408+
var injector = makeInjectorWithDirectives({
3409+
myTranscluder: function() {
3410+
return {transclude: 'element'};
3411+
},
3412+
myOtherDirective: function() {
3413+
return {
3414+
compile: compileSpy
3415+
};
3416+
}
3417+
});
3418+
injector.invoke(function($compile) {
3419+
var el = $('<div><div my-transcluder><div my-other-directive></div></div></div>');
3420+
3421+
$compile(el);
3422+
3423+
expect(compileSpy.calls.count()).toBe(1);
3424+
});
3425+
});
3426+
3427+
it('makes original element available for transclusion', function() {
3428+
var injector = makeInjectorWithDirectives({
3429+
myDouble: function() {
3430+
return {
3431+
transclude: 'element',
3432+
link: function(scope, el, attrs, ctrl, transclude) {
3433+
transclude(function(clone) {
3434+
el.after(clone);
3435+
});
3436+
transclude(function(clone) {
3437+
el.after(clone);
3438+
});
3439+
}
3440+
};
3441+
}
3442+
});
3443+
injector.invoke(function($compile, $rootScope) {
3444+
var el = $('<div><div my-double>Hello</div>');
3445+
3446+
$compile(el)($rootScope);
3447+
3448+
expect(el.find('[my-double]').length).toBe(2);
3449+
});
3450+
});
3451+
3452+
it('sets directive attributes element to comment', function() {
3453+
var injector = makeInjectorWithDirectives({
3454+
myTranscluder: function() {
3455+
return {
3456+
transclude: 'element',
3457+
link: function(scope, element, attrs, ctrl, transclude) {
3458+
attrs.$set('testing', '42');
3459+
element.after(transclude());
3460+
}
3461+
};
3462+
}
3463+
});
3464+
injector.invoke(function($compile, $rootScope) {
3465+
var el = $('<div><div my-transcluder></div></div>');
3466+
3467+
$compile(el)($rootScope);
3468+
3469+
expect(el.find('[my-transcluder]').attr('testing')).toBeUndefined();
3470+
});
3471+
});
3472+
3473+
});
3474+
32803475
});

0 commit comments

Comments
 (0)