I have directive like this:
.directive('noWhitespace', ['$parse', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
/*
scope.$watch(attrs.ngModel, function(value) {
var getter = $parse(value);
update(getter(scope));
});
*/
function update(viewValue) {
console.log(JSON.stringify(viewValue));
if (viewValue.match(/\s/)) {
ngModel.$setValidity('whitespace', false);
return undefined;
} else {
ngModel.$setValidity('whitespace', true);
return viewValue;
}
}
ngModel.$parsers.unshift(update);
}
};
}])
and when I use it like this:
<form name="something" novalidate>
<input ng-model="myValue" no-whitespace/>
<div ng-show="something.myValue.$error.whitespace">
Error
</div>
</form>
and I type something and then few spaces at the end update is not called until I type character after those spaces and then I got error that I have whitespace. (the same happen when I put spaces at the begining or only spaces). Why is that, and how to fix it? As you see in comments I've try to use $watch+$parse but got error Cannot read property 'match' of undefined.
Try this in your template:
<form name="something" novalidate>
<input ng-model="myValue" no-whitespace ng-trim="false"/>
<div ng-show="something.myValue.$error.whitespace">
Error
</div>
</form>
That should solve the issue of ng-model not updating when you enter empty space.
EDIT: Derp, the attribute goes in the input element not in the div ...
This is the expected behavior of model parsers in angular, the idea being that if the entered text is incorrect (has whitespace in the end), the ngModel should return undefined. When you think about it, why would you save a value to ngModel anyway if it's not correct according to your validations?
This is the relevant bit:
if (viewValue.match(/\s/)) {
ngModel.$setValidity('whitespace', false);
return undefined;
}
You are setting validity to false and returning undefined.
You can keep the model updated even if its value isn't validated by just returning viewValue always:
if (viewValue.match(/\s/))
ngModel.$setValidity('whitespace', false);
else
ngModel.$setValidity('whitespace', true);
// Return viewvalue regardless of whether it validates or not
return viewValue;
Related
I'm trying to make a field on a form valid by using the $valid class. I have the below HTML and JS code but even though the JS seems to work by modifying code editors, it doesn't validate the HTML. It doesn't seem to make it so $valid does anything but become true if the field has any text in it.
<input name="user" ng-model="user" ng-controller="SearchController" placeholder="User ID" required><br>
<span style="color:red" ng-show="searchForm.user.$dirty && searchForm.user.$invalid">A valid user ID is required.</span>
and
var searchApp = angular.module('searchApp', []);
searchApp.controller('SearchController', ['$scope', function ($scope) {
var users = ['11', '22', '33', '44']
return {
require: 'ngModel',
link: function (scope, element, attr, mCtrl) {
function idValidation(value) {
if (users.includes(value)) {
mCtrl.$setValidity('charE', true);
} else {
mCtrl.$setValidity('charE', false);
}
return value;
}
mCtrl.$parsers.push(idValidation);
}
};
}]);
The JS logic seems to work, but the HTML is not working correctly.
So you have a few minor errors in your code's logic to make this function correctly.
What you are attempting to do is create a custom validator. Your directive is using a $parser function, which is intended to parse a model's value for display. What you really want to be doing is working with a $validators function.
So, you would want to do something along the following:
var searchApp = angular.module('searchApp', []);
searchApp.directive('validUser', ['$scope', function ($scope) {
var users = ['11', '22', '33', '44']
return {
require: 'ngModel',
link: function (scope, element, attr, mCtrl) {
mCtrl.$validators.charE = function (value) {
return users.includes(value)
}
}
};
}]);
What happens is anytime the ngModel changes, it will run the value through the validators and determine if they are valid or invalid and automatically set ng-invalid-{name} or ng-valid-{name} (in this example, ng-invalid-charE and ng-valid-charE).
You can than use ngMessages to define your errors and they will show and hide as appropriate based on the validity.
Your ngController should more likely be a directive that adds the validator and your controller to contain the entire Search functionality/form HTML and be above your input to define your scope. Changing it to a directive would mean you'd remove ngController from the input and add an attribute for valid-user (as I've updated the name).
I have made an attempt to create a custom validation directive to validate two items simultaneously at server side.
I have two values regNumber and regDate that have to be validated along with each other. So when user enters both of them correctly,they are validate. But, if one of them is entered incorrectly, they both have to be invalidated.
To accomplish the goal, I have written a the following directive base on this post.
Everything is working fine except When I enter both together regNumber and regDate incorrectly. Then, even If I change them both to correct values, still they are invalidated.
By "Everythin is working fine" I mean when I enter an invalid value for regNumber and a valid value for regDate, and change the regNumber back to a valid value, it works fine and vice versa(first regDate and then regNumber).
I think $setValidatity will be set for the latest input which has been changed and not for both of them but even if my guess is true, I don't know how to solve it. :D
Directive:
osiApp.directive('uniqueOrder', function ($http, $rootScope) {
var toId;
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attr, ctrl) {
scope.$watch(attr.ngModel, function (value) {
if (scope.osiRequest.regDate) {
ctrl.$setValidity('uniqueOrder', true);
if (toId) clearTimeout(toId);
toId = setTimeout(function () {
$http({
method: 'GET',
url: $rootScope.baseAddress + '/ValidateOrderRegistrationNumber/Get',
params: {
orderRegistrationDate: scope.osiRequest.regDate ,
orderRegistrationNumber: scope.osiRequest.regNumber
}
}).success(function (isValid) {
ctrl.$setValidity('uniqueOrder', isValid);
});
}, 200);
}
});
}
}
});
HTML:
<div class="form-group" ng-class="myForm.regNumber.$error.uniqueOrder ||
myForm.regDate.$error.uniqueOrder ? 'has-error' : ''">
<input class="form-control"
name="regNumber" ng-model="osiRequest.regNumber" unique-order>
</div>
<div class="form-group" ng-class="myForm.regNumber.$error.uniqueOrder ||
myForm.regDate.$error.uniqueOrder ? 'has-error' : ''">
<input class="form-control"
name="regDate" ng-model="osiRequest.regDate " unique-order>
</div>
As mentioned by #NicolasMoise you have to be able to access both model in your directive, few ways to achieve this :
Adding both ng-model osiRequest.regDate & osiRequest.regNumber as your directive attributes unique-order. Check "scope" parameter for directive.
Access parent controller scope from directive by using $parent ( not ideal )
I have this custom validation directive:
/**
* Overwrites default url validation using Django's URL validator
* Original source: http://stackoverflow.com/questions/21138574/overwriting-the-angularjs-url-validator
*/
angular.module('dmn.vcInputUrl', [])
.directive('vcUrl', function() {
// Match Django's URL validator, which allows schemeless urls.
var URL_REGEXP = /^((?:http|ftp)s?:\/\/)(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::\d+)?(?:\/?|[\/?]\S+)$/i;
var validator = function(value) {
if (!URL_REGEXP.test(value) && URL_REGEXP.test('http://' + value)) {
return 'http://' + value;
} else {
return value;
}
}
return {
require: '?ngModel',
link: function link(scope, element, attrs, ngModel) {
function allowSchemelessUrls() {
// Silently prefixes schemeless URLs with 'http://' when converting a view value to model value.
ngModel.$parsers.unshift(validator);
ngModel.$validators.url = function(value) {
return ngModel.$isEmpty(value) || URL_REGEXP.test(value);
};
}
if (ngModel && attrs.type === 'url') {
allowSchemelessUrls();
}
}
};
});
It works fine when you 'dirty' the input by typing or pasting, but I need it to run this validation, overwriting the default type="url" validation when the value is initially set in the ngModel.
I've tried adding ngModel.$formatters.unshift(validator); but it results in the 'http://' being added to input, which I need to avoid as user's changes are manually approved and it would be a waste of time to approve the addition of 'http://'.
Any help would be appreciated!
Set ng-model-options on the input type field, for example:
<input type="text"
ng-model-options="{ updateOn: 'default', debounce: {'default': 0} }"</input>
This will ensure your validator gets fired "when the value is initially set in the ngModel", as you have stated in the question.
See detailed AngularJs documentaion on ngModelOptions:enter link description here
validation of Url :
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form name="form">
URL: <input type="url" ng-model="url.text" placeholder="Enter Link" name="fb_link"></input>
<span class="error" ng-show="form.fb_link.$error.url"></span>
</form>
I don't get this sorted out. I try to add validation (for form submit deactivation if invalid) wether an array contains items or not. i tried using a custom directive but it never gets called when the model updates :(
as the angular stuff is rendered inside a play applications template and the form is not defined within my scope I cannot do some easy form invalidation.
what i try to achieve is invalidating the form until some category has been added to $scope.app.categories thereby deactivating the submit button which is also not within my scope.
here comes the code (angular version 1.2.23):
<input type="text" ng-model="app.categories" name="size" custom />
<input id="tagsinput" type="text" ng-model="new.category" on-keyup="disabled" keys="[13]"/>
<a class="btn" ng-click="addCategory()">Add</a>
// loading of app happens above this is the contoller function
$scope.addCategory = function () {
$scope.app.categories.push({name: $scope.new.category});
$scope.new.category = "";
}
app.directive('custom', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$formatters.unshift(function(val1, val2, val3, val4) {
// not even gets called would like to validate app.categories here :(
console.log(true);
});
}
};
});
You're using $formatters, but for validation you should use $validators. Instead of your line starting with ctrl.$formatters, try the following:
ctrl.$validators.yourValidatorName = function(modelValue, viewValue) {
console.log("Validator is called.");
return true; // This means validation passed.
};
You can find this in the documentation. Also do not forget to add the directive to the HTML element, so in your case <input ... custom>. To disable a submit button if validation fails, use <input type="submit" data-ng-disabled="formName.$invalid">.
I have a simple html form containing regular text input. ng-minlength, ng-maxlength and ng-pattern angular built-in form input directives are set on the input.
Problem: ng-pattern check is applied before the length check by ng-minlength and ng-maxlength.
Question: how can I change the default check order: i.e. first check for the length, then apply pattern check?
Example:
<body ng-app>
<div>
<form name="myForm">
Name: <input name="name" type="text" ng-model="name" ng-minlength="3" ng-maxlength="16" ng-pattern="/^\w+$/"/>
<div ng-show="myForm.name.$dirty && myForm.name.$invalid">
<span ng-show="myForm.name.$error.pattern">Pattern error</span>
<span ng-show="myForm.name.$error.minlength || myForm.name.$error.maxlength">Length error</span>
</div>
<br/>
<input type="submit" value="Submit">
</form>
</div>
</body>
Current behavior:
enter "#" - see "Pattern error"
enter "###" - see "Pattern error"
Desired behavior:
enter "#" - see "Length error"
enter "###" - see "Pattern error"
FYI, related jsfiddle.
Thanks in advance.
Write your own directive:
var mod = angular.module("myApp", []);
mod.directive("nameValidation", function () {
return {
restrict: "A",
require: "ngModel",
link: function (scope, element, attrs, ngModelCtrl) {
var validate = function (value) {
var minLen = parseInt(attrs.myMinlength, 10),
maxLen = parseInt(attrs.myMaxlength, 10),
pattern = attrs.myPattern,
match = pattern.match(/^\/(.*)\/([gim]*)$/),
lenErr = false;
if (match) {
pattern = new RegExp(match[1], match[2]);
}
if (!ngModelCtrl.$isEmpty(value)) {
ngModelCtrl.$setValidity("pattern", true);
if ((minLen && value.length < minLen) || (maxLen && value.length > maxLen)) {
ngModelCtrl.$setValidity("length", false);
lenErr = true;
}
else {
ngModelCtrl.$setValidity("length", true);
lenErr = false;
}
if (!lenErr) {
if (match && !pattern.test(value)) {
ngModelCtrl.$setValidity("pattern", false);
}
else {
ngModelCtrl.$setValidity("pattern", true);
}
}
}
else {
ngModelCtrl.$setValidity("length", true);
ngModelCtrl.$setValidity("pattern", true);
}
}
ngModelCtrl.$parsers.push(validate);
ngModelCtrl.$formatters.push(validate);
}
}
});
Then in your HTML, include the app and use the directive:
<body ng-app="myApp">
<div>
<form name="myForm">
Name: <input name="name" type="text" ng-model="name" name-validation="" my-minlength="3" my-maxlength="16" my-pattern="/^\w+$/"/>
<div ng-show="myForm.name.$dirty && myForm.name.$invalid">
<span ng-show="myForm.name.$error.pattern">Pattern error</span>
<span ng-show="myForm.name.$error.length">Length error</span>
</div>
<br/>
<input type="submit" value="Submit">
</form>
</div>
</body>
The directive uses my-minlength, my-maxlength, and my-pattern for the three values. If length fails, that will trip first. If not, then pattern will show as error if it doesn't match. Consider renaming this directive if you want to use it other places besides name as minlength, maxlength, and pattern can be passed to it via attributes. If they are left off, they will be ignored.
See jsfiddle: http://jsfiddle.net/4zpxk/6/
I searched in angular code why this behavior. Then in the function 'textInputType' that it's the specific function that handles text inputs for the angular 'input' directive I found this at the end of this function, where we can see three blocks of code.
// pattern validator
if (pattern){
//validator logic
}
// min length validator
if (attr.ngMinlength) {
//validator logic
}
// max length validator
if (attr.ngMaxlength) {
//validator logic
}
So, no matter if you change the declaration order of your ng-* attributes in the html input element you will always get same result but if you change the order of the blocks, I mean, put the min length validator block before pattern validator block you will have the result that you expect.
This is a solution for your problem but you have to make a litte change in angular code and I don't know if you really like this. But you got a very common situation where order of the declaration of validation concepts matters, so, something more must be done to handle this. Thanks
You cannot change the default check order unfortunately.
One solution is to write a custom validator, not that difficult. Based on this answer, I came up with this code (fiddle)
Usage: There is an array of validation functions in the scope, they get passed to our custom directive "validators" as:
<input name="name" type="text" ng-model="name" validators="nameValidators"/>
A validator function would look like (e.g. for the minlength constraint):
function minlength(value, ngModel) {
if( value == null || value == "" || value.length >= 3 ) {
ngModel.$setValidity('minlength', true);
return value;
}
else {
ngModel.$setValidity('minlength', false);
return;
}
}
Important points are: it takes the value and the ngModel as arguments, performs the test (here value.length >= 3) and calls ngModel.$setValidity() as appropriate.
The directive registers the given functions with ngModel.$parsers:
app.directive("validators", function($parse) {
return {
restrict: "A",
require: "ngModel",
link: function(scope, el, attrs, ngModel) {
var getter = $parse(attrs.validators),
validators = getter(scope),
i;
for( i=0; i < validators.length; i++ ) {
ngModel.$parsers.push((function(index) {
return function(value) {
return validators[index](value, ngModel);
};
})(i));
}
}
};
});
Many details can be tweaked and improved, but the outline works (again link to fiddle). Now the order of validation is explicitly set by the order of the validator functions in the nameValidators array.
If you use ng-messages you should be able to set the order via the order of ng-message elements, e.g:
<div ng-messages="field.$error">
<ul class="validation-errors">
<li ng-message="required">This has the highest prio</li>
<li ng-message="min">Second in command</li>
<li ng-message="max">I'm last</li>
</ul>
</div>
Also the docs on this: https://docs.angularjs.org/api/ngMessages/directive/ngMessages
i just changed the order of your directives, pattern first
<input name="name" type="text" ng-model="name" ng-pattern="/^\w+$/" ng-minlength="3" ng-maxlength="16"/>
EDIT: uuum, tested your fiddel without changes and it shows your desired behavior ...
directives are compiled by priority, bbut i don't know how to set angulars directives priority ... sorry, should have tested this first