Skip to content

Commit 72c7cc1

Browse files
committed
feat($compile): support multi-elements in all declaration styles
I've built on the work of angular#2783 to make multi-elements possible in all directive decralation styles. Basically angular#2783 was limited to directives declared via attributes. With this commit, appending X-start and X-end would work in Comments, Attributes, ClassNames and ElementNames ```html <table> <!-- directive: ng-repeat-start item in list --> <tr>If ngRepeat's 'restrict' get changed to include 'M'</tr> <tr>I get repeated</tr> <!-- ng-repeat-end --> </table> ```
1 parent 08daa77 commit 72c7cc1

File tree

1 file changed

+124
-55
lines changed

1 file changed

+124
-55
lines changed

src/ng/compile.js

Lines changed: 124 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ function $CompileProvider($provide) {
152152
var hasDirectives = {},
153153
Suffix = 'Directive',
154154
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
155+
COMMENT_END_DIRECTIVE_REGEXP = /^\s*(?:directive\:)?\s*([\d\w\-_]+)\s*$/,
155156
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
156157
aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/,
157158
imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
@@ -601,20 +602,21 @@ function $CompileProvider($provide) {
601602
var nodeType = node.nodeType,
602603
attrsMap = attrs.$attr,
603604
match,
604-
className;
605+
className,
606+
name, nName,
607+
startName, endName, index;
605608

606609
switch(nodeType) {
607610
case 1: /* Element */
608611
// use the node name: <directive>
609-
addDirective(directives,
610-
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
612+
name = nodeName_(node).toLowerCase();
613+
nName = directiveNormalize(name);
614+
handleStartEndName(name, nName);
615+
addDirective(directives, nName, 'E', maxPriority, ignoreDirective, startName, endName);
611616

612617
// iterate over the attributes
613-
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
618+
for (var attr, ngAttrName, value, nAttrs = node.attributes,
614619
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
615-
var attrStartName;
616-
var attrEndName;
617-
var index;
618620

619621
attr = nAttrs[j];
620622
if (!msie || msie >= 8 || attr.specified) {
@@ -624,12 +626,8 @@ function $CompileProvider($provide) {
624626
if (NG_ATTR_BINDING.test(ngAttrName)) {
625627
name = ngAttrName.substr(6).toLowerCase();
626628
}
627-
if ((index = ngAttrName.lastIndexOf('Start')) != -1 && index == ngAttrName.length - 5) {
628-
attrStartName = name;
629-
attrEndName = name.substr(0, name.length - 5) + 'end';
630-
name = name.substr(0, name.length - 6);
631-
}
632629
nName = directiveNormalize(name.toLowerCase());
630+
handleStartEndName(name, nName);
633631
attrsMap[nName] = name;
634632
attrs[nName] = value = trim((msie && name == 'href')
635633
? decodeURIComponent(node.getAttribute(name, 2))
@@ -638,16 +636,18 @@ function $CompileProvider($provide) {
638636
attrs[nName] = true; // presence means true
639637
}
640638
addAttrInterpolateDirective(node, directives, value, nName);
641-
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, attrEndName);
639+
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, startName, endName);
642640
}
643641
}
644642

645643
// use class as directive
646644
className = node.className;
647645
if (isString(className) && className !== '') {
648646
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
649-
nName = directiveNormalize(match[2]);
650-
if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
647+
name = match[2];
648+
nName = directiveNormalize(name);
649+
handleStartEndName(name, nName);
650+
if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective, startName, endName)) {
651651
attrs[nName] = trim(match[3]);
652652
}
653653
className = className.substr(match.index + match[0].length);
@@ -661,8 +661,10 @@ function $CompileProvider($provide) {
661661
try {
662662
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
663663
if (match) {
664-
nName = directiveNormalize(match[1]);
665-
if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
664+
name = match[1];
665+
nName = directiveNormalize(name);
666+
handleStartEndName(name, nName);
667+
if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective, startName, endName)) {
666668
attrs[nName] = trim(match[2]);
667669
}
668670
}
@@ -675,48 +677,115 @@ function $CompileProvider($provide) {
675677

676678
directives.sort(byPriority);
677679
return directives;
680+
681+
// takes care of populating (or emptying) startName and endName
682+
// Also changes `name` and `nName` in the external scope if necessary
683+
function handleStartEndName(_name, _nName) {
684+
var index;
685+
if ((index = _nName.lastIndexOf('Start')) != -1 && index == _nName.length - 5) {
686+
startName = _name;
687+
endName = _name.substr(0, _name.length - 5) + 'end';
688+
name = _name.substr(0, _name.length - 6);
689+
nName = directiveNormalize(name);
690+
} else {
691+
startName = endName = undefined;
692+
}
693+
return name;
694+
}
678695
}
679696

680697
/**
681698
* Given a node with an directive-start it collects all of the siblings until it find directive-end.
682699
* @param node
683-
* @param attrStart
684-
* @param attrEnd
700+
* @param nameStart
701+
* @param nameEnd
702+
* @param location
685703
* @returns {*}
686704
*/
687-
function groupScan(node, attrStart, attrEnd) {
688-
var nodes = [];
689-
var depth = 0;
690-
if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
691-
var startNode = node;
692-
do {
693-
if (!node) {
694-
throw $compileMinErr('utrat', "Unterminated attribute, found '{0}' but no matching '{1}' found.", attrStart, attrEnd);
695-
}
696-
if (node.nodeType == 1 /** Element **/) {
697-
if (node.hasAttribute(attrStart)) depth++;
698-
if (node.hasAttribute(attrEnd)) depth--;
699-
}
700-
nodes.push(node);
701-
node = node.nextSibling;
702-
} while (depth > 0);
703-
} else {
704-
nodes.push(node);
705+
function groupScan(node, nameStart, nameEnd, location) {
706+
var nodes = [],
707+
depth = 0,
708+
$node, name;
709+
if (nameStart) {
710+
switch (location) {
711+
case 'E': /* Element */
712+
do {
713+
if (!node) {
714+
throw $compileMinErr('utrel', "Unterminated element, found '{0}' but no matching '{1}' found.", nameStart, nameEnd);
715+
}
716+
if (node.nodeType == 1 /** Element **/) {
717+
name = nodeName_(node).toLowerCase()
718+
if (name == nameStart) depth++;
719+
if (name == nameEnd) depth--;
720+
}
721+
nodes.push(node);
722+
node = node.nextSibling;
723+
} while (depth > 0);
724+
break;
725+
726+
case 'A': /* Attribute */
727+
if (node.hasAttribute && node.hasAttribute(nameStart)) {
728+
do {
729+
if (!node) {
730+
throw $compileMinErr('utrat', "Unterminated attribute, found '{0}' but no matching '{1}' found.", nameStart, nameEnd);
731+
}
732+
if (node.nodeType == 1 /** Element **/) {
733+
if (node.hasAttribute(nameStart)) depth++;
734+
if (node.hasAttribute(nameEnd)) depth--;
735+
}
736+
nodes.push(node);
737+
node = node.nextSibling;
738+
} while (depth > 0);
739+
}
740+
break;
741+
742+
case 'C': /* Class */
743+
do {
744+
if (!node) {
745+
throw $compileMinErr('utrcl', "Unterminated class, found '{0}' but no matching '{1}' found.", nameStart, nameEnd);
746+
}
747+
if (node.nodeType == 1 /** Element **/) {
748+
$node = jqLite(node);
749+
if ($node.hasClass(nameStart) || $node.hasClass(nameStart + ":")) depth++;
750+
if ($node.hasClass(nameEnd)) depth--;
751+
}
752+
nodes.push(node);
753+
node = node.nextSibling;
754+
} while (depth > 0);
755+
break;
756+
757+
case 'M': /* Comment */
758+
do {
759+
if (!node) {
760+
throw $compileMinErr('utrcm', "Unterminated comment, found '{0}' but no matching '{1}' found.", nameStart, nameEnd);
761+
}
762+
if (node.nodeType == 8 /** Comment **/) {
763+
name = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
764+
if (name && name[1] == nameStart) depth++;
765+
name = COMMENT_END_DIRECTIVE_REGEXP.exec(node.nodeValue);
766+
if (name && name[1] == nameEnd) depth--;
767+
}
768+
nodes.push(node);
769+
node = node.nextSibling;
770+
} while (depth > 0);
771+
break;
772+
}
705773
}
706-
return jqLite(nodes);
774+
return jqLite(nodes.length ? nodes : node);
707775
}
708776

709777
/**
710778
* Wrapper for linking function which converts normal linking function into a grouped
711779
* linking function.
712780
* @param linkFn
713-
* @param attrStart
714-
* @param attrEnd
781+
* @param nameStart
782+
* @param nameEnd
783+
* @param location
715784
* @returns {Function}
716785
*/
717-
function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
786+
function groupElementsLinkFnWrapper(linkFn, nameStart, nameEnd, location) {
718787
return function(scope, element, attrs, controllers) {
719-
element = groupScan(element[0], attrStart, attrEnd);
788+
element = groupScan(element[0], nameStart, nameEnd, location);
720789
return linkFn(scope, element, attrs, controllers);
721790
}
722791
}
@@ -757,12 +826,12 @@ function $CompileProvider($provide) {
757826
// executes all directives on the current element
758827
for(var i = 0, ii = directives.length; i < ii; i++) {
759828
directive = directives[i];
760-
var attrStart = directive.$$start;
761-
var attrEnd = directive.$$end;
829+
var nameStart = directive.$$start;
830+
var nameEnd = directive.$$end;
762831

763832
// collect multiblock sections
764-
if (attrStart) {
765-
$compileNode = groupScan(compileNode, attrStart, attrEnd)
833+
if (nameStart) {
834+
$compileNode = groupScan(compileNode, nameStart, nameEnd, directive.$$location)
766835
}
767836
$template = undefined;
768837

@@ -794,7 +863,7 @@ function $CompileProvider($provide) {
794863
transcludeDirective = directive;
795864
terminalPriority = directive.priority;
796865
if (directiveValue == 'element') {
797-
$template = groupScan(compileNode, attrStart, attrEnd)
866+
$template = groupScan(compileNode, nameStart, nameEnd, directive.$$location)
798867
$compileNode = templateAttrs.$$element =
799868
jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
800869
compileNode = $compileNode[0];
@@ -868,9 +937,9 @@ function $CompileProvider($provide) {
868937
try {
869938
linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
870939
if (isFunction(linkFn)) {
871-
addLinkFns(null, linkFn, attrStart, attrEnd);
940+
addLinkFns(null, linkFn, nameStart, nameEnd, directive.$$location);
872941
} else if (linkFn) {
873-
addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
942+
addLinkFns(linkFn.pre, linkFn.post, nameStart, nameEnd, directive.$$location);
874943
}
875944
} catch (e) {
876945
$exceptionHandler(e, startingTag($compileNode));
@@ -892,14 +961,14 @@ function $CompileProvider($provide) {
892961

893962
////////////////////
894963

895-
function addLinkFns(pre, post, attrStart, attrEnd) {
964+
function addLinkFns(pre, post, nameStart, nameEnd, location) {
896965
if (pre) {
897-
if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
966+
if (nameStart) pre = groupElementsLinkFnWrapper(pre, nameStart, nameEnd, location);
898967
pre.require = directive.require;
899968
preLinkFns.push(pre);
900969
}
901970
if (post) {
902-
if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
971+
if (nameStart) post = groupElementsLinkFnWrapper(post, nameStart, nameEnd, location);
903972
post.require = directive.require;
904973
postLinkFns.push(post);
905974
}
@@ -1087,7 +1156,7 @@ function $CompileProvider($provide) {
10871156
* * `M`: comment
10881157
* @returns true if directive was added.
10891158
*/
1090-
function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) {
1159+
function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startName, endName) {
10911160
if (name === ignoreDirective) return null;
10921161
var match = null;
10931162
if (hasDirectives.hasOwnProperty(name)) {
@@ -1097,8 +1166,8 @@ function $CompileProvider($provide) {
10971166
directive = directives[i];
10981167
if ( (maxPriority === undefined || maxPriority > directive.priority) &&
10991168
directive.restrict.indexOf(location) != -1) {
1100-
if (startAttrName) {
1101-
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
1169+
if (startName) {
1170+
directive = inherit(directive, {$$start: startName, $$end: endName, $$location: location});
11021171
}
11031172
tDirectives.push(directive);
11041173
match = directive;

0 commit comments

Comments
 (0)