diff --git a/src/ngMessages/messages.js b/src/ngMessages/messages.js index 14ffe00506b0..6edb638dfb82 100644 --- a/src/ngMessages/messages.js +++ b/src/ngMessages/messages.js @@ -21,7 +21,7 @@ var jqLite = angular.element; * sequencing based on the order of how the messages are defined in the template. * * Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude` - * `ngMessage` and `ngMessageExp` directives. + * `ngMessage`, `ngMessageExp` and `ngMessageDefault` directives. * * # Usage * The `ngMessages` directive listens on a key/value collection which is set on the ngMessages attribute. @@ -239,6 +239,19 @@ var jqLite = angular.element; * .some-message.ng-leave.ng-leave-active {} * ``` * + * ## Displaying a default message + * If the ngMessages renders no inner ngMessage directive (that is to say when the key values does not + * match the attribute value present on each ngMessage directive), then it will render a default message + * using the ngMessageDefault directive. + * + * ```html + *
+ *
This field is required
+ *
This field is too short
+ *
This is a default message
+ *
+ * ``` + * * {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate. */ angular.module('ngMessages', []) @@ -260,6 +273,9 @@ angular.module('ngMessages', []) * at a time and this depends on the prioritization of the messages within the template. (This can * be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.) * + * A default message can also be displayed when no `ngMessage` directive is inserted, using the + * `ngMessageDefault` directive. + * * A remote template can also be used to promote message reusability and messages can also be * overridden. * @@ -272,6 +288,7 @@ angular.module('ngMessages', []) * ... * ... * ... + * ... * * * @@ -279,6 +296,7 @@ angular.module('ngMessages', []) * ... * ... * ... + * ... * * ``` * @@ -307,6 +325,7 @@ angular.module('ngMessages', []) *
You did not enter a field
*
Your field is too short
*
Your field is too long
+ *
This is a default message
* * * @@ -379,9 +398,17 @@ angular.module('ngMessages', []) messageCtrl.detach(); }); - unmatchedMessages.length !== totalMessages - ? $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS) - : $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS); + if (unmatchedMessages.length !== totalMessages) { + // Unset default message if setted + if (ctrl.default) { ctrl.default.detach(); } + + $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS); + } else { + // Set default message when no other one matched + if (ctrl.default) { ctrl.default.attach(); } + + $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS); + } }; $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render); @@ -397,23 +424,32 @@ angular.module('ngMessages', []) } }; - this.register = function(comment, messageCtrl) { - var nextKey = latestKey.toString(); - messages[nextKey] = { - message: messageCtrl - }; - insertMessageNode($element[0], comment, nextKey); - comment.$$ngMessageNode = nextKey; - latestKey++; + this.register = function(comment, messageCtrl, isDefault) { + if (isDefault) { + ctrl.default = messageCtrl; + } else { + var nextKey = latestKey.toString(); + messages[nextKey] = { + message: messageCtrl + }; + insertMessageNode($element[0], comment, nextKey); + comment.$$ngMessageNode = nextKey; + latestKey++; + } ctrl.reRender(); }; - this.deregister = function(comment) { - var key = comment.$$ngMessageNode; - delete comment.$$ngMessageNode; - removeMessageNode($element[0], comment, key); - delete messages[key]; + this.deregister = function(comment, isDefault) { + if (isDefault) { + delete ctrl.default; + } else { + var key = comment.$$ngMessageNode; + delete comment.$$ngMessageNode; + removeMessageNode($element[0], comment, key); + delete messages[key]; + } + ctrl.reRender(); }; @@ -594,9 +630,45 @@ angular.module('ngMessages', []) * * @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key. */ - .directive('ngMessageExp', ngMessageDirectiveFactory('A')); + .directive('ngMessageExp', ngMessageDirectiveFactory('A')) + + /** + * @ngdoc directive + * @name ngMessageDefault + * @restrict AE + * @scope + * + * @description + * `ngMessageDefault` is a directive with the purpose to show and hide a default message. + * For `ngMessageDefault` to operate, no `ngMessage` inner directive should be displayed + * in the parent `ngMessages` directive. + * + * More information about using `ngMessageDefault` can be found in the + * {@link module:ngMessages `ngMessages` module documentation}. + * + * @usage + * ```html + * + * + * ... + * ... + * ... + * + * + * + * + * ... + * ... + * ... + * + * ``` + * + * @param {expression} ngMessageDefault|when no ngMessage matches. + */ + .directive('ngMessageDefault', ngMessageDirectiveFactory('AE', true)); -function ngMessageDirectiveFactory(restrict) { + +function ngMessageDirectiveFactory(restrict, isDefault) { return ['$animate', function($animate) { return { restrict: 'AE', @@ -604,25 +676,28 @@ function ngMessageDirectiveFactory(restrict) { terminal: true, require: '^^ngMessages', link: function(scope, element, attrs, ngMessagesCtrl, $transclude) { - var commentNode = element[0]; - - var records; - var staticExp = attrs.ngMessage || attrs.when; - var dynamicExp = attrs.ngMessageExp || attrs.whenExp; - var assignRecords = function(items) { - records = items - ? (isArray(items) - ? items - : items.split(/[\s,]+/)) - : null; - ngMessagesCtrl.reRender(); - }; - - if (dynamicExp) { - assignRecords(scope.$eval(dynamicExp)); - scope.$watchCollection(dynamicExp, assignRecords); - } else { - assignRecords(staticExp); + var commentNode, records, staticExp, dynamicExp; + + if (!isDefault) { + commentNode = element[0]; + staticExp = attrs.ngMessage || attrs.when; + dynamicExp = attrs.ngMessageExp || attrs.whenExp; + + var assignRecords = function(items) { + records = items + ? (isArray(items) + ? items + : items.split(/[\s,]+/)) + : null; + ngMessagesCtrl.reRender(); + }; + + if (dynamicExp) { + assignRecords(scope.$eval(dynamicExp)); + scope.$watchCollection(dynamicExp, assignRecords); + } else { + assignRecords(staticExp); + } } var currentElement, messageCtrl; @@ -641,7 +716,7 @@ function ngMessageDirectiveFactory(restrict) { // to deregister the message from the controller currentElement.on('$destroy', function() { if (currentElement) { - ngMessagesCtrl.deregister(commentNode); + ngMessagesCtrl.deregister(commentNode, isDefault); messageCtrl.detach(); } }); @@ -655,7 +730,7 @@ function ngMessageDirectiveFactory(restrict) { $animate.leave(elm); } } - }); + }, isDefault); } }; }]; diff --git a/test/ngMessages/messagesSpec.js b/test/ngMessages/messagesSpec.js index 037175031ded..f81cd4baadf0 100644 --- a/test/ngMessages/messagesSpec.js +++ b/test/ngMessages/messagesSpec.js @@ -401,6 +401,24 @@ describe('ngMessages', function() { }); }); + it('should render a default message when no one matched', inject(function($rootScope, $compile) { + element = $compile('
' + + '
Message is set
' + + '
Default message is set
' + + '
')($rootScope); + $rootScope.$digest(); + + //expect(element.text()).not.toContain('Message is set'); + expect(element.text()).toContain('Default message is set'); + + $rootScope.$apply(function() { + $rootScope.col = { val: true }; + }); + + //expect(element.text()).toContain('Message is set'); + expect(element.text()).not.toContain('Default message is set'); + })); + describe('when including templates', function() { they('should work with a dynamic collection model which is managed by ngRepeat', {'
': '
' +