How to validate against multiple fields and display a unique error? - angularjs

I want to validate a control against multiple fields. And I want to display an error indicating which field(s) caused validation to fail.
Run the following code. Change the first value to a high number (E.G. 5) and validation will pass. Change the first value to a low number (2) and validation will fail.
In the case of "2" there should be 2 errors: model3, model4 because those models are higher values than 2. Similarly for all other fields.
The validation works fine. I'm having trouble displaying the correct error messages based on the particular validation rule that failed.
Note, any field changes must re-fire validation just like it does now. You should run this snippet in full page view.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.model = {number: "1"};
$scope.model2 = {number: "2"};
$scope.model3 = {number: "3"};
$scope.model4 = {number: "4"};
});
app.directive('theGreatest', function(){
return {
require: 'ngModel',
restrict: 'A',
link: function($scope, $element, $attr, ngModel) {
var compareCollection;
// watch the attribute to get the date we need to compare against
$attr.$observe('theGreatest', function (val) {
console.log('compareCollection set to: ', val);
compareCollection = JSON.parse(val);
ngModel.$validate();
});
ngModel.$validators.theGreatest = function(modelValue, viewValue) {
console.log('validating...', modelValue);
console.log('compareDate: ', compareCollection);
var pass = true;
_.map(compareCollection, function(compare){
console.log('comparing value: ', compare);
if(modelValue < compare){
pass = false;
}
});
console.log('validation pass', pass);
return pass;
};
}
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js"></script>
<section ng-app="app" ng-controller="MainCtrl">
<div>first: <input type="text" ng-model="model.number" the-greatest="{{[model2.number, model3.number, model4.number]}}" />
(change me to a high number)
</div>
<div>second: <input ng-model="model2.number" type="text" /></div>
<div>third: <input ng-model="model3.number" type="text" /></div>
<div>fourth: <input ng-model="model4.number" type="text" /></div>
<div>validation passed if you see a value here: {{model.number}}</div>
<div>The following errors are not implemented correctly. The intention is to show what I am want to accomplish</div>
<div ng-if="!model.number">ERROR: first is less than model 2</div>
<div ng-if="!model.number">ERROR: first is less than model 3</div>
<div ng-if="!model.number">ERROR: first is less than model 4</div>
<div ng-if="!model.number">ERROR: first is required</div>
</section>

You need to send ErrorFlags array into directive and while you are validating mark those flags as false when validation fails.
HTML:
<section ng-app="app" ng-controller="MainCtrl">
<div>first: <input type="text" ng-model="model.number" the-greatest="{{[model2.number, model3.number, model4.number]}}" error-flags="errorFlags" />
(change me to a high number)
</div>
<div>second: <input ng-model="model2.number" type="text" /></div>
<div>third: <input ng-model="model3.number" type="text" /></div>
<div>fourth: <input ng-model="model4.number" type="text" /></div>
<div>validation passed if you see a value here: {{model.number}}</div>
<div>The following errors are not implemented correctly. The intention is to show what I want to accomplish</div>
<div ng-if="!errorFlags[0]">ERROR: first is less than model 2</div>
<div ng-if="!errorFlags[1]">ERROR: first is less than model 3</div>
<div ng-if="!errorFlags[2]">ERROR: first is less than model 4</div>
<div ng-if="!model.number">ERROR: first is required</div>
</section>
AngularJS Code:
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.model = {number: "1"};
$scope.model2 = {number: "2"};
$scope.model3 = {number: "3"};
$scope.model4 = {number: "4"};
$scope.errorFlags = [true, true , true];
});
app.directive('theGreatest', function(){
return {
require: 'ngModel',
restrict: 'A',
scope: {
errorFlags:"="
},
link: function(scope, element, attrs, ngModel) {
var compareCollection;
// watch the attribute to get the date we need to compare against
attrs.$observe('theGreatest', function (val) {
console.log('compareCollection set to: ', val);
compareCollection = JSON.parse(val);
ngModel.$validate();
});
ngModel.$validators.theGreatest = function(modelValue, viewValue) {
console.log('validating...', modelValue);
console.log('compareDate: ', compareCollection);
scope.errorFlags = [true, true, true];
console.log("Before Validation Flags", scope.errorFlags);
var pass = true;
var loopVariable = 0;
_.map(compareCollection, function(compare){
console.log('comparing value: ', compare);
if(modelValue < compare){
pass = false;
scope.errorFlags[loopVariable] = false;
}
loopVariable++;
});
console.log("after Validation Flags", scope.errorFlags);
console.log('validation pass', pass);
return pass;
};
}
};
});

Related

Need to check minimum 2 or more emails in one field in angular js

Right now I have it like this
<input
type="text"
id="EmailAddresses"
required
name="EmailAddresses"
data-ng-pattern="/.+#.+/"
placeholder="yourname#organization.org; name#organization.org">
<div data-ng-message="pattern">
This needs to be a valid email or a semicolon separated list of emails </div>
which works fine if I have atleast 1 email in the field but I want to check if it has at least 2 emails separated by a semicolon in the same field.. How can I do this?
Taken from Modifying built-in validators you need to split the string from the input and invalidate the form if one of the substring is invalid. This is just a naive example to get you started:
Plunk
https://plnkr.co/edit/ABQg3zba7f9sPei4?p=preview
JS
var app = angular.module('customEmailValidation',[]);
app.controller('MainCtrl', ['$scope', function($scope){
$scope.email = '';
$scope.submitForm = function(){
}
}]);
app.directive('multimail', function() {
var EMAIL_REGEXP = /\S+#\S+\.\S+/i;
return {
require: '?ngModel',
link: function(scope, elm, attrs, ctrl) {
// only apply the validator if ngModel is present and AngularJS has added the email validator
if (ctrl && ctrl.$validators.email) {
// this will overwrite the default AngularJS email validator
ctrl.$validators.email = function(modelValue) {
if (ctrl.$isEmpty(modelValue)) {
return true;
}
var mails = [];
if (modelValue.indexOf(' ') > 0) {
mails = modelValue.split(' ');
} else {
mails.push(modelValue);
}
var valid = true;
for(var i = 0; i < mails.length; i++) {
if (!EMAIL_REGEXP.test(mails[i])) {
valid = false;
}
}
return valid;
};
}
}
};
});
HTML
<body ng-app="customEmailValidation" ng-cloak>
<div ng-controller="MainCtrl">
<form name="myForm" ng-submit="submitForm()">
<label>Email Address</label>
<input
multimail
type="email"
id="EmailAddresses"
required
name="EmailAddresses"
ng-model="email"
name="email"
placeholder="yourname#organization.org name#organization.org">
<div class="error" ng-show="!!myForm.email.$error">
<p ng-show="myForm.email.$error.required">This is a required field</p>
<p ng-show="myForm.email.$error.email">Your email address is invalid</p>
</div>
<button type="sumbit">Submit</button>
</form>
</div>
</body>
You need to write a custom directive which can split email address by semicolon, iterate through each & validate.

AngularJS - Validate when ng-model value not in Select options

I have a situation where the ng-model value may sometimes not be present in the ng-options list of a Select dropdown (ID of a user that is no longer with the company saved in a record, for example). AngularJS's behavior is that it adds an empty option to the dropdown, but does not mark it as Invalid or Error, which makes it difficult to validate. I found a solution here that uses a directive to handle this situation upon loading the page. Because I get the user Id and the list using an API call, after researching I added a $watch to make sure the data was loaded before checking, but I can't seem to get it to work. Can someone help me figure out what's wrong with my code? It seems the code inside the $validators function does not get called properly or all the time. What I need is to be able to show a message when the user ID is not in the select list, but when the user selects a name from the dropdown, the message should go away.
Here's the relevant part of my code:
<div ng-app="RfiApp" ng-controller="RfiControllerRef" class="col-sm-12">
<form class="pure-form pure-form-aligned" name="frm" method="post"
autocomplete="off">
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-md-2" for="bsa_analyst_user_id">BSA Analyst</label>
<div class="col-md-10">
<select class="form-control" id="bsa_analyst_user_id" name="bsa_analyst_user_id" ng-model="rfi.bsa_analyst_user_id" ng-options="s.value as s.name for s in BsaAnalysts | orderBy: 'name'" valid-values="BsaAnalysts" required>
<option value="">--Select--</option>
</select>{{frm.bsa_analyst_user_id.$error}} - {{frm.bsa_analyst_user_id.$invalid}} - {{rfi.bsa_analyst_user_id}}
<div class="text-danger"
ng-show="frm.bsa_analyst_user_id.$invalid"
ng-messages="frm.bsa_analyst_user_id.$error">
<div ng-message="required">BSA Analyst is required</div>
<div ng-message="validValues">BSA Analyst in database not valid or not in BSA Unit selected</div>
</div>
</div>
</div>
</div>
</form>
</div>
.JS Code:
RfiApp.directive('validValues', function () {
return {
scope: {
validValues: '=',
model: '=ngModel'
},
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attributes, ngModel) {
scope.$watch('[model,validValues]', function (newVals) {
if (newVals[0] && newVals[1]) {
var values = angular.isArray(scope.validValues)
? scope.validValues
: Object.keys(scope.validValues);
ngModel.$$runValidators(newVals[0], values, function () { });
ngModel.$validators.validValues = function (modelValue) {
var result = values.filter(function (obj) {
return obj.value == modelValue;
});
var ret = result.length > 0;
return ret;
}
}
});
}
}
});
$scope.GetDropdownValues = function () {
var a = 1;
DropdownOptions.get(
function (response) {
//... other code ommitted
$scope.UserAnalysts = response.UserAnalystsList;
},
function () {
showMessage('error', "Error");
}
);
};
$scope.GetExistingRfi = function () {
NewRfiResource.get({ rfiId: $scope.rfiId },
function (response) {
$scope.rfi = response.rfi;
$scope.ButtonText = "Save";
},
function (response) {
showMessage('error', response.data.Message);
$scope.GetNewRfi();
}
);
};
$scope.GetDropdownValues();
if ($scope.rfiId != "0") {
$scope.GetExistingRfi();
}
else {
$scope.GetNewRfi();
}
Thanks!

custom currency filter to the input field in angularjs

How to formatting the value in to indian currency type using angularjs.
for example, 454565 to 4,54,565.00
I have input field like this:
<input type="text" ng-model="item.cost />
function FormatMyNumber(yourNumber) {
// Limit to two decimal places
yourNumber = parseFloat(yourNumber).toFixed(2);
//Seperates the components of the number
var n = yourNumber.toString().split(".");
//Comma-fies the first part
n[0] = n[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
//Combines the two sections
return n.join(".");
}
FormatMyNumber(454565); //yields 454,565.00
You can create directive for it as follows
HTML
<div ng-app="myApp">
<div ng-controller="myCtrl">
{{amount}}
<input format-to-currency amount="amount">
</div>
</div>
JS
angular.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.ampunt = 2;
})
.directive('formatToCurrency', function($filter) {
return {
scope: {
amount: '='
},
link: function(scope, el, attrs) {
el.val($filter('currency')(scope.amount));
el.bind('focus', function() {
el.val(scope.amount);
});
el.bind('input', function() {
scope.amount = el.val();
scope.$apply();
});
el.bind('blur', function() {
el.val($filter('currency')(scope.amount));
});
}
}
});
Link http://jsfiddle.net/moL8ztrw/6/

ng-minlength in directive not populated when invalid

I'm creating an input directive that encapsulates a label, and some error messaging but I'm getting stuck with ng-minlength and ng-maxlength. The issue is that they don't populate the model value until it's a valid value, so my parent scope can display a value while my directive doesn't, and vice versa. View this plunk for an example of what I mean.
Is the only way around this to define my own minlength and maxlength validators? Is there some way configure this behaviour so the model is always populated? I want to use all the built in validators in my directive, so no doubt this will be an issue with all of them and I'd rather not redefine them all.
HTML:
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app" ng-controller="Controller">
<label>Outer scope 1</label>
<input name="input1" type="text" ng-model="model1" ng-minlength="4"/>
<br/>
<label>Directive scope 1</label>
<input-dir ng-model="model1" ng-minlength="0"></input-dir>
<br/>
<br/>
<br/>
<label>Outer scope 2</label>
<input name="input3" type="text" ng-model="model2"/>
<br/>
<label>Directive scope 2</label>
<input-dir ng-model="model2" ng-minlength="4"></input-dir>
</body>
</html>
Javascript:
var app = angular.module('app', []);
app.controller('Controller', function($scope){
$scope.model1 = "Model1";
$scope.model2 = "Model2";
});
app.directive('inputDir', function(){
return {
restrict: 'E',
template: '<input type="text" ng-model="model" ng-minlength="{{ minlength }}" />',
scope: {
model: '=ngModel',
minlength: '=ngMinlength'
}
};
});
For anyone else with the same issue, I solved this by redefining all the validators, except now they return the value when invalid so the model is populated. Just include the valuedValidators module. These validators are available on the $error object as vvminlength etc.
vv-validators.js
(function () {
'use strict';
var validators = {};
validators.vvUrl = function(value){
var urlRegex = new RegExp(/[-a-zA-Z0-9#:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9#:%_\+.~#?&//=]*)?/gi);
return urlRegex.test(value);
}
validators.vvEmail = function(value){
var emailRegex = new RegExp(/^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
return emailRegex.test(value);
}
validators.vvMinlength = function(value, min){
var huh = value.length >= parseInt(min);
return min && value && huh;
}
validators.vvMaxlength = function(value, max){
return max && value && value.length <= parseInt(max);
}
validators.vvMin = function (value, min) {
return min && parseFloat(value) >= parseFloat(min);
}
validators.vvMax = function (value, max) {
return max && parseFloat(value) <= parseFloat(max);
}
validators.vvPattern = function (value, pattern) {
return pattern && new RegExp(pattern).test(value);
}
validators.vvFloat = function (value) {
var floatRegex = new RegExp(/^\-?\d+((\.|\,)\d+)?$/);
return floatRegex.test(value);
}
validators.vvInteger = function (value) {
var integerRegex = new RegExp(/^\-?\d+$/);
return integerRegex.test(value);
}
var app = angular.module('valuedValidators', []);
var names = Object.keys(validators);
names.forEach(function(name){
app.directive(name, function(){
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, controller) {
controller.$parsers.unshift(function (viewValue) {
var valid = true;
var attrValue = attrs[name];
var func = validators[name];
if (attrValue !== 'false') {
valid = func(viewValue, attrValue);
controller.$setValidity(name.toLowerCase(), valid);
}
return viewValue;
});
}
};
});
});
}());
There is an even easier solution: use two textareas.
The first serves as a draft, the second is the model where the verification is to take place.
<textarea name="message_body_draft" cols="47" rows="15" style="width: 100%;"
ng-model="message_body_draft"
ng-change="message_body=message_body_draft"></textarea>
<textarea name="message_body" style="width: 100%;" disabled ng-model="message_body"
ng-minlength="100" required></textarea>
When the user writes in the first one, it automatically will populates the right model (the second one).

Use ngMessages with Angular 1.2

Does anyone know if there is a fork of Angular 1.2 that supports ngMessages?
I'd love to use this but I have a requirement for IE8.
Thanks in advance for your help.
Here is my directive I use:
/**
* Ui-messages is similar implementation of ng-messages from angular 1.3
*
* #author Umed Khudoiberdiev <info#zar.tj>
*/
angular.module('uiMessages', []).directive('uiMessages', function () {
return {
restrict: 'EA',
link: function (scope, element, attrs) {
// hide all message elements
var messageElements = element[0].querySelectorAll('[ui-message]');
angular.forEach(messageElements, function(message) {
message.style.display = 'none';
});
// watch when messages object change - change display state of the elements
scope.$watchCollection(attrs.uiMessages, function(messages) {
var oneElementAlreadyShowed = false;
angular.forEach(messageElements, function(message) {
var uiMessage = angular.element(message).attr('ui-message');
if (!oneElementAlreadyShowed && messages[uiMessage] && messages[uiMessage] === true) {
message.style.display = 'block';
oneElementAlreadyShowed = true;
} else {
message.style.display = 'none';
}
});
});
}
};
});
I've used ui-messages instead of ng-messages to avoid conflicts.
<div ui-messages="form.name.$error">
<div ui-message="minlength">too short</div>
<div ui-message="required">this is required</div>
<div ui-message="pattern">pattern dismatch</div>
</div>
I don't know for sure if a fork exists but it would be easy enough to roll your own ng-message (or something that serves the same purpose). I think the following would do it:
Controller
app.controller("Test", function ($scope) {
$scope.messages = {
"key1": "Message1",
"key2": "Message2",
"key3": "Message3"};
$scope.getMessage = function (keyVariable) {
return $scope.messages[keyVariable.toLowerCase()];
};
$scope.keyVariable = 'key1';
});
HTML (example)
ENTER A KEY: <input type="text" ng-model="keyVariable" />
<h1 ng-bind="getMessage(keyVariable)" ng-show="getMessage(keyVariable) != ''"></h1>
See It Working (Plunker)
I've updated pleerock's answer to handle element directives having for and when attributes like ngMessages and ngMessage. You can find the same in this github repo
angular.module('uiMessages', []).directive('uiMessages', function() {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
// hide all message elements
var messageElements = element.find('ui-message,[ui-message]').css('display', 'none');
// watch when messages object change - change display state of the elements
scope.$watchCollection(attrs.uiMessages || attrs['for'], function(messages) {
var oneElementAlreadyShowed = false;
angular.forEach(messageElements, function(messageElement) {
messageElement = angular.element(messageElement);
var message = messageElement.attr('ui-message') || messageElement.attr('when');
if (!oneElementAlreadyShowed && messages[message] && messages[message] === true) {
messageElement.css('display', 'block');
oneElementAlreadyShowed = true;
} else {
messageElement.css('display', 'none');
}
});
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<form name="userForm" ng-app="uiMessages" novalidate>
<input type="text" name="firstname" ng-model="user.firstname" required />
<ui-messages for="userForm.firstname.$error" ng-show="userForm.firstname.$dirty">
<ui-message when="required">This field is mandatory</ui-message>
</ui-messages>
<br />
<input type="text" name="lastname" ng-model="user.lastname" required />
<div ui-messages="userForm.lastname.$error" ng-show="userForm.lastname.$dirty">
<div ui-message="required">This field is mandatory</div>
</div>
</form>

Resources