-
Notifications
You must be signed in to change notification settings - Fork 27.4k
Transclude type multi-element, use this for ng-repeat #2531
Changes from 3 commits
fca21fe
477ae0c
6861142
efd5bd7
130fd03
95d8999
9692381
0fae109
9af7cb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -357,7 +357,7 @@ function $CompileProvider($provide) { | |
|
||
//================================ | ||
|
||
function compile($compileNodes, transcludeFn, maxPriority) { | ||
function compile($compileNodes, transcludeFn, maxPriority, limitMaxPriorityToFirstElement) { | ||
if (!($compileNodes instanceof jqLite)) { | ||
// jquery always rewraps, whereas we need to preserve the original selector so that we can modify it. | ||
$compileNodes = jqLite($compileNodes); | ||
|
@@ -369,7 +369,8 @@ function $CompileProvider($provide) { | |
$compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; | ||
} | ||
}); | ||
var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority); | ||
var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, | ||
limitMaxPriorityToFirstElement); | ||
return function publicLinkFn(scope, cloneConnectFn){ | ||
assertArg(scope, 'scope'); | ||
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart | ||
|
@@ -418,17 +419,21 @@ function $CompileProvider($provide) { | |
* rootElement must be set the jqLite collection of the compile root. This is | ||
* needed so that the jqLite collection items can be replaced with widgets. | ||
* @param {number=} max directive priority | ||
* @param {boolean=} if the max priority should only apply to the first element in the list. | ||
* A true value here will make the maxPriority only apply to the first element on the | ||
* list while the other elements on the list will not have a maxPriority set | ||
* @returns {?function} A composite linking function of all of the matched directives or null. | ||
*/ | ||
function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority) { | ||
function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, limitMaxPriorityToFirstElement) { | ||
var linkFns = [], | ||
nodeLinkFn, childLinkFn, directives, attrs, linkFnFound; | ||
|
||
for(var i = 0; i < nodeList.length; i++) { | ||
attrs = new Attributes(); | ||
|
||
// we must always refer to nodeList[i] since the nodes can be replaced underneath us. | ||
directives = collectDirectives(nodeList[i], [], attrs, maxPriority); | ||
directives = collectDirectives(nodeList[i], [], attrs, | ||
(limitMaxPriorityToFirstElement && i != 0) ? undefined : maxPriority); | ||
|
||
nodeLinkFn = (directives.length) | ||
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement) | ||
|
@@ -645,6 +650,24 @@ function $CompileProvider($provide) { | |
compileNode = $compileNode[0]; | ||
replaceWith($rootElement, jqLite($template[0]), compileNode); | ||
childTranscludeFn = compile($template, transcludeFn, terminalPriority); | ||
} else if (directiveValue == 'multi-element') { | ||
// We need to compile a clone of the elements, but at the same time we have to be sure that these elements are siblings | ||
var nestedContent = extractMultiElementTransclude(compileNode, directiveName); | ||
var cloneContent = jqLite('<div></div>'); | ||
forEach(nestedContent, function(nestedElement) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason that you clone these nodes for the template but then replace the originals with comments later anyway? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is possible, will make the change and post a new version of the patch |
||
cloneContent.append(JQLiteClone(nestedElement)); | ||
}); | ||
childTranscludeFn = compile(cloneContent.contents(), transcludeFn, terminalPriority, true); | ||
$template = jqLite(compileNode); | ||
$compileNode = templateAttrs.$$element = | ||
jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); | ||
compileNode = $compileNode[0]; | ||
replaceWith($rootElement, $template, compileNode); | ||
forEach(nestedContent.splice(1), | ||
function(toRemove) { | ||
replaceWith($rootElement, jqLite(toRemove), document.createComment(' placeholder ')); | ||
} | ||
); | ||
} else { | ||
$template = jqLite(JQLiteClone(compileNode)).contents(); | ||
$compileNode.html(''); // clear contents | ||
|
@@ -731,6 +754,55 @@ function $CompileProvider($provide) { | |
|
||
//////////////////// | ||
|
||
|
||
function extractMultiElementTransclude(cursor, directiveName) { | ||
var transcludeContent = [], | ||
c, count = 0, | ||
transcludeStart = directiveName + 'Start', | ||
transcludeEnd = directiveName + 'End'; | ||
|
||
do { | ||
if (containsAttr(cursor, transcludeStart)) count++; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do our coding conventions allow single line if statements without {} braces? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was not aware of this. In fact, I want to follow the "implicit" coding convention that, if the action is simple, then do not put braces
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we do occasionally use this style as long as the whole if block is a one-liner. |
||
if (containsAttr(cursor, transcludeEnd)) count--; | ||
transcludeContent.push(cursor); | ||
cursor = cursor.nextSibling; | ||
} while(count > 0 && cursor); | ||
if (count > 0) throw Error('Unmatched ' + transcludeStart + '.'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it should be without |
||
if (count < 0) throw Error('Unexpected ' + transcludeEnd + '.'); | ||
return transcludeContent; | ||
} | ||
|
||
|
||
function containsAttr(node, attributeName) { | ||
var nodeType = node.nodeType, | ||
result = false; | ||
|
||
switch(nodeType) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any need to have this as a switch statement, given that there is only one clause?
Also means that you can ditch |
||
case 1: | ||
// iterate over the attributes | ||
for (var attr, name, nName, ngAttrName, nAttrs = node.attributes, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see that this block of code was mostly copied from collectDirectives(). It can be simplified... |
||
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { | ||
attr = nAttrs[j]; | ||
if (attr.specified) { | ||
name = attr.name; | ||
// support ngAttr attribute binding | ||
ngAttrName = directiveNormalize(name); | ||
if (NG_ATTR_BINDING.test(ngAttrName)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
name = ngAttrName.substr(6).toLowerCase(); | ||
} | ||
nName = directiveNormalize(name.toLowerCase()); | ||
if (nName == attributeName) { | ||
result = true; | ||
break; | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
return result; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact I would suggest that this whole function could be replaced with:
But I might be missing something :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Much better, thanks! |
||
|
||
function addLinkFns(pre, post) { | ||
if (pre) { | ||
pre.require = directive.require; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,7 +145,7 @@ | |
var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) { | ||
var NG_REMOVED = '$$NG_REMOVED'; | ||
return { | ||
transclude: 'element', | ||
transclude: 'multi-element', | ||
priority: 1000, | ||
terminal: true, | ||
compile: function(element, attr, linker) { | ||
|
@@ -258,7 +258,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) { | |
if (lastBlockMap.hasOwnProperty(key)) { | ||
block = lastBlockMap[key]; | ||
animate.leave(block.element); | ||
block.element[0][NG_REMOVED] = true; | ||
forEach(block.element, function(leavingElement) { leavingElement[NG_REMOVED] = true; }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wouldn't be needed if the repeat template was replaced with only one comment - am I missing something important here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is marking all the elements in the multi-element block that they will be removed. |
||
block.scope.$destroy(); | ||
} | ||
} | ||
|
@@ -274,7 +274,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) { | |
// associated scope/element | ||
childScope = block.scope; | ||
|
||
nextCursor = cursor[0]; | ||
nextCursor = cursor[cursor.length - 1]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nor this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, we need the cursor to be at the last element on the previous multi-element block |
||
do { | ||
nextCursor = nextCursor.nextSibling; | ||
} while(nextCursor && nextCursor[NG_REMOVED]); | ||
|
@@ -284,7 +284,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) { | |
cursor = block.element; | ||
} else { | ||
// existing item which got moved | ||
animate.move(block.element, null, cursor); | ||
animate.move(block.element, null, cursor.eq(-1)); | ||
cursor = block.element; | ||
} | ||
} else { | ||
|
@@ -301,7 +301,7 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) { | |
|
||
if (!block.element) { | ||
linker(childScope, function(clone) { | ||
animate.enter(clone, null, cursor); | ||
animate.enter(clone, null, cursor.eq(-1)); | ||
cursor = clone; | ||
block.scope = childScope; | ||
block.element = clone; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo:
should be:
Also, I think that it would be useful to explain why this parameter has been added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will make the change to
@param
but I am not sure about adding the full explanation on why this new parameter was added. Just for completeness, it would be something like thisWhen extracting a multi-element block, we need to remove the block and compile it to create the linking function for the entire block. Now, before the patch, then calling this compile function, sometimes we call it with one element and a maxPriotity (this was for transclusion = 'element') or with multiple elements and no maxPriotity. With multi-element transclusion, we need to handle another case, this is to get multiple elements but there should be a limit on the priority but just to the first element.
Note: In a strict sense it should be possible to remove this parameter and make the assumption that the maxPriority only applies to the first element when nodeList has multiple elements, but I think it makes clearer that, even when there is some redundancy