mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
761 lines
28 KiB
JavaScript
761 lines
28 KiB
JavaScript
'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.
|
|
*
|
|
<doc:example module="compile">
|
|
<doc:source>
|
|
<script>
|
|
// declare a new module, and inject the $compileProvider
|
|
angular.module('compile', [], function($compileProvider) {
|
|
// configure new 'compile' directive by passing a directive
|
|
// factory function. The factory function injects the '$compile'
|
|
$compileProvider.directive('compile', function($compile) {
|
|
// directive factory creates a link function
|
|
return function(scope, element, attrs) {
|
|
scope.$watch(
|
|
function(scope) {
|
|
// watch the 'compile' expression for changes
|
|
return scope.$eval(attrs.compile);
|
|
},
|
|
function(value) {
|
|
// when the 'compile' expression changes
|
|
// assign it into the current DOM
|
|
element.html(value);
|
|
|
|
// compile the new DOM and link it to the current
|
|
// scope.
|
|
// NOTE: we only compile .childNodes so that
|
|
// we don't get into infinite loop compiling ourselves
|
|
$compile(element.contents())(scope);
|
|
}
|
|
);
|
|
};
|
|
})
|
|
});
|
|
|
|
function Ctrl($scope) {
|
|
$scope.name = 'Angular';
|
|
$scope.html = 'Hello {{name}}';
|
|
}
|
|
</script>
|
|
<div ng-controller="Ctrl">
|
|
<input ng:model="name"> <br>
|
|
<textarea ng:model="html"></textarea> <br>
|
|
<div compile="html"></div>
|
|
</div>
|
|
</doc:source>
|
|
<doc:scenario>
|
|
it('should auto compile', function() {
|
|
expect(element('div[compile]').text()).toBe('Hello Angular');
|
|
input('html').enter('{{name}}!');
|
|
expect(element('div[compile]').text()).toBe('Angular!');
|
|
});
|
|
</doc:scenario>
|
|
</doc:example>
|
|
|
|
*
|
|
*
|
|
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
|
|
* @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: <br> `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.
|
|
* <pre>
|
|
* var element = $compile('<p>{{total}}</p>')(scope);
|
|
* </pre>
|
|
*
|
|
* - 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:
|
|
* <pre>
|
|
* var templateHTML = angular.element('<p>{{total}}</p>'),
|
|
* 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`
|
|
* </pre>
|
|
*
|
|
*
|
|
* 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 = /\<\<content\>\>/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 = 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',
|
|
function($injector, $interpolate, $exceptionHandler, $http, $templateCache) {
|
|
|
|
return function(templateElement) {
|
|
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 <span>
|
|
forEach(templateElement, function(node, index){
|
|
if (node.nodeType == 3 /* text node */) {
|
|
templateElement[index] = jqLite(node).wrap('<span>').parent()[0];
|
|
}
|
|
});
|
|
var linkingFn = compileNodes(templateElement, templateElement);
|
|
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;
|
|
};
|
|
};
|
|
|
|
//================================
|
|
|
|
/**
|
|
* 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 {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.
|
|
* @returns {?function} A composite linking function of all of the matched directives or null.
|
|
*/
|
|
function compileNodes(nodeList, rootElement) {
|
|
var linkingFns = [],
|
|
directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;
|
|
|
|
for(var i = 0, ii = nodeList.length; i < ii; i++) {
|
|
attrs = {
|
|
$attr: {},
|
|
$normalize: directiveNormalize,
|
|
$set: attrSetter
|
|
};
|
|
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
|
|
directives = collectDirectives(nodeList[i], [], attrs);
|
|
|
|
directiveLinkingFn = (directives.length)
|
|
? applyDirectivesToNode(directives, nodeList[i], attrs, rootElement)
|
|
: null;
|
|
|
|
childLinkingFn = (directiveLinkingFn && directiveLinkingFn.terminal)
|
|
? null
|
|
: compileNodes(nodeList[i].childNodes);
|
|
|
|
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;
|
|
|
|
function linkingFn(scope, nodeList, rootElement) {
|
|
if (linkingFns.length != nodeList.length * 2) {
|
|
throw Error('Template changed structure!');
|
|
}
|
|
|
|
var childLinkingFn, directiveLinkingFn, node, childScope;
|
|
|
|
for(var i=0, n=0, ii=linkingFns.length; i<ii; n++) {
|
|
node = nodeList[n];
|
|
directiveLinkingFn = linkingFns[i++];
|
|
childLinkingFn = linkingFns[i++];
|
|
|
|
if (directiveLinkingFn) {
|
|
if (directiveLinkingFn.scope && !rootElement) {
|
|
childScope = scope.$new();
|
|
jqLite(node).data('$scope', childScope);
|
|
} else {
|
|
childScope = scope;
|
|
}
|
|
directiveLinkingFn(childLinkingFn, childScope, node, rootElement);
|
|
} else if (childLinkingFn) {
|
|
childLinkingFn(scope, node.childNodes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Looks for directives on the given node ands them to the directive collection which is sorted.
|
|
*
|
|
* @param node node to search
|
|
* @param directives an array to which the directives are added to. This array is sorted before
|
|
* the function returns.
|
|
* @param attrs the shared attrs object which is used to populate the normalized attributes.
|
|
*/
|
|
function collectDirectives(node, directives, attrs) {
|
|
var nodeType = node.nodeType,
|
|
attrsMap = attrs.$attr,
|
|
match,
|
|
className;
|
|
|
|
switch(nodeType) {
|
|
case 1: /* Element */
|
|
// use the node name: <directive>
|
|
addDirective(directives, directiveNormalize(nodeName_(node).toLowerCase()), 'E');
|
|
|
|
// 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');
|
|
}
|
|
|
|
// use class as directive
|
|
className = node.className;
|
|
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
|
|
nName = directiveNormalize(match[2]);
|
|
if (addDirective(directives, nName, 'C')) {
|
|
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')) {
|
|
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 {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, rootElement) {
|
|
var terminalPriority = -Number.MAX_VALUE,
|
|
preLinkingFns = [],
|
|
postLinkingFns = [],
|
|
newScopeDirective = null,
|
|
templateDirective = null,
|
|
delayedLinkingFn = null,
|
|
element = templateAttrs.$element = jqLite(templateNode),
|
|
directive, linkingFn;
|
|
|
|
// executes all directives on the current element
|
|
for(var i = 0, ii = directives.length; i < ii; i++) {
|
|
directive = directives[i];
|
|
|
|
if (terminalPriority > directive.priority) {
|
|
break; // prevent further processing of directives
|
|
}
|
|
|
|
if (directive.scope) {
|
|
assertNoDuplicate('new scope', newScopeDirective, directive, element);
|
|
element.addClass('ng-scope');
|
|
newScopeDirective = directive;
|
|
}
|
|
|
|
if (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 = directive.template.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),
|
|
compositeLinkFn, element, templateAttrs, rootElement, directive.replace);
|
|
ii = directives.length;
|
|
} else if (directive.compile) {
|
|
try {
|
|
linkingFn = directive.compile(element, templateAttrs);
|
|
if (isFunction(linkingFn)) {
|
|
postLinkingFns.push(linkingFn);
|
|
} else if (linkingFn) {
|
|
if (linkingFn.pre) preLinkingFns.push(linkingFn.pre);
|
|
if (linkingFn.post) postLinkingFns.push(linkingFn.post);
|
|
}
|
|
} catch (e) {
|
|
$exceptionHandler(e, startingTag(element));
|
|
}
|
|
}
|
|
|
|
if (directive.terminal) {
|
|
compositeLinkFn.terminal = true;
|
|
terminalPriority = Math.max(terminalPriority, directive.priority);
|
|
}
|
|
|
|
}
|
|
compositeLinkFn.scope = !!newScopeDirective;
|
|
|
|
// if we have templateUrl, then we have to delay linking
|
|
return delayedLinkingFn || compositeLinkFn;
|
|
|
|
////////////////////
|
|
|
|
|
|
function compositeLinkFn(childLinkingFn, scope, linkNode) {
|
|
var attrs, element, i, ii;
|
|
|
|
if (templateNode === linkNode) {
|
|
attrs = templateAttrs;
|
|
} else {
|
|
attrs = shallowCopy(templateAttrs);
|
|
attrs.$element = jqLite(linkNode);
|
|
}
|
|
element = attrs.$element;
|
|
|
|
// PRELINKING
|
|
for(i = 0, ii = preLinkingFns.length; i < ii; i++) {
|
|
try {
|
|
preLinkingFns[i](scope, element, attrs);
|
|
} catch (e) {
|
|
$exceptionHandler(e, startingTag(element));
|
|
}
|
|
}
|
|
|
|
// RECURSION
|
|
childLinkingFn && childLinkingFn(scope, linkNode.childNodes);
|
|
|
|
// POSTLINKING
|
|
for(i = 0, ii = postLinkingFns.length; i < ii; i++) {
|
|
try {
|
|
postLinkingFns[i](scope, element, attrs);
|
|
} 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) {
|
|
var match = false;
|
|
if (hasDirectives.hasOwnProperty(name)) {
|
|
for(var directive, directives = $injector.get(name + Suffix),
|
|
i=0, ii = directives.length; i<ii; i++) {
|
|
try {
|
|
directive = directives[i];
|
|
if (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, beforeWidgetLinkFn, tElement, tAttrs, rootElement,
|
|
replace) {
|
|
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}),
|
|
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 = applyDirectivesToNode(directives, tElement, tAttrs);
|
|
afterWidgetChildrenLinkFn = compileNodes(tElement.contents());
|
|
|
|
|
|
while(linkQueue.length) {
|
|
var 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);
|
|
}, scope, node);
|
|
}
|
|
linkQueue = null;
|
|
}).
|
|
error(function(response, code, headers, config) {
|
|
throw Error('Failed to load template: ' + config.url);
|
|
});
|
|
|
|
return function(ignoreChildLinkingFn, scope, node, rootElement) {
|
|
if (linkQueue) {
|
|
linkQueue.push(scope);
|
|
linkQueue.push(node);
|
|
linkQueue.push(rootElement);
|
|
} else {
|
|
afterWidgetLinkFn(function() {
|
|
beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node);
|
|
}, scope, node);
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* 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) {
|
|
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<ii; i++) {
|
|
if (rootElement[i] == oldNode) {
|
|
rootElement[i] = newNode;
|
|
}
|
|
}
|
|
}
|
|
if (parent) {
|
|
parent.replaceChild(newNode, oldNode);
|
|
}
|
|
element[0] = newNode;
|
|
}
|
|
}];
|
|
|
|
|
|
/**
|
|
* 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 {string=} attrName Optional none normalized name. Defaults to key.
|
|
*/
|
|
function attrSetter(key, value, attrName) {
|
|
var booleanKey = BOOLEAN_ATTR[key.toLowerCase()];
|
|
|
|
if (booleanKey) {
|
|
value = toBoolean(value);
|
|
this.$element.prop(key, value);
|
|
this[key] = value;
|
|
attrName = key = booleanKey;
|
|
value = value ? booleanKey : undefined;
|
|
} else {
|
|
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 (value === null || value === undefined) {
|
|
this.$element.removeAttr(attrName);
|
|
} else {
|
|
this.$element.attr(attrName, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
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, ''));
|
|
}
|