'use strict'; /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! * * DOM-related variables: * * - "node" - DOM Node * - "element" - DOM Element or Node * - "$node" or "$element" - jqLite-wrapped node or element * * * Compiler related stuff: * * - "linkFn" - linking fn of a single directive * - "nodeLinkFn" - function that aggregates all linking fns for a particular node * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) */ var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: '; /** * @ngdoc function * @name 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 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 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 ng.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 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/compiler Angular HTML Compiler} section of the Developer Guide. */ /** * @ngdoc service * @name ng.$compileProvider * @function * * @description */ $CompileProvider.$inject = ['$provide']; function $CompileProvider($provide) { var hasDirectives = {}, Suffix = 'Directive', COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ', urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/; /** * @ngdoc function * @name ng.$compileProvider#directive * @methodOf ng.$compileProvider * @function * * @description * Register a new directive with the compiler. * * @param {string} name Name of the directive in camel-case. (ie ngBind which will match as * ng-bind). * @param {function|Array} directiveFactory An injectable directive factory function. See {@link guide/directive} for more * info. * @returns {ng.$compileProvider} Self for chaining. */ 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; }; /** * @ngdoc function * @name ng.$compileProvider#urlSanitizationWhitelist * @methodOf ng.$compileProvider * @function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during a[href] sanitization. * * The sanitization is a security measure aimed at prevent XSS attacks via html links. * * Any url about to be assigned to a[href] via data-binding is first normalized and turned into an * absolute url. Afterwards the url is matched against the `urlSanitizationWhitelist` regular * expression. If a match is found the original url is written into the dom. Otherwise the * absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ this.urlSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { urlSanitizationWhitelist = regexp; return this; } return urlSanitizationWhitelist; }; this.$get = [ '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', '$controller', '$rootScope', '$document', function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, $controller, $rootScope, $document) { var Attributes = function(element, attr) { this.$$element = element; 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 = getBooleanAttrName(this.$$element[0], key), $$observers = this.$$observers, normalizedVal; 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, '-'); } } // sanitize a[href] values if (nodeName_(this.$$element[0]) === 'A' && key === 'href') { urlSanitizationNode.setAttribute('href', value); // href property always returns normalized absolute url, so we can match against that normalizedVal = urlSanitizationNode.href; if (normalizedVal !== '' && !normalizedVal.match(urlSanitizationWhitelist)) { this[key] = value = 'unsafe:' + normalizedVal; } } if (writeAttr !== false) { if (value === null || value === undefined) { this.$$element.removeAttr(attrName); } else { this.$$element.attr(attrName, value); } } // fire observers $$observers && forEach($$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. * @returns {function(*)} the `fn` Function passed in. */ $observe: function(key, fn) { var attrs = this, $$observers = (attrs.$$observers || (attrs.$$observers = {})), listeners = ($$observers[key] || ($$observers[key] = [])); listeners.push(fn); $rootScope.$evalAsync(function() { if (!listeners.$$inter) { // no one registered attribute interpolation function, so lets call it manually fn(attrs[key]); } }); return fn; } }; var urlSanitizationNode = $document[0].createElement('a'), startSymbol = $interpolate.startSymbol(), endSymbol = $interpolate.endSymbol(), denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') ? identity : function denormalizeTemplate(template) { return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); }; return compile; //================================ function compile($compileNodes, transcludeFn, maxPriority) { if (!($compileNodes instanceof jqLite)) { // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it. $compileNodes = jqLite($compileNodes); } // 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($compileNodes, function(node, index){ if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { $compileNodes[index] = jqLite(node).wrap('').parent()[0]; } }); var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority); return function publicLinkFn(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 $linkNode = cloneConnectFn ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! : $compileNodes; // Attach scope only to non-text nodes. for(var i = 0, ii = $linkNode.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 (!msie || msie >= 8 || 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 (getBooleanAttrName(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) && 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 are 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} compileNode 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 {JQLite} jqCollection If we are working on the root of the compile tree then this * argument has the root jqLite array so that we can replace nodes on it. * @returns linkFn */ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection) { var terminalPriority = -Number.MAX_VALUE, preLinkFns = [], postLinkFns = [], newScopeDirective = null, newIsolateScopeDirective = null, templateDirective = null, $compileNode = templateAttrs.$$element = jqLite(compileNode), directive, directiveName, $template, transcludeDirective, childTranscludeFn = transcludeFn, controllerDirectives, linkFn, 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', newIsolateScopeDirective, directive, $compileNode); if (isObject(directiveValue)) { safeAddClass($compileNode, 'ng-isolate-scope'); newIsolateScopeDirective = directive; } safeAddClass($compileNode, 'ng-scope'); newScopeDirective = newScopeDirective || directive; } directiveName = directive.name; if (directiveValue = directive.controller) { controllerDirectives = controllerDirectives || {}; assertNoDuplicate("'" + directiveName + "' controller", controllerDirectives[directiveName], directive, $compileNode); controllerDirectives[directiveName] = directive; } if (directiveValue = directive.transclude) { assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode); transcludeDirective = directive; terminalPriority = directive.priority; if (directiveValue == 'element') { $template = jqLite(compileNode); $compileNode = templateAttrs.$$element = jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); compileNode = $compileNode[0]; replaceWith(jqCollection, jqLite($template[0]), compileNode); childTranscludeFn = compile($template, transcludeFn, terminalPriority); } else { $template = jqLite(JQLiteClone(compileNode)).contents(); $compileNode.html(''); // clear contents childTranscludeFn = compile($template, transcludeFn); } } if ((directiveValue = directive.template)) { assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; directiveValue = denormalizeTemplate(directiveValue); if (directive.replace) { $template = jqLite('
' + trim(directiveValue) + '
').contents(); compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue); } replaceWith(jqCollection, $compileNode, compileNode); 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( compileNode, directives.splice(i + 1, directives.length - (i + 1)), newTemplateAttrs ) ); mergeTemplateAttributes(templateAttrs, newTemplateAttrs); ii = directives.length; } else { $compileNode.html(directiveValue); } } if (directive.templateUrl) { assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), nodeLinkFn, $compileNode, templateAttrs, jqCollection, directive.replace, childTranscludeFn); ii = directives.length; } else if (directive.compile) { try { linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); if (isFunction(linkFn)) { addLinkFns(null, linkFn); } else if (linkFn) { addLinkFns(linkFn.pre, linkFn.post); } } catch (e) { $exceptionHandler(e, startingTag($compileNode)); } } if (directive.terminal) { nodeLinkFn.terminal = true; terminalPriority = Math.max(terminalPriority, directive.priority); } } nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope; nodeLinkFn.transclude = transcludeDirective && childTranscludeFn; // might be normal or delayed nodeLinkFn depending on if templateUrl is present return nodeLinkFn; //////////////////// function addLinkFns(pre, post) { if (pre) { pre.require = directive.require; preLinkFns.push(pre); } if (post) { post.require = directive.require; postLinkFns.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; } function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { var attrs, $element, i, ii, linkFn, controller; if (compileNode === linkNode) { attrs = templateAttrs; } else { attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); } $element = attrs.$$element; if (newIsolateScopeDirective) { var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/; var parentScope = scope.$parent || scope; forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) { var match = definiton.match(LOCAL_REGEXP) || [], attrName = match[2]|| scopeName, mode = match[1], // @, =, or & lastValue, parentGet, parentSet; scope.$$isolateBindings[scopeName] = mode + attrName; switch (mode) { case '@': { attrs.$observe(attrName, function(value) { scope[scopeName] = value; }); attrs.$$observers[attrName].$$scope = parentScope; break; } case '=': { parentGet = $parse(attrs[attrName]); parentSet = parentGet.assign || function() { // reset the change, or we will throw this exception on every $digest lastValue = scope[scopeName] = parentGet(parentScope); throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] + ' (directive: ' + newIsolateScopeDirective.name + ')'); }; lastValue = scope[scopeName] = parentGet(parentScope); scope.$watch(function parentValueWatch() { var parentValue = parentGet(parentScope); if (parentValue !== scope[scopeName]) { // we are out of sync and need to copy if (parentValue !== lastValue) { // parent changed and it has precedence lastValue = scope[scopeName] = parentValue; } else { // if the parent can be assigned then do so parentSet(parentScope, parentValue = lastValue = scope[scopeName]); } } return parentValue; }); break; } case '&': { parentGet = $parse(attrs[attrName]); scope[scopeName] = function(locals) { return parentGet(parentScope, locals); }; break; } default: { throw Error('Invalid isolate scope definition for directive ' + newIsolateScopeDirective.name + ': ' + definiton); } } }); } if (controllerDirectives) { forEach(controllerDirectives, function(directive) { var locals = { $scope: scope, $element: $element, $attrs: attrs, $transclude: boundTranscludeFn }; controller = directive.controller; if (controller == '@') { controller = attrs[directive.name]; } $element.data( '$' + directive.name + 'Controller', $controller(controller, locals)); }); } // PRELINKING for(i = 0, ii = preLinkFns.length; i < ii; i++) { try { linkFn = preLinkFns[i]; linkFn(scope, $element, attrs, linkFn.require && getControllers(linkFn.require, $element)); } catch (e) { $exceptionHandler(e, startingTag($element)); } } // RECURSION childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn); // POSTLINKING for(i = 0, ii = postLinkFns.length; i < ii; i++) { try { linkFn = postLinkFns[i]; linkFn(scope, $element, attrs, linkFn.require && getControllers(linkFn.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); dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + 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, beforeTemplateNodeLinkFn, $compileNode, tAttrs, $rootElement, replace, childTranscludeFn) { var linkQueue = [], afterTemplateNodeLinkFn, afterTemplateChildLinkFn, beforeTemplateCompileNode = $compileNode[0], origAsyncDirective = directives.shift(), // The fact that we have to copy and patch the directive seems wrong! derivedSyncDirective = extend({}, origAsyncDirective, { controller: null, templateUrl: null, transclude: null, scope: null }); $compileNode.html(''); $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}). success(function(content) { var compileNode, tempTemplateAttrs, $template; content = denormalizeTemplate(content); if (replace) { $template = jqLite('
' + trim(content) + '
').contents(); compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content); } tempTemplateAttrs = {$attr: {}}; replaceWith($rootElement, $compileNode, compileNode); collectDirectives(compileNode, directives, tempTemplateAttrs); mergeTemplateAttributes(tAttrs, tempTemplateAttrs); } else { compileNode = beforeTemplateCompileNode; $compileNode.html(content); } directives.unshift(derivedSyncDirective); afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn); afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); while(linkQueue.length) { var controller = linkQueue.pop(), linkRootElement = linkQueue.pop(), beforeTemplateLinkNode = linkQueue.pop(), scope = linkQueue.pop(), linkNode = compileNode; if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { // it was cloned therefore we have to clone as well. linkNode = JQLiteClone(compileNode); replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); } afterTemplateNodeLinkFn(function() { beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller); }, scope, linkNode, $rootElement, controller); } linkQueue = null; }). error(function(response, code, headers, config) { throw Error('Failed to load template: ' + config.url); }); return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) { if (linkQueue) { linkQueue.push(scope); linkQueue.push(node); linkQueue.push(rootElement); linkQueue.push(controller); } else { afterTemplateNodeLinkFn(function() { beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, 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 textInterpolateLinkFn(scope, node) { var parent = node.parent(), bindings = parent.data('$binding') || []; bindings.push(interpolateFn); safeAddClass(parent.data('$binding', bindings), 'ng-binding'); scope.$watch(interpolateFn, function interpolateFnWatchAction(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 attrInterpolateLinkFn(scope, element, attr) { var $$observers = (attr.$$observers || (attr.$$observers = {})); 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); } attr[name] = undefined; ($$observers[name] || ($$observers[name] = [])).$$inter = true; (attr.$$observers && attr.$$observers[name].$$scope || scope). $watch(interpolateFn, function interpolateFnWatchAction(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 < ii; i++) { if ($rootElement[i] == oldNode) { $rootElement[i] = newNode; break; } } } if (parent) { parent.replaceChild(newNode, oldNode); } newNode[jqLite.expando] = oldNode[jqLite.expando]; $element[0] = newNode; } }]; } var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; /** * Converts all accepted directives format into proper directive name. * All of these will become 'myDirective': * my:DiRective * my-directive * x-my-directive * data-my:directive * * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ function directiveNormalize(name) { return camelCase(name.replace(PREFIX_REGEXP, '')); } /** * @ngdoc object * @name ng.$compile.directive.Attributes * @description * * A shared object between directive compile / linking functions which contains normalized DOM element * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed * since all of these are treated as equivalent in Angular: * * */ /** * @ngdoc property * @name ng.$compile.directive.Attributes#$attr * @propertyOf ng.$compile.directive.Attributes * @returns {object} A map of DOM element attribute names to the normalized name. This is * needed to do reverse lookup from normalized name back to actual name. */ /** * @ngdoc function * @name ng.$compile.directive.Attributes#$set * @methodOf ng.$compile.directive.Attributes * @function * * @description * Set DOM element attribute value. * * * @param {string} name Normalized element attribute name of the property to modify. The name is * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr} * property to the original name. * @param {string} value Value to set the attribute to. */ /** * Closure compiler type information */ function nodesetLinkingFn( /* angular.Scope */ scope, /* NodeList */ nodeList, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn ){} function directiveLinkingFn( /* nodesetLinkingFn */ nodesetLinkingFn, /* angular.Scope */ scope, /* Node */ node, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn ){}