Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

feat($compile): support directive virtual groups #2783

Merged
merged 1 commit into from
May 29, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions src/jqLite.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ function JQLite(element) {
div.innerHTML = '<div>&#160;</div>' + element; // IE insanity to make NoScope elements work!
div.removeChild(div.firstChild); // remove the superfluous div
JQLiteAddNodes(this, div.childNodes);
this.remove(); // detach the elements from the temporary DOM div.
var fragment = jqLite(document.createDocumentFragment());
fragment.append(this); // detach the elements from the temporary DOM div.
} else {
JQLiteAddNodes(this, element);
}
Expand Down Expand Up @@ -456,24 +457,26 @@ forEach({
}
},

text: extend((msie < 9)
? function(element, value) {
if (element.nodeType == 1 /** Element */) {
if (isUndefined(value))
return element.innerText;
element.innerText = value;
} else {
if (isUndefined(value))
return element.nodeValue;
element.nodeValue = value;
}
text: (function() {
var NODE_TYPE_TEXT_PROPERTY = [];
if (msie < 9) {
NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/
NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/
} else {
NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/
NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/
}
getText.$dv = '';
return getText;

function getText(element, value) {
var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]
if (isUndefined(value)) {
return textProp ? element[textProp] : '';
}
: function(element, value) {
if (isUndefined(value)) {
return element.textContent;
}
element.textContent = value;
}, {$dv:''}),
element[textProp] = value;
}
})(),

val: function(element, value) {
if (isUndefined(value)) {
Expand Down Expand Up @@ -518,8 +521,14 @@ forEach({
return this;
} else {
// we are a read, so read the first child.
if (this.length)
return fn(this[0], arg1, arg2);
var value = fn.$dv;
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
var jj = value == undefined ? Math.min(this.length, 1) : this.length;
for (var j = 0; j < jj; j++) {
var nodeValue = fn(this[j], arg1, arg2);
value = value ? value + nodeValue : nodeValue;
}
return value;
}
} else {
// we are a write, so apply to all children
Expand All @@ -529,7 +538,6 @@ forEach({
// return self for chaining
return this;
}
return fn.$dv;
};
});

Expand Down
15 changes: 10 additions & 5 deletions src/ng/animator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
123 changes: 103 additions & 20 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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')
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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));
Expand All @@ -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);
}
Expand Down Expand Up @@ -907,17 +967,20 @@ 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++) {
try {
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); }
}
Expand Down Expand Up @@ -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
}
}];
}
Expand Down
2 changes: 1 addition & 1 deletion src/ng/directive/ngRepeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down
12 changes: 8 additions & 4 deletions test/jqLiteSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ describe('jqLite', function() {

it('should allow construction with html', function() {
var nodes = jqLite('<div>1</div><span>2</span>');
expect(nodes[0].parentNode).toBeDefined();
expect(nodes[0].parentNode.nodeType).toBe(11); /** Document Fragment **/;
expect(nodes[0].parentNode).toBe(nodes[1].parentNode);
expect(nodes.length).toEqual(2);
expect(nodes[0].innerHTML).toEqual('1');
expect(nodes[1].innerHTML).toEqual('2');
Expand Down Expand Up @@ -644,12 +647,13 @@ describe('jqLite', function() {


it('should read/write value', function() {
var element = jqLite('<div>abc</div>');
expect(element.length).toEqual(1);
expect(element[0].innerHTML).toEqual('abc');
var element = jqLite('<div>ab</div><span>c</span>');
expect(element.length).toEqual(2);
expect(element[0].innerHTML).toEqual('ab');
expect(element[1].innerHTML).toEqual('c');
expect(element.text()).toEqual('abc');
expect(element.text('xyz') == element).toBeTruthy();
expect(element.text()).toEqual('xyz');
expect(element.text()).toEqual('xyzxyz');
});
});

Expand Down
Loading