PageRenderTime 52ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/src/ng/compile.js

https://github.com/thanushcst/angular.js
JavaScript | 2105 lines | 1030 code | 202 blank | 873 comment | 282 complexity | 1e27f01b59d52dea425a781b6a4a6e5f MD5 | raw file
Possible License(s): JSON

Large files files are truncated, but you can click here to view the full file

  1. 'use strict';
  2. /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
  3. *
  4. * DOM-related variables:
  5. *
  6. * - "node" - DOM Node
  7. * - "element" - DOM Element or Node
  8. * - "$node" or "$element" - jqLite-wrapped node or element
  9. *
  10. *
  11. * Compiler related stuff:
  12. *
  13. * - "linkFn" - linking fn of a single directive
  14. * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
  15. * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
  16. * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
  17. */
  18. /**
  19. * @ngdoc service
  20. * @name $compile
  21. * @kind function
  22. *
  23. * @description
  24. * Compiles an HTML string or DOM into a template and produces a template function, which
  25. * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
  26. *
  27. * The compilation is a process of walking the DOM tree and matching DOM elements to
  28. * {@link ng.$compileProvider#directive directives}.
  29. *
  30. * <div class="alert alert-warning">
  31. * **Note:** This document is an in-depth reference of all directive options.
  32. * For a gentle introduction to directives with examples of common use cases,
  33. * see the {@link guide/directive directive guide}.
  34. * </div>
  35. *
  36. * ## Comprehensive Directive API
  37. *
  38. * There are many different options for a directive.
  39. *
  40. * The difference resides in the return value of the factory function.
  41. * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
  42. * or just the `postLink` function (all other properties will have the default values).
  43. *
  44. * <div class="alert alert-success">
  45. * **Best Practice:** It's recommended to use the "directive definition object" form.
  46. * </div>
  47. *
  48. * Here's an example directive declared with a Directive Definition Object:
  49. *
  50. * ```js
  51. * var myModule = angular.module(...);
  52. *
  53. * myModule.directive('directiveName', function factory(injectables) {
  54. * var directiveDefinitionObject = {
  55. * priority: 0,
  56. * template: '<div></div>', // or // function(tElement, tAttrs) { ... },
  57. * // or
  58. * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
  59. * transclude: false,
  60. * restrict: 'A',
  61. * scope: false,
  62. * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
  63. * controllerAs: 'stringAlias',
  64. * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
  65. * compile: function compile(tElement, tAttrs, transclude) {
  66. * return {
  67. * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
  68. * post: function postLink(scope, iElement, iAttrs, controller) { ... }
  69. * }
  70. * // or
  71. * // return function postLink( ... ) { ... }
  72. * },
  73. * // or
  74. * // link: {
  75. * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
  76. * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
  77. * // }
  78. * // or
  79. * // link: function postLink( ... ) { ... }
  80. * };
  81. * return directiveDefinitionObject;
  82. * });
  83. * ```
  84. *
  85. * <div class="alert alert-warning">
  86. * **Note:** Any unspecified options will use the default value. You can see the default values below.
  87. * </div>
  88. *
  89. * Therefore the above can be simplified as:
  90. *
  91. * ```js
  92. * var myModule = angular.module(...);
  93. *
  94. * myModule.directive('directiveName', function factory(injectables) {
  95. * var directiveDefinitionObject = {
  96. * link: function postLink(scope, iElement, iAttrs) { ... }
  97. * };
  98. * return directiveDefinitionObject;
  99. * // or
  100. * // return function postLink(scope, iElement, iAttrs) { ... }
  101. * });
  102. * ```
  103. *
  104. *
  105. *
  106. * ### Directive Definition Object
  107. *
  108. * The directive definition object provides instructions to the {@link ng.$compile
  109. * compiler}. The attributes are:
  110. *
  111. * #### `priority`
  112. * When there are multiple directives defined on a single DOM element, sometimes it
  113. * is necessary to specify the order in which the directives are applied. The `priority` is used
  114. * to sort the directives before their `compile` functions get called. Priority is defined as a
  115. * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
  116. * are also run in priority order, but post-link functions are run in reverse order. The order
  117. * of directives with the same priority is undefined. The default priority is `0`.
  118. *
  119. * #### `terminal`
  120. * If set to true then the current `priority` will be the last set of directives
  121. * which will execute (any directives at the current priority will still execute
  122. * as the order of execution on same `priority` is undefined).
  123. *
  124. * #### `scope`
  125. * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the
  126. * same element request a new scope, only one new scope is created. The new scope rule does not
  127. * apply for the root of the template since the root of the template always gets a new scope.
  128. *
  129. * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from
  130. * normal scope in that it does not prototypically inherit from the parent scope. This is useful
  131. * when creating reusable components, which should not accidentally read or modify data in the
  132. * parent scope.
  133. *
  134. * The 'isolate' scope takes an object hash which defines a set of local scope properties
  135. * derived from the parent scope. These local properties are useful for aliasing values for
  136. * templates. Locals definition is a hash of local scope property to its source:
  137. *
  138. * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
  139. * always a string since DOM attributes are strings. If no `attr` name is specified then the
  140. * attribute name is assumed to be the same as the local name.
  141. * Given `<widget my-attr="hello {{name}}">` and widget definition
  142. * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
  143. * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
  144. * `localName` property on the widget scope. The `name` is read from the parent scope (not
  145. * component scope).
  146. *
  147. * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
  148. * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
  149. * name is specified then the attribute name is assumed to be the same as the local name.
  150. * Given `<widget my-attr="parentModel">` and widget definition of
  151. * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
  152. * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
  153. * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
  154. * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
  155. * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional.
  156. *
  157. * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
  158. * If no `attr` name is specified then the attribute name is assumed to be the same as the
  159. * local name. Given `<widget my-attr="count = count + value">` and widget definition of
  160. * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
  161. * a function wrapper for the `count = count + value` expression. Often it's desirable to
  162. * pass data from the isolated scope via an expression and to the parent scope, this can be
  163. * done by passing a map of local variable names and values into the expression wrapper fn.
  164. * For example, if the expression is `increment(amount)` then we can specify the amount value
  165. * by calling the `localFn` as `localFn({amount: 22})`.
  166. *
  167. *
  168. *
  169. * #### `controller`
  170. * Controller constructor function. The controller is instantiated before the
  171. * pre-linking phase and it is shared with other directives (see
  172. * `require` attribute). This allows the directives to communicate with each other and augment
  173. * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
  174. *
  175. * * `$scope` - Current scope associated with the element
  176. * * `$element` - Current element
  177. * * `$attrs` - Current attributes object for the element
  178. * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope.
  179. * The scope can be overridden by an optional first argument.
  180. * `function([scope], cloneLinkingFn)`.
  181. *
  182. *
  183. * #### `require`
  184. * Require another directive and inject its controller as the fourth argument to the linking function. The
  185. * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
  186. * injected argument will be an array in corresponding order. If no such directive can be
  187. * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with:
  188. *
  189. * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
  190. * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
  191. * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found.
  192. * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the
  193. * `link` fn if not found.
  194. *
  195. *
  196. * #### `controllerAs`
  197. * Controller alias at the directive scope. An alias for the controller so it
  198. * can be referenced at the directive template. The directive needs to define a scope for this
  199. * configuration to be used. Useful in the case when directive is used as component.
  200. *
  201. *
  202. * #### `restrict`
  203. * String of subset of `EACM` which restricts the directive to a specific directive
  204. * declaration style. If omitted, the default (attributes only) is used.
  205. *
  206. * * `E` - Element name: `<my-directive></my-directive>`
  207. * * `A` - Attribute (default): `<div my-directive="exp"></div>`
  208. * * `C` - Class: `<div class="my-directive: exp;"></div>`
  209. * * `M` - Comment: `<!-- directive: my-directive exp -->`
  210. *
  211. *
  212. * #### `type`
  213. * String representing the document type used by the markup. This is useful for templates where the root
  214. * node is non-HTML content (such as SVG or MathML). The default value is "html".
  215. *
  216. * * `html` - All root template nodes are HTML, and don't need to be wrapped. Root nodes may also be
  217. * top-level elements such as `<svg>` or `<math>`.
  218. * * `svg` - The template contains only SVG content, and must be wrapped in an `<svg>` node prior to
  219. * processing.
  220. * * `math` - The template contains only MathML content, and must be wrapped in an `<math>` node prior to
  221. * processing.
  222. *
  223. * If no `type` is specified, then the type is considered to be html.
  224. *
  225. * #### `template`
  226. * replace the current element with the contents of the HTML. The replacement process
  227. * migrates all of the attributes / classes from the old element to the new one. See the
  228. * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive
  229. * Directives Guide} for an example.
  230. *
  231. * You can specify `template` as a string representing the template or as a function which takes
  232. * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and
  233. * returns a string value representing the template.
  234. *
  235. *
  236. * #### `templateUrl`
  237. * Same as `template` but the template is loaded from the specified URL. Because
  238. * the template loading is asynchronous the compilation/linking is suspended until the template
  239. * is loaded.
  240. *
  241. * You can specify `templateUrl` as a string representing the URL or as a function which takes two
  242. * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
  243. * a string value representing the url. In either case, the template URL is passed through {@link
  244. * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
  245. *
  246. *
  247. * #### `replace` ([*DEPRECATED*!], will be removed in next major release)
  248. * specify where the template should be inserted. Defaults to `false`.
  249. *
  250. * * `true` - the template will replace the current element.
  251. * * `false` - the template will replace the contents of the current element.
  252. *
  253. *
  254. * #### `transclude`
  255. * compile the content of the element and make it available to the directive.
  256. * Typically used with {@link ng.directive:ngTransclude
  257. * ngTransclude}. The advantage of transclusion is that the linking function receives a
  258. * transclusion function which is pre-bound to the correct scope. In a typical setup the widget
  259. * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate`
  260. * scope. This makes it possible for the widget to have private state, and the transclusion to
  261. * be bound to the parent (pre-`isolate`) scope.
  262. *
  263. * * `true` - transclude the content of the directive.
  264. * * `'element'` - transclude the whole element including any directives defined at lower priority.
  265. *
  266. *
  267. * #### `compile`
  268. *
  269. * ```js
  270. * function compile(tElement, tAttrs, transclude) { ... }
  271. * ```
  272. *
  273. * The compile function deals with transforming the template DOM. Since most directives do not do
  274. * template transformation, it is not used often. The compile function takes the following arguments:
  275. *
  276. * * `tElement` - template element - The element where the directive has been declared. It is
  277. * safe to do template transformation on the element and child elements only.
  278. *
  279. * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
  280. * between all directive compile functions.
  281. *
  282. * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
  283. *
  284. * <div class="alert alert-warning">
  285. * **Note:** The template instance and the link instance may be different objects if the template has
  286. * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
  287. * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
  288. * should be done in a linking function rather than in a compile function.
  289. * </div>
  290. * <div class="alert alert-warning">
  291. * **Note:** The compile function cannot handle directives that recursively use themselves in their
  292. * own templates or compile functions. Compiling these directives results in an infinite loop and a
  293. * stack overflow errors.
  294. *
  295. * This can be avoided by manually using $compile in the postLink function to imperatively compile
  296. * a directive's template instead of relying on automatic template compilation via `template` or
  297. * `templateUrl` declaration or manual compilation inside the compile function.
  298. * </div>
  299. *
  300. * <div class="alert alert-error">
  301. * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
  302. * e.g. does not know about the right outer scope. Please use the transclude function that is passed
  303. * to the link function instead.
  304. * </div>
  305. * A compile function can have a return value which can be either a function or an object.
  306. *
  307. * * returning a (post-link) function - is equivalent to registering the linking function via the
  308. * `link` property of the config object when the compile function is empty.
  309. *
  310. * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
  311. * control when a linking function should be called during the linking phase. See info about
  312. * pre-linking and post-linking functions below.
  313. *
  314. *
  315. * #### `link`
  316. * This property is used only if the `compile` property is not defined.
  317. *
  318. * ```js
  319. * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
  320. * ```
  321. *
  322. * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
  323. * executed after the template has been cloned. This is where most of the directive logic will be
  324. * put.
  325. *
  326. * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the
  327. * directive for registering {@link ng.$rootScope.Scope#$watch watches}.
  328. *
  329. * * `iElement` - instance element - The element where the directive is to be used. It is safe to
  330. * manipulate the children of the element only in `postLink` function since the children have
  331. * already been linked.
  332. *
  333. * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
  334. * between all directive linking functions.
  335. *
  336. * * `controller` - a controller instance - A controller instance if at least one directive on the
  337. * element defines a controller. The controller is shared among all the directives, which allows
  338. * the directives to use the controllers as a communication channel.
  339. *
  340. * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
  341. * The scope can be overridden by an optional first argument. This is the same as the `$transclude`
  342. * parameter of directive controllers.
  343. * `function([scope], cloneLinkingFn)`.
  344. *
  345. *
  346. * #### Pre-linking function
  347. *
  348. * Executed before the child elements are linked. Not safe to do DOM transformation since the
  349. * compiler linking function will fail to locate the correct elements for linking.
  350. *
  351. * #### Post-linking function
  352. *
  353. * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function.
  354. *
  355. * <a name="Attributes"></a>
  356. * ### Attributes
  357. *
  358. * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
  359. * `link()` or `compile()` functions. It has a variety of uses.
  360. *
  361. * accessing *Normalized attribute names:*
  362. * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
  363. * the attributes object allows for normalized access to
  364. * the attributes.
  365. *
  366. * * *Directive inter-communication:* All directives share the same instance of the attributes
  367. * object which allows the directives to use the attributes object as inter directive
  368. * communication.
  369. *
  370. * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
  371. * allowing other directives to read the interpolated value.
  372. *
  373. * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
  374. * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
  375. * the only way to easily get the actual value because during the linking phase the interpolation
  376. * hasn't been evaluated yet and so the value is at this time set to `undefined`.
  377. *
  378. * ```js
  379. * function linkingFn(scope, elm, attrs, ctrl) {
  380. * // get the attribute value
  381. * console.log(attrs.ngModel);
  382. *
  383. * // change the attribute
  384. * attrs.$set('ngModel', 'new value');
  385. *
  386. * // observe changes to interpolated attribute
  387. * attrs.$observe('ngModel', function(value) {
  388. * console.log('ngModel has changed value to ' + value);
  389. * });
  390. * }
  391. * ```
  392. *
  393. * Below is an example using `$compileProvider`.
  394. *
  395. * <div class="alert alert-warning">
  396. * **Note**: Typically directives are registered with `module.directive`. The example below is
  397. * to illustrate how `$compile` works.
  398. * </div>
  399. *
  400. <example module="compile">
  401. <file name="index.html">
  402. <script>
  403. angular.module('compile', [], function($compileProvider) {
  404. // configure new 'compile' directive by passing a directive
  405. // factory function. The factory function injects the '$compile'
  406. $compileProvider.directive('compile', function($compile) {
  407. // directive factory creates a link function
  408. return function(scope, element, attrs) {
  409. scope.$watch(
  410. function(scope) {
  411. // watch the 'compile' expression for changes
  412. return scope.$eval(attrs.compile);
  413. },
  414. function(value) {
  415. // when the 'compile' expression changes
  416. // assign it into the current DOM
  417. element.html(value);
  418. // compile the new DOM and link it to the current
  419. // scope.
  420. // NOTE: we only compile .childNodes so that
  421. // we don't get into infinite loop compiling ourselves
  422. $compile(element.contents())(scope);
  423. }
  424. );
  425. };
  426. })
  427. });
  428. function Ctrl($scope) {
  429. $scope.name = 'Angular';
  430. $scope.html = 'Hello {{name}}';
  431. }
  432. </script>
  433. <div ng-controller="Ctrl">
  434. <input ng-model="name"> <br>
  435. <textarea ng-model="html"></textarea> <br>
  436. <div compile="html"></div>
  437. </div>
  438. </file>
  439. <file name="protractor.js" type="protractor">
  440. it('should auto compile', function() {
  441. var textarea = $('textarea');
  442. var output = $('div[compile]');
  443. // The initial state reads 'Hello Angular'.
  444. expect(output.getText()).toBe('Hello Angular');
  445. textarea.clear();
  446. textarea.sendKeys('{{name}}!');
  447. expect(output.getText()).toBe('Angular!');
  448. });
  449. </file>
  450. </example>
  451. *
  452. *
  453. * @param {string|DOMElement} element Element or HTML string to compile into a template function.
  454. * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives.
  455. * @param {number} maxPriority only apply directives lower than given priority (Only effects the
  456. * root element(s), not their children)
  457. * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template
  458. * (a DOM element/tree) to a scope. Where:
  459. *
  460. * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
  461. * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
  462. * `template` and call the `cloneAttachFn` function allowing the caller to attach the
  463. * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
  464. * called as: <br> `cloneAttachFn(clonedElement, scope)` where:
  465. *
  466. * * `clonedElement` - is a clone of the original `element` passed into the compiler.
  467. * * `scope` - is the current scope with which the linking function is working with.
  468. *
  469. * Calling the linking function returns the element of the template. It is either the original
  470. * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
  471. *
  472. * After linking the view is not updated until after a call to $digest which typically is done by
  473. * Angular automatically.
  474. *
  475. * If you need access to the bound view, there are two ways to do it:
  476. *
  477. * - If you are not asking the linking function to clone the template, create the DOM element(s)
  478. * before you send them to the compiler and keep this reference around.
  479. * ```js
  480. * var element = $compile('<p>{{total}}</p>')(scope);
  481. * ```
  482. *
  483. * - if on the other hand, you need the element to be cloned, the view reference from the original
  484. * example would not point to the clone, but rather to the original template that was cloned. In
  485. * this case, you can access the clone via the cloneAttachFn:
  486. * ```js
  487. * var templateElement = angular.element('<p>{{total}}</p>'),
  488. * scope = ....;
  489. *
  490. * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
  491. * //attach the clone to DOM document at the right place
  492. * });
  493. *
  494. * //now we have reference to the cloned DOM via `clonedElement`
  495. * ```
  496. *
  497. *
  498. * For information on how the compiler works, see the
  499. * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
  500. */
  501. var $compileMinErr = minErr('$compile');
  502. /**
  503. * @ngdoc provider
  504. * @name $compileProvider
  505. * @kind function
  506. *
  507. * @description
  508. */
  509. $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
  510. function $CompileProvider($provide, $$sanitizeUriProvider) {
  511. var hasDirectives = {},
  512. Suffix = 'Directive',
  513. COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
  514. CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
  515. ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset');
  516. // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
  517. // The assumption is that future DOM event attribute names will begin with
  518. // 'on' and be composed of only English letters.
  519. var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
  520. /**
  521. * @ngdoc method
  522. * @name $compileProvider#directive
  523. * @kind function
  524. *
  525. * @description
  526. * Register a new directive with the compiler.
  527. *
  528. * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
  529. * will match as <code>ng-bind</code>), or an object map of directives where the keys are the
  530. * names and the values are the factories.
  531. * @param {Function|Array} directiveFactory An injectable directive factory function. See
  532. * {@link guide/directive} for more info.
  533. * @returns {ng.$compileProvider} Self for chaining.
  534. */
  535. this.directive = function registerDirective(name, directiveFactory) {
  536. assertNotHasOwnProperty(name, 'directive');
  537. if (isString(name)) {
  538. assertArg(directiveFactory, 'directiveFactory');
  539. if (!hasDirectives.hasOwnProperty(name)) {
  540. hasDirectives[name] = [];
  541. $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
  542. function($injector, $exceptionHandler) {
  543. var directives = [];
  544. forEach(hasDirectives[name], function(directiveFactory, index) {
  545. try {
  546. var directive = $injector.invoke(directiveFactory);
  547. if (isFunction(directive)) {
  548. directive = { compile: valueFn(directive) };
  549. } else if (!directive.compile && directive.link) {
  550. directive.compile = valueFn(directive.link);
  551. }
  552. directive.priority = directive.priority || 0;
  553. directive.index = index;
  554. directive.name = directive.name || name;
  555. directive.require = directive.require || (directive.controller && directive.name);
  556. directive.restrict = directive.restrict || 'A';
  557. directives.push(directive);
  558. } catch (e) {
  559. $exceptionHandler(e);
  560. }
  561. });
  562. return directives;
  563. }]);
  564. }
  565. hasDirectives[name].push(directiveFactory);
  566. } else {
  567. forEach(name, reverseParams(registerDirective));
  568. }
  569. return this;
  570. };
  571. /**
  572. * @ngdoc method
  573. * @name $compileProvider#aHrefSanitizationWhitelist
  574. * @kind function
  575. *
  576. * @description
  577. * Retrieves or overrides the default regular expression that is used for whitelisting of safe
  578. * urls during a[href] sanitization.
  579. *
  580. * The sanitization is a security measure aimed at prevent XSS attacks via html links.
  581. *
  582. * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
  583. * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
  584. * regular expression. If a match is found, the original url is written into the dom. Otherwise,
  585. * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
  586. *
  587. * @param {RegExp=} regexp New regexp to whitelist urls with.
  588. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
  589. * chaining otherwise.
  590. */
  591. this.aHrefSanitizationWhitelist = function(regexp) {
  592. if (isDefined(regexp)) {
  593. $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
  594. return this;
  595. } else {
  596. return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
  597. }
  598. };
  599. /**
  600. * @ngdoc method
  601. * @name $compileProvider#imgSrcSanitizationWhitelist
  602. * @kind function
  603. *
  604. * @description
  605. * Retrieves or overrides the default regular expression that is used for whitelisting of safe
  606. * urls during img[src] sanitization.
  607. *
  608. * The sanitization is a security measure aimed at prevent XSS attacks via html links.
  609. *
  610. * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
  611. * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
  612. * regular expression. If a match is found, the original url is written into the dom. Otherwise,
  613. * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
  614. *
  615. * @param {RegExp=} regexp New regexp to whitelist urls with.
  616. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
  617. * chaining otherwise.
  618. */
  619. this.imgSrcSanitizationWhitelist = function(regexp) {
  620. if (isDefined(regexp)) {
  621. $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
  622. return this;
  623. } else {
  624. return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
  625. }
  626. };
  627. this.$get = [
  628. '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
  629. '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
  630. function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
  631. $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
  632. var Attributes = function(element, attr) {
  633. this.$$element = element;
  634. this.$attr = attr || {};
  635. };
  636. Attributes.prototype = {
  637. $normalize: directiveNormalize,
  638. /**
  639. * @ngdoc method
  640. * @name $compile.directive.Attributes#$addClass
  641. * @kind function
  642. *
  643. * @description
  644. * Adds the CSS class value specified by the classVal parameter to the element. If animations
  645. * are enabled then an animation will be triggered for the class addition.
  646. *
  647. * @param {string} classVal The className value that will be added to the element
  648. */
  649. $addClass : function(classVal) {
  650. if(classVal && classVal.length > 0) {
  651. $animate.addClass(this.$$element, classVal);
  652. }
  653. },
  654. /**
  655. * @ngdoc method
  656. * @name $compile.directive.Attributes#$removeClass
  657. * @kind function
  658. *
  659. * @description
  660. * Removes the CSS class value specified by the classVal parameter from the element. If
  661. * animations are enabled then an animation will be triggered for the class removal.
  662. *
  663. * @param {string} classVal The className value that will be removed from the element
  664. */
  665. $removeClass : function(classVal) {
  666. if(classVal && classVal.length > 0) {
  667. $animate.removeClass(this.$$element, classVal);
  668. }
  669. },
  670. /**
  671. * @ngdoc method
  672. * @name $compile.directive.Attributes#$updateClass
  673. * @kind function
  674. *
  675. * @description
  676. * Adds and removes the appropriate CSS class values to the element based on the difference
  677. * between the new and old CSS class values (specified as newClasses and oldClasses).
  678. *
  679. * @param {string} newClasses The current CSS className value
  680. * @param {string} oldClasses The former CSS className value
  681. */
  682. $updateClass : function(newClasses, oldClasses) {
  683. var toAdd = tokenDifference(newClasses, oldClasses);
  684. var toRemove = tokenDifference(oldClasses, newClasses);
  685. if(toAdd.length === 0) {
  686. $animate.removeClass(this.$$element, toRemove);
  687. } else if(toRemove.length === 0) {
  688. $animate.addClass(this.$$element, toAdd);
  689. } else {
  690. $animate.setClass(this.$$element, toAdd, toRemove);
  691. }
  692. },
  693. /**
  694. * Set a normalized attribute on the element in a way such that all directives
  695. * can share the attribute. This function properly handles boolean attributes.
  696. * @param {string} key Normalized key. (ie ngAttribute)
  697. * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
  698. * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
  699. * Defaults to true.
  700. * @param {string=} attrName Optional none normalized name. Defaults to key.
  701. */
  702. $set: function(key, value, writeAttr, attrName) {
  703. // TODO: decide whether or not to throw an error if "class"
  704. //is set through this function since it may cause $updateClass to
  705. //become unstable.
  706. var node = this.$$element[0],
  707. booleanKey = getBooleanAttrName(node, key),
  708. aliasedKey = getAliasedAttrName(node, key),
  709. observer = key,
  710. normalizedVal,
  711. nodeName;
  712. if (booleanKey) {
  713. this.$$element.prop(key, value);
  714. attrName = booleanKey;
  715. } else if(aliasedKey) {
  716. this[aliasedKey] = value;
  717. observer = aliasedKey;
  718. }
  719. this[key] = value;
  720. // translate normalized key to actual key
  721. if (attrName) {
  722. this.$attr[key] = attrName;
  723. } else {
  724. attrName = this.$attr[key];
  725. if (!attrName) {
  726. this.$attr[key] = attrName = snake_case(key, '-');
  727. }
  728. }
  729. nodeName = nodeName_(this.$$element);
  730. // sanitize a[href] and img[src] values
  731. if ((nodeName === 'A' && key === 'href') ||
  732. (nodeName === 'IMG' && key === 'src')) {
  733. this[key] = value = $$sanitizeUri(value, key === 'src');
  734. }
  735. if (writeAttr !== false) {
  736. if (value === null || value === undefined) {
  737. this.$$element.removeAttr(attrName);
  738. } else {
  739. this.$$element.attr(attrName, value);
  740. }
  741. }
  742. // fire observers
  743. var $$observers = this.$$observers;
  744. $$observers && forEach($$observers[observer], function(fn) {
  745. try {
  746. fn(value);
  747. } catch (e) {
  748. $exceptionHandler(e);
  749. }
  750. });
  751. },
  752. /**
  753. * @ngdoc method
  754. * @name $compile.directive.Attributes#$observe
  755. * @kind function
  756. *
  757. * @description
  758. * Observes an interpolated attribute.
  759. *
  760. * The observer function will be invoked once during the next `$digest` following
  761. * compilation. The observer is then invoked whenever the interpolated value
  762. * changes.
  763. *
  764. * @param {string} key Normalized key. (ie ngAttribute) .
  765. * @param {function(interpolatedValue)} fn Function that will be called whenever
  766. the interpolated value of the attribute changes.
  767. * See the {@link guide/directive#Attributes Directives} guide for more info.
  768. * @returns {function()} Returns a deregistration function for this observer.
  769. */
  770. $observe: function(key, fn) {
  771. var attrs = this,
  772. $$observers = (attrs.$$observers || (attrs.$$observers = {})),
  773. listeners = ($$observers[key] || ($$observers[key] = []));
  774. listeners.push(fn);
  775. $rootScope.$evalAsync(function() {
  776. if (!listeners.$$inter) {
  777. // no one registered attribute interpolation function, so lets call it manually
  778. fn(attrs[key]);
  779. }
  780. });
  781. return function() {
  782. arrayRemove(listeners, fn);
  783. };
  784. }
  785. };
  786. var startSymbol = $interpolate.startSymbol(),
  787. endSymbol = $interpolate.endSymbol(),
  788. denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
  789. ? identity
  790. : function denormalizeTemplate(template) {
  791. return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
  792. },
  793. NG_ATTR_BINDING = /^ngAttr[A-Z]/;
  794. return compile;
  795. //================================
  796. function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
  797. previousCompileContext) {
  798. if (!($compileNodes instanceof jqLite)) {
  799. // jquery always rewraps, whereas we need to preserve the original selector so that we can
  800. // modify it.
  801. $compileNodes = jqLite($compileNodes);
  802. }
  803. // We can not compile top level text elements since text nodes can be merged and we will
  804. // not be able to attach scope data to them, so we will wrap them in <span>
  805. forEach($compileNodes, function(node, index){
  806. if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
  807. $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0];
  808. }
  809. });
  810. var compositeLinkFn =
  811. compileNodes($compileNodes, transcludeFn, $compileNodes,
  812. maxPriority, ignoreDirective, previousCompileContext);
  813. safeAddClass($compileNodes, 'ng-scope');
  814. return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){
  815. assertArg(scope, 'scope');
  816. // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
  817. // and sometimes changes the structure of the DOM.
  818. var $linkNode = cloneConnectFn
  819. ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
  820. : $compileNodes;
  821. forEach(transcludeControllers, function(instance, name) {
  822. $linkNode.data('$' + name + 'Controller', instance);
  823. });
  824. // Attach scope only to non-text nodes.
  825. for(var i = 0, ii = $linkNode.length; i<ii; i++) {
  826. var node = $linkNode[i],
  827. nodeType = node.nodeType;
  828. if (nodeType === 1 /* element */ || nodeType === 9 /* document */) {
  829. $linkNode.eq(i).data('$scope', scope);
  830. }
  831. }
  832. if (cloneConnectFn) cloneConnectFn($linkNode, scope);
  833. if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
  834. return $linkNode;
  835. };
  836. }
  837. function safeAddClass($element, className) {
  838. try {
  839. $element.addClass(className);
  840. } catch(e) {
  841. // ignore, since it means that we are trying to set class on
  842. // SVG element, where class name is read-only.
  843. }
  844. }
  845. /**
  846. * Compile function matches each node in nodeList against the directives. Once all directives
  847. * for a particular node are collected their compile functions are executed. The compile
  848. * functions return values - the linking functions - are combined into a composite linking
  849. * function, which is the a linking function for the node.
  850. *
  851. * @param {NodeList} nodeList an array of nodes or NodeList to compile
  852. * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
  853. * scope argument is auto-generated to the new child of the transcluded parent scope.
  854. * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
  855. * the rootElement must be set the jqLite collection of the compile root. This is
  856. * needed so that the jqLite collection items can be replaced with widgets.
  857. * @param {number=} maxPriority Max directive priority.
  858. * @returns {Function} A composite linking function of all of the matched directives or null.
  859. */
  860. function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
  861. previousCompileContext) {
  862. var linkFns = [],
  863. attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound;
  864. for (var i = 0; i < nodeList.length; i++) {
  865. attrs = new Attributes();
  866. // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
  867. directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
  868. ignoreDirective);
  869. nodeLinkFn = (directives.length)
  870. ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
  871. null, [], [], previousCompileContext)
  872. : null;
  873. if (nodeLinkFn && nodeLinkFn.scope) {
  874. safeAddClass(jqLite(nodeList[i]), 'ng-scope');
  875. }
  876. childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
  877. !(childNodes = nodeList[i].childNodes) ||
  878. !childNodes.length)
  879. ? null
  880. : compileNodes(childNodes,
  881. nodeLinkFn ? (
  882. (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
  883. && nodeLinkFn.transclude) : transcludeFn);
  884. linkFns.push(nodeLinkFn, childLinkFn);
  885. linkFnFound = linkFnFound || nodeLinkFn || childLinkFn;
  886. //use the previous context only for the first element in the virtual group
  887. previousCompileContext = null;
  888. }
  889. // return a linking function if we have found anything, null otherwise
  890. return linkFnFound ? compositeLinkFn : null;
  891. function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
  892. var nodeLinkFn, childLinkFn, node, $node, childScope, i, ii, n, childBoundTranscludeFn;
  893. // copy nodeList so that linking doesn't break due to live list updates.
  894. var nodeListLength = nodeList.length,
  895. stableNodeList = new Array(nodeListLength);
  896. for (i = 0; i < nodeListLength; i++) {
  897. stableNodeList[i] = nodeList[i];
  898. }
  899. for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
  900. node = stableNodeList[n];
  901. nodeLinkFn = linkFns[i++];
  902. childLinkFn = linkFns[i++];
  903. $node = jqLite(node);
  904. if (nodeLinkFn) {
  905. if (nodeLinkFn.scope) {
  906. childScope = scope.$new();
  907. $node.data('$scope', childScope);
  908. } else {
  909. childScope = scope;
  910. }
  911. if ( nodeLinkFn.transcludeOnThisElement ) {
  912. childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
  913. } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
  914. childBoundTranscludeFn = parentBoundTranscludeFn;
  915. } else if (!parentBoundTranscludeFn && transcludeFn) {
  916. childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
  917. } else {
  918. childBoundTranscludeFn = null;
  919. }
  920. nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
  921. } else if (childLinkFn) {
  922. childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
  923. }
  924. }
  925. }
  926. }
  927. function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
  928. var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) {
  929. var scopeCreated = false;
  930. if (!transcludedScope) {
  931. transcludedScope = scope.$new();
  932. transcludedScope.$$transcluded = true;
  933. scopeCreated = true;
  934. }
  935. var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn);
  936. if (scopeCreated) {
  937. clone.on('$destroy', function() { transcludedScope.$destroy(); });
  938. }
  939. return clone;
  940. };
  941. return boundTranscludeFn;
  942. }
  943. /**
  944. * Looks for directives on the given node and adds them to the directive collection which is
  945. * sorted.
  946. *
  947. * @param node Node to search.
  948. * @param directives An array to which the directives are added to. This array is sorted before
  949. * the function returns.
  950. * @param attrs The shared attrs object which is used to populate the normalized attributes.
  951. * @param {number=} maxPriority Max directive priority.
  952. */
  953. function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
  954. var nodeType = node.nodeType,
  955. attrsMap = attrs.$attr,
  956. match,
  957. className;
  958. switch(nodeType) {
  959. case 1: /* Element */
  960. // use the node name: <directive>
  961. addDirective(directives,
  962. directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
  963. // iterate over the attributes
  964. for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
  965. j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
  966. var attrStartName = false;
  967. var attrEndName = false;
  968. attr = nAttrs[j];
  969. if (!msie || msie >= 8 || attr.specified) {
  970. name = attr.name;
  971. // support ngAttr attribute binding
  972. ngAttrName = directiveNormalize(name);
  973. if (NG_ATTR_BINDING.test(ngAttrName)) {
  974. name = snake_case(ngAttrName.substr(6), '-');
  975. }
  976. var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
  977. if (ngAttrName === directiveNName + 'Start') {
  978. attrStartName = name;
  979. attrEndName = name.substr(0, name.length - 5) + 'end';
  980. name = name.substr(0, name.length - 6);
  981. }
  982. nName = directiveNormalize(name.toLowerCase());
  983. attrsMap[nName] = name;
  984. attrs[nName] = value = trim(attr.value);
  985. if (getBooleanAttrName(node, nName)) {
  986. attrs[nName] = true; // presence means true
  987. }
  988. addAttrInterpolateDirective(node, directives, value, nName);
  989. addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
  990. attrEndName);
  991. }
  992. }
  993. // use class as directive
  994. className = node.className;
  995. if (isString(className) && className !== '') {
  996. while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
  997. nName = directiveNormalize(match[2]);
  998. if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
  999. attrs[nName] = trim(match[3]);
  1000. }
  1001. className = className.substr(match.index + match[0].length);
  1002. }
  1003. }
  1004. break;
  1005. case 3: /* Text Node */
  1006. addTextInterpolateDirective(directives, node.nodeValue);
  1007. break;
  1008. case 8: /* Comment */
  1009. try {
  1010. match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
  1011. if (match) {
  1012. nName = directiveNormalize(match[1]);
  1013. if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
  1014. attrs[nName] = trim(match[2]);
  1015. }
  1016. }
  1017. } catch (e) {
  1018. // turns out that under some circumstances IE9 throws errors when one attempts to read
  1019. // comment's node value.
  1020. // Just ignore it and continue. (Can't seem to reproduce in test case.)
  1021. }
  1022. break;
  1023. }
  1024. directives.sort(byPriority);
  1025. return directives;
  1026. }
  1027. /**
  1028. * Given a node with an directive-start it collects all of the siblings until it finds
  1029. * directive-end.
  1030. * @param node
  1031. * @param attrStart
  1032. * @param attrEnd
  1033. * @returns {*}
  1034. */
  1035. function groupScan(node, attrStart, attrEnd) {
  1036. var nodes = [];
  1037. var depth = 0;
  1038. if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
  1039. var startNode = node;
  1040. do {
  1041. if (!node) {
  1042. throw $compileMinErr('uterdir',
  1043. "Unterminated attribute, found '{0}' but no matching '{1}' found.",
  1044. attrStart, attrEnd);
  1045. }
  1046. if (node.nodeType == 1 /** Element **/) {
  1047. if (node.hasAttribute(attrStart)) depth++;
  1048. if (node.hasAttribute(attrEnd)) depth--;
  1049. }
  1050. nodes.push(node);
  1051. node = node.nextSibling;
  1052. } while (depth > 0);
  1053. } else {
  1054. nodes.push(node);
  1055. }
  1056. return jqLite(nodes);
  1057. }
  1058. /**
  1059. * Wrapper for linking function which converts normal linking function into a grouped
  1060. * linking function.
  1061. * @param linkFn
  1062. * @param attrStart
  1063. * @param attrEnd
  1064. * @returns {Function}
  1065. */
  1066. function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
  1067. return function(scope, element, attrs, controllers, transcludeFn) {
  1068. element = groupScan(element[0], attrStart, attrEnd);
  1069. return linkFn(scope, element, attrs, controllers, transcludeFn);
  1070. };
  1071. }
  1072. /**
  1073. * Once the directives have been collected, their compile functions are executed. This method
  1074. * is responsible for inlining directive templates as well as terminating the application
  1075. * of the directives if the terminal directive has been reached.
  1076. *
  1077. * @param {Array} directives Array of collected directives to execute their compile function.
  1078. * this needs to be pre-sorted by priority order.
  1079. * @param {Node} compileNode The raw DOM node to apply the compile functions to
  1080. * @param {Object} templateAttrs The shared attribute function
  1081. * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the
  1082. * scope argument is auto-generated to the new
  1083. * child of the transcluded parent scope.
  1084. * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
  1085. * argument has the root jqLite array so that we can replace nodes
  1086. * on it.
  1087. * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
  1088. * …

Large files files are truncated, but you can click here to view the full file