PageRenderTime 60ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/ng/compile.js

https://github.com/a-shardar-iit/angular.js
JavaScript | 1144 lines | 671 code | 137 blank | 336 comment | 160 complexity | d6f8574c569ffe25050ab125a9202b8c MD5 | raw file
Possible License(s): JSON, Apache-2.0
  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. var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
  19. /**
  20. * @ngdoc function
  21. * @name ng.$compile
  22. * @function
  23. *
  24. * @description
  25. * Compiles a piece of HTML string or DOM into a template and produces a template function, which
  26. * can then be used to link {@link ng.$rootScope.Scope scope} and the template together.
  27. *
  28. * The compilation is a process of walking the DOM tree and trying to match DOM elements to
  29. * {@link ng.$compileProvider#directive directives}. For each match it
  30. * executes corresponding template function and collects the
  31. * instance functions into a single template function which is then returned.
  32. *
  33. * The template function can then be used once to produce the view or as it is the case with
  34. * {@link ng.directive:ngRepeat repeater} many-times, in which
  35. * case each call results in a view that is a DOM clone of the original template.
  36. *
  37. <doc:example module="compile">
  38. <doc:source>
  39. <script>
  40. // declare a new module, and inject the $compileProvider
  41. angular.module('compile', [], function($compileProvider) {
  42. // configure new 'compile' directive by passing a directive
  43. // factory function. The factory function injects the '$compile'
  44. $compileProvider.directive('compile', function($compile) {
  45. // directive factory creates a link function
  46. return function(scope, element, attrs) {
  47. scope.$watch(
  48. function(scope) {
  49. // watch the 'compile' expression for changes
  50. return scope.$eval(attrs.compile);
  51. },
  52. function(value) {
  53. // when the 'compile' expression changes
  54. // assign it into the current DOM
  55. element.html(value);
  56. // compile the new DOM and link it to the current
  57. // scope.
  58. // NOTE: we only compile .childNodes so that
  59. // we don't get into infinite loop compiling ourselves
  60. $compile(element.contents())(scope);
  61. }
  62. );
  63. };
  64. })
  65. });
  66. function Ctrl($scope) {
  67. $scope.name = 'Angular';
  68. $scope.html = 'Hello {{name}}';
  69. }
  70. </script>
  71. <div ng-controller="Ctrl">
  72. <input ng-model="name"> <br>
  73. <textarea ng-model="html"></textarea> <br>
  74. <div compile="html"></div>
  75. </div>
  76. </doc:source>
  77. <doc:scenario>
  78. it('should auto compile', function() {
  79. expect(element('div[compile]').text()).toBe('Hello Angular');
  80. input('html').enter('{{name}}!');
  81. expect(element('div[compile]').text()).toBe('Angular!');
  82. });
  83. </doc:scenario>
  84. </doc:example>
  85. *
  86. *
  87. * @param {string|DOMElement} element Element or HTML string to compile into a template function.
  88. * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
  89. * @param {number} maxPriority only apply directives lower then given priority (Only effects the
  90. * root element(s), not their children)
  91. * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
  92. * (a DOM element/tree) to a scope. Where:
  93. *
  94. * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
  95. * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
  96. * `template` and call the `cloneAttachFn` function allowing the caller to attach the
  97. * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
  98. * called as: <br> `cloneAttachFn(clonedElement, scope)` where:
  99. *
  100. * * `clonedElement` - is a clone of the original `element` passed into the compiler.
  101. * * `scope` - is the current scope with which the linking function is working with.
  102. *
  103. * Calling the linking function returns the element of the template. It is either the original element
  104. * passed in, or the clone of the element if the `cloneAttachFn` is provided.
  105. *
  106. * After linking the view is not updated until after a call to $digest which typically is done by
  107. * Angular automatically.
  108. *
  109. * If you need access to the bound view, there are two ways to do it:
  110. *
  111. * - If you are not asking the linking function to clone the template, create the DOM element(s)
  112. * before you send them to the compiler and keep this reference around.
  113. * <pre>
  114. * var element = $compile('<p>{{total}}</p>')(scope);
  115. * </pre>
  116. *
  117. * - if on the other hand, you need the element to be cloned, the view reference from the original
  118. * example would not point to the clone, but rather to the original template that was cloned. In
  119. * this case, you can access the clone via the cloneAttachFn:
  120. * <pre>
  121. * var templateHTML = angular.element('<p>{{total}}</p>'),
  122. * scope = ....;
  123. *
  124. * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
  125. * //attach the clone to DOM document at the right place
  126. * });
  127. *
  128. * //now we have reference to the cloned DOM via `clone`
  129. * </pre>
  130. *
  131. *
  132. * For information on how the compiler works, see the
  133. * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
  134. */
  135. /**
  136. * @ngdoc service
  137. * @name ng.$compileProvider
  138. * @function
  139. *
  140. * @description
  141. */
  142. $CompileProvider.$inject = ['$provide'];
  143. function $CompileProvider($provide) {
  144. var hasDirectives = {},
  145. Suffix = 'Directive',
  146. COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
  147. CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
  148. MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ';
  149. /**
  150. * @ngdoc function
  151. * @name ng.$compileProvider#directive
  152. * @methodOf ng.$compileProvider
  153. * @function
  154. *
  155. * @description
  156. * Register a new directives with the compiler.
  157. *
  158. * @param {string} name Name of the directive in camel-case. (ie <code>ngBind</code> which will match as
  159. * <code>ng-bind</code>).
  160. * @param {function} directiveFactory An injectable directive factroy function. See {@link guide/directive} for more
  161. * info.
  162. * @returns {ng.$compileProvider} Self for chaining.
  163. */
  164. this.directive = function registerDirective(name, directiveFactory) {
  165. if (isString(name)) {
  166. assertArg(directiveFactory, 'directive');
  167. if (!hasDirectives.hasOwnProperty(name)) {
  168. hasDirectives[name] = [];
  169. $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
  170. function($injector, $exceptionHandler) {
  171. var directives = [];
  172. forEach(hasDirectives[name], function(directiveFactory) {
  173. try {
  174. var directive = $injector.invoke(directiveFactory);
  175. if (isFunction(directive)) {
  176. directive = { compile: valueFn(directive) };
  177. } else if (!directive.compile && directive.link) {
  178. directive.compile = valueFn(directive.link);
  179. }
  180. directive.priority = directive.priority || 0;
  181. directive.name = directive.name || name;
  182. directive.require = directive.require || (directive.controller && directive.name);
  183. directive.restrict = directive.restrict || 'A';
  184. directives.push(directive);
  185. } catch (e) {
  186. $exceptionHandler(e);
  187. }
  188. });
  189. return directives;
  190. }]);
  191. }
  192. hasDirectives[name].push(directiveFactory);
  193. } else {
  194. forEach(name, reverseParams(registerDirective));
  195. }
  196. return this;
  197. };
  198. this.$get = [
  199. '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
  200. '$controller', '$rootScope',
  201. function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
  202. $controller, $rootScope) {
  203. var Attributes = function(element, attr) {
  204. this.$$element = element;
  205. this.$attr = attr || {};
  206. };
  207. Attributes.prototype = {
  208. $normalize: directiveNormalize,
  209. /**
  210. * Set a normalized attribute on the element in a way such that all directives
  211. * can share the attribute. This function properly handles boolean attributes.
  212. * @param {string} key Normalized key. (ie ngAttribute)
  213. * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
  214. * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
  215. * Defaults to true.
  216. * @param {string=} attrName Optional none normalized name. Defaults to key.
  217. */
  218. $set: function(key, value, writeAttr, attrName) {
  219. var booleanKey = getBooleanAttrName(this.$$element[0], key),
  220. $$observers = this.$$observers;
  221. if (booleanKey) {
  222. this.$$element.prop(key, value);
  223. attrName = booleanKey;
  224. }
  225. this[key] = value;
  226. // translate normalized key to actual key
  227. if (attrName) {
  228. this.$attr[key] = attrName;
  229. } else {
  230. attrName = this.$attr[key];
  231. if (!attrName) {
  232. this.$attr[key] = attrName = snake_case(key, '-');
  233. }
  234. }
  235. if (writeAttr !== false) {
  236. if (value === null || value === undefined) {
  237. this.$$element.removeAttr(attrName);
  238. } else {
  239. this.$$element.attr(attrName, value);
  240. }
  241. }
  242. // fire observers
  243. $$observers && forEach($$observers[key], function(fn) {
  244. try {
  245. fn(value);
  246. } catch (e) {
  247. $exceptionHandler(e);
  248. }
  249. });
  250. },
  251. /**
  252. * Observe an interpolated attribute.
  253. * The observer will never be called, if given attribute is not interpolated.
  254. *
  255. * @param {string} key Normalized key. (ie ngAttribute) .
  256. * @param {function(*)} fn Function that will be called whenever the attribute value changes.
  257. * @returns {function(*)} the `fn` Function passed in.
  258. */
  259. $observe: function(key, fn) {
  260. var attrs = this,
  261. $$observers = (attrs.$$observers || (attrs.$$observers = {})),
  262. listeners = ($$observers[key] || ($$observers[key] = []));
  263. listeners.push(fn);
  264. $rootScope.$evalAsync(function() {
  265. if (!listeners.$$inter) {
  266. // no one registered attribute interpolation function, so lets call it manually
  267. fn(attrs[key]);
  268. }
  269. });
  270. return fn;
  271. }
  272. };
  273. var startSymbol = $interpolate.startSymbol(),
  274. endSymbol = $interpolate.endSymbol(),
  275. denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
  276. ? identity
  277. : function denormalizeTemplate(template) {
  278. return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
  279. };
  280. return compile;
  281. //================================
  282. function compile($compileNodes, transcludeFn, maxPriority) {
  283. if (!($compileNodes instanceof jqLite)) {
  284. // jquery always rewraps, where as we need to preserve the original selector so that we can modify it.
  285. $compileNodes = jqLite($compileNodes);
  286. }
  287. // We can not compile top level text elements since text nodes can be merged and we will
  288. // not be able to attach scope data to them, so we will wrap them in <span>
  289. forEach($compileNodes, function(node, index){
  290. if (node.nodeType == 3 /* text node */) {
  291. $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
  292. }
  293. });
  294. var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority);
  295. return function publicLinkFn(scope, cloneConnectFn){
  296. assertArg(scope, 'scope');
  297. // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
  298. // and sometimes changes the structure of the DOM.
  299. var $linkNode = cloneConnectFn
  300. ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
  301. : $compileNodes;
  302. $linkNode.data('$scope', scope);
  303. safeAddClass($linkNode, 'ng-scope');
  304. if (cloneConnectFn) cloneConnectFn($linkNode, scope);
  305. if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
  306. return $linkNode;
  307. };
  308. }
  309. function wrongMode(localName, mode) {
  310. throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
  311. }
  312. function safeAddClass($element, className) {
  313. try {
  314. $element.addClass(className);
  315. } catch(e) {
  316. // ignore, since it means that we are trying to set class on
  317. // SVG element, where class name is read-only.
  318. }
  319. }
  320. /**
  321. * Compile function matches each node in nodeList against the directives. Once all directives
  322. * for a particular node are collected their compile functions are executed. The compile
  323. * functions return values - the linking functions - are combined into a composite linking
  324. * function, which is the a linking function for the node.
  325. *
  326. * @param {NodeList} nodeList an array of nodes to compile
  327. * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
  328. * scope argument is auto-generated to the new child of the transcluded parent scope.
  329. * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
  330. * rootElement must be set the jqLite collection of the compile root. This is
  331. * needed so that the jqLite collection items can be replaced with widgets.
  332. * @param {number=} max directive priority
  333. * @returns {?function} A composite linking function of all of the matched directives or null.
  334. */
  335. function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority) {
  336. var linkFns = [],
  337. nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
  338. for(var i = 0; i < nodeList.length; i++) {
  339. attrs = new Attributes();
  340. // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
  341. directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
  342. nodeLinkFn = (directives.length)
  343. ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
  344. : null;
  345. childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes.length)
  346. ? null
  347. : compileNodes(nodeList[i].childNodes,
  348. nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
  349. linkFns.push(nodeLinkFn);
  350. linkFns.push(childLinkFn);
  351. linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
  352. }
  353. // return a linking function if we have found anything, null otherwise
  354. return linkFnFound ? compositeLinkFn : null;
  355. function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
  356. var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn;
  357. for(var i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
  358. node = nodeList[n];
  359. nodeLinkFn = linkFns[i++];
  360. childLinkFn = linkFns[i++];
  361. if (nodeLinkFn) {
  362. if (nodeLinkFn.scope) {
  363. childScope = scope.$new(isObject(nodeLinkFn.scope));
  364. jqLite(node).data('$scope', childScope);
  365. } else {
  366. childScope = scope;
  367. }
  368. childTranscludeFn = nodeLinkFn.transclude;
  369. if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
  370. nodeLinkFn(childLinkFn, childScope, node, $rootElement,
  371. (function(transcludeFn) {
  372. return function(cloneFn) {
  373. var transcludeScope = scope.$new();
  374. return transcludeFn(transcludeScope, cloneFn).
  375. bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
  376. };
  377. })(childTranscludeFn || transcludeFn)
  378. );
  379. } else {
  380. nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
  381. }
  382. } else if (childLinkFn) {
  383. childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
  384. }
  385. }
  386. }
  387. }
  388. /**
  389. * Looks for directives on the given node and adds them to the directive collection which is
  390. * sorted.
  391. *
  392. * @param node Node to search.
  393. * @param directives An array to which the directives are added to. This array is sorted before
  394. * the function returns.
  395. * @param attrs The shared attrs object which is used to populate the normalized attributes.
  396. * @param {number=} maxPriority Max directive priority.
  397. */
  398. function collectDirectives(node, directives, attrs, maxPriority) {
  399. var nodeType = node.nodeType,
  400. attrsMap = attrs.$attr,
  401. match,
  402. className;
  403. switch(nodeType) {
  404. case 1: /* Element */
  405. // use the node name: <directive>
  406. addDirective(directives,
  407. directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
  408. // iterate over the attributes
  409. for (var attr, name, nName, value, nAttrs = node.attributes,
  410. j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
  411. attr = nAttrs[j];
  412. if (attr.specified) {
  413. name = attr.name;
  414. nName = directiveNormalize(name.toLowerCase());
  415. attrsMap[nName] = name;
  416. attrs[nName] = value = trim((msie && name == 'href')
  417. ? decodeURIComponent(node.getAttribute(name, 2))
  418. : attr.value);
  419. if (getBooleanAttrName(node, nName)) {
  420. attrs[nName] = true; // presence means true
  421. }
  422. addAttrInterpolateDirective(node, directives, value, nName);
  423. addDirective(directives, nName, 'A', maxPriority);
  424. }
  425. }
  426. // use class as directive
  427. className = node.className;
  428. if (isString(className) && className !== '') {
  429. while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
  430. nName = directiveNormalize(match[2]);
  431. if (addDirective(directives, nName, 'C', maxPriority)) {
  432. attrs[nName] = trim(match[3]);
  433. }
  434. className = className.substr(match.index + match[0].length);
  435. }
  436. }
  437. break;
  438. case 3: /* Text Node */
  439. addTextInterpolateDirective(directives, node.nodeValue);
  440. break;
  441. case 8: /* Comment */
  442. try {
  443. match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
  444. if (match) {
  445. nName = directiveNormalize(match[1]);
  446. if (addDirective(directives, nName, 'M', maxPriority)) {
  447. attrs[nName] = trim(match[2]);
  448. }
  449. }
  450. } catch (e) {
  451. // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value.
  452. // Just ignore it and continue. (Can't seem to reproduce in test case.)
  453. }
  454. break;
  455. }
  456. directives.sort(byPriority);
  457. return directives;
  458. }
  459. /**
  460. * Once the directives have been collected their compile functions is executed. This method
  461. * is responsible for inlining directive templates as well as terminating the application
  462. * of the directives if the terminal directive has been reached..
  463. *
  464. * @param {Array} directives Array of collected directives to execute their compile function.
  465. * this needs to be pre-sorted by priority order.
  466. * @param {Node} compileNode The raw DOM node to apply the compile functions to
  467. * @param {Object} templateAttrs The shared attribute function
  468. * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
  469. * scope argument is auto-generated to the new child of the transcluded parent scope.
  470. * @param {DOMElement} $rootElement If we are working on the root of the compile tree then this
  471. * argument has the root jqLite array so that we can replace widgets on it.
  472. * @returns linkFn
  473. */
  474. function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, $rootElement) {
  475. var terminalPriority = -Number.MAX_VALUE,
  476. preLinkFns = [],
  477. postLinkFns = [],
  478. newScopeDirective = null,
  479. newIsolateScopeDirective = null,
  480. templateDirective = null,
  481. $compileNode = templateAttrs.$$element = jqLite(compileNode),
  482. directive,
  483. directiveName,
  484. $template,
  485. transcludeDirective,
  486. childTranscludeFn = transcludeFn,
  487. controllerDirectives,
  488. linkFn,
  489. directiveValue;
  490. // executes all directives on the current element
  491. for(var i = 0, ii = directives.length; i < ii; i++) {
  492. directive = directives[i];
  493. $template = undefined;
  494. if (terminalPriority > directive.priority) {
  495. break; // prevent further processing of directives
  496. }
  497. if (directiveValue = directive.scope) {
  498. assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode);
  499. if (isObject(directiveValue)) {
  500. safeAddClass($compileNode, 'ng-isolate-scope');
  501. newIsolateScopeDirective = directive;
  502. }
  503. safeAddClass($compileNode, 'ng-scope');
  504. newScopeDirective = newScopeDirective || directive;
  505. }
  506. directiveName = directive.name;
  507. if (directiveValue = directive.controller) {
  508. controllerDirectives = controllerDirectives || {};
  509. assertNoDuplicate("'" + directiveName + "' controller",
  510. controllerDirectives[directiveName], directive, $compileNode);
  511. controllerDirectives[directiveName] = directive;
  512. }
  513. if (directiveValue = directive.transclude) {
  514. assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
  515. transcludeDirective = directive;
  516. terminalPriority = directive.priority;
  517. if (directiveValue == 'element') {
  518. $template = jqLite(compileNode);
  519. $compileNode = templateAttrs.$$element =
  520. jqLite('<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->');
  521. compileNode = $compileNode[0];
  522. replaceWith($rootElement, jqLite($template[0]), compileNode);
  523. childTranscludeFn = compile($template, transcludeFn, terminalPriority);
  524. } else {
  525. $template = jqLite(JQLiteClone(compileNode)).contents();
  526. $compileNode.html(''); // clear contents
  527. childTranscludeFn = compile($template, transcludeFn);
  528. }
  529. }
  530. if ((directiveValue = directive.template)) {
  531. assertNoDuplicate('template', templateDirective, directive, $compileNode);
  532. templateDirective = directive;
  533. directiveValue = denormalizeTemplate(directiveValue);
  534. if (directive.replace) {
  535. $template = jqLite('<div>' +
  536. trim(directiveValue) +
  537. '</div>').contents();
  538. compileNode = $template[0];
  539. if ($template.length != 1 || compileNode.nodeType !== 1) {
  540. throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue);
  541. }
  542. replaceWith($rootElement, $compileNode, compileNode);
  543. var newTemplateAttrs = {$attr: {}};
  544. // combine directives from the original node and from the template:
  545. // - take the array of directives for this element
  546. // - split it into two parts, those that were already applied and those that weren't
  547. // - collect directives from the template, add them to the second group and sort them
  548. // - append the second group with new directives to the first group
  549. directives = directives.concat(
  550. collectDirectives(
  551. compileNode,
  552. directives.splice(i + 1, directives.length - (i + 1)),
  553. newTemplateAttrs
  554. )
  555. );
  556. mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
  557. ii = directives.length;
  558. } else {
  559. $compileNode.html(directiveValue);
  560. }
  561. }
  562. if (directive.templateUrl) {
  563. assertNoDuplicate('template', templateDirective, directive, $compileNode);
  564. templateDirective = directive;
  565. nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i),
  566. nodeLinkFn, $compileNode, templateAttrs, $rootElement, directive.replace,
  567. childTranscludeFn);
  568. ii = directives.length;
  569. } else if (directive.compile) {
  570. try {
  571. linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
  572. if (isFunction(linkFn)) {
  573. addLinkFns(null, linkFn);
  574. } else if (linkFn) {
  575. addLinkFns(linkFn.pre, linkFn.post);
  576. }
  577. } catch (e) {
  578. $exceptionHandler(e, startingTag($compileNode));
  579. }
  580. }
  581. if (directive.terminal) {
  582. nodeLinkFn.terminal = true;
  583. terminalPriority = Math.max(terminalPriority, directive.priority);
  584. }
  585. }
  586. nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
  587. nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
  588. // might be normal or delayed nodeLinkFn depending on if templateUrl is present
  589. return nodeLinkFn;
  590. ////////////////////
  591. function addLinkFns(pre, post) {
  592. if (pre) {
  593. pre.require = directive.require;
  594. preLinkFns.push(pre);
  595. }
  596. if (post) {
  597. post.require = directive.require;
  598. postLinkFns.push(post);
  599. }
  600. }
  601. function getControllers(require, $element) {
  602. var value, retrievalMethod = 'data', optional = false;
  603. if (isString(require)) {
  604. while((value = require.charAt(0)) == '^' || value == '?') {
  605. require = require.substr(1);
  606. if (value == '^') {
  607. retrievalMethod = 'inheritedData';
  608. }
  609. optional = optional || value == '?';
  610. }
  611. value = $element[retrievalMethod]('$' + require + 'Controller');
  612. if (!value && !optional) {
  613. throw Error("No controller: " + require);
  614. }
  615. return value;
  616. } else if (isArray(require)) {
  617. value = [];
  618. forEach(require, function(require) {
  619. value.push(getControllers(require, $element));
  620. });
  621. }
  622. return value;
  623. }
  624. function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
  625. var attrs, $element, i, ii, linkFn, controller;
  626. if (compileNode === linkNode) {
  627. attrs = templateAttrs;
  628. } else {
  629. attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
  630. }
  631. $element = attrs.$$element;
  632. if (newIsolateScopeDirective) {
  633. var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
  634. var parentScope = scope.$parent || scope;
  635. forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
  636. var match = definiton.match(LOCAL_REGEXP) || [],
  637. attrName = match[2]|| scopeName,
  638. mode = match[1], // @, =, or &
  639. lastValue,
  640. parentGet, parentSet;
  641. switch (mode) {
  642. case '@': {
  643. attrs.$observe(attrName, function(value) {
  644. scope[scopeName] = value;
  645. });
  646. attrs.$$observers[attrName].$$scope = parentScope;
  647. break;
  648. }
  649. case '=': {
  650. parentGet = $parse(attrs[attrName]);
  651. parentSet = parentGet.assign || function() {
  652. // reset the change, or we will throw this exception on every $digest
  653. lastValue = scope[scopeName] = parentGet(parentScope);
  654. throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] +
  655. ' (directive: ' + newIsolateScopeDirective.name + ')');
  656. };
  657. lastValue = scope[scopeName] = parentGet(parentScope);
  658. scope.$watch(function parentValueWatch() {
  659. var parentValue = parentGet(parentScope);
  660. if (parentValue !== scope[scopeName]) {
  661. // we are out of sync and need to copy
  662. if (parentValue !== lastValue) {
  663. // parent changed and it has precedence
  664. lastValue = scope[scopeName] = parentValue;
  665. } else {
  666. // if the parent can be assigned then do so
  667. parentSet(parentScope, parentValue = lastValue = scope[scopeName]);
  668. }
  669. }
  670. return parentValue;
  671. });
  672. break;
  673. }
  674. case '&': {
  675. parentGet = $parse(attrs[attrName]);
  676. scope[scopeName] = function(locals) {
  677. return parentGet(parentScope, locals);
  678. }
  679. break;
  680. }
  681. default: {
  682. throw Error('Invalid isolate scope definition for directive ' +
  683. newIsolateScopeDirective.name + ': ' + definiton);
  684. }
  685. }
  686. });
  687. }
  688. if (controllerDirectives) {
  689. forEach(controllerDirectives, function(directive) {
  690. var locals = {
  691. $scope: scope,
  692. $element: $element,
  693. $attrs: attrs,
  694. $transclude: boundTranscludeFn
  695. };
  696. controller = directive.controller;
  697. if (controller == '@') {
  698. controller = attrs[directive.name];
  699. }
  700. $element.data(
  701. '$' + directive.name + 'Controller',
  702. $controller(controller, locals));
  703. });
  704. }
  705. // PRELINKING
  706. for(i = 0, ii = preLinkFns.length; i < ii; i++) {
  707. try {
  708. linkFn = preLinkFns[i];
  709. linkFn(scope, $element, attrs,
  710. linkFn.require && getControllers(linkFn.require, $element));
  711. } catch (e) {
  712. $exceptionHandler(e, startingTag($element));
  713. }
  714. }
  715. // RECURSION
  716. childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
  717. // POSTLINKING
  718. for(i = 0, ii = postLinkFns.length; i < ii; i++) {
  719. try {
  720. linkFn = postLinkFns[i];
  721. linkFn(scope, $element, attrs,
  722. linkFn.require && getControllers(linkFn.require, $element));
  723. } catch (e) {
  724. $exceptionHandler(e, startingTag($element));
  725. }
  726. }
  727. }
  728. }
  729. /**
  730. * looks up the directive and decorates it with exception handling and proper parameters. We
  731. * call this the boundDirective.
  732. *
  733. * @param {string} name name of the directive to look up.
  734. * @param {string} location The directive must be found in specific format.
  735. * String containing any of theses characters:
  736. *
  737. * * `E`: element name
  738. * * `A': attribute
  739. * * `C`: class
  740. * * `M`: comment
  741. * @returns true if directive was added.
  742. */
  743. function addDirective(tDirectives, name, location, maxPriority) {
  744. var match = false;
  745. if (hasDirectives.hasOwnProperty(name)) {
  746. for(var directive, directives = $injector.get(name + Suffix),
  747. i = 0, ii = directives.length; i<ii; i++) {
  748. try {
  749. directive = directives[i];
  750. if ( (maxPriority === undefined || maxPriority > directive.priority) &&
  751. directive.restrict.indexOf(location) != -1) {
  752. tDirectives.push(directive);
  753. match = true;
  754. }
  755. } catch(e) { $exceptionHandler(e); }
  756. }
  757. }
  758. return match;
  759. }
  760. /**
  761. * When the element is replaced with HTML template then the new attributes
  762. * on the template need to be merged with the existing attributes in the DOM.
  763. * The desired effect is to have both of the attributes present.
  764. *
  765. * @param {object} dst destination attributes (original DOM)
  766. * @param {object} src source attributes (from the directive template)
  767. */
  768. function mergeTemplateAttributes(dst, src) {
  769. var srcAttr = src.$attr,
  770. dstAttr = dst.$attr,
  771. $element = dst.$$element;
  772. // reapply the old attributes to the new element
  773. forEach(dst, function(value, key) {
  774. if (key.charAt(0) != '$') {
  775. if (src[key]) {
  776. value += (key === 'style' ? ';' : ' ') + src[key];
  777. }
  778. dst.$set(key, value, true, srcAttr[key]);
  779. }
  780. });
  781. // copy the new attributes on the old attrs object
  782. forEach(src, function(value, key) {
  783. if (key == 'class') {
  784. safeAddClass($element, value);
  785. dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
  786. } else if (key == 'style') {
  787. $element.attr('style', $element.attr('style') + ';' + value);
  788. } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
  789. dst[key] = value;
  790. dstAttr[key] = srcAttr[key];
  791. }
  792. });
  793. }
  794. function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs,
  795. $rootElement, replace, childTranscludeFn) {
  796. var linkQueue = [],
  797. afterTemplateNodeLinkFn,
  798. afterTemplateChildLinkFn,
  799. beforeTemplateCompileNode = $compileNode[0],
  800. origAsyncDirective = directives.shift(),
  801. // The fact that we have to copy and patch the directive seems wrong!
  802. derivedSyncDirective = extend({}, origAsyncDirective, {
  803. controller: null, templateUrl: null, transclude: null, scope: null
  804. });
  805. $compileNode.html('');
  806. $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}).
  807. success(function(content) {
  808. var compileNode, tempTemplateAttrs, $template;
  809. content = denormalizeTemplate(content);
  810. if (replace) {
  811. $template = jqLite('<div>' + trim(content) + '</div>').contents();
  812. compileNode = $template[0];
  813. if ($template.length != 1 || compileNode.nodeType !== 1) {
  814. throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content);
  815. }
  816. tempTemplateAttrs = {$attr: {}};
  817. replaceWith($rootElement, $compileNode, compileNode);
  818. collectDirectives(compileNode, directives, tempTemplateAttrs);
  819. mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
  820. } else {
  821. compileNode = beforeTemplateCompileNode;
  822. $compileNode.html(content);
  823. }
  824. directives.unshift(derivedSyncDirective);
  825. afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn);
  826. afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
  827. while(linkQueue.length) {
  828. var controller = linkQueue.pop(),
  829. linkRootElement = linkQueue.pop(),
  830. beforeTemplateLinkNode = linkQueue.pop(),
  831. scope = linkQueue.pop(),
  832. linkNode = compileNode;
  833. if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
  834. // it was cloned therefore we have to clone as well.
  835. linkNode = JQLiteClone(compileNode);
  836. replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
  837. }
  838. afterTemplateNodeLinkFn(function() {
  839. beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
  840. }, scope, linkNode, $rootElement, controller);
  841. }
  842. linkQueue = null;
  843. }).
  844. error(function(response, code, headers, config) {
  845. throw Error('Failed to load template: ' + config.url);
  846. });
  847. return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
  848. if (linkQueue) {
  849. linkQueue.push(scope);
  850. linkQueue.push(node);
  851. linkQueue.push(rootElement);
  852. linkQueue.push(controller);
  853. } else {
  854. afterTemplateNodeLinkFn(function() {
  855. beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
  856. }, scope, node, rootElement, controller);
  857. }
  858. };
  859. }
  860. /**
  861. * Sorting function for bound directives.
  862. */
  863. function byPriority(a, b) {
  864. return b.priority - a.priority;
  865. }
  866. function assertNoDuplicate(what, previousDirective, directive, element) {
  867. if (previousDirective) {
  868. throw Error('Multiple directives [' + previousDirective.name + ', ' +
  869. directive.name + '] asking for ' + what + ' on: ' + startingTag(element));
  870. }
  871. }
  872. function addTextInterpolateDirective(directives, text) {
  873. var interpolateFn = $interpolate(text, true);
  874. if (interpolateFn) {
  875. directives.push({
  876. priority: 0,
  877. compile: valueFn(function textInterpolateLinkFn(scope, node) {
  878. var parent = node.parent(),
  879. bindings = parent.data('$binding') || [];
  880. bindings.push(interpolateFn);
  881. safeAddClass(parent.data('$binding', bindings), 'ng-binding');
  882. scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
  883. node[0].nodeValue = value;
  884. });
  885. })
  886. });
  887. }
  888. }
  889. function addAttrInterpolateDirective(node, directives, value, name) {
  890. var interpolateFn = $interpolate(value, true);
  891. // no interpolation found -> ignore
  892. if (!interpolateFn) return;
  893. directives.push({
  894. priority: 100,
  895. compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
  896. var $$observers = (attr.$$observers || (attr.$$observers = {}));
  897. if (name === 'class') {
  898. // we need to interpolate classes again, in the case the element was replaced
  899. // and therefore the two class attrs got merged - we want to interpolate the result
  900. interpolateFn = $interpolate(attr[name], true);
  901. }
  902. attr[name] = undefined;
  903. ($$observers[name] || ($$observers[name] = [])).$$inter = true;
  904. (attr.$$observers && attr.$$observers[name].$$scope || scope).
  905. $watch(interpolateFn, function interpolateFnWatchAction(value) {
  906. attr.$set(name, value);
  907. });
  908. })
  909. });
  910. }
  911. /**
  912. * This is a special jqLite.replaceWith, which can replace items which
  913. * have no parents, provided that the containing jqLite collection is provided.
  914. *
  915. * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
  916. * in the root of the tree.
  917. * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell,
  918. * but replace its DOM node reference.
  919. * @param {Node} newNode The new DOM node.
  920. */
  921. function replaceWith($rootElement, $element, newNode) {
  922. var oldNode = $element[0],
  923. parent = oldNode.parentNode,
  924. i, ii;
  925. if ($rootElement) {
  926. for(i = 0, ii = $rootElement.length; i < ii; i++) {
  927. if ($rootElement[i] == oldNode) {
  928. $rootElement[i] = newNode;
  929. break;
  930. }
  931. }
  932. }
  933. if (parent) {
  934. parent.replaceChild(newNode, oldNode);
  935. }
  936. newNode[jqLite.expando] = oldNode[jqLite.expando];
  937. $element[0] = newNode;
  938. }
  939. }];
  940. }
  941. var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
  942. /**
  943. * Converts all accepted directives format into proper directive name.
  944. * All of these will become 'myDirective':
  945. * my:DiRective
  946. * my-directive
  947. * x-my-directive
  948. * data-my:directive
  949. *
  950. * Also there is special case for Moz prefix starting with upper case letter.
  951. * @param name Name to normalize
  952. */
  953. function directiveNormalize(name) {
  954. return camelCase(name.replace(PREFIX_REGEXP, ''));
  955. }
  956. /**
  957. * @ngdoc object
  958. * @name ng.$compile.directive.Attributes
  959. * @description
  960. *
  961. * A shared object between directive compile / linking functions which contains normalized DOM element
  962. * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed
  963. * since all of these are treated as equivalent in Angular:
  964. *
  965. * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
  966. */
  967. /**
  968. * @ngdoc property
  969. * @name ng.$compile.directive.Attributes#$attr
  970. * @propertyOf ng.$compile.directive.Attributes
  971. * @returns {object} A map of DOM element attribute names to the normalized name. This is
  972. * needed to do reverse lookup from normalized name back to actual name.
  973. */
  974. /**
  975. * @ngdoc function
  976. * @name ng.$compile.directive.Attributes#$set
  977. * @methodOf ng.$compile.directive.Attributes
  978. * @function
  979. *
  980. * @description
  981. * Set DOM element attribute value.
  982. *
  983. *
  984. * @param {string} name Normalized element attribute name of the property to modify. The name is
  985. * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
  986. * property to the original name.
  987. * @param {string} value Value to set the attribute to.
  988. */
  989. /**
  990. * Closure compiler type information
  991. */
  992. function nodesetLinkingFn(
  993. /* angular.Scope */ scope,
  994. /* NodeList */ nodeList,
  995. /* Element */ rootElement,
  996. /* function(Function) */ boundTranscludeFn
  997. ){}
  998. function directiveLinkingFn(
  999. /* nodesetLinkingFn */ nodesetLinkingFn,
  1000. /* angular.Scope */ scope,
  1001. /* Node */ node,
  1002. /* Element */ rootElement,
  1003. /* function(Function) */ boundTranscludeFn
  1004. ){}