@@ -18,7 +18,7 @@ var jqLite;
18
18
* sequencing based on the order of how the messages are defined in the template.
19
19
*
20
20
* Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
21
- * `ngMessage` and `ngMessageExp ` directives.
21
+ * `ngMessage`, `ngMessageExp` and `ngMessageDefault ` directives.
22
22
*
23
23
* ## Usage
24
24
* The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
@@ -257,7 +257,26 @@ var jqLite;
257
257
* .some-message.ng-leave.ng-leave-active {}
258
258
* ```
259
259
*
260
- * {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate.
260
+ * {@link ngAnimate See the ngAnimate docs} to learn how to use JavaScript animations or to learn
261
+ * more about ngAnimate.
262
+ *
263
+ * ## Displaying a default message
264
+ * If the ngMessages renders no inner ngMessage directive (i.e. when none of the truthy
265
+ * keys are matched by a defined message), then it will render a default message
266
+ * using the {@link ngMessageDefault} directive.
267
+ * Note that matched messages will always take precedence over unmatched messages. That means
268
+ * the default message will not be displayed when another message is matched. This is also
269
+ * true for `ng-messages-multiple`.
270
+ *
271
+ * ```html
272
+ * <div ng-messages="myForm.myField.$error" role="alert">
273
+ * <div ng-message="required">This field is required</div>
274
+ * <div ng-message="minlength">This field is too short</div>
275
+ * <div ng-message-default>This field has an input error</div>
276
+ * </div>
277
+ * ```
278
+ *
279
+
261
280
*/
262
281
angular . module ( 'ngMessages' , [ ] , function initAngularHelpers ( ) {
263
282
// Access helpers from AngularJS core.
@@ -286,8 +305,11 @@ angular.module('ngMessages', [], function initAngularHelpers() {
286
305
* at a time and this depends on the prioritization of the messages within the template. (This can
287
306
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
288
307
*
289
- * A remote template can also be used to promote message reusability and messages can also be
290
- * overridden.
308
+ * A remote template can also be used (With {@link ngMessagesInclude}) to promote message
309
+ * reusability and messages can also be overridden.
310
+ *
311
+ * A default message can also be displayed when no `ngMessage` directive is inserted, using the
312
+ * {@link ngMessageDefault} directive.
291
313
*
292
314
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
293
315
*
@@ -298,13 +320,15 @@ angular.module('ngMessages', [], function initAngularHelpers() {
298
320
* <ANY ng-message="stringValue">...</ANY>
299
321
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
300
322
* <ANY ng-message-exp="expressionValue">...</ANY>
323
+ * <ANY ng-message-default>...</ANY>
301
324
* </ANY>
302
325
*
303
326
* <!-- or by using element directives -->
304
327
* <ng-messages for="expression" role="alert">
305
328
* <ng-message when="stringValue">...</ng-message>
306
329
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
307
330
* <ng-message when-exp="expressionValue">...</ng-message>
331
+ * <ng-message-default>...</ng-message-default>
308
332
* </ng-messages>
309
333
* ```
310
334
*
@@ -333,6 +357,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
333
357
* <div ng-message="required">You did not enter a field</div>
334
358
* <div ng-message="minlength">Your field is too short</div>
335
359
* <div ng-message="maxlength">Your field is too long</div>
360
+ * <div ng-message-default>This field has an input error</div>
336
361
* </div>
337
362
* </form>
338
363
* </file>
@@ -370,6 +395,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
370
395
371
396
var unmatchedMessages = [ ] ;
372
397
var matchedKeys = { } ;
398
+ var truthyKeys = 0 ;
373
399
var messageItem = ctrl . head ;
374
400
var messageFound = false ;
375
401
var totalMessages = 0 ;
@@ -382,13 +408,17 @@ angular.module('ngMessages', [], function initAngularHelpers() {
382
408
var messageUsed = false ;
383
409
if ( ! messageFound ) {
384
410
forEach ( collection , function ( value , key ) {
385
- if ( ! messageUsed && truthy ( value ) && messageCtrl . test ( key ) ) {
386
- // this is to prevent the same error name from showing up twice
387
- if ( matchedKeys [ key ] ) return ;
388
- matchedKeys [ key ] = true ;
411
+ if ( truthy ( value ) && ! messageUsed ) {
412
+ truthyKeys ++ ;
413
+
414
+ if ( messageCtrl . test ( key ) ) {
415
+ // this is to prevent the same error name from showing up twice
416
+ if ( matchedKeys [ key ] ) return ;
417
+ matchedKeys [ key ] = true ;
389
418
390
- messageUsed = true ;
391
- messageCtrl . attach ( ) ;
419
+ messageUsed = true ;
420
+ messageCtrl . attach ( ) ;
421
+ }
392
422
}
393
423
} ) ;
394
424
}
@@ -408,7 +438,16 @@ angular.module('ngMessages', [], function initAngularHelpers() {
408
438
messageCtrl . detach ( ) ;
409
439
} ) ;
410
440
411
- if ( unmatchedMessages . length !== totalMessages ) {
441
+ var messageMatched = unmatchedMessages . length !== totalMessages ;
442
+ var attachDefault = ctrl . default && ! messageMatched && truthyKeys > 0 ;
443
+
444
+ if ( attachDefault ) {
445
+ ctrl . default . attach ( ) ;
446
+ } else if ( ctrl . default ) {
447
+ ctrl . default . detach ( ) ;
448
+ }
449
+
450
+ if ( messageMatched || attachDefault ) {
412
451
$animate . setClass ( $element , ACTIVE_CLASS , INACTIVE_CLASS ) ;
413
452
} else {
414
453
$animate . setClass ( $element , INACTIVE_CLASS , ACTIVE_CLASS ) ;
@@ -428,23 +467,31 @@ angular.module('ngMessages', [], function initAngularHelpers() {
428
467
}
429
468
} ;
430
469
431
- this . register = function ( comment , messageCtrl ) {
432
- var nextKey = latestKey . toString ( ) ;
433
- messages [ nextKey ] = {
434
- message : messageCtrl
435
- } ;
436
- insertMessageNode ( $element [ 0 ] , comment , nextKey ) ;
437
- comment . $$ngMessageNode = nextKey ;
438
- latestKey ++ ;
470
+ this . register = function ( comment , messageCtrl , isDefault ) {
471
+ if ( isDefault ) {
472
+ ctrl . default = messageCtrl ;
473
+ } else {
474
+ var nextKey = latestKey . toString ( ) ;
475
+ messages [ nextKey ] = {
476
+ message : messageCtrl
477
+ } ;
478
+ insertMessageNode ( $element [ 0 ] , comment , nextKey ) ;
479
+ comment . $$ngMessageNode = nextKey ;
480
+ latestKey ++ ;
481
+ }
439
482
440
483
ctrl . reRender ( ) ;
441
484
} ;
442
485
443
- this . deregister = function ( comment ) {
444
- var key = comment . $$ngMessageNode ;
445
- delete comment . $$ngMessageNode ;
446
- removeMessageNode ( $element [ 0 ] , comment , key ) ;
447
- delete messages [ key ] ;
486
+ this . deregister = function ( comment , isDefault ) {
487
+ if ( isDefault ) {
488
+ delete ctrl . default ;
489
+ } else {
490
+ var key = comment . $$ngMessageNode ;
491
+ delete comment . $$ngMessageNode ;
492
+ removeMessageNode ( $element [ 0 ] , comment , key ) ;
493
+ delete messages [ key ] ;
494
+ }
448
495
ctrl . reRender ( ) ;
449
496
} ;
450
497
@@ -647,9 +694,41 @@ angular.module('ngMessages', [], function initAngularHelpers() {
647
694
*
648
695
* @param {expression } ngMessageExp|whenExp an expression value corresponding to the message key.
649
696
*/
650
- . directive ( 'ngMessageExp' , ngMessageDirectiveFactory ( ) ) ;
697
+ . directive ( 'ngMessageExp' , ngMessageDirectiveFactory ( ) )
698
+
699
+ /**
700
+ * @ngdoc directive
701
+ * @name ngMessageDefault
702
+ * @restrict AE
703
+ * @scope
704
+ *
705
+ * @description
706
+ * `ngMessageDefault` is a directive with the purpose to show and hide a default message for
707
+ * {@link ngMessages}, when none of provided messages matches.
708
+ *
709
+ * More information about using `ngMessageDefault` can be found in the
710
+ * {@link module:ngMessages `ngMessages` module documentation}.
711
+ *
712
+ * @usage
713
+ * ```html
714
+ * <!-- using attribute directives -->
715
+ * <ANY ng-messages="expression" role="alert">
716
+ * <ANY ng-message="stringValue">...</ANY>
717
+ * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
718
+ * <ANY ng-message-default>...</ANY>
719
+ * </ANY>
720
+ *
721
+ * <!-- or by using element directives -->
722
+ * <ng-messages for="expression" role="alert">
723
+ * <ng-message when="stringValue">...</ng-message>
724
+ * <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
725
+ * <ng-message-default>...</ng-message-default>
726
+ * </ng-messages>
727
+ *
728
+ */
729
+ . directive ( 'ngMessageDefault' , ngMessageDirectiveFactory ( true ) ) ;
651
730
652
- function ngMessageDirectiveFactory ( ) {
731
+ function ngMessageDirectiveFactory ( isDefault ) {
653
732
return [ '$animate' , function ( $animate ) {
654
733
return {
655
734
restrict : 'AE' ,
@@ -658,25 +737,28 @@ function ngMessageDirectiveFactory() {
658
737
terminal : true ,
659
738
require : '^^ngMessages' ,
660
739
link : function ( scope , element , attrs , ngMessagesCtrl , $transclude ) {
661
- var commentNode = element [ 0 ] ;
662
-
663
- var records ;
664
- var staticExp = attrs . ngMessage || attrs . when ;
665
- var dynamicExp = attrs . ngMessageExp || attrs . whenExp ;
666
- var assignRecords = function ( items ) {
667
- records = items
668
- ? ( isArray ( items )
669
- ? items
670
- : items . split ( / [ \s , ] + / ) )
671
- : null ;
672
- ngMessagesCtrl . reRender ( ) ;
673
- } ;
740
+ var commentNode , records , staticExp , dynamicExp ;
741
+
742
+ if ( ! isDefault ) {
743
+ commentNode = element [ 0 ] ;
744
+ staticExp = attrs . ngMessage || attrs . when ;
745
+ dynamicExp = attrs . ngMessageExp || attrs . whenExp ;
746
+
747
+ var assignRecords = function ( items ) {
748
+ records = items
749
+ ? ( isArray ( items )
750
+ ? items
751
+ : items . split ( / [ \s , ] + / ) )
752
+ : null ;
753
+ ngMessagesCtrl . reRender ( ) ;
754
+ } ;
674
755
675
- if ( dynamicExp ) {
676
- assignRecords ( scope . $eval ( dynamicExp ) ) ;
677
- scope . $watchCollection ( dynamicExp , assignRecords ) ;
678
- } else {
679
- assignRecords ( staticExp ) ;
756
+ if ( dynamicExp ) {
757
+ assignRecords ( scope . $eval ( dynamicExp ) ) ;
758
+ scope . $watchCollection ( dynamicExp , assignRecords ) ;
759
+ } else {
760
+ assignRecords ( staticExp ) ;
761
+ }
680
762
}
681
763
682
764
var currentElement , messageCtrl ;
@@ -701,7 +783,7 @@ function ngMessageDirectiveFactory() {
701
783
// If the message element was removed via a call to `detach` then `currentElement` will be null
702
784
// So this handler only handles cases where something else removed the message element.
703
785
if ( currentElement && currentElement . $$attachId === $$attachId ) {
704
- ngMessagesCtrl . deregister ( commentNode ) ;
786
+ ngMessagesCtrl . deregister ( commentNode , isDefault ) ;
705
787
messageCtrl . detach ( ) ;
706
788
}
707
789
newScope . $destroy ( ) ;
@@ -716,14 +798,14 @@ function ngMessageDirectiveFactory() {
716
798
$animate . leave ( elm ) ;
717
799
}
718
800
}
719
- } ) ;
801
+ } , isDefault ) ;
720
802
721
803
// We need to ensure that this directive deregisters itself when it no longer exists
722
804
// Normally this is done when the attached element is destroyed; but if this directive
723
805
// gets removed before we attach the message to the DOM there is nothing to watch
724
806
// in which case we must deregister when the containing scope is destroyed.
725
807
scope . $on ( '$destroy' , function ( ) {
726
- ngMessagesCtrl . deregister ( commentNode ) ;
808
+ ngMessagesCtrl . deregister ( commentNode , isDefault ) ;
727
809
} ) ;
728
810
}
729
811
} ;
0 commit comments