You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

739 lines
27 KiB

7 years ago
  1. /**
  2. * @license AngularJS v1.6.1
  3. * (c) 2010-2016 Google, Inc. http://angularjs.org
  4. * License: MIT
  5. */
  6. (function(window, angular) {'use strict';
  7. var forEach;
  8. var isArray;
  9. var isString;
  10. var jqLite;
  11. /**
  12. * @ngdoc module
  13. * @name ngMessages
  14. * @description
  15. *
  16. * The `ngMessages` module provides enhanced support for displaying messages within templates
  17. * (typically within forms or when rendering message objects that return key/value data).
  18. * Instead of relying on JavaScript code and/or complex ng-if statements within your form template to
  19. * show and hide error messages specific to the state of an input field, the `ngMessages` and
  20. * `ngMessage` directives are designed to handle the complexity, inheritance and priority
  21. * sequencing based on the order of how the messages are defined in the template.
  22. *
  23. * Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
  24. * `ngMessage` and `ngMessageExp` directives.
  25. *
  26. * # Usage
  27. * The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
  28. * (or 'message') that will show or hide based on the truthiness of that key's value in the collection. A common use
  29. * case for `ngMessages` is to display error messages for inputs using the `$error` object exposed by the
  30. * {@link ngModel ngModel} directive.
  31. *
  32. * The child elements of the `ngMessages` directive are matched to the collection keys by a `ngMessage` or
  33. * `ngMessageExp` directive. The value of these attributes must match a key in the collection that is provided by
  34. * the `ngMessages` directive.
  35. *
  36. * Consider the following example, which illustrates a typical use case of `ngMessages`. Within the form `myForm` we
  37. * have a text input named `myField` which is bound to the scope variable `field` using the {@link ngModel ngModel}
  38. * directive.
  39. *
  40. * The `myField` field is a required input of type `email` with a maximum length of 15 characters.
  41. *
  42. * ```html
  43. * <form name="myForm">
  44. * <label>
  45. * Enter text:
  46. * <input type="email" ng-model="field" name="myField" required maxlength="15" />
  47. * </label>
  48. * <div ng-messages="myForm.myField.$error" role="alert">
  49. * <div ng-message="required">Please enter a value for this field.</div>
  50. * <div ng-message="email">This field must be a valid email address.</div>
  51. * <div ng-message="maxlength">This field can be at most 15 characters long.</div>
  52. * </div>
  53. * </form>
  54. * ```
  55. *
  56. * In order to show error messages corresponding to `myField` we first create an element with an `ngMessages` attribute
  57. * set to the `$error` object owned by the `myField` input in our `myForm` form.
  58. *
  59. * Within this element we then create separate elements for each of the possible errors that `myField` could have.
  60. * The `ngMessage` attribute is used to declare which element(s) will appear for which error - for example,
  61. * setting `ng-message="required"` specifies that this particular element should be displayed when there
  62. * is no value present for the required field `myField` (because the key `required` will be `true` in the object
  63. * `myForm.myField.$error`).
  64. *
  65. * ### Message order
  66. *
  67. * By default, `ngMessages` will only display one message for a particular key/value collection at any time. If more
  68. * than one message (or error) key is currently true, then which message is shown is determined by the order of messages
  69. * in the HTML template code (messages declared first are prioritised). This mechanism means the developer does not have
  70. * to prioritize messages using custom JavaScript code.
  71. *
  72. * Given the following error object for our example (which informs us that the field `myField` currently has both the
  73. * `required` and `email` errors):
  74. *
  75. * ```javascript
  76. * <!-- keep in mind that ngModel automatically sets these error flags -->
  77. * myField.$error = { required : true, email: true, maxlength: false };
  78. * ```
  79. * The `required` message will be displayed to the user since it appears before the `email` message in the DOM.
  80. * Once the user types a single character, the `required` message will disappear (since the field now has a value)
  81. * but the `email` message will be visible because it is still applicable.
  82. *
  83. * ### Displaying multiple messages at the same time
  84. *
  85. * While `ngMessages` will by default only display one error element at a time, the `ng-messages-multiple` attribute can
  86. * be applied to the `ngMessages` container element to cause it to display all applicable error messages at once:
  87. *
  88. * ```html
  89. * <!-- attribute-style usage -->
  90. * <div ng-messages="myForm.myField.$error" ng-messages-multiple>...</div>
  91. *
  92. * <!-- element-style usage -->
  93. * <ng-messages for="myForm.myField.$error" multiple>...</ng-messages>
  94. * ```
  95. *
  96. * ## Reusing and Overriding Messages
  97. * In addition to prioritization, ngMessages also allows for including messages from a remote or an inline
  98. * template. This allows for generic collection of messages to be reused across multiple parts of an
  99. * application.
  100. *
  101. * ```html
  102. * <script type="text/ng-template" id="error-messages">
  103. * <div ng-message="required">This field is required</div>
  104. * <div ng-message="minlength">This field is too short</div>
  105. * </script>
  106. *
  107. * <div ng-messages="myForm.myField.$error" role="alert">
  108. * <div ng-messages-include="error-messages"></div>
  109. * </div>
  110. * ```
  111. *
  112. * However, including generic messages may not be useful enough to match all input fields, therefore,
  113. * `ngMessages` provides the ability to override messages defined in the remote template by redefining
  114. * them within the directive container.
  115. *
  116. * ```html
  117. * <!-- a generic template of error messages known as "my-custom-messages" -->
  118. * <script type="text/ng-template" id="my-custom-messages">
  119. * <div ng-message="required">This field is required</div>
  120. * <div ng-message="minlength">This field is too short</div>
  121. * </script>
  122. *
  123. * <form name="myForm">
  124. * <label>
  125. * Email address
  126. * <input type="email"
  127. * id="email"
  128. * name="myEmail"
  129. * ng-model="email"
  130. * minlength="5"
  131. * required />
  132. * </label>
  133. * <!-- any ng-message elements that appear BEFORE the ng-messages-include will
  134. * override the messages present in the ng-messages-include template -->
  135. * <div ng-messages="myForm.myEmail.$error" role="alert">
  136. * <!-- this required message has overridden the template message -->
  137. * <div ng-message="required">You did not enter your email address</div>
  138. *
  139. * <!-- this is a brand new message and will appear last in the prioritization -->
  140. * <div ng-message="email">Your email address is invalid</div>
  141. *
  142. * <!-- and here are the generic error messages -->
  143. * <div ng-messages-include="my-custom-messages"></div>
  144. * </div>
  145. * </form>
  146. * ```
  147. *
  148. * In the example HTML code above the message that is set on required will override the corresponding
  149. * required message defined within the remote template. Therefore, with particular input fields (such
  150. * email addresses, date fields, autocomplete inputs, etc...), specialized error messages can be applied
  151. * while more generic messages can be used to handle other, more general input errors.
  152. *
  153. * ## Dynamic Messaging
  154. * ngMessages also supports using expressions to dynamically change key values. Using arrays and
  155. * repeaters to list messages is also supported. This means that the code below will be able to
  156. * fully adapt itself and display the appropriate message when any of the expression data changes:
  157. *
  158. * ```html
  159. * <form name="myForm">
  160. * <label>
  161. * Email address
  162. * <input type="email"
  163. * name="myEmail"
  164. * ng-model="email"
  165. * minlength="5"
  166. * required />
  167. * </label>
  168. * <div ng-messages="myForm.myEmail.$error" role="alert">
  169. * <div ng-message="required">You did not enter your email address</div>
  170. * <div ng-repeat="errorMessage in errorMessages">
  171. * <!-- use ng-message-exp for a message whose key is given by an expression -->
  172. * <div ng-message-exp="errorMessage.type">{{ errorMessage.text }}</div>
  173. * </div>
  174. * </div>
  175. * </form>
  176. * ```
  177. *
  178. * The `errorMessage.type` expression can be a string value or it can be an array so
  179. * that multiple errors can be associated with a single error message:
  180. *
  181. * ```html
  182. * <label>
  183. * Email address
  184. * <input type="email"
  185. * ng-model="data.email"
  186. * name="myEmail"
  187. * ng-minlength="5"
  188. * ng-maxlength="100"
  189. * required />
  190. * </label>
  191. * <div ng-messages="myForm.myEmail.$error" role="alert">
  192. * <div ng-message-exp="'required'">You did not enter your email address</div>
  193. * <div ng-message-exp="['minlength', 'maxlength']">
  194. * Your email must be between 5 and 100 characters long
  195. * </div>
  196. * </div>
  197. * ```
  198. *
  199. * Feel free to use other structural directives such as ng-if and ng-switch to further control
  200. * what messages are active and when. Be careful, if you place ng-message on the same element
  201. * as these structural directives, Angular may not be able to determine if a message is active
  202. * or not. Therefore it is best to place the ng-message on a child element of the structural
  203. * directive.
  204. *
  205. * ```html
  206. * <div ng-messages="myForm.myEmail.$error" role="alert">
  207. * <div ng-if="showRequiredError">
  208. * <div ng-message="required">Please enter something</div>
  209. * </div>
  210. * </div>
  211. * ```
  212. *
  213. * ## Animations
  214. * If the `ngAnimate` module is active within the application then the `ngMessages`, `ngMessage` and
  215. * `ngMessageExp` directives will trigger animations whenever any messages are added and removed from
  216. * the DOM by the `ngMessages` directive.
  217. *
  218. * Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS
  219. * class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no
  220. * messages present. Therefore, CSS transitions and keyframes as well as JavaScript animations can
  221. * hook into the animations whenever these classes are added/removed.
  222. *
  223. * Let's say that our HTML code for our messages container looks like so:
  224. *
  225. * ```html
  226. * <div ng-messages="myMessages" class="my-messages" role="alert">
  227. * <div ng-message="alert" class="some-message">...</div>
  228. * <div ng-message="fail" class="some-message">...</div>
  229. * </div>
  230. * ```
  231. *
  232. * Then the CSS animation code for the message container looks like so:
  233. *
  234. * ```css
  235. * .my-messages {
  236. * transition:1s linear all;
  237. * }
  238. * .my-messages.ng-active {
  239. * // messages are visible
  240. * }
  241. * .my-messages.ng-inactive {
  242. * // messages are hidden
  243. * }
  244. * ```
  245. *
  246. * Whenever an inner message is attached (becomes visible) or removed (becomes hidden) then the enter
  247. * and leave animation is triggered for each particular element bound to the `ngMessage` directive.
  248. *
  249. * Therefore, the CSS code for the inner messages looks like so:
  250. *
  251. * ```css
  252. * .some-message {
  253. * transition:1s linear all;
  254. * }
  255. *
  256. * .some-message.ng-enter {}
  257. * .some-message.ng-enter.ng-enter-active {}
  258. *
  259. * .some-message.ng-leave {}
  260. * .some-message.ng-leave.ng-leave-active {}
  261. * ```
  262. *
  263. * {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate.
  264. */
  265. angular.module('ngMessages', [], function initAngularHelpers() {
  266. // Access helpers from angular core.
  267. // Do it inside a `config` block to ensure `window.angular` is available.
  268. forEach = angular.forEach;
  269. isArray = angular.isArray;
  270. isString = angular.isString;
  271. jqLite = angular.element;
  272. })
  273. /**
  274. * @ngdoc directive
  275. * @module ngMessages
  276. * @name ngMessages
  277. * @restrict AE
  278. *
  279. * @description
  280. * `ngMessages` is a directive that is designed to show and hide messages based on the state
  281. * of a key/value object that it listens on. The directive itself complements error message
  282. * reporting with the `ngModel` $error object (which stores a key/value state of validation errors).
  283. *
  284. * `ngMessages` manages the state of internal messages within its container element. The internal
  285. * messages use the `ngMessage` directive and will be inserted/removed from the page depending
  286. * on if they're present within the key/value object. By default, only one message will be displayed
  287. * at a time and this depends on the prioritization of the messages within the template. (This can
  288. * be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
  289. *
  290. * A remote template can also be used to promote message reusability and messages can also be
  291. * overridden.
  292. *
  293. * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
  294. *
  295. * @usage
  296. * ```html
  297. * <!-- using attribute directives -->
  298. * <ANY ng-messages="expression" role="alert">
  299. * <ANY ng-message="stringValue">...</ANY>
  300. * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
  301. * <ANY ng-message-exp="expressionValue">...</ANY>
  302. * </ANY>
  303. *
  304. * <!-- or by using element directives -->
  305. * <ng-messages for="expression" role="alert">
  306. * <ng-message when="stringValue">...</ng-message>
  307. * <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
  308. * <ng-message when-exp="expressionValue">...</ng-message>
  309. * </ng-messages>
  310. * ```
  311. *
  312. * @param {string} ngMessages an angular expression evaluating to a key/value object
  313. * (this is typically the $error object on an ngModel instance).
  314. * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
  315. *
  316. * @example
  317. * <example name="ngMessages-directive" module="ngMessagesExample"
  318. * deps="angular-messages.js"
  319. * animations="true" fixBase="true">
  320. * <file name="index.html">
  321. * <form name="myForm">
  322. * <label>
  323. * Enter your name:
  324. * <input type="text"
  325. * name="myName"
  326. * ng-model="name"
  327. * ng-minlength="5"
  328. * ng-maxlength="20"
  329. * required />
  330. * </label>
  331. * <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre>
  332. *
  333. * <div ng-messages="myForm.myName.$error" style="color:maroon" role="alert">
  334. * <div ng-message="required">You did not enter a field</div>
  335. * <div ng-message="minlength">Your field is too short</div>
  336. * <div ng-message="maxlength">Your field is too long</div>
  337. * </div>
  338. * </form>
  339. * </file>
  340. * <file name="script.js">
  341. * angular.module('ngMessagesExample', ['ngMessages']);
  342. * </file>
  343. * </example>
  344. */
  345. .directive('ngMessages', ['$animate', function($animate) {
  346. var ACTIVE_CLASS = 'ng-active';
  347. var INACTIVE_CLASS = 'ng-inactive';
  348. return {
  349. require: 'ngMessages',
  350. restrict: 'AE',
  351. controller: ['$element', '$scope', '$attrs', function NgMessagesCtrl($element, $scope, $attrs) {
  352. var ctrl = this;
  353. var latestKey = 0;
  354. var nextAttachId = 0;
  355. this.getAttachId = function getAttachId() { return nextAttachId++; };
  356. var messages = this.messages = {};
  357. var renderLater, cachedCollection;
  358. this.render = function(collection) {
  359. collection = collection || {};
  360. renderLater = false;
  361. cachedCollection = collection;
  362. // this is true if the attribute is empty or if the attribute value is truthy
  363. var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) ||
  364. isAttrTruthy($scope, $attrs.multiple);
  365. var unmatchedMessages = [];
  366. var matchedKeys = {};
  367. var messageItem = ctrl.head;
  368. var messageFound = false;
  369. var totalMessages = 0;
  370. // we use != instead of !== to allow for both undefined and null values
  371. while (messageItem != null) {
  372. totalMessages++;
  373. var messageCtrl = messageItem.message;
  374. var messageUsed = false;
  375. if (!messageFound) {
  376. forEach(collection, function(value, key) {
  377. if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
  378. // this is to prevent the same error name from showing up twice
  379. if (matchedKeys[key]) return;
  380. matchedKeys[key] = true;
  381. messageUsed = true;
  382. messageCtrl.attach();
  383. }
  384. });
  385. }
  386. if (messageUsed) {
  387. // unless we want to display multiple messages then we should
  388. // set a flag here to avoid displaying the next message in the list
  389. messageFound = !multiple;
  390. } else {
  391. unmatchedMessages.push(messageCtrl);
  392. }
  393. messageItem = messageItem.next;
  394. }
  395. forEach(unmatchedMessages, function(messageCtrl) {
  396. messageCtrl.detach();
  397. });
  398. if (unmatchedMessages.length !== totalMessages) {
  399. $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS);
  400. } else {
  401. $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
  402. }
  403. };
  404. $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
  405. // If the element is destroyed, proactively destroy all the currently visible messages
  406. $element.on('$destroy', function() {
  407. forEach(messages, function(item) {
  408. item.message.detach();
  409. });
  410. });
  411. this.reRender = function() {
  412. if (!renderLater) {
  413. renderLater = true;
  414. $scope.$evalAsync(function() {
  415. if (renderLater && cachedCollection) {
  416. ctrl.render(cachedCollection);
  417. }
  418. });
  419. }
  420. };
  421. this.register = function(comment, messageCtrl) {
  422. var nextKey = latestKey.toString();
  423. messages[nextKey] = {
  424. message: messageCtrl
  425. };
  426. insertMessageNode($element[0], comment, nextKey);
  427. comment.$$ngMessageNode = nextKey;
  428. latestKey++;
  429. ctrl.reRender();
  430. };
  431. this.deregister = function(comment) {
  432. var key = comment.$$ngMessageNode;
  433. delete comment.$$ngMessageNode;
  434. removeMessageNode($element[0], comment, key);
  435. delete messages[key];
  436. ctrl.reRender();
  437. };
  438. function findPreviousMessage(parent, comment) {
  439. var prevNode = comment;
  440. var parentLookup = [];
  441. while (prevNode && prevNode !== parent) {
  442. var prevKey = prevNode.$$ngMessageNode;
  443. if (prevKey && prevKey.length) {
  444. return messages[prevKey];
  445. }
  446. // dive deeper into the DOM and examine its children for any ngMessage
  447. // comments that may be in an element that appears deeper in the list
  448. if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) === -1) {
  449. parentLookup.push(prevNode);
  450. prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
  451. } else if (prevNode.previousSibling) {
  452. prevNode = prevNode.previousSibling;
  453. } else {
  454. prevNode = prevNode.parentNode;
  455. parentLookup.push(prevNode);
  456. }
  457. }
  458. }
  459. function insertMessageNode(parent, comment, key) {
  460. var messageNode = messages[key];
  461. if (!ctrl.head) {
  462. ctrl.head = messageNode;
  463. } else {
  464. var match = findPreviousMessage(parent, comment);
  465. if (match) {
  466. messageNode.next = match.next;
  467. match.next = messageNode;
  468. } else {
  469. messageNode.next = ctrl.head;
  470. ctrl.head = messageNode;
  471. }
  472. }
  473. }
  474. function removeMessageNode(parent, comment, key) {
  475. var messageNode = messages[key];
  476. var match = findPreviousMessage(parent, comment);
  477. if (match) {
  478. match.next = messageNode.next;
  479. } else {
  480. ctrl.head = messageNode.next;
  481. }
  482. }
  483. }]
  484. };
  485. function isAttrTruthy(scope, attr) {
  486. return (isString(attr) && attr.length === 0) || //empty attribute
  487. truthy(scope.$eval(attr));
  488. }
  489. function truthy(val) {
  490. return isString(val) ? val.length : !!val;
  491. }
  492. }])
  493. /**
  494. * @ngdoc directive
  495. * @name ngMessagesInclude
  496. * @restrict AE
  497. * @scope
  498. *
  499. * @description
  500. * `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template
  501. * code from a remote template and place the downloaded template code into the exact spot
  502. * that the ngMessagesInclude directive is placed within the ngMessages container. This allows
  503. * for a series of pre-defined messages to be reused and also allows for the developer to
  504. * determine what messages are overridden due to the placement of the ngMessagesInclude directive.
  505. *
  506. * @usage
  507. * ```html
  508. * <!-- using attribute directives -->
  509. * <ANY ng-messages="expression" role="alert">
  510. * <ANY ng-messages-include="remoteTplString">...</ANY>
  511. * </ANY>
  512. *
  513. * <!-- or by using element directives -->
  514. * <ng-messages for="expression" role="alert">
  515. * <ng-messages-include src="expressionValue1">...</ng-messages-include>
  516. * </ng-messages>
  517. * ```
  518. *
  519. * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
  520. *
  521. * @param {string} ngMessagesInclude|src a string value corresponding to the remote template.
  522. */
  523. .directive('ngMessagesInclude',
  524. ['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) {
  525. return {
  526. restrict: 'AE',
  527. require: '^^ngMessages', // we only require this for validation sake
  528. link: function($scope, element, attrs) {
  529. var src = attrs.ngMessagesInclude || attrs.src;
  530. $templateRequest(src).then(function(html) {
  531. if ($scope.$$destroyed) return;
  532. if (isString(html) && !html.trim()) {
  533. // Empty template - nothing to compile
  534. replaceElementWithMarker(element, src);
  535. } else {
  536. // Non-empty template - compile and link
  537. $compile(html)($scope, function(contents) {
  538. element.after(contents);
  539. replaceElementWithMarker(element, src);
  540. });
  541. }
  542. });
  543. }
  544. };
  545. // Helpers
  546. function replaceElementWithMarker(element, src) {
  547. // A comment marker is placed for debugging purposes
  548. var comment = $compile.$$createComment ?
  549. $compile.$$createComment('ngMessagesInclude', src) :
  550. $document[0].createComment(' ngMessagesInclude: ' + src + ' ');
  551. var marker = jqLite(comment);
  552. element.after(marker);
  553. // Don't pollute the DOM anymore by keeping an empty directive element
  554. element.remove();
  555. }
  556. }])
  557. /**
  558. * @ngdoc directive
  559. * @name ngMessage
  560. * @restrict AE
  561. * @scope
  562. *
  563. * @description
  564. * `ngMessage` is a directive with the purpose to show and hide a particular message.
  565. * For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element
  566. * must be situated since it determines which messages are visible based on the state
  567. * of the provided key/value map that `ngMessages` listens on.
  568. *
  569. * More information about using `ngMessage` can be found in the
  570. * {@link module:ngMessages `ngMessages` module documentation}.
  571. *
  572. * @usage
  573. * ```html
  574. * <!-- using attribute directives -->
  575. * <ANY ng-messages="expression" role="alert">
  576. * <ANY ng-message="stringValue">...</ANY>
  577. * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
  578. * </ANY>
  579. *
  580. * <!-- or by using element directives -->
  581. * <ng-messages for="expression" role="alert">
  582. * <ng-message when="stringValue">...</ng-message>
  583. * <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
  584. * </ng-messages>
  585. * ```
  586. *
  587. * @param {expression} ngMessage|when a string value corresponding to the message key.
  588. */
  589. .directive('ngMessage', ngMessageDirectiveFactory())
  590. /**
  591. * @ngdoc directive
  592. * @name ngMessageExp
  593. * @restrict AE
  594. * @priority 1
  595. * @scope
  596. *
  597. * @description
  598. * `ngMessageExp` is the same as {@link directive:ngMessage `ngMessage`}, but instead of a static
  599. * value, it accepts an expression to be evaluated for the message key.
  600. *
  601. * @usage
  602. * ```html
  603. * <!-- using attribute directives -->
  604. * <ANY ng-messages="expression">
  605. * <ANY ng-message-exp="expressionValue">...</ANY>
  606. * </ANY>
  607. *
  608. * <!-- or by using element directives -->
  609. * <ng-messages for="expression">
  610. * <ng-message when-exp="expressionValue">...</ng-message>
  611. * </ng-messages>
  612. * ```
  613. *
  614. * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
  615. *
  616. * @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
  617. */
  618. .directive('ngMessageExp', ngMessageDirectiveFactory());
  619. function ngMessageDirectiveFactory() {
  620. return ['$animate', function($animate) {
  621. return {
  622. restrict: 'AE',
  623. transclude: 'element',
  624. priority: 1, // must run before ngBind, otherwise the text is set on the comment
  625. terminal: true,
  626. require: '^^ngMessages',
  627. link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
  628. var commentNode = element[0];
  629. var records;
  630. var staticExp = attrs.ngMessage || attrs.when;
  631. var dynamicExp = attrs.ngMessageExp || attrs.whenExp;
  632. var assignRecords = function(items) {
  633. records = items
  634. ? (isArray(items)
  635. ? items
  636. : items.split(/[\s,]+/))
  637. : null;
  638. ngMessagesCtrl.reRender();
  639. };
  640. if (dynamicExp) {
  641. assignRecords(scope.$eval(dynamicExp));
  642. scope.$watchCollection(dynamicExp, assignRecords);
  643. } else {
  644. assignRecords(staticExp);
  645. }
  646. var currentElement, messageCtrl;
  647. ngMessagesCtrl.register(commentNode, messageCtrl = {
  648. test: function(name) {
  649. return contains(records, name);
  650. },
  651. attach: function() {
  652. if (!currentElement) {
  653. $transclude(function(elm, newScope) {
  654. $animate.enter(elm, null, element);
  655. currentElement = elm;
  656. // Each time we attach this node to a message we get a new id that we can match
  657. // when we are destroying the node later.
  658. var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();
  659. // in the event that the element or a parent element is destroyed
  660. // by another structural directive then it's time
  661. // to deregister the message from the controller
  662. currentElement.on('$destroy', function() {
  663. if (currentElement && currentElement.$$attachId === $$attachId) {
  664. ngMessagesCtrl.deregister(commentNode);
  665. messageCtrl.detach();
  666. }
  667. newScope.$destroy();
  668. });
  669. });
  670. }
  671. },
  672. detach: function() {
  673. if (currentElement) {
  674. var elm = currentElement;
  675. currentElement = null;
  676. $animate.leave(elm);
  677. }
  678. }
  679. });
  680. }
  681. };
  682. }];
  683. function contains(collection, key) {
  684. if (collection) {
  685. return isArray(collection)
  686. ? collection.indexOf(key) >= 0
  687. : collection.hasOwnProperty(key);
  688. }
  689. }
  690. }
  691. })(window, window.angular);