angular-messages.js 27 KB

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