PageRenderTime 112ms CodeModel.GetById 19ms RepoModel.GetById 7ms app.codeStats 1ms

/src/ng/compile.js

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