2011-07-17 08:05:43 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
/* ! 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)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compile
|
2011-11-29 20:11:32 +00:00
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Compiles a piece of HTML string or DOM into a template and produces a template function, which
|
2012-06-12 06:49:24 +00:00
|
|
|
* can then be used to link {@link ng.$rootScope.Scope scope} and the template together.
|
2011-11-29 20:11:32 +00:00
|
|
|
*
|
|
|
|
|
* The compilation is a process of walking the DOM tree and trying to match DOM elements to
|
2012-08-12 17:36:09 +00:00
|
|
|
* {@link ng.$compileProvider#directive directives}. For each match it
|
2011-11-29 20:11:32 +00:00
|
|
|
* 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
|
2012-06-12 06:49:24 +00:00
|
|
|
* {@link ng.directive:ngRepeat repeater} many-times, in which
|
2011-11-29 20:11:32 +00:00
|
|
|
* 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);
|
|
|
|
|
},
|
2011-11-23 05:28:39 +00:00
|
|
|
function(value) {
|
2011-11-29 20:11:32 +00:00
|
|
|
// when the 'compile' expression changes
|
|
|
|
|
// assign it into the current DOM
|
|
|
|
|
element.html(value);
|
2010-03-19 23:41:02 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
2011-12-14 01:55:31 +00:00
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.name = 'Angular';
|
|
|
|
|
$scope.html = 'Hello {{name}}';
|
2011-10-26 05:21:21 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
</script>
|
|
|
|
|
<div ng-controller="Ctrl">
|
2012-03-09 08:00:05 +00:00
|
|
|
<input ng-model="name"> <br>
|
|
|
|
|
<textarea ng-model="html"></textarea> <br>
|
2011-11-29 20:11:32 +00:00
|
|
|
<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>
|
2010-03-20 05:18:39 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
|
2012-01-28 00:18:16 +00:00
|
|
|
* @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)
|
2011-11-29 20:11:32 +00:00
|
|
|
* @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
|
|
|
|
|
* (a DOM element/tree) to a scope. Where:
|
|
|
|
|
*
|
2012-06-12 06:49:24 +00:00
|
|
|
* * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
|
2013-09-28 04:10:50 +00:00
|
|
|
* * `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:
|
2011-11-29 20:11:32 +00:00
|
|
|
*
|
|
|
|
|
* * `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.
|
|
|
|
|
*
|
2012-04-17 21:35:00 +00:00
|
|
|
* After linking the view is not updated until after a call to $digest which typically is done by
|
2011-11-29 20:11:32 +00:00
|
|
|
* 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>
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* For information on how the compiler works, see the
|
2012-03-01 19:28:50 +00:00
|
|
|
* {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
|
2011-11-29 20:11:32 +00:00
|
|
|
*/
|
|
|
|
|
|
2013-06-08 01:24:30 +00:00
|
|
|
var $compileMinErr = minErr('$compile');
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 23:30:36 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc service
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compileProvider
|
2012-05-03 23:30:36 +00:00
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
2012-03-01 19:28:50 +00:00
|
|
|
*/
|
2011-11-29 20:11:32 +00:00
|
|
|
$CompileProvider.$inject = ['$provide'];
|
|
|
|
|
function $CompileProvider($provide) {
|
|
|
|
|
var hasDirectives = {},
|
|
|
|
|
Suffix = 'Directive',
|
|
|
|
|
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
|
|
|
|
|
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
|
2013-09-10 11:47:23 +00:00
|
|
|
aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
|
2013-07-16 19:48:29 +00:00
|
|
|
imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2013-06-21 20:03:56 +00:00
|
|
|
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
|
|
|
|
|
// The assumption is that future DOM event attribute names will begin with
|
|
|
|
|
// 'on' and be composed of only English letters.
|
2013-08-31 09:48:11 +00:00
|
|
|
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 23:30:36 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2012-08-12 17:45:14 +00:00
|
|
|
* @name ng.$compileProvider#directive
|
2012-06-12 06:49:24 +00:00
|
|
|
* @methodOf ng.$compileProvider
|
2012-05-03 23:30:36 +00:00
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
2013-08-05 09:00:45 +00:00
|
|
|
* Register a new directive with the compiler.
|
2012-05-03 23:30:36 +00:00
|
|
|
*
|
2013-09-24 01:45:48 +00:00
|
|
|
* @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
|
|
|
|
|
* will match as <code>ng-bind</code>), or an object map of directives where the keys are the
|
|
|
|
|
* names and the values are the factories.
|
|
|
|
|
* @param {function|Array} directiveFactory An injectable directive factory function. See
|
|
|
|
|
* {@link guide/directive} for more info.
|
2012-08-12 17:45:14 +00:00
|
|
|
* @returns {ng.$compileProvider} Self for chaining.
|
2012-05-03 23:30:36 +00:00
|
|
|
*/
|
|
|
|
|
this.directive = function registerDirective(name, directiveFactory) {
|
2011-11-29 20:11:32 +00:00
|
|
|
if (isString(name)) {
|
2013-06-05 22:30:31 +00:00
|
|
|
assertArg(directiveFactory, 'directiveFactory');
|
2011-11-29 20:11:32 +00:00
|
|
|
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;
|
2012-01-28 00:18:16 +00:00
|
|
|
directive.name = directive.name || name;
|
|
|
|
|
directive.require = directive.require || (directive.controller && directive.name);
|
2012-03-08 06:47:01 +00:00
|
|
|
directive.restrict = directive.restrict || 'A';
|
2011-11-29 20:11:32 +00:00
|
|
|
directives.push(directive);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
$exceptionHandler(e);
|
2011-11-09 01:40:03 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
});
|
|
|
|
|
return directives;
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
hasDirectives[name].push(directiveFactory);
|
|
|
|
|
} else {
|
|
|
|
|
forEach(name, reverseParams(registerDirective));
|
|
|
|
|
}
|
|
|
|
|
return this;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2013-02-19 17:55:05 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2013-07-16 19:48:29 +00:00
|
|
|
* @name ng.$compileProvider#aHrefSanitizationWhitelist
|
2013-02-19 17:55:05 +00:00
|
|
|
* @methodOf ng.$compileProvider
|
|
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
2013-07-16 19:48:29 +00:00
|
|
|
* urls during a[href] sanitization.
|
2013-02-19 17:55:05 +00:00
|
|
|
*
|
|
|
|
|
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
|
|
|
|
*
|
2013-07-16 19:48:29 +00:00
|
|
|
* 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 `aHrefSanitizationWhitelist`
|
|
|
|
|
* 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 is it written into the DOM.
|
2013-02-19 17:55:05 +00:00
|
|
|
*
|
|
|
|
|
* @param {RegExp=} regexp New regexp to whitelist urls with.
|
|
|
|
|
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
|
|
|
|
|
* chaining otherwise.
|
|
|
|
|
*/
|
2013-07-16 19:48:29 +00:00
|
|
|
this.aHrefSanitizationWhitelist = function(regexp) {
|
2013-02-19 17:55:05 +00:00
|
|
|
if (isDefined(regexp)) {
|
2013-07-16 19:48:29 +00:00
|
|
|
aHrefSanitizationWhitelist = regexp;
|
2013-02-19 17:55:05 +00:00
|
|
|
return this;
|
|
|
|
|
}
|
2013-07-16 19:48:29 +00:00
|
|
|
return aHrefSanitizationWhitelist;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
|
|
|
|
* @name ng.$compileProvider#imgSrcSanitizationWhitelist
|
|
|
|
|
* @methodOf ng.$compileProvider
|
|
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
|
|
|
|
* urls during img[src] sanitization.
|
|
|
|
|
*
|
|
|
|
|
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
|
|
|
|
*
|
|
|
|
|
* Any url about to be assigned to img[src] via data-binding is first normalized and turned into an
|
|
|
|
|
* absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 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 is it 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.imgSrcSanitizationWhitelist = function(regexp) {
|
|
|
|
|
if (isDefined(regexp)) {
|
|
|
|
|
imgSrcSanitizationWhitelist = regexp;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
return imgSrcSanitizationWhitelist;
|
2013-02-19 17:55:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
this.$get = [
|
|
|
|
|
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
|
2013-08-02 00:13:36 +00:00
|
|
|
'$controller', '$rootScope', '$document', '$sce', '$$urlUtils', '$animate',
|
2012-01-28 00:18:16 +00:00
|
|
|
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
|
2013-08-02 00:13:36 +00:00
|
|
|
$controller, $rootScope, $document, $sce, $$urlUtils, $animate) {
|
2012-01-28 00:18:16 +00:00
|
|
|
|
2012-03-28 23:03:59 +00:00
|
|
|
var Attributes = function(element, attr) {
|
|
|
|
|
this.$$element = element;
|
|
|
|
|
this.$attr = attr || {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Attributes.prototype = {
|
|
|
|
|
$normalize: directiveNormalize,
|
|
|
|
|
|
|
|
|
|
|
2013-08-02 00:13:36 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
|
|
|
|
* @name ng.$compile.directive.Attributes#$addClass
|
|
|
|
|
* @methodOf ng.$compile.directive.Attributes
|
|
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Adds the CSS class value specified by the classVal parameter to the element. If animations
|
2013-08-08 17:59:48 +00:00
|
|
|
* are enabled then an animation will be triggered for the class addition.
|
2013-08-02 00:13:36 +00:00
|
|
|
*
|
|
|
|
|
* @param {string} classVal The className value that will be added to the element
|
|
|
|
|
*/
|
|
|
|
|
$addClass : function(classVal) {
|
|
|
|
|
if(classVal && classVal.length > 0) {
|
|
|
|
|
$animate.addClass(this.$$element, classVal);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
|
|
|
|
* @name ng.$compile.directive.Attributes#$removeClass
|
|
|
|
|
* @methodOf ng.$compile.directive.Attributes
|
|
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Removes the CSS class value specified by the classVal parameter from the element. If animations
|
2013-08-08 17:59:48 +00:00
|
|
|
* are enabled then an animation will be triggered for the class removal.
|
2013-08-02 00:13:36 +00:00
|
|
|
*
|
|
|
|
|
* @param {string} classVal The className value that will be removed from the element
|
|
|
|
|
*/
|
|
|
|
|
$removeClass : function(classVal) {
|
|
|
|
|
if(classVal && classVal.length > 0) {
|
|
|
|
|
$animate.removeClass(this.$$element, classVal);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2012-03-28 23:03:59 +00:00
|
|
|
/**
|
|
|
|
|
* 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) {
|
2013-08-02 00:13:36 +00:00
|
|
|
//special case for class attribute addition + removal
|
|
|
|
|
//so that class changes can tap into the animation
|
|
|
|
|
//hooks provided by the $animate service
|
|
|
|
|
if(key == 'class') {
|
|
|
|
|
value = value || '';
|
|
|
|
|
var current = this.$$element.attr('class') || '';
|
|
|
|
|
this.$removeClass(tokenDifference(current, value).join(' '));
|
|
|
|
|
this.$addClass(tokenDifference(value, current).join(' '));
|
|
|
|
|
} else {
|
|
|
|
|
var booleanKey = getBooleanAttrName(this.$$element[0], key),
|
|
|
|
|
normalizedVal,
|
|
|
|
|
nodeName;
|
2012-03-28 23:03:59 +00:00
|
|
|
|
2013-08-02 00:13:36 +00:00
|
|
|
if (booleanKey) {
|
|
|
|
|
this.$$element.prop(key, value);
|
|
|
|
|
attrName = booleanKey;
|
|
|
|
|
}
|
2012-03-28 23:03:59 +00:00
|
|
|
|
2013-08-02 00:13:36 +00:00
|
|
|
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, '-');
|
|
|
|
|
}
|
2012-03-28 23:03:59 +00:00
|
|
|
}
|
|
|
|
|
|
2013-08-02 00:13:36 +00:00
|
|
|
nodeName = nodeName_(this.$$element);
|
|
|
|
|
|
|
|
|
|
// sanitize a[href] and img[src] values
|
|
|
|
|
if ((nodeName === 'A' && key === 'href') ||
|
|
|
|
|
(nodeName === 'IMG' && key === 'src')) {
|
|
|
|
|
// NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case.
|
|
|
|
|
if (!msie || msie >= 8 ) {
|
|
|
|
|
normalizedVal = $$urlUtils.resolve(value);
|
|
|
|
|
if (normalizedVal !== '') {
|
|
|
|
|
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
|
|
|
|
|
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
|
|
|
|
|
this[key] = value = 'unsafe:' + normalizedVal;
|
|
|
|
|
}
|
2013-07-15 19:26:46 +00:00
|
|
|
}
|
2013-07-16 19:48:29 +00:00
|
|
|
}
|
2013-02-19 17:55:05 +00:00
|
|
|
}
|
|
|
|
|
|
2013-08-02 00:13:36 +00:00
|
|
|
if (writeAttr !== false) {
|
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
|
this.$$element.removeAttr(attrName);
|
|
|
|
|
} else {
|
|
|
|
|
this.$$element.attr(attrName, value);
|
|
|
|
|
}
|
2012-03-28 23:03:59 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fire observers
|
2013-08-02 00:13:36 +00:00
|
|
|
var $$observers = this.$$observers;
|
2012-06-04 22:06:02 +00:00
|
|
|
$$observers && forEach($$observers[key], function(fn) {
|
2012-03-28 23:03:59 +00:00
|
|
|
try {
|
|
|
|
|
fn(value);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
$exceptionHandler(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
2013-08-02 00:13:36 +00:00
|
|
|
|
|
|
|
|
function tokenDifference(str1, str2) {
|
|
|
|
|
var values = [],
|
|
|
|
|
tokens1 = str1.split(/\s+/),
|
|
|
|
|
tokens2 = str2.split(/\s+/);
|
|
|
|
|
|
|
|
|
|
outer:
|
|
|
|
|
for(var i=0;i<tokens1.length;i++) {
|
|
|
|
|
var token = tokens1[i];
|
|
|
|
|
for(var j=0;j<tokens2.length;j++) {
|
|
|
|
|
if(token == tokens2[j]) continue outer;
|
|
|
|
|
}
|
|
|
|
|
values.push(token);
|
|
|
|
|
}
|
|
|
|
|
return values;
|
|
|
|
|
};
|
2012-03-28 23:03:59 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2013-09-11 01:13:15 +00:00
|
|
|
* @ngdoc function
|
|
|
|
|
* @name ng.$compile.directive.Attributes#$observe
|
|
|
|
|
* @methodOf ng.$compile.directive.Attributes
|
|
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
2013-09-25 02:59:47 +00:00
|
|
|
* Observes an interpolated attribute.
|
|
|
|
|
*
|
|
|
|
|
* The observer function will be invoked once during the next `$digest` following
|
|
|
|
|
* compilation. The observer is then invoked whenever the interpolated value
|
|
|
|
|
* changes.
|
2012-03-28 23:03:59 +00:00
|
|
|
*
|
|
|
|
|
* @param {string} key Normalized key. (ie ngAttribute) .
|
2013-09-11 01:13:15 +00:00
|
|
|
* @param {function(interpolatedValue)} fn Function that will be called whenever
|
|
|
|
|
the interpolated value of the attribute changes.
|
|
|
|
|
* See the {@link guide/directive#Attributes Directives} guide for more info.
|
|
|
|
|
* @returns {function()} the `fn` parameter.
|
2012-03-28 23:03:59 +00:00
|
|
|
*/
|
|
|
|
|
$observe: function(key, fn) {
|
2012-06-04 22:06:02 +00:00
|
|
|
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]);
|
|
|
|
|
}
|
|
|
|
|
});
|
2012-05-03 23:30:36 +00:00
|
|
|
return fn;
|
2012-03-28 23:03:59 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2013-02-19 17:55:05 +00:00
|
|
|
var urlSanitizationNode = $document[0].createElement('a'),
|
|
|
|
|
startSymbol = $interpolate.startSymbol(),
|
2012-08-11 07:13:10 +00:00
|
|
|
endSymbol = $interpolate.endSymbol(),
|
|
|
|
|
denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
|
|
|
|
|
? identity
|
|
|
|
|
: function denormalizeTemplate(template) {
|
|
|
|
|
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
|
2013-02-25 23:00:47 +00:00
|
|
|
},
|
|
|
|
|
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
|
2012-08-11 07:13:10 +00:00
|
|
|
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
return compile;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
//================================
|
|
|
|
|
|
2013-07-03 00:23:51 +00:00
|
|
|
function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective) {
|
2012-10-25 07:56:34 +00:00
|
|
|
if (!($compileNodes instanceof jqLite)) {
|
2013-01-22 15:59:09 +00:00
|
|
|
// jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
|
2012-10-25 07:56:34 +00:00
|
|
|
$compileNodes = jqLite($compileNodes);
|
2012-03-16 05:18:06 +00:00
|
|
|
}
|
2012-02-04 00:20:24 +00:00
|
|
|
// 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>
|
2012-10-25 07:56:34 +00:00
|
|
|
forEach($compileNodes, function(node, index){
|
2013-01-09 22:23:50 +00:00
|
|
|
if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
|
2013-05-24 19:41:38 +00:00
|
|
|
$compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0];
|
2012-02-04 00:20:24 +00:00
|
|
|
}
|
|
|
|
|
});
|
2013-07-03 00:23:51 +00:00
|
|
|
var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective);
|
2012-10-25 07:56:34 +00:00
|
|
|
return function publicLinkFn(scope, cloneConnectFn){
|
2011-11-29 20:11:32 +00:00
|
|
|
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.
|
2012-05-03 07:15:07 +00:00
|
|
|
var $linkNode = cloneConnectFn
|
2012-10-25 07:56:34 +00:00
|
|
|
? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
|
|
|
|
|
: $compileNodes;
|
2013-02-18 12:05:16 +00:00
|
|
|
|
|
|
|
|
// Attach scope only to non-text nodes.
|
|
|
|
|
for(var i = 0, ii = $linkNode.length; i<ii; i++) {
|
|
|
|
|
var node = $linkNode[i];
|
|
|
|
|
if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
|
|
|
|
|
$linkNode.eq(i).data('$scope', scope);
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-05-03 07:15:07 +00:00
|
|
|
safeAddClass($linkNode, 'ng-scope');
|
|
|
|
|
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
|
|
|
|
|
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
|
|
|
|
|
return $linkNode;
|
2011-11-29 20:11:32 +00:00
|
|
|
};
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
function safeAddClass($element, className) {
|
2012-03-12 23:49:28 +00:00
|
|
|
try {
|
2012-05-03 07:15:07 +00:00
|
|
|
$element.addClass(className);
|
2012-03-12 23:49:28 +00:00
|
|
|
} catch(e) {
|
|
|
|
|
// ignore, since it means that we are trying to set class on
|
|
|
|
|
// SVG element, where class name is read-only.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
2013-01-22 15:59:09 +00:00
|
|
|
* @param {NodeList} nodeList an array of nodes or NodeList to compile
|
2012-01-28 00:18:16 +00:00
|
|
|
* @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.
|
2012-05-03 07:15:07 +00:00
|
|
|
* @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
|
2011-11-29 20:11:32 +00:00
|
|
|
* 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.
|
2012-01-28 00:18:16 +00:00
|
|
|
* @param {number=} max directive priority
|
2011-11-29 20:11:32 +00:00
|
|
|
* @returns {?function} A composite linking function of all of the matched directives or null.
|
|
|
|
|
*/
|
2013-07-03 00:23:51 +00:00
|
|
|
function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective) {
|
2013-01-10 00:54:29 +00:00
|
|
|
var linkFns = [],
|
|
|
|
|
nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
|
|
|
|
|
|
|
|
|
|
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.
|
2013-07-03 00:23:51 +00:00
|
|
|
directives = collectDirectives(nodeList[i], [], attrs, i == 0 ? maxPriority : undefined, ignoreDirective);
|
2013-01-10 00:54:29 +00:00
|
|
|
|
|
|
|
|
nodeLinkFn = (directives.length)
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [])
|
2013-01-10 00:54:29 +00:00
|
|
|
: null;
|
|
|
|
|
|
2013-02-24 06:54:35 +00:00
|
|
|
childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length)
|
2013-01-10 00:54:29 +00:00
|
|
|
? null
|
|
|
|
|
: compileNodes(nodeList[i].childNodes,
|
|
|
|
|
nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
|
|
|
|
|
|
|
|
|
|
linkFns.push(nodeLinkFn);
|
|
|
|
|
linkFns.push(childLinkFn);
|
|
|
|
|
linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return a linking function if we have found anything, null otherwise
|
|
|
|
|
return linkFnFound ? compositeLinkFn : null;
|
|
|
|
|
|
|
|
|
|
function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
|
2013-01-08 19:11:51 +00:00
|
|
|
var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn, i, ii, n;
|
2013-01-10 00:54:29 +00:00
|
|
|
|
2013-01-08 19:11:51 +00:00
|
|
|
// copy nodeList so that linking doesn't break due to live list updates.
|
|
|
|
|
var stableNodeList = [];
|
|
|
|
|
for (i = 0, ii = nodeList.length; i < ii; i++) {
|
|
|
|
|
stableNodeList.push(nodeList[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
|
|
|
|
|
node = stableNodeList[n];
|
2013-01-10 00:54:29 +00:00
|
|
|
nodeLinkFn = linkFns[i++];
|
|
|
|
|
childLinkFn = linkFns[i++];
|
|
|
|
|
|
|
|
|
|
if (nodeLinkFn) {
|
|
|
|
|
if (nodeLinkFn.scope) {
|
|
|
|
|
childScope = scope.$new(isObject(nodeLinkFn.scope));
|
|
|
|
|
jqLite(node).data('$scope', childScope);
|
|
|
|
|
} else {
|
|
|
|
|
childScope = scope;
|
|
|
|
|
}
|
|
|
|
|
childTranscludeFn = nodeLinkFn.transclude;
|
|
|
|
|
if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
|
|
|
|
|
nodeLinkFn(childLinkFn, childScope, node, $rootElement,
|
|
|
|
|
(function(transcludeFn) {
|
|
|
|
|
return function(cloneFn) {
|
|
|
|
|
var transcludeScope = scope.$new();
|
2013-01-23 05:01:13 +00:00
|
|
|
transcludeScope.$$transcluded = true;
|
2013-01-10 00:54:29 +00:00
|
|
|
|
|
|
|
|
return transcludeFn(transcludeScope, cloneFn).
|
2013-06-19 19:52:50 +00:00
|
|
|
on('$destroy', bind(transcludeScope, transcludeScope.$destroy));
|
2012-01-28 00:18:16 +00:00
|
|
|
};
|
2012-05-03 07:15:07 +00:00
|
|
|
})(childTranscludeFn || transcludeFn)
|
2013-01-10 00:54:29 +00:00
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
|
|
|
|
|
}
|
|
|
|
|
} else if (childLinkFn) {
|
|
|
|
|
childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-10-25 07:56:34 +00:00
|
|
|
* Looks for directives on the given node and adds them to the directive collection which is
|
|
|
|
|
* sorted.
|
2011-11-29 20:11:32 +00:00
|
|
|
*
|
2012-10-25 07:56:34 +00:00
|
|
|
* @param node Node to search.
|
|
|
|
|
* @param directives An array to which the directives are added to. This array is sorted before
|
2011-11-29 20:11:32 +00:00
|
|
|
* the function returns.
|
2012-10-25 07:56:34 +00:00
|
|
|
* @param attrs The shared attrs object which is used to populate the normalized attributes.
|
|
|
|
|
* @param {number=} maxPriority Max directive priority.
|
2011-11-29 20:11:32 +00:00
|
|
|
*/
|
2013-07-03 00:23:51 +00:00
|
|
|
function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var nodeType = node.nodeType,
|
|
|
|
|
attrsMap = attrs.$attr,
|
|
|
|
|
match,
|
|
|
|
|
className;
|
|
|
|
|
|
|
|
|
|
switch(nodeType) {
|
|
|
|
|
case 1: /* Element */
|
|
|
|
|
// use the node name: <directive>
|
2012-01-28 00:18:16 +00:00
|
|
|
addDirective(directives,
|
2013-07-03 00:23:51 +00:00
|
|
|
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
// iterate over the attributes
|
2013-02-25 23:00:47 +00:00
|
|
|
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
|
2011-11-29 20:11:32 +00:00
|
|
|
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
|
2013-09-25 22:35:50 +00:00
|
|
|
var attrStartName = false;
|
|
|
|
|
var attrEndName = false;
|
2013-05-24 19:41:38 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
attr = nAttrs[j];
|
2013-07-25 20:24:58 +00:00
|
|
|
if (!msie || msie >= 8 || attr.specified) {
|
2012-03-19 19:20:57 +00:00
|
|
|
name = attr.name;
|
2013-02-25 23:00:47 +00:00
|
|
|
// support ngAttr attribute binding
|
|
|
|
|
ngAttrName = directiveNormalize(name);
|
|
|
|
|
if (NG_ATTR_BINDING.test(ngAttrName)) {
|
2013-09-18 14:00:34 +00:00
|
|
|
name = snake_case(ngAttrName.substr(6), '-');
|
2013-02-25 23:00:47 +00:00
|
|
|
}
|
2013-09-25 22:35:50 +00:00
|
|
|
|
|
|
|
|
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
|
|
|
|
|
if (ngAttrName === directiveNName + 'Start') {
|
2013-05-24 19:41:38 +00:00
|
|
|
attrStartName = name;
|
|
|
|
|
attrEndName = name.substr(0, name.length - 5) + 'end';
|
|
|
|
|
name = name.substr(0, name.length - 6);
|
|
|
|
|
}
|
2013-09-25 22:35:50 +00:00
|
|
|
|
2012-03-19 19:20:57 +00:00
|
|
|
nName = directiveNormalize(name.toLowerCase());
|
|
|
|
|
attrsMap[nName] = name;
|
|
|
|
|
attrs[nName] = value = trim((msie && name == 'href')
|
2011-11-29 20:11:32 +00:00
|
|
|
? decodeURIComponent(node.getAttribute(name, 2))
|
|
|
|
|
: attr.value);
|
2012-06-05 04:11:35 +00:00
|
|
|
if (getBooleanAttrName(node, nName)) {
|
2012-03-19 19:20:57 +00:00
|
|
|
attrs[nName] = true; // presence means true
|
|
|
|
|
}
|
2012-04-10 21:29:49 +00:00
|
|
|
addAttrInterpolateDirective(node, directives, value, nName);
|
2013-07-03 00:23:51 +00:00
|
|
|
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, attrEndName);
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
2011-10-26 05:21:21 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// use class as directive
|
|
|
|
|
className = node.className;
|
2012-10-25 08:00:21 +00:00
|
|
|
if (isString(className) && className !== '') {
|
2012-03-12 23:49:28 +00:00
|
|
|
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
|
|
|
|
|
nName = directiveNormalize(match[2]);
|
2013-07-03 00:23:51 +00:00
|
|
|
if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
|
2012-03-12 23:49:28 +00:00
|
|
|
attrs[nName] = trim(match[3]);
|
|
|
|
|
}
|
|
|
|
|
className = className.substr(match.index + match[0].length);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
break;
|
|
|
|
|
case 3: /* Text Node */
|
|
|
|
|
addTextInterpolateDirective(directives, node.nodeValue);
|
|
|
|
|
break;
|
|
|
|
|
case 8: /* Comment */
|
2012-04-17 20:58:25 +00:00
|
|
|
try {
|
|
|
|
|
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
|
|
|
|
|
if (match) {
|
|
|
|
|
nName = directiveNormalize(match[1]);
|
2013-07-03 00:23:51 +00:00
|
|
|
if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
|
2012-04-17 20:58:25 +00:00
|
|
|
attrs[nName] = trim(match[2]);
|
|
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-04-17 20:58:25 +00:00
|
|
|
} 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.)
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
directives.sort(byPriority);
|
|
|
|
|
return directives;
|
|
|
|
|
}
|
2010-03-19 23:41:02 +00:00
|
|
|
|
2013-05-24 19:41:38 +00:00
|
|
|
/**
|
|
|
|
|
* Given a node with an directive-start it collects all of the siblings until it find directive-end.
|
|
|
|
|
* @param node
|
|
|
|
|
* @param attrStart
|
|
|
|
|
* @param attrEnd
|
|
|
|
|
* @returns {*}
|
|
|
|
|
*/
|
|
|
|
|
function groupScan(node, attrStart, attrEnd) {
|
|
|
|
|
var nodes = [];
|
|
|
|
|
var depth = 0;
|
|
|
|
|
if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
|
|
|
|
|
var startNode = node;
|
|
|
|
|
do {
|
|
|
|
|
if (!node) {
|
2013-08-08 17:59:48 +00:00
|
|
|
throw $compileMinErr('uterdir', "Unterminated attribute, found '{0}' but no matching '{1}' found.", attrStart, attrEnd);
|
2013-05-24 19:41:38 +00:00
|
|
|
}
|
2013-06-11 20:08:21 +00:00
|
|
|
if (node.nodeType == 1 /** Element **/) {
|
|
|
|
|
if (node.hasAttribute(attrStart)) depth++;
|
|
|
|
|
if (node.hasAttribute(attrEnd)) depth--;
|
|
|
|
|
}
|
2013-05-24 19:41:38 +00:00
|
|
|
nodes.push(node);
|
|
|
|
|
node = node.nextSibling;
|
|
|
|
|
} while (depth > 0);
|
|
|
|
|
} else {
|
|
|
|
|
nodes.push(node);
|
|
|
|
|
}
|
2013-09-25 22:35:50 +00:00
|
|
|
|
2013-05-24 19:41:38 +00:00
|
|
|
return jqLite(nodes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wrapper for linking function which converts normal linking function into a grouped
|
|
|
|
|
* linking function.
|
|
|
|
|
* @param linkFn
|
|
|
|
|
* @param attrStart
|
|
|
|
|
* @param attrEnd
|
|
|
|
|
* @returns {Function}
|
|
|
|
|
*/
|
|
|
|
|
function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
|
|
|
|
|
return function(scope, element, attrs, controllers) {
|
|
|
|
|
element = groupScan(element[0], attrStart, attrEnd);
|
|
|
|
|
return linkFn(scope, element, attrs, controllers);
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-03-20 05:18:39 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
/**
|
2013-04-02 21:38:23 +00:00
|
|
|
* Once the directives have been collected, their compile functions are executed. This method
|
2012-03-13 04:30:03 +00:00
|
|
|
* is responsible for inlining directive templates as well as terminating the application
|
2013-04-02 21:38:23 +00:00
|
|
|
* of the directives if the terminal directive has been reached.
|
2011-11-29 20:11:32 +00:00
|
|
|
*
|
|
|
|
|
* @param {Array} directives Array of collected directives to execute their compile function.
|
|
|
|
|
* this needs to be pre-sorted by priority order.
|
2012-05-03 07:15:07 +00:00
|
|
|
* @param {Node} compileNode The raw DOM node to apply the compile functions to
|
2011-11-29 20:11:32 +00:00
|
|
|
* @param {Object} templateAttrs The shared attribute function
|
2012-01-28 00:18:16 +00:00
|
|
|
* @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.
|
2012-12-03 19:03:24 +00:00
|
|
|
* @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.
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
* @param {Object=} ignoreDirective An optional directive that will be ignored when compiling
|
|
|
|
|
* the transclusion.
|
|
|
|
|
* @param {Array.<Function>} preLinkFns
|
|
|
|
|
* @param {Array.<Function>} postLinkFns
|
2012-05-03 07:15:07 +00:00
|
|
|
* @returns linkFn
|
2011-11-29 20:11:32 +00:00
|
|
|
*/
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection,
|
|
|
|
|
originalReplaceDirective, preLinkFns, postLinkFns) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var terminalPriority = -Number.MAX_VALUE,
|
|
|
|
|
newScopeDirective = null,
|
2012-10-25 07:56:34 +00:00
|
|
|
newIsolateScopeDirective = null,
|
2011-11-29 20:11:32 +00:00
|
|
|
templateDirective = null,
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode = templateAttrs.$$element = jqLite(compileNode),
|
2012-01-28 00:18:16 +00:00
|
|
|
directive,
|
|
|
|
|
directiveName,
|
2012-05-03 07:15:07 +00:00
|
|
|
$template,
|
2012-01-28 00:18:16 +00:00
|
|
|
transcludeDirective,
|
2013-07-03 00:23:51 +00:00
|
|
|
replaceDirective = originalReplaceDirective,
|
2012-01-28 00:18:16 +00:00
|
|
|
childTranscludeFn = transcludeFn,
|
|
|
|
|
controllerDirectives,
|
2012-05-03 07:15:07 +00:00
|
|
|
linkFn,
|
2012-01-28 00:18:16 +00:00
|
|
|
directiveValue;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
// executes all directives on the current element
|
|
|
|
|
for(var i = 0, ii = directives.length; i < ii; i++) {
|
|
|
|
|
directive = directives[i];
|
2013-05-24 19:41:38 +00:00
|
|
|
var attrStart = directive.$$start;
|
|
|
|
|
var attrEnd = directive.$$end;
|
|
|
|
|
|
|
|
|
|
// collect multiblock sections
|
|
|
|
|
if (attrStart) {
|
|
|
|
|
$compileNode = groupScan(compileNode, attrStart, attrEnd)
|
|
|
|
|
}
|
2012-05-03 07:15:07 +00:00
|
|
|
$template = undefined;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
if (terminalPriority > directive.priority) {
|
|
|
|
|
break; // prevent further processing of directives
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
if (directiveValue = directive.scope) {
|
|
|
|
|
newScopeDirective = newScopeDirective || directive;
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
|
|
|
|
|
// skip the check for directives with async templates, we'll check the derived sync directive when
|
|
|
|
|
// the template arrives
|
|
|
|
|
if (!directive.templateUrl) {
|
|
|
|
|
assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, $compileNode);
|
|
|
|
|
if (isObject(directiveValue)) {
|
|
|
|
|
safeAddClass($compileNode, 'ng-isolate-scope');
|
|
|
|
|
newIsolateScopeDirective = directive;
|
|
|
|
|
}
|
|
|
|
|
safeAddClass($compileNode, 'ng-scope');
|
|
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
directiveName = directive.name;
|
|
|
|
|
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
if (!directive.templateUrl && directive.controller) {
|
|
|
|
|
directiveValue = directive.controller;
|
2013-08-10 02:56:10 +00:00
|
|
|
controllerDirectives = controllerDirectives || {};
|
|
|
|
|
assertNoDuplicate("'" + directiveName + "' controller",
|
|
|
|
|
controllerDirectives[directiveName], directive, $compileNode);
|
|
|
|
|
controllerDirectives[directiveName] = directive;
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
if (directiveValue = directive.transclude) {
|
2012-05-03 07:15:07 +00:00
|
|
|
assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
|
2012-01-28 00:18:16 +00:00
|
|
|
transcludeDirective = directive;
|
2013-10-03 23:24:24 +00:00
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
if (directiveValue == 'element') {
|
2013-10-03 23:24:24 +00:00
|
|
|
terminalPriority = directive.priority;
|
2013-05-24 19:41:38 +00:00
|
|
|
$template = groupScan(compileNode, attrStart, attrEnd)
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode = templateAttrs.$$element =
|
2013-01-10 06:07:33 +00:00
|
|
|
jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
|
2012-05-03 07:15:07 +00:00
|
|
|
compileNode = $compileNode[0];
|
2013-05-24 19:41:38 +00:00
|
|
|
replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode);
|
2013-07-03 00:23:51 +00:00
|
|
|
|
|
|
|
|
childTranscludeFn = compile($template, transcludeFn, terminalPriority,
|
|
|
|
|
replaceDirective && replaceDirective.name);
|
2012-01-28 00:18:16 +00:00
|
|
|
} else {
|
2012-05-03 07:15:07 +00:00
|
|
|
$template = jqLite(JQLiteClone(compileNode)).contents();
|
|
|
|
|
$compileNode.html(''); // clear contents
|
|
|
|
|
childTranscludeFn = compile($template, transcludeFn);
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-21 20:56:40 +00:00
|
|
|
if (directive.template) {
|
2012-05-03 07:15:07 +00:00
|
|
|
assertNoDuplicate('template', templateDirective, directive, $compileNode);
|
2011-11-29 20:11:32 +00:00
|
|
|
templateDirective = directive;
|
2013-02-21 20:56:40 +00:00
|
|
|
|
|
|
|
|
directiveValue = (isFunction(directive.template))
|
|
|
|
|
? directive.template($compileNode, templateAttrs)
|
|
|
|
|
: directive.template;
|
|
|
|
|
|
2012-08-11 07:13:10 +00:00
|
|
|
directiveValue = denormalizeTemplate(directiveValue);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
if (directive.replace) {
|
2013-07-03 00:23:51 +00:00
|
|
|
replaceDirective = directive;
|
2012-08-11 06:46:42 +00:00
|
|
|
$template = jqLite('<div>' +
|
|
|
|
|
trim(directiveValue) +
|
|
|
|
|
'</div>').contents();
|
|
|
|
|
compileNode = $template[0];
|
|
|
|
|
|
2012-05-04 08:24:46 +00:00
|
|
|
if ($template.length != 1 || compileNode.nodeType !== 1) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}", directiveName, '');
|
2012-05-04 08:24:46 +00:00
|
|
|
}
|
|
|
|
|
|
2012-12-03 19:03:24 +00:00
|
|
|
replaceWith(jqCollection, $compileNode, compileNode);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
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(
|
2012-05-03 07:15:07 +00:00
|
|
|
compileNode,
|
2011-11-29 20:11:32 +00:00
|
|
|
directives.splice(i + 1, directives.length - (i + 1)),
|
|
|
|
|
newTemplateAttrs
|
|
|
|
|
)
|
|
|
|
|
);
|
2013-07-03 00:15:24 +00:00
|
|
|
mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
ii = directives.length;
|
|
|
|
|
} else {
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode.html(directiveValue);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (directive.templateUrl) {
|
2012-05-03 07:15:07 +00:00
|
|
|
assertNoDuplicate('template', templateDirective, directive, $compileNode);
|
2011-11-29 20:11:32 +00:00
|
|
|
templateDirective = directive;
|
2013-07-03 00:23:51 +00:00
|
|
|
|
|
|
|
|
if (directive.replace) {
|
|
|
|
|
replaceDirective = directive;
|
|
|
|
|
}
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
|
|
|
|
|
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
|
|
|
|
|
templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns);
|
2011-11-29 20:11:32 +00:00
|
|
|
ii = directives.length;
|
|
|
|
|
} else if (directive.compile) {
|
|
|
|
|
try {
|
2012-05-03 07:15:07 +00:00
|
|
|
linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
|
|
|
|
|
if (isFunction(linkFn)) {
|
2013-05-24 19:41:38 +00:00
|
|
|
addLinkFns(null, linkFn, attrStart, attrEnd);
|
2012-05-03 07:15:07 +00:00
|
|
|
} else if (linkFn) {
|
2013-05-24 19:41:38 +00:00
|
|
|
addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
} catch (e) {
|
2012-05-03 07:15:07 +00:00
|
|
|
$exceptionHandler(e, startingTag($compileNode));
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2010-03-19 23:41:02 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
if (directive.terminal) {
|
2012-05-03 07:15:07 +00:00
|
|
|
nodeLinkFn.terminal = true;
|
2011-11-29 20:11:32 +00:00
|
|
|
terminalPriority = Math.max(terminalPriority, directive.priority);
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-02-25 22:03:02 +00:00
|
|
|
|
2010-04-30 19:22:07 +00:00
|
|
|
}
|
2012-01-28 00:18:16 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
|
|
|
|
|
nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
|
|
|
|
|
return nodeLinkFn;
|
2010-03-19 23:41:02 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
////////////////////
|
|
|
|
|
|
2013-05-24 19:41:38 +00:00
|
|
|
function addLinkFns(pre, post, attrStart, attrEnd) {
|
2012-01-28 00:18:16 +00:00
|
|
|
if (pre) {
|
2013-05-24 19:41:38 +00:00
|
|
|
if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
|
2012-01-28 00:18:16 +00:00
|
|
|
pre.require = directive.require;
|
2012-05-03 07:15:07 +00:00
|
|
|
preLinkFns.push(pre);
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
|
|
|
|
if (post) {
|
2013-05-24 19:41:38 +00:00
|
|
|
if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
|
2012-01-28 00:18:16 +00:00
|
|
|
post.require = directive.require;
|
2012-05-03 07:15:07 +00:00
|
|
|
postLinkFns.push(post);
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
function getControllers(require, $element) {
|
2012-01-28 00:18:16 +00:00
|
|
|
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 == '?';
|
|
|
|
|
}
|
2013-09-25 19:30:51 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
value = $element[retrievalMethod]('$' + require + 'Controller');
|
2013-09-25 19:30:51 +00:00
|
|
|
|
|
|
|
|
if ($element[0].nodeType == 8 && $element[0].$$controller) { // Transclusion comment node
|
|
|
|
|
value = value || $element[0].$$controller;
|
|
|
|
|
$element[0].$$controller = null;
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
if (!value && !optional) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('ctreq', "Controller '{0}', required by directive '{1}', can't be found!", require, directiveName);
|
2012-01-28 00:18:16 +00:00
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
} else if (isArray(require)) {
|
|
|
|
|
value = [];
|
|
|
|
|
forEach(require, function(require) {
|
2012-05-03 07:15:07 +00:00
|
|
|
value.push(getControllers(require, $element));
|
2012-01-28 00:18:16 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
|
|
|
|
|
var attrs, $element, i, ii, linkFn, controller;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
if (compileNode === linkNode) {
|
2011-11-29 20:11:32 +00:00
|
|
|
attrs = templateAttrs;
|
|
|
|
|
} else {
|
2012-03-28 23:03:59 +00:00
|
|
|
attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-05-03 07:15:07 +00:00
|
|
|
$element = attrs.$$element;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-10-25 07:56:34 +00:00
|
|
|
if (newIsolateScopeDirective) {
|
2013-01-26 19:15:06 +00:00
|
|
|
var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
|
2012-06-06 20:58:10 +00:00
|
|
|
|
|
|
|
|
var parentScope = scope.$parent || scope;
|
|
|
|
|
|
2013-05-24 18:00:14 +00:00
|
|
|
forEach(newIsolateScopeDirective.scope, function(definition, scopeName) {
|
|
|
|
|
var match = definition.match(LOCAL_REGEXP) || [],
|
2013-01-26 19:15:06 +00:00
|
|
|
attrName = match[3] || scopeName,
|
|
|
|
|
optional = (match[2] == '?'),
|
2012-06-06 20:58:10 +00:00
|
|
|
mode = match[1], // @, =, or &
|
|
|
|
|
lastValue,
|
|
|
|
|
parentGet, parentSet;
|
|
|
|
|
|
2013-01-23 05:01:13 +00:00
|
|
|
scope.$$isolateBindings[scopeName] = mode + attrName;
|
|
|
|
|
|
2012-06-06 20:58:10 +00:00
|
|
|
switch (mode) {
|
|
|
|
|
|
|
|
|
|
case '@': {
|
|
|
|
|
attrs.$observe(attrName, function(value) {
|
|
|
|
|
scope[scopeName] = value;
|
|
|
|
|
});
|
|
|
|
|
attrs.$$observers[attrName].$$scope = parentScope;
|
2012-10-24 10:06:36 +00:00
|
|
|
if( attrs[attrName] ) {
|
|
|
|
|
// If the attribute has been provided then we trigger an interpolation to ensure the value is there for use in the link fn
|
|
|
|
|
scope[scopeName] = $interpolate(attrs[attrName])(parentScope);
|
|
|
|
|
}
|
2012-06-06 20:58:10 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2013-02-19 17:55:05 +00:00
|
|
|
|
2012-06-06 20:58:10 +00:00
|
|
|
case '=': {
|
2013-01-26 19:15:06 +00:00
|
|
|
if (optional && !attrs[attrName]) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2012-06-06 20:58:10 +00:00
|
|
|
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);
|
2013-08-01 22:39:22 +00:00
|
|
|
throw $compileMinErr('nonassign', "Expression '{0}' used with directive '{1}' is non-assignable!",
|
2013-05-24 18:00:14 +00:00
|
|
|
attrs[attrName], newIsolateScopeDirective.name);
|
2012-06-06 20:58:10 +00:00
|
|
|
};
|
|
|
|
|
lastValue = scope[scopeName] = parentGet(parentScope);
|
2012-07-06 09:53:40 +00:00
|
|
|
scope.$watch(function parentValueWatch() {
|
2012-06-06 20:58:10 +00:00
|
|
|
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
|
2012-09-13 09:40:00 +00:00
|
|
|
parentSet(parentScope, parentValue = lastValue = scope[scopeName]);
|
2012-06-06 20:58:10 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return parentValue;
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case '&': {
|
|
|
|
|
parentGet = $parse(attrs[attrName]);
|
|
|
|
|
scope[scopeName] = function(locals) {
|
|
|
|
|
return parentGet(parentScope, locals);
|
2013-05-07 04:56:51 +00:00
|
|
|
};
|
2012-06-06 20:58:10 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default: {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('iscp', "Invalid isolate scope definition for directive '{0}'. Definition: {... {1}: '{2}' ...}",
|
2013-05-24 18:00:14 +00:00
|
|
|
newIsolateScopeDirective.name, scopeName, definition);
|
2012-06-06 20:58:10 +00:00
|
|
|
}
|
|
|
|
|
}
|
2012-01-28 00:18:16 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (controllerDirectives) {
|
|
|
|
|
forEach(controllerDirectives, function(directive) {
|
|
|
|
|
var locals = {
|
|
|
|
|
$scope: scope,
|
2012-05-03 07:15:07 +00:00
|
|
|
$element: $element,
|
2012-01-28 00:18:16 +00:00
|
|
|
$attrs: attrs,
|
|
|
|
|
$transclude: boundTranscludeFn
|
2013-05-25 00:18:51 +00:00
|
|
|
}, controllerInstance;
|
2012-01-28 00:18:16 +00:00
|
|
|
|
|
|
|
|
controller = directive.controller;
|
|
|
|
|
if (controller == '@') {
|
|
|
|
|
controller = attrs[directive.name];
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-25 00:18:51 +00:00
|
|
|
controllerInstance = $controller(controller, locals);
|
2013-09-25 19:30:51 +00:00
|
|
|
|
|
|
|
|
// Directives with element transclusion and a controller need to attach controller
|
|
|
|
|
// to the comment node created by the compiler, but jQuery .data doesn't support
|
|
|
|
|
// attaching data to comment nodes so instead we set it directly on the element and
|
|
|
|
|
// remove it after we read it later.
|
|
|
|
|
if ($element[0].nodeType == 8) { // Transclusion comment node
|
|
|
|
|
$element[0].$$controller = controllerInstance;
|
|
|
|
|
} else {
|
|
|
|
|
$element.data('$' + directive.name + 'Controller', controllerInstance);
|
|
|
|
|
}
|
2013-05-25 00:18:51 +00:00
|
|
|
if (directive.controllerAs) {
|
|
|
|
|
locals.$scope[directive.controllerAs] = controllerInstance;
|
|
|
|
|
}
|
2012-01-28 00:18:16 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// PRELINKING
|
2012-05-03 07:15:07 +00:00
|
|
|
for(i = 0, ii = preLinkFns.length; i < ii; i++) {
|
2011-11-29 20:11:32 +00:00
|
|
|
try {
|
2012-05-03 07:15:07 +00:00
|
|
|
linkFn = preLinkFns[i];
|
|
|
|
|
linkFn(scope, $element, attrs,
|
|
|
|
|
linkFn.require && getControllers(linkFn.require, $element));
|
2011-11-29 20:11:32 +00:00
|
|
|
} catch (e) {
|
2012-05-03 07:15:07 +00:00
|
|
|
$exceptionHandler(e, startingTag($element));
|
2011-10-26 05:21:21 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RECURSION
|
2012-05-03 07:15:07 +00:00
|
|
|
childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
// POSTLINKING
|
fix($compile): fix (reverse) directive postLink fn execution order
previously the compile/link fns executed in this order controlled via priority:
- CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow
- PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow
- link children
- PostLinkPriorityHigh, PostLinkPriorityMedium, PostLinkPriorityLow
This was changed to:
- CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow
- PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow
- link children
- PostLinkPriorityLow, PostLinkPriorityMedium , PostLinkPriorityHigh
Using this order the child transclusion directive that gets replaced
onto the current element get executed correctly (see issue #3558),
and more generally, the order of execution of post linking function
makes more sense. The incorrect order was an oversight that has
gone unnoticed for many suns and moons.
(FYI: postLink functions are the default linking functions)
BREAKING CHANGE: the order of postLink fn is now mirror opposite of
the order in which corresponding preLinking and compile functions
execute.
Very few directives in practice rely on order of postLinking function
(unlike on the order of compile functions), so in the rare case
of this change affecting an existing directive, it might be necessary
to convert it to a preLinking function or give it negative priority
(look at the diff of this commit to see how an internal attribute
interpolation directive was adjusted).
Closes #3558
2013-10-01 14:55:47 +00:00
|
|
|
for(i = postLinkFns.length - 1; i >= 0; i--) {
|
2011-11-29 20:11:32 +00:00
|
|
|
try {
|
2012-05-03 07:15:07 +00:00
|
|
|
linkFn = postLinkFns[i];
|
|
|
|
|
linkFn(scope, $element, attrs,
|
|
|
|
|
linkFn.require && getControllers(linkFn.require, $element));
|
2011-11-29 20:11:32 +00:00
|
|
|
} catch (e) {
|
2012-05-03 07:15:07 +00:00
|
|
|
$exceptionHandler(e, startingTag($element));
|
2011-10-26 05:21:21 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2013-07-03 00:23:51 +00:00
|
|
|
function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) {
|
|
|
|
|
if (name === ignoreDirective) return null;
|
2013-05-24 19:41:38 +00:00
|
|
|
var match = null;
|
2011-11-29 20:11:32 +00:00
|
|
|
if (hasDirectives.hasOwnProperty(name)) {
|
|
|
|
|
for(var directive, directives = $injector.get(name + Suffix),
|
2012-05-03 07:15:07 +00:00
|
|
|
i = 0, ii = directives.length; i<ii; i++) {
|
2011-11-29 20:11:32 +00:00
|
|
|
try {
|
|
|
|
|
directive = directives[i];
|
2012-01-28 00:18:16 +00:00
|
|
|
if ( (maxPriority === undefined || maxPriority > directive.priority) &&
|
|
|
|
|
directive.restrict.indexOf(location) != -1) {
|
2013-05-24 19:41:38 +00:00
|
|
|
if (startAttrName) {
|
|
|
|
|
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
|
|
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
tDirectives.push(directive);
|
2013-05-24 19:41:38 +00:00
|
|
|
match = directive;
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
} 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)
|
|
|
|
|
*/
|
2013-07-03 00:15:24 +00:00
|
|
|
function mergeTemplateAttributes(dst, src) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var srcAttr = src.$attr,
|
|
|
|
|
dstAttr = dst.$attr,
|
2012-05-03 07:15:07 +00:00
|
|
|
$element = dst.$$element;
|
2012-06-06 14:23:07 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// reapply the old attributes to the new element
|
|
|
|
|
forEach(dst, function(value, key) {
|
2013-07-03 00:15:24 +00:00
|
|
|
if (key.charAt(0) != '$') {
|
2012-03-20 00:26:05 +00:00
|
|
|
if (src[key]) {
|
|
|
|
|
value += (key === 'style' ? ';' : ' ') + src[key];
|
|
|
|
|
}
|
2012-03-23 20:04:52 +00:00
|
|
|
dst.$set(key, value, true, srcAttr[key]);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
});
|
2012-06-06 14:23:07 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
// copy the new attributes on the old attrs object
|
|
|
|
|
forEach(src, function(value, key) {
|
|
|
|
|
if (key == 'class') {
|
2012-05-03 07:15:07 +00:00
|
|
|
safeAddClass($element, value);
|
2012-06-06 14:23:07 +00:00
|
|
|
dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
|
2011-11-29 20:11:32 +00:00
|
|
|
} else if (key == 'style') {
|
2012-05-03 07:15:07 +00:00
|
|
|
$element.attr('style', $element.attr('style') + ';' + value);
|
2013-07-03 00:15:24 +00:00
|
|
|
} else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
|
2011-11-29 20:11:32 +00:00
|
|
|
dst[key] = value;
|
|
|
|
|
dstAttr[key] = srcAttr[key];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
function compileTemplateUrl(directives, $compileNode, tAttrs,
|
|
|
|
|
$rootElement, childTranscludeFn, preLinkFns, postLinkFns) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var linkQueue = [],
|
2012-05-03 07:15:07 +00:00
|
|
|
afterTemplateNodeLinkFn,
|
|
|
|
|
afterTemplateChildLinkFn,
|
2012-05-04 04:50:55 +00:00
|
|
|
beforeTemplateCompileNode = $compileNode[0],
|
2012-05-03 07:15:07 +00:00
|
|
|
origAsyncDirective = directives.shift(),
|
2011-11-29 20:11:32 +00:00
|
|
|
// The fact that we have to copy and patch the directive seems wrong!
|
2012-05-03 07:15:07 +00:00
|
|
|
derivedSyncDirective = extend({}, origAsyncDirective, {
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
templateUrl: null, transclude: null, replace: null
|
2013-02-21 20:56:40 +00:00
|
|
|
}),
|
|
|
|
|
templateUrl = (isFunction(origAsyncDirective.templateUrl))
|
|
|
|
|
? origAsyncDirective.templateUrl($compileNode, tAttrs)
|
|
|
|
|
: origAsyncDirective.templateUrl;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode.html('');
|
2011-11-29 20:11:32 +00:00
|
|
|
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
$http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).
|
2011-11-29 20:11:32 +00:00
|
|
|
success(function(content) {
|
2012-05-04 08:24:46 +00:00
|
|
|
var compileNode, tempTemplateAttrs, $template;
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-08-11 07:13:10 +00:00
|
|
|
content = denormalizeTemplate(content);
|
|
|
|
|
|
2013-07-03 00:23:51 +00:00
|
|
|
if (origAsyncDirective.replace) {
|
2012-05-04 08:24:46 +00:00
|
|
|
$template = jqLite('<div>' + trim(content) + '</div>').contents();
|
|
|
|
|
compileNode = $template[0];
|
|
|
|
|
|
|
|
|
|
if ($template.length != 1 || compileNode.nodeType !== 1) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('tplrt', "Template for directive '{0}' must have exactly one root element. {1}",
|
2013-05-24 18:00:14 +00:00
|
|
|
origAsyncDirective.name, templateUrl);
|
2012-05-04 08:24:46 +00:00
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
tempTemplateAttrs = {$attr: {}};
|
2012-05-03 07:15:07 +00:00
|
|
|
replaceWith($rootElement, $compileNode, compileNode);
|
|
|
|
|
collectDirectives(compileNode, directives, tempTemplateAttrs);
|
2013-07-03 00:15:24 +00:00
|
|
|
mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
|
2011-11-29 20:11:32 +00:00
|
|
|
} else {
|
2012-05-04 04:50:55 +00:00
|
|
|
compileNode = beforeTemplateCompileNode;
|
2012-05-03 07:15:07 +00:00
|
|
|
$compileNode.html(content);
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
directives.unshift(derivedSyncDirective);
|
2013-07-03 00:23:51 +00:00
|
|
|
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
|
|
|
|
|
childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns);
|
2013-03-13 23:29:26 +00:00
|
|
|
forEach($rootElement, function(node, i) {
|
|
|
|
|
if (node == compileNode) {
|
|
|
|
|
$rootElement[i] = $compileNode[0];
|
|
|
|
|
}
|
|
|
|
|
});
|
2013-01-22 15:59:09 +00:00
|
|
|
afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
|
2011-11-29 20:11:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
while(linkQueue.length) {
|
2013-02-26 09:22:12 +00:00
|
|
|
var scope = linkQueue.shift(),
|
|
|
|
|
beforeTemplateLinkNode = linkQueue.shift(),
|
|
|
|
|
linkRootElement = linkQueue.shift(),
|
|
|
|
|
controller = linkQueue.shift(),
|
2013-03-13 23:29:26 +00:00
|
|
|
linkNode = $compileNode[0];
|
2011-11-29 20:11:32 +00:00
|
|
|
|
2012-05-04 04:50:55 +00:00
|
|
|
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
|
2011-11-29 20:11:32 +00:00
|
|
|
// it was cloned therefore we have to clone as well.
|
2012-05-03 07:15:07 +00:00
|
|
|
linkNode = JQLiteClone(compileNode);
|
2012-05-04 04:50:55 +00:00
|
|
|
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
|
2012-05-03 04:08:02 +00:00
|
|
|
}
|
|
|
|
|
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
linkQueue = null;
|
|
|
|
|
}).
|
|
|
|
|
error(function(response, code, headers, config) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url);
|
2011-11-29 20:11:32 +00:00
|
|
|
});
|
2011-11-02 23:32:46 +00:00
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
|
2011-11-29 20:11:32 +00:00
|
|
|
if (linkQueue) {
|
|
|
|
|
linkQueue.push(scope);
|
|
|
|
|
linkQueue.push(node);
|
|
|
|
|
linkQueue.push(rootElement);
|
2012-01-28 00:18:16 +00:00
|
|
|
linkQueue.push(controller);
|
2011-11-29 20:11:32 +00:00
|
|
|
} else {
|
fix($compile): link parents before traversing
How did compiling a templateUrl (async) directive with `replace:true` work before this commit?
1/ apply all directives with higher priority than the templateUrl directive
2/ partially apply the templateUrl directive (create `beforeTemplateNodeLinkFn`)
3/ fetch the template
4/ apply second part of the templateUrl directive on the fetched template
(`afterTemplateNodeLinkFn`)
That is, the templateUrl directive is basically split into two parts (two `nodeLinkFn` functions),
which has to be both applied.
Normally we compose linking functions (`nodeLinkFn`) using continuation - calling the linking
function of a parent element, passing the linking function of the child elements as an argument. The
parent linking function then does:
1/ execute its pre-link functions
2/ call the child elements linking function (traverse)
3/ execute its post-link functions
Now, we have two linking functions for the same DOM element level (because the templateUrl directive
has been split).
There has been multiple issues because of the order of these two linking functions (creating
controller before setting up scope locals, running linking functions before instantiating
controller, etc.). It is easy to fix one use case, but it breaks some other use case. It is hard to
decide what is the "correct" order of these two linking functions as they are essentially on the
same level.
Running them side-by-side screws up pre/post linking functions for the high priority directives
(those executed before the templateUrl directive). It runs post-linking functions before traversing:
```js
beforeTemplateNodeLinkFn(null); // do not travers
afterTemplateNodeLinkFn(afterTemplateChildLinkFn);
```
Composing them (in any order) screws up the order of post-linking functions. We could fix this by
having post-linking functions to execute in reverse order (from the lowest priority to the highest)
which might actually make a sense.
**My solution is to remove this splitting.** This commit removes the `beforeTemplateNodeLinkFn`. The
first run (before we have the template) only schedules fetching the template. The rest (creating
scope locals, instantiating a controller, linking functions, etc) is done when processing the
directive again (in the context of the already fetched template; this is the cloned
`derivedSyncDirective`).
We still need to pass-through the linking functions of the higher priority directives (those
executed before the templateUrl directive), that's why I added `preLinkFns` and `postLinkFns`
arguments to `applyDirectivesToNode`.
This also changes the "$compile transclude should make the result of a transclusion available to the
parent directive in post- linking phase (templateUrl)" unit test. It was testing that a parent
directive can see the content of transclusion in its pre-link function. That is IMHO wrong (as the
`ngTransclude` directive inserts the translusion in its linking function). This test was only passing because of
c173ca412878d537b18df01f39e400ea48a4b398, which changed the behavior of the compiler to traverse
before executing the parent linking function. That was wrong and also caused the #3792 issue, which
this change fixes.
Closes #3792
Closes #3923
Closes #3935
Closes #3927
2013-09-08 17:05:25 +00:00
|
|
|
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sorting function for bound directives.
|
|
|
|
|
*/
|
|
|
|
|
function byPriority(a, b) {
|
|
|
|
|
return b.priority - a.priority;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function assertNoDuplicate(what, previousDirective, directive, element) {
|
|
|
|
|
if (previousDirective) {
|
2013-06-08 01:24:30 +00:00
|
|
|
throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}',
|
2013-05-24 18:00:14 +00:00
|
|
|
previousDirective.name, directive.name, what, startingTag(element));
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function addTextInterpolateDirective(directives, text) {
|
|
|
|
|
var interpolateFn = $interpolate(text, true);
|
|
|
|
|
if (interpolateFn) {
|
|
|
|
|
directives.push({
|
|
|
|
|
priority: 0,
|
2012-10-25 07:56:34 +00:00
|
|
|
compile: valueFn(function textInterpolateLinkFn(scope, node) {
|
2011-11-29 20:11:32 +00:00
|
|
|
var parent = node.parent(),
|
|
|
|
|
bindings = parent.data('$binding') || [];
|
|
|
|
|
bindings.push(interpolateFn);
|
2012-03-12 23:49:28 +00:00
|
|
|
safeAddClass(parent.data('$binding', bindings), 'ng-binding');
|
2012-07-06 09:53:40 +00:00
|
|
|
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
|
2011-11-29 20:11:32 +00:00
|
|
|
node[0].nodeValue = value;
|
2011-11-02 23:32:46 +00:00
|
|
|
});
|
2011-11-29 20:11:32 +00:00
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
function getTrustedContext(node, attrNormalizedName) {
|
2013-06-22 02:00:45 +00:00
|
|
|
// maction[xlink:href] can source SVG. It's not limited to <maction>.
|
|
|
|
|
if (attrNormalizedName == "xlinkHref" ||
|
|
|
|
|
(nodeName_(node) != "IMG" && (attrNormalizedName == "src" ||
|
|
|
|
|
attrNormalizedName == "ngSrc"))) {
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
return $sce.RESOURCE_URL;
|
2013-06-22 02:00:45 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-03-13 21:42:26 +00:00
|
|
|
function addAttrInterpolateDirective(node, directives, value, name) {
|
2012-03-23 22:53:04 +00:00
|
|
|
var interpolateFn = $interpolate(value, true);
|
2012-03-20 07:00:09 +00:00
|
|
|
|
2012-03-23 22:53:04 +00:00
|
|
|
// no interpolation found -> ignore
|
|
|
|
|
if (!interpolateFn) return;
|
2012-03-20 07:00:09 +00:00
|
|
|
|
2013-02-19 17:55:05 +00:00
|
|
|
|
2013-07-24 23:42:52 +00:00
|
|
|
if (name === "multiple" && nodeName_(node) === "SELECT") {
|
2013-08-01 22:53:59 +00:00
|
|
|
throw $compileMinErr("selmulti", "Binding to the 'multiple' attribute is not supported. Element: {0}",
|
2013-07-24 23:42:52 +00:00
|
|
|
startingTag(node));
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
directives.push({
|
fix($compile): fix (reverse) directive postLink fn execution order
previously the compile/link fns executed in this order controlled via priority:
- CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow
- PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow
- link children
- PostLinkPriorityHigh, PostLinkPriorityMedium, PostLinkPriorityLow
This was changed to:
- CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow
- PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow
- link children
- PostLinkPriorityLow, PostLinkPriorityMedium , PostLinkPriorityHigh
Using this order the child transclusion directive that gets replaced
onto the current element get executed correctly (see issue #3558),
and more generally, the order of execution of post linking function
makes more sense. The incorrect order was an oversight that has
gone unnoticed for many suns and moons.
(FYI: postLink functions are the default linking functions)
BREAKING CHANGE: the order of postLink fn is now mirror opposite of
the order in which corresponding preLinking and compile functions
execute.
Very few directives in practice rely on order of postLinking function
(unlike on the order of compile functions), so in the rare case
of this change affecting an existing directive, it might be necessary
to convert it to a preLinking function or give it negative priority
(look at the diff of this commit to see how an internal attribute
interpolation directive was adjusted).
Closes #3558
2013-10-01 14:55:47 +00:00
|
|
|
priority: -100,
|
2012-10-25 07:56:34 +00:00
|
|
|
compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
|
2012-06-04 22:06:02 +00:00
|
|
|
var $$observers = (attr.$$observers || (attr.$$observers = {}));
|
|
|
|
|
|
2013-06-21 20:03:56 +00:00
|
|
|
if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
|
|
|
|
|
throw $compileMinErr('nodomevents',
|
|
|
|
|
"Interpolations for HTML DOM event attributes are disallowed. Please use the ng- " +
|
|
|
|
|
"versions (such as ng-click instead of onclick) instead.");
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-26 10:14:27 +00:00
|
|
|
// we need to interpolate again, in case the attribute value has been updated
|
|
|
|
|
// (e.g. by another directive's compile function)
|
feat($sce): new $sce service for Strict Contextual Escaping.
$sce is a service that provides Strict Contextual Escaping services to AngularJS.
Strict Contextual Escaping
--------------------------
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires
bindings in certain contexts to result in a value that is marked as safe
to use for that context One example of such a context is binding
arbitrary html controlled by the user via ng-bind-html-unsafe. We
refer to these contexts as privileged or SCE contexts.
As of version 1.2, Angular ships with SCE enabled by default.
Note: When enabled (the default), IE8 in quirks mode is not supported.
In this mode, IE8 allows one to execute arbitrary javascript by the use
of the expression() syntax. Refer
http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx
to learn more about them. You can ensure your document is in standards
mode and not quirks mode by adding <!doctype html> to the top of your
HTML document.
SCE assists in writing code in way that (a) is secure by default and (b)
makes auditing for security vulnerabilities such as XSS, clickjacking,
etc. a lot easier.
Here's an example of a binding in a privileged context:
<input ng-model="userHtml">
<div ng-bind-html-unsafe="{{userHtml}}">
Notice that ng-bind-html-unsafe is bound to {{userHtml}} controlled by
the user. With SCE disabled, this application allows the user to render
arbitrary HTML into the DIV. In a more realistic example, one may be
rendering user comments, blog articles, etc. via bindings. (HTML is
just one example of a context where rendering user controlled input
creates security vulnerabilities.)
For the case of HTML, you might use a library, either on the client side, or on the server side,
to sanitize unsafe HTML before binding to the value and rendering it in the document.
How would you ensure that every place that used these types of bindings was bound to a value that
was sanitized by your library (or returned as safe for rendering by your server?) How can you
ensure that you didn't accidentally delete the line that sanitized the value, or renamed some
properties/fields and forgot to update the binding to the sanitized value?
To be secure by default, you want to ensure that any such bindings are disallowed unless you can
determine that something explicitly says it's safe to use a value for binding in that
context. You can then audit your code (a simple grep would do) to ensure that this is only done
for those values that you can easily tell are safe - because they were received from your server,
sanitized by your library, etc. You can organize your codebase to help with this - perhaps
allowing only the files in a specific directory to do this. Ensuring that the internal API
exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
In the case of AngularJS' SCE service, one uses $sce.trustAs (and
shorthand methods such as $sce.trustAsHtml, etc.) to obtain values that
will be accepted by SCE / privileged contexts.
In privileged contexts, directives and code will bind to the result of
$sce.getTrusted(context, value) rather than to the value directly.
Directives use $sce.parseAs rather than $parse to watch attribute
bindings, which performs the $sce.getTrusted behind the scenes on
non-constant literals.
As an example, ngBindHtmlUnsafe uses $sce.parseAsHtml(binding
expression). Here's the actual code (slightly simplified):
var ngBindHtmlUnsafeDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) {
element.html(value || '');
});
};
}];
Impact on loading templates
---------------------------
This applies both to the ng-include directive as well as templateUrl's
specified by directives.
By default, Angular only loads templates from the same domain and
protocol as the application document. This is done by calling
$sce.getTrustedResourceUrl on the template URL. To load templates from
other domains and/or protocols, you may either either whitelist them or
wrap it into a trusted value.
*Please note*:
The browser's Same Origin Policy and Cross-Origin Resource Sharing
(CORS) policy apply in addition to this and may further restrict whether
the template is successfully loaded. This means that without the right
CORS policy, loading templates from a different domain won't work on all
browsers. Also, loading templates from file:// URL does not work on
some browsers.
This feels like too much overhead for the developer?
----------------------------------------------------
It's important to remember that SCE only applies to interpolation expressions.
If your expressions are constant literals, they're automatically trusted
and you don't need to call $sce.trustAs on them.
e.g. <div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div> just works.
Additionally, a[href] and img[src] automatically sanitize their URLs and
do not pass them through $sce.getTrusted. SCE doesn't play a role here.
The included $sceDelegate comes with sane defaults to allow you to load
templates in ng-include from your application's domain without having to
even know about SCE. It blocks loading templates from other domains or
loading templates over http from an https served document. You can
change these by setting your own custom whitelists and blacklists for
matching such URLs.
This significantly reduces the overhead. It is far easier to pay the
small overhead and have an application that's secure and can be audited
to verify that with much more ease than bolting security onto an
application later.
2013-05-14 21:51:39 +00:00
|
|
|
interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name));
|
2013-02-26 10:14:27 +00:00
|
|
|
|
|
|
|
|
// if attribute was updated so that there is no interpolation going on we don't want to
|
|
|
|
|
// register any observers
|
|
|
|
|
if (!interpolateFn) return;
|
2012-03-23 22:53:04 +00:00
|
|
|
|
fix($compile): fix (reverse) directive postLink fn execution order
previously the compile/link fns executed in this order controlled via priority:
- CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow
- PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow
- link children
- PostLinkPriorityHigh, PostLinkPriorityMedium, PostLinkPriorityLow
This was changed to:
- CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow
- PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow
- link children
- PostLinkPriorityLow, PostLinkPriorityMedium , PostLinkPriorityHigh
Using this order the child transclusion directive that gets replaced
onto the current element get executed correctly (see issue #3558),
and more generally, the order of execution of post linking function
makes more sense. The incorrect order was an oversight that has
gone unnoticed for many suns and moons.
(FYI: postLink functions are the default linking functions)
BREAKING CHANGE: the order of postLink fn is now mirror opposite of
the order in which corresponding preLinking and compile functions
execute.
Very few directives in practice rely on order of postLinking function
(unlike on the order of compile functions), so in the rare case
of this change affecting an existing directive, it might be necessary
to convert it to a preLinking function or give it negative priority
(look at the diff of this commit to see how an internal attribute
interpolation directive was adjusted).
Closes #3558
2013-10-01 14:55:47 +00:00
|
|
|
// TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the actual attr value
|
2012-11-19 10:01:53 +00:00
|
|
|
attr[name] = interpolateFn(scope);
|
2012-06-04 22:06:02 +00:00
|
|
|
($$observers[name] || ($$observers[name] = [])).$$inter = true;
|
2012-06-06 20:58:10 +00:00
|
|
|
(attr.$$observers && attr.$$observers[name].$$scope || scope).
|
2012-07-06 09:53:40 +00:00
|
|
|
$watch(interpolateFn, function interpolateFnWatchAction(value) {
|
2012-06-06 20:58:10 +00:00
|
|
|
attr.$set(name, value);
|
|
|
|
|
});
|
2012-03-23 22:53:04 +00:00
|
|
|
})
|
2011-11-29 20:11:32 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is a special jqLite.replaceWith, which can replace items which
|
|
|
|
|
* have no parents, provided that the containing jqLite collection is provided.
|
|
|
|
|
*
|
2012-05-03 07:15:07 +00:00
|
|
|
* @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
|
2011-11-29 20:11:32 +00:00
|
|
|
* in the root of the tree.
|
2013-05-24 19:41:38 +00:00
|
|
|
* @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep the shell,
|
2011-11-29 20:11:32 +00:00
|
|
|
* but replace its DOM node reference.
|
|
|
|
|
* @param {Node} newNode The new DOM node.
|
|
|
|
|
*/
|
2013-05-24 19:41:38 +00:00
|
|
|
function replaceWith($rootElement, elementsToRemove, newNode) {
|
|
|
|
|
var firstElementToRemove = elementsToRemove[0],
|
|
|
|
|
removeCount = elementsToRemove.length,
|
|
|
|
|
parent = firstElementToRemove.parentNode,
|
2011-11-29 20:11:32 +00:00
|
|
|
i, ii;
|
|
|
|
|
|
2012-05-03 07:15:07 +00:00
|
|
|
if ($rootElement) {
|
|
|
|
|
for(i = 0, ii = $rootElement.length; i < ii; i++) {
|
2013-05-24 19:41:38 +00:00
|
|
|
if ($rootElement[i] == firstElementToRemove) {
|
|
|
|
|
$rootElement[i++] = newNode;
|
|
|
|
|
for (var j = i, j2 = j + removeCount - 1,
|
|
|
|
|
jj = $rootElement.length;
|
|
|
|
|
j < jj; j++, j2++) {
|
|
|
|
|
if (j2 < jj) {
|
|
|
|
|
$rootElement[j] = $rootElement[j2];
|
|
|
|
|
} else {
|
|
|
|
|
delete $rootElement[j];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$rootElement.length -= removeCount - 1;
|
2012-05-04 04:49:06 +00:00
|
|
|
break;
|
2011-11-02 23:32:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-05-04 04:49:06 +00:00
|
|
|
|
2011-11-29 20:11:32 +00:00
|
|
|
if (parent) {
|
2013-05-24 19:41:38 +00:00
|
|
|
parent.replaceChild(newNode, firstElementToRemove);
|
|
|
|
|
}
|
|
|
|
|
var fragment = document.createDocumentFragment();
|
|
|
|
|
fragment.appendChild(firstElementToRemove);
|
|
|
|
|
newNode[jqLite.expando] = firstElementToRemove[jqLite.expando];
|
|
|
|
|
for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
|
|
|
|
|
var element = elementsToRemove[k];
|
|
|
|
|
jqLite(element).remove(); // must do this way to clean up expando
|
|
|
|
|
fragment.appendChild(element);
|
|
|
|
|
delete elementsToRemove[k];
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-05-04 04:49:06 +00:00
|
|
|
|
2013-05-24 19:41:38 +00:00
|
|
|
elementsToRemove[0] = newNode;
|
|
|
|
|
elementsToRemove.length = 1
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
2012-02-15 19:34:56 +00:00
|
|
|
}];
|
2011-11-29 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
|
|
|
|
|
/**
|
|
|
|
|
* Converts all accepted directives format into proper directive name.
|
|
|
|
|
* All of these will become 'myDirective':
|
2013-07-22 15:01:00 +00:00
|
|
|
* my:Directive
|
2011-11-29 20:11:32 +00:00
|
|
|
* 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, ''));
|
2010-03-30 21:55:04 +00:00
|
|
|
}
|
2012-01-28 00:18:16 +00:00
|
|
|
|
2012-03-01 19:28:50 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc object
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compile.directive.Attributes
|
2012-03-01 19:28:50 +00:00
|
|
|
* @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:
|
|
|
|
|
*
|
|
|
|
|
* <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc property
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compile.directive.Attributes#$attr
|
|
|
|
|
* @propertyOf ng.$compile.directive.Attributes
|
2012-03-01 19:28:50 +00:00
|
|
|
* @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
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.$compile.directive.Attributes#$set
|
|
|
|
|
* @methodOf ng.$compile.directive.Attributes
|
2012-03-01 19:28:50 +00:00
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Set DOM element attribute value.
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param {string} name Normalized element attribute name of the property to modify. The name is
|
2012-06-12 06:49:24 +00:00
|
|
|
* revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
|
2012-03-01 19:28:50 +00:00
|
|
|
* property to the original name.
|
2013-02-26 10:14:27 +00:00
|
|
|
* @param {string} value Value to set the attribute to. The value can be an interpolated string.
|
2012-03-01 19:28:50 +00:00
|
|
|
*/
|
|
|
|
|
|
2012-01-28 00:18:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
){}
|