aboutsummaryrefslogtreecommitdiffstats
path: root/src/ng
diff options
context:
space:
mode:
authorMisko Hevery2013-05-24 12:41:38 -0700
committerMisko Hevery2013-05-28 22:28:32 -0700
commite46100f7097d9a8f174bdb9e15d4c6098395c3f2 (patch)
tree781564141fc9cf580886201d97f7d45064218d82 /src/ng
parentb8ea7f6aba2e675b85826b0bee1f21ddd7b866a5 (diff)
downloadangular.js-e46100f7097d9a8f174bdb9e15d4c6098395c3f2.tar.bz2
feat($compile): support multi-element directive
By appending directive-start and directive-end to a directive it is now possible to have the directive act on a group of elements. It is now possible to iterate over multiple elements like so: <table> <tr ng-repeat-start="item in list">I get repeated</tr> <tr ng-repeat-end>I also get repeated</tr> </table>
Diffstat (limited to 'src/ng')
-rw-r--r--src/ng/animator.js15
-rw-r--r--src/ng/compile.js123
-rw-r--r--src/ng/directive/ngRepeat.js2
3 files changed, 114 insertions, 26 deletions
diff --git a/src/ng/animator.js b/src/ng/animator.js
index 2965717b..2b399813 100644
--- a/src/ng/animator.js
+++ b/src/ng/animator.js
@@ -395,11 +395,16 @@ var $AnimatorProvider = function() {
}
function insert(element, parent, after) {
- if (after) {
- after.after(element);
- } else {
- parent.append(element);
- }
+ var afterNode = after && after[after.length - 1];
+ var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
+ var afterNextSibling = afterNode && afterNode.nextSibling;
+ forEach(element, function(node) {
+ if (afterNextSibling) {
+ parentNode.insertBefore(node, afterNextSibling);
+ } else {
+ parentNode.appendChild(node);
+ }
+ });
}
function remove(element) {
diff --git a/src/ng/compile.js b/src/ng/compile.js
index be22482b..2dddf82d 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -358,11 +358,12 @@ function $CompileProvider($provide) {
// jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
$compileNodes = jqLite($compileNodes);
}
+ var tempParent = document.createDocumentFragment();
// We can not compile top level text elements since text nodes can be merged and we will
// not be able to attach scope data to them, so we will wrap them in <span>
forEach($compileNodes, function(node, index){
if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
- $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
+ $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0];
}
});
var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority);
@@ -420,7 +421,7 @@ function $CompileProvider($provide) {
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, i == 0 ? maxPriority : undefined);
nodeLinkFn = (directives.length)
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
@@ -509,6 +510,10 @@ function $CompileProvider($provide) {
// iterate over the attributes
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
+ var attrStartName;
+ var attrEndName;
+ var index;
+
attr = nAttrs[j];
if (attr.specified) {
name = attr.name;
@@ -517,6 +522,11 @@ function $CompileProvider($provide) {
if (NG_ATTR_BINDING.test(ngAttrName)) {
name = ngAttrName.substr(6).toLowerCase();
}
+ if ((index = ngAttrName.lastIndexOf('Start')) != -1 && index == ngAttrName.length - 5) {
+ attrStartName = name;
+ attrEndName = name.substr(0, name.length - 5) + 'end';
+ name = name.substr(0, name.length - 6);
+ }
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
attrs[nName] = value = trim((msie && name == 'href')
@@ -526,7 +536,7 @@ function $CompileProvider($provide) {
attrs[nName] = true; // presence means true
}
addAttrInterpolateDirective(node, directives, value, nName);
- addDirective(directives, nName, 'A', maxPriority);
+ addDirective(directives, nName, 'A', maxPriority, attrStartName, attrEndName);
}
}
@@ -565,6 +575,47 @@ function $CompileProvider($provide) {
return directives;
}
+ /**
+ * Given a node with an directive-start it collects all of the siblings until it find directive-end.
+ * @param node
+ * @param attrStart
+ * @param attrEnd
+ * @returns {*}
+ */
+ function groupScan(node, attrStart, attrEnd) {
+ var nodes = [];
+ var depth = 0;
+ if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
+ var startNode = node;
+ do {
+ if (!node) {
+ throw ngError(51, "Unterminated attribute, found '{0}' but no matching '{1}' found.", attrStart, attrEnd);
+ }
+ if (node.hasAttribute(attrStart)) depth++;
+ if (node.hasAttribute(attrEnd)) depth--;
+ nodes.push(node);
+ node = node.nextSibling;
+ } while (depth > 0);
+ } else {
+ nodes.push(node);
+ }
+ return jqLite(nodes);
+ }
+
+ /**
+ * Wrapper for linking function which converts normal linking function into a grouped
+ * linking function.
+ * @param linkFn
+ * @param attrStart
+ * @param attrEnd
+ * @returns {Function}
+ */
+ function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
+ return function(scope, element, attrs, controllers) {
+ element = groupScan(element[0], attrStart, attrEnd);
+ return linkFn(scope, element, attrs, controllers);
+ }
+ }
/**
* Once the directives have been collected, their compile functions are executed. This method
@@ -601,6 +652,13 @@ function $CompileProvider($provide) {
// executes all directives on the current element
for(var i = 0, ii = directives.length; i < ii; i++) {
directive = directives[i];
+ var attrStart = directive.$$start;
+ var attrEnd = directive.$$end;
+
+ // collect multiblock sections
+ if (attrStart) {
+ $compileNode = groupScan(compileNode, attrStart, attrEnd)
+ }
$template = undefined;
if (terminalPriority > directive.priority) {
@@ -631,11 +689,11 @@ function $CompileProvider($provide) {
transcludeDirective = directive;
terminalPriority = directive.priority;
if (directiveValue == 'element') {
- $template = jqLite(compileNode);
+ $template = groupScan(compileNode, attrStart, attrEnd)
$compileNode = templateAttrs.$$element =
jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
compileNode = $compileNode[0];
- replaceWith(jqCollection, jqLite($template[0]), compileNode);
+ replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode);
childTranscludeFn = compile($template, transcludeFn, terminalPriority);
} else {
$template = jqLite(JQLiteClone(compileNode)).contents();
@@ -699,9 +757,9 @@ function $CompileProvider($provide) {
try {
linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
if (isFunction(linkFn)) {
- addLinkFns(null, linkFn);
+ addLinkFns(null, linkFn, attrStart, attrEnd);
} else if (linkFn) {
- addLinkFns(linkFn.pre, linkFn.post);
+ addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
}
} catch (e) {
$exceptionHandler(e, startingTag($compileNode));
@@ -723,12 +781,14 @@ function $CompileProvider($provide) {
////////////////////
- function addLinkFns(pre, post) {
+ function addLinkFns(pre, post, attrStart, attrEnd) {
if (pre) {
+ if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
pre.require = directive.require;
preLinkFns.push(pre);
}
if (post) {
+ if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
post.require = directive.require;
postLinkFns.push(post);
}
@@ -907,8 +967,8 @@ function $CompileProvider($provide) {
* * `M`: comment
* @returns true if directive was added.
*/
- function addDirective(tDirectives, name, location, maxPriority) {
- var match = false;
+ function addDirective(tDirectives, name, location, maxPriority, startAttrName, endAttrName) {
+ var match = null;
if (hasDirectives.hasOwnProperty(name)) {
for(var directive, directives = $injector.get(name + Suffix),
i = 0, ii = directives.length; i<ii; i++) {
@@ -916,8 +976,11 @@ function $CompileProvider($provide) {
directive = directives[i];
if ( (maxPriority === undefined || maxPriority > directive.priority) &&
directive.restrict.indexOf(location) != -1) {
+ if (startAttrName) {
+ directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
+ }
tDirectives.push(directive);
- match = true;
+ match = directive;
}
} catch(e) { $exceptionHandler(e); }
}
@@ -1120,30 +1183,50 @@ function $CompileProvider($provide) {
*
* @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
* in the root of the tree.
- * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell,
+ * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep the shell,
* but replace its DOM node reference.
* @param {Node} newNode The new DOM node.
*/
- function replaceWith($rootElement, $element, newNode) {
- var oldNode = $element[0],
- parent = oldNode.parentNode,
+ function replaceWith($rootElement, elementsToRemove, newNode) {
+ var firstElementToRemove = elementsToRemove[0],
+ removeCount = elementsToRemove.length,
+ parent = firstElementToRemove.parentNode,
i, ii;
if ($rootElement) {
for(i = 0, ii = $rootElement.length; i < ii; i++) {
- if ($rootElement[i] == oldNode) {
- $rootElement[i] = newNode;
+ if ($rootElement[i] == firstElementToRemove) {
+ $rootElement[i++] = newNode;
+ for (var j = i, j2 = j + removeCount - 1,
+ jj = $rootElement.length;
+ j < jj; j++, j2++) {
+ if (j2 < jj) {
+ $rootElement[j] = $rootElement[j2];
+ } else {
+ delete $rootElement[j];
+ }
+ }
+ $rootElement.length -= removeCount - 1;
break;
}
}
}
if (parent) {
- parent.replaceChild(newNode, oldNode);
+ parent.replaceChild(newNode, firstElementToRemove);
+ }
+ var fragment = document.createDocumentFragment();
+ fragment.appendChild(firstElementToRemove);
+ newNode[jqLite.expando] = firstElementToRemove[jqLite.expando];
+ for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
+ var element = elementsToRemove[k];
+ jqLite(element).remove(); // must do this way to clean up expando
+ fragment.appendChild(element);
+ delete elementsToRemove[k];
}
- newNode[jqLite.expando] = oldNode[jqLite.expando];
- $element[0] = newNode;
+ elementsToRemove[0] = newNode;
+ elementsToRemove.length = 1
}
}];
}
diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js
index 34d32f59..6c2da071 100644
--- a/src/ng/directive/ngRepeat.js
+++ b/src/ng/directive/ngRepeat.js
@@ -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(element) { element[NG_REMOVED] = true});
block.scope.$destroy();
}
}