From beea3a4beda0aaed5fc54af1a992b1c161db7752 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Wed, 2 May 2012 14:32:27 -0700 Subject: style($compile): rename compiler.js to compile.js --- src/ng/compile.js | 1018 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ng/compiler.js | 1018 ---------------------------------------------------- 2 files changed, 1018 insertions(+), 1018 deletions(-) create mode 100644 src/ng/compile.js delete mode 100644 src/ng/compiler.js (limited to 'src') diff --git a/src/ng/compile.js b/src/ng/compile.js new file mode 100644 index 00000000..59a70145 --- /dev/null +++ b/src/ng/compile.js @@ -0,0 +1,1018 @@ +'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.ngRepeat 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 updated 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` + *
+ * + * + * 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]*\>$/; + + + 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 || 'A'; + 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); + }; + } + }; + + var Attributes = function(element, attr) { + this.$$element = element; + this.$$observers = {}; + this.$attr = attr || {}; + }; + + Attributes.prototype = { + $normalize: directiveNormalize, + + + /** + * Set a normalized attribute on the element in a way such that all directives + * can share the attribute. This function properly handles boolean attributes. + * @param {string} key Normalized key. (ie ngAttribute) + * @param {string|boolean} value The value to set. If `null` attribute will be deleted. + * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. + * Defaults to true. + * @param {string=} attrName Optional none normalized name. Defaults to key. + */ + $set: function(key, value, writeAttr, attrName) { + var booleanKey = isBooleanAttr(this.$$element[0], key.toLowerCase()); + + if (booleanKey) { + this.$$element.prop(key, value); + attrName = booleanKey; + } + + this[key] = value; + + // translate normalized key to actual key + if (attrName) { + this.$attr[key] = attrName; + } else { + attrName = this.$attr[key]; + if (!attrName) { + this.$attr[key] = attrName = snake_case(key, '-'); + } + } + + if (writeAttr !== false) { + if (value === null || value === undefined) { + this.$$element.removeAttr(attrName); + } else { + this.$$element.attr(attrName, value); + } + } + + // fire observers + forEach(this.$$observers[key], function(fn) { + try { + fn(value); + } catch (e) { + $exceptionHandler(e); + } + }); + }, + + + /** + * Observe an interpolated attribute. + * The observer will never be called, if given attribute is not interpolated. + * + * @param {string} key Normalized key. (ie ngAttribute) . + * @param {function(*)} fn Function that will be called whenever the attribute value changes. + */ + $observe: function(key, fn) { + // keep only observers for interpolated attrs + if (this.$$observers[key]) { + this.$$observers[key].push(fn); + } + } + }; + + return compile; + + //================================ + + function compile(templateElement, transcludeFn, maxPriority) { + if (!(templateElement instanceof jqLite)) { + // jquery always rewraps, where as we need to preserve the original selector so that we can modify it. + 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; + safeAddClass(element.data('$scope', scope), '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 + "'."); + } + + function safeAddClass(element, className) { + try { + element.addClass(className); + } catch(e) { + // ignore, since it means that we are trying to set class on + // SVG element, where class name is read-only. + } + } + + /** + * 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; 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); + + 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) { + 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]; + if (attr.specified) { + 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 (isBooleanAttr(node, nName)) { + attrs[nName] = true; // presence means true + } + addAttrInterpolateDirective(node, directives, value, nName); + addDirective(directives, nName, 'A', maxPriority); + } + } + + // use class as directive + className = node.className; + if (isString(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 */ + try { + match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); + if (match) { + nName = directiveNormalize(match[1]); + if (addDirective(directives, nName, 'M', maxPriority)) { + attrs[nName] = trim(match[2]); + } + } + } catch (e) { + // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value. + // Just ignore it and continue. (Can't seem to reproduce in test case.) + } + break; + } + + directives.sort(byPriority); + return directives; + } + + + /** + * Once the directives have been collected their compile functions is executed. This method + * is responsible for inlining directive 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)) { + safeAddClass(element, 'ng-isolate-scope'); + newIsolatedScopeDirective = directive; + } + safeAddClass(element, '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]; + replaceWith(rootElement, jqLite(template[0]), 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, new Attributes(jqLite(linkNode), templateAttrs.$attr)); + } + 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) != '$') { + if (src[key]) { + value += (key === 'style' ? ';' : ' ') + src[key]; + } + dst.$set(key, value, true, srcAttr[key]); + } + }); + // copy the new attributes on the old attrs object + forEach(src, function(value, key) { + if (key == 'class') { + safeAddClass(element, 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); + safeAddClass(parent.data('$binding', bindings), 'ng-binding'); + scope.$watch(interpolateFn, function(value) { + node[0].nodeValue = value; + }); + }) + }); + } + } + + + function addAttrInterpolateDirective(node, directives, value, name) { + var interpolateFn = $interpolate(value, true); + + + // no interpolation found -> ignore + if (!interpolateFn) return; + + directives.push({ + priority: 100, + compile: valueFn(function(scope, element, attr) { + if (name === 'class') { + // we need to interpolate classes again, in the case the element was replaced + // and therefore the two class attrs got merged - we want to interpolate the result + interpolateFn = $interpolate(attr[name], true); + } + + // 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); + }); + }) + }); + } + + + /** + * 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 - - -
-
-
-
-
-
- - 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 updated 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` - *
- * - * - * 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]*\>$/; - - - 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 || 'A'; - 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); - }; - } - }; - - var Attributes = function(element, attr) { - this.$$element = element; - this.$$observers = {}; - this.$attr = attr || {}; - }; - - Attributes.prototype = { - $normalize: directiveNormalize, - - - /** - * Set a normalized attribute on the element in a way such that all directives - * can share the attribute. This function properly handles boolean attributes. - * @param {string} key Normalized key. (ie ngAttribute) - * @param {string|boolean} value The value to set. If `null` attribute will be deleted. - * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. - * Defaults to true. - * @param {string=} attrName Optional none normalized name. Defaults to key. - */ - $set: function(key, value, writeAttr, attrName) { - var booleanKey = isBooleanAttr(this.$$element[0], key.toLowerCase()); - - if (booleanKey) { - this.$$element.prop(key, value); - attrName = booleanKey; - } - - this[key] = value; - - // translate normalized key to actual key - if (attrName) { - this.$attr[key] = attrName; - } else { - attrName = this.$attr[key]; - if (!attrName) { - this.$attr[key] = attrName = snake_case(key, '-'); - } - } - - if (writeAttr !== false) { - if (value === null || value === undefined) { - this.$$element.removeAttr(attrName); - } else { - this.$$element.attr(attrName, value); - } - } - - // fire observers - forEach(this.$$observers[key], function(fn) { - try { - fn(value); - } catch (e) { - $exceptionHandler(e); - } - }); - }, - - - /** - * Observe an interpolated attribute. - * The observer will never be called, if given attribute is not interpolated. - * - * @param {string} key Normalized key. (ie ngAttribute) . - * @param {function(*)} fn Function that will be called whenever the attribute value changes. - */ - $observe: function(key, fn) { - // keep only observers for interpolated attrs - if (this.$$observers[key]) { - this.$$observers[key].push(fn); - } - } - }; - - return compile; - - //================================ - - function compile(templateElement, transcludeFn, maxPriority) { - if (!(templateElement instanceof jqLite)) { - // jquery always rewraps, where as we need to preserve the original selector so that we can modify it. - 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; - safeAddClass(element.data('$scope', scope), '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 + "'."); - } - - function safeAddClass(element, className) { - try { - element.addClass(className); - } catch(e) { - // ignore, since it means that we are trying to set class on - // SVG element, where class name is read-only. - } - } - - /** - * 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; 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); - - 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) { - 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]; - if (attr.specified) { - 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 (isBooleanAttr(node, nName)) { - attrs[nName] = true; // presence means true - } - addAttrInterpolateDirective(node, directives, value, nName); - addDirective(directives, nName, 'A', maxPriority); - } - } - - // use class as directive - className = node.className; - if (isString(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 */ - try { - match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); - if (match) { - nName = directiveNormalize(match[1]); - if (addDirective(directives, nName, 'M', maxPriority)) { - attrs[nName] = trim(match[2]); - } - } - } catch (e) { - // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value. - // Just ignore it and continue. (Can't seem to reproduce in test case.) - } - break; - } - - directives.sort(byPriority); - return directives; - } - - - /** - * Once the directives have been collected their compile functions is executed. This method - * is responsible for inlining directive 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)) { - safeAddClass(element, 'ng-isolate-scope'); - newIsolatedScopeDirective = directive; - } - safeAddClass(element, '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]; - replaceWith(rootElement, jqLite(template[0]), 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, new Attributes(jqLite(linkNode), templateAttrs.$attr)); - } - 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) != '$') { - if (src[key]) { - value += (key === 'style' ? ';' : ' ') + src[key]; - } - dst.$set(key, value, true, srcAttr[key]); - } - }); - // copy the new attributes on the old attrs object - forEach(src, function(value, key) { - if (key == 'class') { - safeAddClass(element, 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); - safeAddClass(parent.data('$binding', bindings), 'ng-binding'); - scope.$watch(interpolateFn, function(value) { - node[0].nodeValue = value; - }); - }) - }); - } - } - - - function addAttrInterpolateDirective(node, directives, value, name) { - var interpolateFn = $interpolate(value, true); - - - // no interpolation found -> ignore - if (!interpolateFn) return; - - directives.push({ - priority: 100, - compile: valueFn(function(scope, element, attr) { - if (name === 'class') { - // we need to interpolate classes again, in the case the element was replaced - // and therefore the two class attrs got merged - we want to interpolate the result - interpolateFn = $interpolate(attr[name], true); - } - - // 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); - }); - }) - }); - } - - - /** - * 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