angular-messages.js 33 KB

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