This repository was archived by the owner on Apr 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27.4k
feat(ngMessages): add support for default message #16587
Merged
Merged
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ var jqLite; | |
* 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 allows keys in a key/value collection to be associated with a child element | ||
|
@@ -257,7 +257,24 @@ var jqLite; | |
* .some-message.ng-leave.ng-leave-active {} | ||
* ``` | ||
* | ||
* {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate. | ||
* {@link ngAnimate See the ngAnimate docs} to learn how to use JavaScript animations or to learn | ||
* more about ngAnimate. | ||
* | ||
* ## Displaying a default message | ||
* If the ngMessages renders no inner ngMessage directive (i.e. when none of the truthy | ||
* keys are matched by a defined message), then it will render a default message | ||
* using the {@link ngMessageDefault} directive. | ||
* Note that matched messages will always take precedence over unmatched messages. That means | ||
* the default message will not be displayed when another message is matched. This is also | ||
* true for `ng-messages-multiple`. | ||
* | ||
* ```html | ||
* <div ng-messages="myForm.myField.$error" role="alert"> | ||
* <div ng-message="required">This field is required</div> | ||
* <div ng-message="minlength">This field is too short</div> | ||
* <div ng-message-default>This field has an input error</div> | ||
* </div> | ||
* ``` | ||
*/ | ||
angular.module('ngMessages', [], function initAngularHelpers() { | ||
// Access helpers from AngularJS core. | ||
|
@@ -286,8 +303,11 @@ angular.module('ngMessages', [], function initAngularHelpers() { | |
* 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 remote template can also be used to promote message reusability and messages can also be | ||
* overridden. | ||
* A remote template can also be used (With {@link ngMessagesInclude}) to promote message | ||
* reusability and messages can also be overridden. | ||
* | ||
* A default message can also be displayed when no `ngMessage` directive is inserted, using the | ||
* {@link ngMessageDefault} directive. | ||
* | ||
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. | ||
* | ||
|
@@ -298,13 +318,15 @@ angular.module('ngMessages', [], function initAngularHelpers() { | |
* <ANY ng-message="stringValue">...</ANY> | ||
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY> | ||
* <ANY ng-message-exp="expressionValue">...</ANY> | ||
* <ANY ng-message-default>...</ANY> | ||
* </ANY> | ||
* | ||
* <!-- or by using element directives --> | ||
* <ng-messages for="expression" role="alert"> | ||
* <ng-message when="stringValue">...</ng-message> | ||
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message> | ||
* <ng-message when-exp="expressionValue">...</ng-message> | ||
* <ng-message-default>...</ng-message-default> | ||
* </ng-messages> | ||
* ``` | ||
* | ||
|
@@ -333,6 +355,7 @@ angular.module('ngMessages', [], function initAngularHelpers() { | |
* <div ng-message="required">You did not enter a field</div> | ||
* <div ng-message="minlength">Your field is too short</div> | ||
* <div ng-message="maxlength">Your field is too long</div> | ||
* <div ng-message-default>This field has an input error</div> | ||
* </div> | ||
* </form> | ||
* </file> | ||
|
@@ -370,6 +393,7 @@ angular.module('ngMessages', [], function initAngularHelpers() { | |
|
||
var unmatchedMessages = []; | ||
var matchedKeys = {}; | ||
var truthyKeys = 0; | ||
var messageItem = ctrl.head; | ||
var messageFound = false; | ||
var totalMessages = 0; | ||
|
@@ -382,13 +406,17 @@ angular.module('ngMessages', [], function initAngularHelpers() { | |
var messageUsed = false; | ||
if (!messageFound) { | ||
forEach(collection, function(value, key) { | ||
if (!messageUsed && truthy(value) && messageCtrl.test(key)) { | ||
// this is to prevent the same error name from showing up twice | ||
if (matchedKeys[key]) return; | ||
matchedKeys[key] = true; | ||
if (truthy(value) && !messageUsed) { | ||
truthyKeys++; | ||
|
||
messageUsed = true; | ||
messageCtrl.attach(); | ||
if (messageCtrl.test(key)) { | ||
// this is to prevent the same error name from showing up twice | ||
if (matchedKeys[key]) return; | ||
matchedKeys[key] = true; | ||
|
||
messageUsed = true; | ||
messageCtrl.attach(); | ||
} | ||
} | ||
}); | ||
} | ||
|
@@ -409,8 +437,15 @@ angular.module('ngMessages', [], function initAngularHelpers() { | |
}); | ||
|
||
if (unmatchedMessages.length !== totalMessages) { | ||
// Unset default message if set | ||
if (ctrl.default) ctrl.default.detach(); | ||
|
||
$animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS); | ||
} else { | ||
|
||
// Set default message if keys in collection do not match any message | ||
if (ctrl.default && truthyKeys > 0) ctrl.default.attach(); | ||
|
||
$animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS); | ||
} | ||
}; | ||
|
@@ -428,23 +463,31 @@ angular.module('ngMessages', [], function initAngularHelpers() { | |
} | ||
}; | ||
|
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the og PR you wondered if we need to detach the default controller first here @gkalpak - I've tested this and in defautl circumstances it definitely gets detached. |
||
} else { | ||
var key = comment.$$ngMessageNode; | ||
delete comment.$$ngMessageNode; | ||
removeMessageNode($element[0], comment, key); | ||
delete messages[key]; | ||
} | ||
ctrl.reRender(); | ||
}; | ||
|
||
|
@@ -647,9 +690,41 @@ angular.module('ngMessages', [], function initAngularHelpers() { | |
* | ||
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key. | ||
*/ | ||
.directive('ngMessageExp', ngMessageDirectiveFactory()); | ||
.directive('ngMessageExp', ngMessageDirectiveFactory()) | ||
|
||
/** | ||
* @ngdoc directive | ||
* @name ngMessageDefault | ||
* @restrict AE | ||
* @scope | ||
* | ||
* @description | ||
* `ngMessageDefault` is a directive with the purpose to show and hide a default message for | ||
* {@link ngMessages}, when none of provided messages matches. | ||
* | ||
* More information about using `ngMessageDefault` can be found in the | ||
* {@link module:ngMessages `ngMessages` module documentation}. | ||
* | ||
* @usage | ||
* ```html | ||
* <!-- using attribute directives --> | ||
* <ANY ng-messages="expression" role="alert"> | ||
* <ANY ng-message="stringValue">...</ANY> | ||
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY> | ||
* <ANY ng-message-default>...</ANY> | ||
* </ANY> | ||
* | ||
* <!-- or by using element directives --> | ||
* <ng-messages for="expression" role="alert"> | ||
* <ng-message when="stringValue">...</ng-message> | ||
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message> | ||
* <ng-message-default>...</ng-message-default> | ||
* </ng-messages> | ||
* | ||
*/ | ||
.directive('ngMessageDefault', ngMessageDirectiveFactory(true)); | ||
|
||
function ngMessageDirectiveFactory() { | ||
function ngMessageDirectiveFactory(isDefault) { | ||
return ['$animate', function($animate) { | ||
return { | ||
restrict: 'AE', | ||
|
@@ -658,25 +733,28 @@ function ngMessageDirectiveFactory() { | |
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(); | ||
}; | ||
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); | ||
if (dynamicExp) { | ||
assignRecords(scope.$eval(dynamicExp)); | ||
scope.$watchCollection(dynamicExp, assignRecords); | ||
} else { | ||
assignRecords(staticExp); | ||
} | ||
} | ||
|
||
var currentElement, messageCtrl; | ||
|
@@ -701,7 +779,7 @@ function ngMessageDirectiveFactory() { | |
// If the message element was removed via a call to `detach` then `currentElement` will be null | ||
// So this handler only handles cases where something else removed the message element. | ||
if (currentElement && currentElement.$$attachId === $$attachId) { | ||
ngMessagesCtrl.deregister(commentNode); | ||
ngMessagesCtrl.deregister(commentNode, isDefault); | ||
messageCtrl.detach(); | ||
} | ||
newScope.$destroy(); | ||
|
@@ -716,14 +794,14 @@ function ngMessageDirectiveFactory() { | |
$animate.leave(elm); | ||
} | ||
} | ||
}); | ||
}, isDefault); | ||
|
||
// We need to ensure that this directive deregisters itself when it no longer exists | ||
// Normally this is done when the attached element is destroyed; but if this directive | ||
// gets removed before we attach the message to the DOM there is nothing to watch | ||
// in which case we must deregister when the containing scope is destroyed. | ||
scope.$on('$destroy', function() { | ||
ngMessagesCtrl.deregister(commentNode); | ||
ngMessagesCtrl.deregister(commentNode, isDefault); | ||
}); | ||
} | ||
}; | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line should only be called if there is no default... Or perhaps this whole block should look like: