Passed
Push — master ( 283033...66ce56 )
by Guangyu
07:51 queued 11s
created

admin/js/angular/angular-messages.js (1 issue)

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