-
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 8 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,22 @@ 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=} limitMaxPriorityToFirstElement 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 +651,24 @@ function $CompileProvider($provide) { | |
compileNode = $compileNode[0]; | ||
replaceWith(jqCollection, 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>'); | ||
|
||
$template = jqLite(compileNode); | ||
$compileNode = templateAttrs.$$element = | ||
jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); | ||
compileNode = $compileNode[0]; | ||
replaceWith(jqCollection, jqLite($template[0]), compileNode); | ||
cloneContent.append($template); | ||
forEach(nestedContent.splice(1, nestedContent.length - 1), | ||
function(toRemove) { | ||
removeElement(jqCollection, toRemove); | ||
cloneContent.append(toRemove); | ||
} | ||
); | ||
childTranscludeFn = compile(cloneContent.contents(), transcludeFn, terminalPriority, true); | ||
} else { | ||
$template = jqLite(JQLiteClone(compileNode)).contents(); | ||
$compileNode.html(''); // clear contents | ||
|
@@ -731,6 +755,39 @@ function $CompileProvider($provide) { | |
|
||
//////////////////// | ||
|
||
|
||
function extractMultiElementTransclude(cursor, directiveName) { | ||
var transcludeContent = [], | ||
c, count = 0, | ||
transcludeStart = directiveName + 'Start', | ||
transcludeEnd = directiveName + 'End'; | ||
|
||
do { | ||
if (containsAttr(cursor, transcludeStart)) count++; | ||
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 attr, attrs = node.attributes, length = attrs && attrs.length; | ||
if ( length ) { | ||
for (var j = 0; j < length; j++) { | ||
attr = attrs[j]; | ||
if (attr.specified && directiveNormalize(attr.name) == attributeName) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
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; | ||
|
@@ -1152,6 +1209,23 @@ function $CompileProvider($provide) { | |
newNode[jqLite.expando] = oldNode[jqLite.expando]; | ||
$element[0] = newNode; | ||
} | ||
|
||
|
||
function removeElement($rootElement, element) { | ||
var i, ii, parent = element.parentNode; | ||
|
||
if ($rootElement) { | ||
for(i = 0, ii = $rootElement.length; i < ii; i++) { | ||
if ($rootElement[i] == element) { | ||
$rootElement.splice(i, 1); | ||
break; | ||
} | ||
} | ||
} | ||
if (parent) { | ||
parent.removeChild(element); | ||
} | ||
} | ||
}]; | ||
} | ||
|
||
|
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.
Do our coding conventions allow single line if statements without {} braces?
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.
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 comment
The 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.