'use strict';
/**
* @ngdoc function
* @name angular.module.ng.$compile
* @function
*
* @description
* Compiles a piece of HTML string or DOM into a template and produces a template function, which
* can then be used to link {@link angular.module.ng.$rootScope.Scope scope} and the template together.
*
* The compilation is a process of walking the DOM tree and trying to match DOM elements to
* {@link angular.module.ng.$compileProvider.directive directives}. For each match it
* executes corresponding template function and collects the
* instance functions into a single template function which is then returned.
*
* The template function can then be used once to produce the view or as it is the case with
* {@link angular.module.ng.$compileProvider.directive.ng:repeat repeater} many-times, in which
* case each call results in a view that is a DOM clone of the original template.
*
it('should auto compile', function() {
expect(element('div[compile]').text()).toBe('Hello Angular');
input('html').enter('{{name}}!');
expect(element('div[compile]').text()).toBe('Angular!');
});
*
*
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
* @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
* @param {number} maxPriority only apply directives lower then given priority (Only effects the
* root element(s), not their children)
* @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
* (a DOM element/tree) to a scope. Where:
*
* * `scope` - A {@link angular.module.ng.$rootScope.Scope Scope} to bind to.
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
* `template` and call the `cloneAttachFn` function allowing the caller to attach the
* cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
* called as:
`cloneAttachFn(clonedElement, scope)` where:
*
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
* * `scope` - is the current scope with which the linking function is working with.
*
* Calling the linking function returns the element of the template. It is either the original element
* passed in, or the clone of the element if the `cloneAttachFn` is provided.
*
* After linking the view is not updateh until after a call to $digest which typically is done by
* Angular automatically.
*
* If you need access to the bound view, there are two ways to do it:
*
* - If you are not asking the linking function to clone the template, create the DOM element(s)
* before you send them to the compiler and keep this reference around.
*
* var element = $compile('{{total}}
')(scope);
*
*
* - if on the other hand, you need the element to be cloned, the view reference from the original
* example would not point to the clone, but rather to the original template that was cloned. In
* this case, you can access the clone via the cloneAttachFn:
*
* var templateHTML = angular.element('{{total}}
'),
* scope = ....;
*
* var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
* //attach the clone to DOM document at the right place
* });
*
* //now we have reference to the cloned DOM via `clone`
*
*
*
* Compiler Methods For Widgets and Directives:
*
* The following methods are available for use when you write your own widgets, directives,
* and markup.
*
*
* For information on how the compiler works, see the
* {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide.
*/
$CompileProvider.$inject = ['$provide'];
function $CompileProvider($provide) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
CONTENT_REGEXP = /\<\\>/i,
HAS_ROOT_ELEMENT = /^\<[\s\S]*\>$/,
SIDE_EFFECT_ATTRS = {};
forEach('src,href,multiple,selected,checked,disabled,readonly,required'.split(','), function(name) {
SIDE_EFFECT_ATTRS[name] = name;
SIDE_EFFECT_ATTRS[directiveNormalize('ng_' + name)] = name;
});
this.directive = function registerDirective(name, directiveFactory) {
if (isString(name)) {
assertArg(directiveFactory, 'directive');
if (!hasDirectives.hasOwnProperty(name)) {
hasDirectives[name] = [];
$provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
function($injector, $exceptionHandler) {
var directives = [];
forEach(hasDirectives[name], function(directiveFactory) {
try {
var directive = $injector.invoke(directiveFactory);
if (isFunction(directive)) {
directive = { compile: valueFn(directive) };
} else if (!directive.compile && directive.link) {
directive.compile = valueFn(directive.link);
}
directive.priority = directive.priority || 0;
directive.name = directive.name || name;
directive.require = directive.require || (directive.controller && directive.name);
directive.restrict = directive.restrict || 'EACM';
directives.push(directive);
} catch (e) {
$exceptionHandler(e);
}
});
return directives;
}]);
}
hasDirectives[name].push(directiveFactory);
} else {
forEach(name, reverseParams(registerDirective));
}
return this;
};
this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
'$controller',
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
$controller) {
var LOCAL_MODE = {
attribute: function(localName, mode, parentScope, scope, attr) {
scope[localName] = attr[localName];
},
evaluate: function(localName, mode, parentScope, scope, attr) {
scope[localName] = parentScope.$eval(attr[localName]);
},
bind: function(localName, mode, parentScope, scope, attr) {
var getter = $interpolate(attr[localName]);
scope.$watch(
function() { return getter(parentScope); },
function(v) { scope[localName] = v; }
);
},
accessor: function(localName, mode, parentScope, scope, attr) {
var getter = noop,
setter = noop,
exp = attr[localName];
if (exp) {
getter = $parse(exp);
setter = getter.assign || function() {
throw Error("Expression '" + exp + "' not assignable.");
};
}
scope[localName] = function(value) {
return arguments.length ? setter(parentScope, value) : getter(parentScope);
};
},
expression: function(localName, mode, parentScope, scope, attr) {
scope[localName] = function(locals) {
$parse(attr[localName])(parentScope, locals);
};
}
};
return compile;
//================================
function compile(templateElement, transcludeFn, maxPriority) {
templateElement = jqLite(templateElement);
// 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
forEach(templateElement, function(node, index){
if (node.nodeType == 3 /* text node */) {
templateElement[index] = jqLite(node).wrap('').parent()[0];
}
});
var linkingFn = compileNodes(templateElement, transcludeFn, templateElement, maxPriority);
return function(scope, cloneConnectFn){
assertArg(scope, 'scope');
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
var element = cloneConnectFn
? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!!
: templateElement;
element.data('$scope', scope).addClass('ng-scope');
if (cloneConnectFn) cloneConnectFn(element, scope);
if (linkingFn) linkingFn(scope, element, element);
return element;
};
}
function wrongMode(localName, mode) {
throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
}
/**
* Compile function matches each node in nodeList against the directives. Once all directives
* for a particular node are collected their compile functions are executed. The compile
* functions return values - the linking functions - are combined into a composite linking
* function, which is the a linking function for the node.
*
* @param {NodeList} nodeList an array of nodes to compile
* @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
* scope argument is auto-generated to the new child of the transcluded parent scope.
* @param {DOMElement=} rootElement If the nodeList is the root of the compilation tree then the
* 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
* @returns {?function} A composite linking function of all of the matched directives or null.
*/
function compileNodes(nodeList, transcludeFn, rootElement, maxPriority) {
var linkingFns = [],
directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;
for(var i = 0, ii = nodeList.length; i < ii; i++) {
attrs = {
$attr: {},
$normalize: directiveNormalize,
$set: attrSetter,
$observe: interpolatedAttrObserve,
$observers: {}
};
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
directiveLinkingFn = (directives.length)
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, rootElement)
: null;
childLinkingFn = (directiveLinkingFn && directiveLinkingFn.terminal)
? null
: compileNodes(nodeList[i].childNodes,
directiveLinkingFn ? directiveLinkingFn.transclude : transcludeFn);
linkingFns.push(directiveLinkingFn);
linkingFns.push(childLinkingFn);
linkingFnFound = (linkingFnFound || directiveLinkingFn || childLinkingFn);
}
// return a linking function if we have found anything, null otherwise
return linkingFnFound ? linkingFn : null;
/* nodesetLinkingFn */ function linkingFn(scope, nodeList, rootElement, boundTranscludeFn) {
if (linkingFns.length != nodeList.length * 2) {
throw Error('Template changed structure!');
}
var childLinkingFn, directiveLinkingFn, node, childScope, childTransclusionFn;
for(var i=0, n=0, ii=linkingFns.length; i
addDirective(directives,
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
// iterate over the attributes
for (var attr, name, nName, value, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
attr = nAttrs[j];
name = attr.name;
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
attrs[nName] = value = trim((msie && name == 'href')
? decodeURIComponent(node.getAttribute(name, 2))
: attr.value);
if (BOOLEAN_ATTR[nName]) {
attrs[nName] = true; // presence means true
}
addAttrInterpolateDirective(directives, value, nName)
addDirective(directives, nName, 'A', maxPriority);
}
// use class as directive
className = node.className;
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
nName = directiveNormalize(match[2]);
if (addDirective(directives, nName, 'C', maxPriority)) {
attrs[nName] = trim(match[3]);
}
className = className.substr(match.index + match[0].length);
}
break;
case 3: /* Text Node */
addTextInterpolateDirective(directives, node.nodeValue);
break;
case 8: /* Comment */
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
if (match) {
nName = directiveNormalize(match[1]);
if (addDirective(directives, nName, 'M', maxPriority)) {
attrs[nName] = trim(match[2]);
}
}
break;
}
directives.sort(byPriority);
return directives;
}
/**
* Once the directives have been collected their compile functions is executed. This method
* is responsible for inlining widget templates as well as terminating the application
* of the directives if the terminal directive has been reached..
*
* @param {Array} directives Array of collected directives to execute their compile function.
* this needs to be pre-sorted by priority order.
* @param {Node} templateNode The raw DOM node to apply the compile functions to
* @param {Object} templateAttrs The shared attribute function
* @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
* scope argument is auto-generated to the new child of the transcluded parent scope.
* @param {DOMElement} rootElement If we are working on the root of the compile tree then this
* argument has the root jqLite array so that we can replace widgets on it.
* @returns linkingFn
*/
function applyDirectivesToNode(directives, templateNode, templateAttrs, transcludeFn, rootElement) {
var terminalPriority = -Number.MAX_VALUE,
preLinkingFns = [],
postLinkingFns = [],
newScopeDirective = null,
newIsolatedScopeDirective = null,
templateDirective = null,
delayedLinkingFn = null,
element = templateAttrs.$element = jqLite(templateNode),
directive,
directiveName,
template,
transcludeDirective,
childTranscludeFn = transcludeFn,
controllerDirectives,
linkingFn,
directiveValue;
// executes all directives on the current element
for(var i = 0, ii = directives.length; i < ii; i++) {
directive = directives[i];
template = undefined;
if (terminalPriority > directive.priority) {
break; // prevent further processing of directives
}
if (directiveValue = directive.scope) {
assertNoDuplicate('isolated scope', newIsolatedScopeDirective, directive, element);
if (isObject(directiveValue)) {
element.addClass('ng-isolate-scope');
newIsolatedScopeDirective = directive;
}
element.addClass('ng-scope');
newScopeDirective = newScopeDirective || directive;
}
directiveName = directive.name;
if (directiveValue = directive.controller) {
controllerDirectives = controllerDirectives || {};
assertNoDuplicate("'" + directiveName + "' controller",
controllerDirectives[directiveName], directive, element);
controllerDirectives[directiveName] = directive;
}
if (directiveValue = directive.transclude) {
assertNoDuplicate('transclusion', transcludeDirective, directive, element);
transcludeDirective = directive;
terminalPriority = directive.priority;
if (directiveValue == 'element') {
template = jqLite(templateNode);
templateNode = (element = templateAttrs.$element = jqLite(
''))[0];
template.replaceWith(templateNode);
childTranscludeFn = compile(template, transcludeFn, terminalPriority);
} else {
template = jqLite(JQLiteClone(templateNode));
element.html(''); // clear contents
childTranscludeFn = compile(template.contents(), transcludeFn);
}
}
if (directiveValue = directive.template) {
assertNoDuplicate('template', templateDirective, directive, element);
templateDirective = directive;
// include the contents of the original element into the template and replace the element
var content = directiveValue.replace(CONTENT_REGEXP, element.html());
templateNode = jqLite(content)[0];
if (directive.replace) {
replaceWith(rootElement, element, templateNode);
var newTemplateAttrs = {$attr: {}};
// combine directives from the original node and from the template:
// - take the array of directives for this element
// - split it into two parts, those that were already applied and those that weren't
// - collect directives from the template, add them to the second group and sort them
// - append the second group with new directives to the first group
directives = directives.concat(
collectDirectives(
templateNode,
directives.splice(i + 1, directives.length - (i + 1)),
newTemplateAttrs
)
);
mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
ii = directives.length;
} else {
element.html(content);
}
}
if (directive.templateUrl) {
assertNoDuplicate('template', templateDirective, directive, element);
templateDirective = directive;
delayedLinkingFn = compileTemplateUrl(directives.splice(i, directives.length - i),
/* directiveLinkingFn */ compositeLinkFn, element, templateAttrs, rootElement,
directive.replace, childTranscludeFn);
ii = directives.length;
} else if (directive.compile) {
try {
linkingFn = directive.compile(element, templateAttrs, childTranscludeFn);
if (isFunction(linkingFn)) {
addLinkingFns(null, linkingFn);
} else if (linkingFn) {
addLinkingFns(linkingFn.pre, linkingFn.post);
}
} catch (e) {
$exceptionHandler(e, startingTag(element));
}
}
if (directive.terminal) {
compositeLinkFn.terminal = true;
terminalPriority = Math.max(terminalPriority, directive.priority);
}
}
linkingFn = delayedLinkingFn || compositeLinkFn;
linkingFn.scope = newScopeDirective && newScopeDirective.scope;
linkingFn.transclude = transcludeDirective && childTranscludeFn;
// if we have templateUrl, then we have to delay linking
return linkingFn;
////////////////////
function addLinkingFns(pre, post) {
if (pre) {
pre.require = directive.require;
preLinkingFns.push(pre);
}
if (post) {
post.require = directive.require;
postLinkingFns.push(post);
}
}
function getControllers(require, element) {
var value, retrievalMethod = 'data', optional = false;
if (isString(require)) {
while((value = require.charAt(0)) == '^' || value == '?') {
require = require.substr(1);
if (value == '^') {
retrievalMethod = 'inheritedData';
}
optional = optional || value == '?';
}
value = element[retrievalMethod]('$' + require + 'Controller');
if (!value && !optional) {
throw Error("No controller: " + require);
}
return value;
} else if (isArray(require)) {
value = [];
forEach(require, function(require) {
value.push(getControllers(require, element));
});
}
return value;
}
/* directiveLinkingFn */
function compositeLinkFn(/* nodesetLinkingFn */ childLinkingFn,
scope, linkNode, rootElement, boundTranscludeFn) {
var attrs, element, i, ii, linkingFn, controller;
if (templateNode === linkNode) {
attrs = templateAttrs;
} else {
attrs = shallowCopy(templateAttrs);
attrs.$element = jqLite(linkNode);
}
element = attrs.$element;
if (newScopeDirective && isObject(newScopeDirective.scope)) {
forEach(newScopeDirective.scope, function(mode, name) {
(LOCAL_MODE[mode] || wrongMode)(name, mode,
scope.$parent || scope, scope, attrs);
});
}
if (controllerDirectives) {
forEach(controllerDirectives, function(directive) {
var locals = {
$scope: scope,
$element: element,
$attrs: attrs,
$transclude: boundTranscludeFn
};
forEach(directive.inject || {}, function(mode, name) {
(LOCAL_MODE[mode] || wrongMode)(name, mode,
newScopeDirective ? scope.$parent || scope : scope, locals, attrs);
});
controller = directive.controller;
if (controller == '@') {
controller = attrs[directive.name];
}
element.data(
'$' + directive.name + 'Controller',
$controller(controller, locals));
});
}
// PRELINKING
for(i = 0, ii = preLinkingFns.length; i < ii; i++) {
try {
linkingFn = preLinkingFns[i];
linkingFn(scope, element, attrs,
linkingFn.require && getControllers(linkingFn.require, element));
} catch (e) {
$exceptionHandler(e, startingTag(element));
}
}
// RECURSION
childLinkingFn && childLinkingFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
// POSTLINKING
for(i = 0, ii = postLinkingFns.length; i < ii; i++) {
try {
linkingFn = postLinkingFns[i];
linkingFn(scope, element, attrs,
linkingFn.require && getControllers(linkingFn.require, element));
} catch (e) {
$exceptionHandler(e, startingTag(element));
}
}
}
}
/**
* looks up the directive and decorates it with exception handling and proper parameters. We
* call this the boundDirective.
*
* @param {string} name name of the directive to look up.
* @param {string} location The directive must be found in specific format.
* String containing any of theses characters:
*
* * `E`: element name
* * `A': attribute
* * `C`: class
* * `M`: comment
* @returns true if directive was added.
*/
function addDirective(tDirectives, name, location, maxPriority) {
var match = false;
if (hasDirectives.hasOwnProperty(name)) {
for(var directive, directives = $injector.get(name + Suffix),
i=0, ii = directives.length; i directive.priority) &&
directive.restrict.indexOf(location) != -1) {
tDirectives.push(directive);
match = true;
}
} catch(e) { $exceptionHandler(e); }
}
}
return match;
}
/**
* When the element is replaced with HTML template then the new attributes
* on the template need to be merged with the existing attributes in the DOM.
* The desired effect is to have both of the attributes present.
*
* @param {object} dst destination attributes (original DOM)
* @param {object} src source attributes (from the directive template)
*/
function mergeTemplateAttributes(dst, src) {
var srcAttr = src.$attr,
dstAttr = dst.$attr,
element = dst.$element;
// reapply the old attributes to the new element
forEach(dst, function(value, key) {
if (key.charAt(0) != '$') {
dst.$set(key, value, srcAttr[key]);
}
});
// copy the new attributes on the old attrs object
forEach(src, function(value, key) {
if (key == 'class') {
element.addClass(value);
} else if (key == 'style') {
element.attr('style', element.attr('style') + ';' + value);
} else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
dst[key] = value;
dstAttr[key] = srcAttr[key];
}
});
}
function compileTemplateUrl(directives, /* directiveLinkingFn */ beforeWidgetLinkFn,
tElement, tAttrs, rootElement, replace, transcludeFn) {
var linkQueue = [],
afterWidgetLinkFn,
afterWidgetChildrenLinkFn,
originalWidgetNode = tElement[0],
asyncWidgetDirective = directives.shift(),
// The fact that we have to copy and patch the directive seems wrong!
syncWidgetDirective = extend({}, asyncWidgetDirective, {templateUrl:null, transclude:null}),
html = tElement.html();
tElement.html('');
$http.get(asyncWidgetDirective.templateUrl, {cache: $templateCache}).
success(function(content) {
content = trim(content).replace(CONTENT_REGEXP, html);
if (replace && !content.match(HAS_ROOT_ELEMENT)) {
throw Error('Template must have exactly one root element: ' + content);
}
var templateNode, tempTemplateAttrs;
if (replace) {
tempTemplateAttrs = {$attr: {}};
templateNode = jqLite(content)[0];
replaceWith(rootElement, tElement, templateNode);
collectDirectives(tElement[0], directives, tempTemplateAttrs);
mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
} else {
templateNode = tElement[0];
tElement.html(content);
}
directives.unshift(syncWidgetDirective);
afterWidgetLinkFn = /* directiveLinkingFn */ applyDirectivesToNode(directives, tElement, tAttrs, transcludeFn);
afterWidgetChildrenLinkFn = /* nodesetLinkingFn */ compileNodes(tElement.contents(), transcludeFn);
while(linkQueue.length) {
var controller = linkQueue.pop(),
linkRootElement = linkQueue.pop(),
cLinkNode = linkQueue.pop(),
scope = linkQueue.pop(),
node = templateNode;
if (cLinkNode !== originalWidgetNode) {
// it was cloned therefore we have to clone as well.
node = JQLiteClone(templateNode);
replaceWith(linkRootElement, jqLite(cLinkNode), node);
}
afterWidgetLinkFn(function() {
beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node, rootElement, controller);
}, scope, node, rootElement, controller);
}
linkQueue = null;
}).
error(function(response, code, headers, config) {
throw Error('Failed to load template: ' + config.url);
});
return /* directiveLinkingFn */ function(ignoreChildLinkingFn, scope, node, rootElement,
controller) {
if (linkQueue) {
linkQueue.push(scope);
linkQueue.push(node);
linkQueue.push(rootElement);
linkQueue.push(controller);
} else {
afterWidgetLinkFn(function() {
beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node, rootElement, controller);
}, scope, node, rootElement, controller);
}
};
}
/**
* Sorting function for bound directives.
*/
function byPriority(a, b) {
return b.priority - a.priority;
}
function assertNoDuplicate(what, previousDirective, directive, element) {
if (previousDirective) {
throw Error('Multiple directives [' + previousDirective.name + ', ' +
directive.name + '] asking for ' + what + ' on: ' + startingTag(element));
}
}
function addTextInterpolateDirective(directives, text) {
var interpolateFn = $interpolate(text, true);
if (interpolateFn) {
directives.push({
priority: 0,
compile: valueFn(function(scope, node) {
var parent = node.parent(),
bindings = parent.data('$binding') || [];
bindings.push(interpolateFn);
parent.data('$binding', bindings).addClass('ng-binding');
scope.$watch(interpolateFn, function(value) {
node[0].nodeValue = value;
});
})
});
}
}
function addAttrInterpolateDirective(directives, value, name) {
var interpolateFn = $interpolate(value, true);
if (SIDE_EFFECT_ATTRS[name]) {
name = SIDE_EFFECT_ATTRS[name];
if (BOOLEAN_ATTR[name]) {
value = true;
}
} else if (!interpolateFn) {
// we are not a side-effect attr, and we have no side-effects -> ignore
return;
}
directives.push({
priority: 100,
compile: function(element, attr) {
if (interpolateFn) {
return function(scope, element, attr) {
// we define observers array only for interpolated attrs
// and ignore observers for non interpolated attrs to save some memory
attr.$observers[name] = [];
attr[name] = undefined;
scope.$watch(interpolateFn, function(value) {
attr.$set(name, value);
});
};
} else {
attr.$set(name, value);
}
}
});
}
/**
* This is a special jqLite.replaceWith, which can replace items which
* have no parents, provided that the containing jqLite collection is provided.
*
* @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,
* 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,
i, ii;
if (rootElement) {
for(i = 0, ii = rootElement.length; i