Force validation on submit - angularjs

I'm validating an input element with the directive below. The problem is that this way it's only executed when the input element is activated. Is there a way to force execute the parsers methods of all input elements of a form?
"use strict";
angular.module("kap.directive")
.directive('kapValidationDuration', ['$timeout', '$log', function ($timeout, $log) {
return {
restrict: 'A',
require: 'ngModel',
scope: { minReservationDurationMinutes: '=minReservationDurationMinutes' },
link: function (scope, element, attrs, ctrl) {
if (attrs.type === 'radio' || attrs.type === 'checkbox') {
return;
}
ctrl.$parsers.push(function (value) {
if(value && !element[0].hidden) {
var lTimeValues = value.split(":"),
lHoursToMinutes = parseInt(lTimeValues[0], 10) * 60,
lMinutes = parseInt(lTimeValues[1], 10),
lMinReservationDurationMinutes = parseInt(attrs.minreservationdurationminutes, 10) || 10,
lValidity = true;
if ((lHoursToMinutes + lMinutes) < lMinReservationDurationMinutes) {
lValidity = false;
}
ctrl.$setValidity('kapValidationDuration', lValidity);
}
return value;
});
}
};
}]);

In order to do that, i.e. validate the initial values as well, you also have to use the $formatters. Luckilly, in your case, you just have to unshift the same function to the $formatters, as the one used for the $parsers.
The reason is that the parsers are used when going form → model. In general this means data conversion: if the model was a number, the input from the user is always a string and has to be converted; the errors may not only be validation (e.g. "age must be positive") but also parsing (e.g. "'tata' is not a valid number"). The formatters when going model → form. This is, as the name implies, formatting: e.g. a Date object may need to be displayed as dd/MM/yyyy in my locale (greek), but MM/dd/yyyy in other locales.
See this fiddle: http://jsfiddle.net/52dAB/
In the fiddle I am still using two separate functions for the formatters and the parsers, despite them being identical in implementation. Just for the sake of generality.

Related

Using element.bind in validation directive

I trying to validate input with custom directive:
.directive('customValidation', function () {
return {
require: 'ngModel',
link: function (scope, element, attr, ngModelCtrl) {
function fromUser(text) {
element.bind("keydown keypress", function (event) {
if (!(event.keyCode >= 48 && event.keyCode <= 57)) {
return undefined;
}
})
}
ngModelCtrl.$parsers.push(fromUser);
}
};
});
but it doesn't work. Any character is passes validation. What I doing wrong?
So basically what you are trying to achieve is to check if an input contains only numbers. At least that is what I can understand from your explanation and the sample code.
First, you are using a parser, which are used for sanitizing and converting values passed from the DOM to the model. Validation comes afterwards. If you just want to check if only numbers are written, then you need something like this:
ngModel.$validators.validCharacters = function(modelValue, viewValue) {
var value = modelValue || viewValue;
return /[0-9]+/.test(value);
};
I suggest reading the API docs as they explain all ngModelController functionality very thoroughly: click here for thorough docs
Second, you are binding to a event everytime your parser is called. The parser is called each time you change the contents of your input element. If you type the word everytime into your input, you end up binding the event nine times! Apart from the fact that binding to the event after you changed something is too late as your first event was already fired.

angular 1.3.0 and input[type=string]

We recently updated our application to use the latest angular version, from a version to before 1.3.0 to 1.5.0. Apparently we now bump into a breaking change introduced in 1.3.0:
https://github.com/angular/angular.js/issues/9218
We had a custom directive that enabled us to use the pikaday date picker:
module.directive('pikaday', function () {
return {
require: 'ngModel',
link: function preLink(scope, element, attrs, controller) {
var $element = $(element),
momentFormat = 'DD/MM/YYYY',
year = new Date().getFullYear();
// init datepicker
var picker = new Pikaday(
{
field: document.getElementById(attrs.id),
firstDay: 1,
format: momentFormat,
minDate: new Date((year - 1) + '-01-01'),
maxDate: new Date((year + 1) + '-12-31'),
yearRange: [year - 1, year + 1],
onSelect: function (date) {
controller.$setViewValue(date);
},
defaultDate: scope.$eval(attrs.ngModel), // require: 'ngModel'
setDefaultDate: true
});
// format model values to view
controller.$formatters.unshift(function (modelValue) {
var formatted = (modelValue)
? moment(modelValue).format(momentFormat)
: modelValue;
return formatted;
});
// parse view values to model
controller.$parsers.unshift(function (viewValue) {
if (viewValue instanceof Date) {
return viewValue;
}
else {
return moment(viewValue, momentFormat).toDate();
}
});
}
};
})
This used to work fine, but now after binding a form that has this control, my scope value is suddenly changed from a Date object to a string (without ever interacting with the control!) The funny thing is that this happens without the formatter or parsers ever being called. So it looks like angular just decides to change the scope value just because it is being bound a an input of type "text", even if the value in the input is never touched.
I dont want to use input[type=text] because i dont want the browser to force its own handling of dates.
If my formatter/parser would be called i would know how to work around this, but this has me puzzled.
I could just display a date in a span and have a button the user could click to spawn the pikaday plugin, but i would prefer the behavior to stay as is ...
Have you seen this workaround from https://github.com/angular-ui/bootstrap/issues/2659 ?
All you have to do is add a directive:
directive('datepickerPopup', function (){
return {
restrict: 'EAC',
require: 'ngModel',
link: function(scope, element, attr, controller) {
//remove the default formatter from the input directive to prevent conflict
controller.$formatters.shift();
}
}
})
Work around removes the not so nice working formatter introduced by the 1.3.0 version and thus resolves the issue with it.
This fix should make it as it is widely thanked in the github thread.

How to create a directive to only number only fields without ng model

I am using this angular directive to allow only numeric input in a text box. Here is a jsfiddle to it : http://jsfiddle.net/vfsHX/673/
This works. However it required ng model, my issue is that I have a table which I generate on-fly and each row has a numeric field for which I need to use this directive.
Now since each of the field in this row does not have a ng model this does not work.
Please advice.
I think your directive is too tied to your model, it's never a good idea for a directive to know about the model, I took the main idea from How to allow only a number (digits and decimal point) to be typed in an input?
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
});
app.directive('validNumber', function() {
return {
require: '?ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
if(ngModelCtrl) {
ngModelCtrl.$parsers.push(function(val) {
//this will stop your from adding inputs that aren't numbers, you can change it to just affect validity
var clean = val.replace( /[^0-9]+/g, '');
if (val !== clean) {
ngModelCtrl.$setViewValue(clean);
ngModelCtrl.$render();
}
return clean;
});
}
else {
element.bind('keypress', function(event) {
//your logic here, not sure if you want to ignore or add or whatever you want to do without a model, this is the place
var newVal = element.val() + String.fromCharCode(event.keyCode);
//check if newVal is ok for your and then set validity
});
}
};
});
Another approach would be to push into the $parsers a validity check only instead of replacing the value:
ngModel.$parsers.unshift(function(value) {
var valid = angular.isNumber(value);
ngModel.$setValidity('isNumber', valid);
return valid ? value : undefined;
});
Hope this helps.
If restricting the typing is not an issue and you support IE10+, you may consider HTML5 number input field (e.g. <input type="number"/>)

Angular - Directive reflecting ngModel array

This is piggy-backing a little off an earlier question. I have been trying to work with directives and data from a model/array
My model looks something like this:
$scope.testModel = {
inputA:[1,2,3]
};
Then I would have inputs for each.
My directive is checking, on keyup, if the input is greater than a given number (10). If it is, then it sets it as 10.
link: function(scope, element) {
scope.$watch('ngModel',function(val){
element.val(scope.ngModel);
});
element.bind("keyup", function(event) {
if(parseInt(element.val())>10){
element.val(10);
scope.ngModel=element.val();
scope.$apply();
}
});
}
The problem is that I get an error, depending on the input:
TypeError: Cannot set property '0' of undefined
Here is the fiddle to see the error and code I have set up: https://jsfiddle.net/prXm3/3/
NOTE
I would prefer not to change the data set as I receive it directly from the server. I know I can change the model to inputA0:1,inputA1:2,inputA2:3 and get it to work, but then I would have to transform the data when my app gets it, and then re-transform it when I send to back to the server. I would prefer to leave the model as I have it set.
Since your directive is interacting with ngModel, you should work along with it in order to update both the model and the view:
angular.module('test', []).directive('myDirective', function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$parsers.unshift(function(viewValue) {
if(parseInt(viewValue) > 10) {
ngModel.$setViewValue(10);
ngModel.$render();
return 10;
}
else
return viewValue;
});
}
}
});
Working jsFiddle.
Perhaps you'd be interested in checking out the following posts for further information:
NgModelController
Developer's Guide on Forms

How to disable angulars type=email validation?

How would you go about disabling, or at the very least changing, how Angular validates type=email inputs?
Currently, if you use type=email, Angular essentially double validates.. as the Browser (Chrome in this case) validates the email, and then angular does too. Not only that, but what is valid in Chrome foo#bar is not valid in Angularjs.
The best i could find, is ng-pattern, but ng-pattern simply adds a 3rd pattern validation for the input type.. instead of replacing Angular's email validation. heh
Any ideas?
Note: This is example is for angular 1.2.0-rc.3. Things might behave differently on other versions
Like others have stated it is a bit complex to turn off angulars default input validation. You need to add your own directive to the input element and handle things in there. Sergey's answer is correct, however it presents some problems if you need several validators on the element and don't want the built in validator to fire.
Here is an example validating an email field with a required validator added. I have added comments to the code to explain what is going on.
Input element
<input type="email" required>
Directive
angular.module('myValidations', [])
.directive('input', function () {
var self = {
// we use ?ngModel since not all input elements
// specify a model, e.g. type="submit"
require: '?ngModel'
// we need to set the priority higher than the base 0, otherwise the
// built in directive will still be applied
, priority: 1
// restrict this directive to elements
, restrict: 'E'
, link: function (scope, element, attrs, controller) {
// as stated above, a controller may not be present
if (controller) {
// in this case we only want to override the email validation
if (attrs.type === 'email') {
// clear this elements $parsers and $formatters
// NOTE: this will disable *ALL* previously set parsers
// and validators for this element. Beware!
controller.$parsers = [];
controller.$formatters = [];
// this function handles the actual validation
// see angular docs on how to write custom validators
// http://docs.angularjs.org/guide/forms
//
// in this example we are not going to actually validate an email
// properly since the regex can be damn long, so apply your own rules
var validateEmail = function (value) {
console.log("Validating as email", value);
if (controller.$isEmpty(value) || /#/.test(value)) {
controller.$setValidity('email', true);
return value;
} else {
controller.$setValidity('email', false);
return undefined;
}
};
// add the validator to the $parsers and $formatters
controller.$parsers.push(validateEmail);
controller.$formatters.push(validateEmail);
}
}
}
};
return self;
})
// define our required directive. It is a pretty standard
// validation directive with the exception of it's priority.
// a similar approach must be take with all validation directives
// you would want to use alongside our `input` directive
.directive('required', function () {
var self = {
// required should always be applied to a model element
require: 'ngModel'
, restrict: 'A'
// The priority needs to be higher than the `input` directive
// above, or it will be removed when that directive is run
, priority: 2
, link: function (scope, element, attrs, controller) {
var validateRequired = function (value) {
if (value) {
// it is valid
controller.$setValidity('required', true);
return value;
} else {
// it is invalid, return undefined (no model update)
controller.$setValidity('required', false);
return undefined;
}
};
controller.$parsers.push(validateRequired);
}
};
return self;
})
;
There you have it. You now have control over type="email" input validations. Please use a proper regex to test the email though.
One thing to note is that in this example validateEmail is run before validateRequired. If you need validateRequired to run before any other validations, then just prepend it to the $parsers array (using unshift instead of push).
Very simple. I had to alter the email regex to match a business requirement, so I made this directive that makes the email regex customizable. It essentially overwrites the original validator with my custom one. You don't have to mess with all the $parsers and $formatters (unless I'm missing something). So my directive was this...
module.directive('emailPattern', function(){
return {
require : 'ngModel',
link : function(scope, element, attrs, ngModel) {
var EMAIL_REGEX = new RegExp(attrs.emailPattern, "i");
ngModel.$validators["email"] = function (modelValue, viewValue) {
var value = modelValue || viewValue;
return ngModel.$isEmpty(value) || EMAIL_REGEX.test(value);
};
}
}
});
Then use it like this, supplying whatever email pattern you personally want:
<input type="email" email-pattern=".+#.+\..+"/>
But if you just want to permanently disable it then you could do this.
module.directive('removeNgEmailValidation', function(){
return {
require : 'ngModel',
link : function(scope, element, attrs, ngModel) {
ngModel.$validators["email"] = function () {
return true;
};
}
}
});
Then use it like this...
<input type="email" remove-ng-email-validation>
On HTML5 you can use the form's attribute novalidate to disable browser's validation:
<form novalidate>
<input type="email"/>
</form>
If you want to create a custom validator in angularjs, you have a good tutorial and example here: http://www.benlesh.com/2012/12/angular-js-custom-validation-via.html
Echoing nfiniteloop, you don't need to mess with the $parsers or $formatters to override the default validators. As referenced in the Angular 1.3 docs, the $validators object is accessible on the ngModelController. With custom directives you can write as many different email validation functions as you need and call them wherever you want.
Here's one with a very nice standard email format regex from tuts: 8 Regular Expressions You Should Now (probably identical to Angular's default, idk).
var app = angular.module('myApp', []);
app.directive('customEmailValidate', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
var EMAIL_REGEXP = /^([a-z0-9_\.-]+)#([\da-z\.-]+)\.([a-z\.]{2,6})$/;
ctrl.$validators.email = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
if (EMAIL_REGEXP.test(viewValue)) {
// it is valid
return true;
}
// it is invalid
return false;
};
}
};
});
Here's one that removes validation entirely:
var app = angular.module('myApp', []);
app.directive('noValidation', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$validators.email = function(modelValue, viewValue) {
// everything is valid
return true;
};
}
};
});
To use in your markup:
<!-- 'test#example.com' is valid, '#efe#eh.c' is invalid -->
<input type="email" custom-email-validate>
<!-- both 'test#example.com' and '#efe#eh.c' are valid -->
<input type="email" no-validation>
In my project I do something like this (custom directive the erases all other validations including ones installed by angularjs):
angular.module('my-project').directive('validEmail', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl){
var validator = function(value){
if (value == '' || typeof value == 'undefined') {
ctrl.$setValidity('validEmail', true);
} else {
ctrl.$setValidity('validEmail', /your-regexp-here/.test(value));
}
return value;
};
// replace all other validators!
ctrl.$parsers = [validator];
ctrl.$formatters = [validator];
}
}
});
How to use it (note novalidate, it's required to turn off browser validation):
<form novalidate>
<input type="email" model="email" class="form-control" valid-email>

Resources