Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 62b285d

Browse files
gdangeloNarretz
authored andcommitted
feat(ngMessages): add support for default message
Added support for showing default message when no values are mapped with ng-message. Closes #12008
1 parent 459997b commit 62b285d

File tree

2 files changed

+153
-40
lines changed

2 files changed

+153
-40
lines changed

src/ngMessages/messages.js

+110-40
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var jqLite;
1818
* sequencing based on the order of how the messages are defined in the template.
1919
*
2020
* Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
21-
* `ngMessage` and `ngMessageExp` directives.
21+
* `ngMessage`, `ngMessageExp` and `ngMessageDefault` directives.
2222
*
2323
* ## Usage
2424
* The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
@@ -257,7 +257,21 @@ var jqLite;
257257
* .some-message.ng-leave.ng-leave-active {}
258258
* ```
259259
*
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 (that is to say when the key values does not
265+
* match the attribute value present on each ngMessage directive), then it will render a default message
266+
* using the {@link ngMessageDefault} directive.
267+
*
268+
* ```html
269+
* <div ng-messages="myForm.myField.$error" role="alert">
270+
* <div ng-message="required">This field is required</div>
271+
* <div ng-message="minlength">This field is too short</div>
272+
* <div ng-message-default>This field has an input error</div>
273+
* </div>
274+
* ```
261275
*/
262276
angular.module('ngMessages', [], function initAngularHelpers() {
263277
// Access helpers from AngularJS core.
@@ -286,8 +300,11 @@ angular.module('ngMessages', [], function initAngularHelpers() {
286300
* at a time and this depends on the prioritization of the messages within the template. (This can
287301
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
288302
*
289-
* A remote template can also be used to promote message reusability and messages can also be
290-
* overridden.
303+
* A remote template can also be used (With {@link ngMessagesInclude}) to promote message
304+
* reusability and messages can also be overridden.
305+
*
306+
* A default message can also be displayed when no `ngMessage` directive is inserted, using the
307+
* {@link ngMessageDefault} directive.
291308
*
292309
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
293310
*
@@ -298,13 +315,15 @@ angular.module('ngMessages', [], function initAngularHelpers() {
298315
* <ANY ng-message="stringValue">...</ANY>
299316
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
300317
* <ANY ng-message-exp="expressionValue">...</ANY>
318+
* <ANY ng-message-default>...</ANY>
301319
* </ANY>
302320
*
303321
* <!-- or by using element directives -->
304322
* <ng-messages for="expression" role="alert">
305323
* <ng-message when="stringValue">...</ng-message>
306324
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
307325
* <ng-message when-exp="expressionValue">...</ng-message>
326+
* <ng-message-default>...</ng-message-default>
308327
* </ng-messages>
309328
* ```
310329
*
@@ -333,6 +352,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
333352
* <div ng-message="required">You did not enter a field</div>
334353
* <div ng-message="minlength">Your field is too short</div>
335354
* <div ng-message="maxlength">Your field is too long</div>
355+
* <div ng-message-default>This field has an input error</div>
336356
* </div>
337357
* </form>
338358
* </file>
@@ -409,8 +429,15 @@ angular.module('ngMessages', [], function initAngularHelpers() {
409429
});
410430

411431
if (unmatchedMessages.length !== totalMessages) {
432+
// Unset default message if set
433+
if (ctrl.default) ctrl.default.detach();
434+
412435
$animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS);
413436
} else {
437+
438+
// Set default message if no other matched
439+
if (ctrl.default) ctrl.default.attach();
440+
414441
$animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
415442
}
416443
};
@@ -428,23 +455,31 @@ angular.module('ngMessages', [], function initAngularHelpers() {
428455
}
429456
};
430457

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++;
458+
this.register = function(comment, messageCtrl, isDefault) {
459+
if (isDefault) {
460+
ctrl.default = messageCtrl;
461+
} else {
462+
var nextKey = latestKey.toString();
463+
messages[nextKey] = {
464+
message: messageCtrl
465+
};
466+
insertMessageNode($element[0], comment, nextKey);
467+
comment.$$ngMessageNode = nextKey;
468+
latestKey++;
469+
}
439470

440471
ctrl.reRender();
441472
};
442473

443-
this.deregister = function(comment) {
444-
var key = comment.$$ngMessageNode;
445-
delete comment.$$ngMessageNode;
446-
removeMessageNode($element[0], comment, key);
447-
delete messages[key];
474+
this.deregister = function(comment, isDefault) {
475+
if (isDefault) {
476+
delete ctrl.default;
477+
} else {
478+
var key = comment.$$ngMessageNode;
479+
delete comment.$$ngMessageNode;
480+
removeMessageNode($element[0], comment, key);
481+
delete messages[key];
482+
}
448483
ctrl.reRender();
449484
};
450485

@@ -647,9 +682,41 @@ angular.module('ngMessages', [], function initAngularHelpers() {
647682
*
648683
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
649684
*/
650-
.directive('ngMessageExp', ngMessageDirectiveFactory());
685+
.directive('ngMessageExp', ngMessageDirectiveFactory())
686+
687+
/**
688+
* @ngdoc directive
689+
* @name ngMessageDefault
690+
* @restrict AE
691+
* @scope
692+
*
693+
* @description
694+
* `ngMessageDefault` is a directive with the purpose to show and hide a default message for
695+
* {@link ngMessages}, when none of provided messages matches.
696+
*
697+
* More information about using `ngMessageDefault` can be found in the
698+
* {@link module:ngMessages `ngMessages` module documentation}.
699+
*
700+
* @usage
701+
* ```html
702+
* <!-- using attribute directives -->
703+
* <ANY ng-messages="expression" role="alert">
704+
* <ANY ng-message="stringValue">...</ANY>
705+
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
706+
* <ANY ng-message-default>...</ANY>
707+
* </ANY>
708+
*
709+
* <!-- or by using element directives -->
710+
* <ng-messages for="expression" role="alert">
711+
* <ng-message when="stringValue">...</ng-message>
712+
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
713+
* <ng-message-default>...</ng-message-default>
714+
* </ng-messages>
715+
*
716+
*/
717+
.directive('ngMessageDefault', ngMessageDirectiveFactory(true));
651718

652-
function ngMessageDirectiveFactory() {
719+
function ngMessageDirectiveFactory(isDefault) {
653720
return ['$animate', function($animate) {
654721
return {
655722
restrict: 'AE',
@@ -658,25 +725,28 @@ function ngMessageDirectiveFactory() {
658725
terminal: true,
659726
require: '^^ngMessages',
660727
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-
};
728+
var commentNode, records, staticExp, dynamicExp;
729+
730+
if (!isDefault) {
731+
commentNode = element[0];
732+
staticExp = attrs.ngMessage || attrs.when;
733+
dynamicExp = attrs.ngMessageExp || attrs.whenExp;
734+
735+
var assignRecords = function(items) {
736+
records = items
737+
? (isArray(items)
738+
? items
739+
: items.split(/[\s,]+/))
740+
: null;
741+
ngMessagesCtrl.reRender();
742+
};
674743

675-
if (dynamicExp) {
676-
assignRecords(scope.$eval(dynamicExp));
677-
scope.$watchCollection(dynamicExp, assignRecords);
678-
} else {
679-
assignRecords(staticExp);
744+
if (dynamicExp) {
745+
assignRecords(scope.$eval(dynamicExp));
746+
scope.$watchCollection(dynamicExp, assignRecords);
747+
} else {
748+
assignRecords(staticExp);
749+
}
680750
}
681751

682752
var currentElement, messageCtrl;
@@ -701,7 +771,7 @@ function ngMessageDirectiveFactory() {
701771
// If the message element was removed via a call to `detach` then `currentElement` will be null
702772
// So this handler only handles cases where something else removed the message element.
703773
if (currentElement && currentElement.$$attachId === $$attachId) {
704-
ngMessagesCtrl.deregister(commentNode);
774+
ngMessagesCtrl.deregister(commentNode, isDefault);
705775
messageCtrl.detach();
706776
}
707777
newScope.$destroy();
@@ -716,14 +786,14 @@ function ngMessageDirectiveFactory() {
716786
$animate.leave(elm);
717787
}
718788
}
719-
});
789+
}, isDefault);
720790

721791
// We need to ensure that this directive deregisters itself when it no longer exists
722792
// Normally this is done when the attached element is destroyed; but if this directive
723793
// gets removed before we attach the message to the DOM there is nothing to watch
724794
// in which case we must deregister when the containing scope is destroyed.
725795
scope.$on('$destroy', function() {
726-
ngMessagesCtrl.deregister(commentNode);
796+
ngMessagesCtrl.deregister(commentNode, isDefault);
727797
});
728798
}
729799
};

test/ngMessages/messagesSpec.js

+43
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,49 @@ describe('ngMessages', function() {
661661
);
662662

663663

664+
describe('default message', function() {
665+
it('should render a default message when no message matches', inject(function($rootScope, $compile) {
666+
element = $compile('<div ng-messages="col">' +
667+
' <div ng-message="val">Message is set</div>' +
668+
' <div ng-message-default>Default message is set</div>' +
669+
'</div>')($rootScope);
670+
$rootScope.$digest();
671+
672+
expect(element.text().trim()).toBe('Default message is set');
673+
674+
$rootScope.$apply(function() {
675+
$rootScope.col = { val: true };
676+
});
677+
678+
expect(element.text().trim()).toBe('Message is set');
679+
}));
680+
681+
it('should handle a default message with ngIf', inject(function($rootScope, $compile) {
682+
element = $compile('<div ng-messages="col">' +
683+
' <div ng-message="val">Message is set</div>' +
684+
' <div ng-if="default" ng-message-default>Default message is set</div>' +
685+
'</div>')($rootScope);
686+
$rootScope.default = true;
687+
$rootScope.$digest();
688+
689+
expect(element.text().trim()).toBe('Default message is set');
690+
691+
$rootScope.$apply('default = false');
692+
693+
expect(element.text().trim()).toBe('');
694+
695+
$rootScope.$apply('default = true');
696+
697+
expect(element.text().trim()).toBe('Default message is set');
698+
699+
$rootScope.$apply(function() {
700+
$rootScope.col = { val: true };
701+
});
702+
703+
expect(element.text().trim()).toBe('Message is set');
704+
}));
705+
});
706+
664707
describe('when including templates', function() {
665708
they('should work with a dynamic collection model which is managed by ngRepeat',
666709
{'<div ng-messages-include="...">': '<div ng-messages="item">' +

0 commit comments

Comments
 (0)