I have a certain custom validation directive in my application(code attached below). The problem is that when one or more of the form fields are required, and chrome fills them automatically, the fields remain invalid until the user changes them manually. I suspect that this happens due to the fact that chrome fills the fields before angular even bootstraps.
Is there a way to fix this?.
Code:
app.directive('myValidate', function($timeout, $filter) {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
var validator = function(viewValue){
var viewValueStr = viewValue + '';
scope.valid = true;
scope.fieldName = attrs.name;
var nameStr = attrs.name + '';
if(!attrs.displayName || attrs.displayName.length == 0){
// var nameObj = nameStr.split('_');
// for(var i = 0; i < nameObj.length; ++i){
// nameObj[i] = nameObj[i].substr(0, 1).toUpperCase() + nameObj[i].slice(1);
// }
// var nameStrParsed = nameObj.join(' ');//olde code, working on name, not Hebrew comaptible
var nameStrParsed = attrs.placeholder + '';
}
else{//data-display-name attribute, the error display is different than the placeholder value
var nameStrParsed = attrs.displayName;
}
scope.fieldErrorDisplay = Boolean(nameStrParsed) ? nameStrParsed : $filter('translate')('THISFIELD');
var valueRequired = scope.$eval(attrs.valueRequired);
if(valueRequired && viewValueStr.length == 0 && !attrs.minLength){
scope.valid = false;
scope.requirementSpec[nameStr] = [{
'msg' : scope.fieldErrorDisplay + ' ' + $filter('translate')('ISREQUIRED'),
'class' : undefined
}];
}
else{
// scope.fieldErrorDisplayObj[nameStr] = scope.fieldErrorDisplay + ' must meet the following requirements: ';
scope.requirementSpec[nameStr] = [];
if(attrs.minLength){
var itemValidity = viewValue.length >= attrs.minLength;
scope.valid = !itemValidity ? false : scope.valid;
var item = {
'msg' : $filter('translate')('MINLENGTH', {PARAM: attrs.minLength + ''}),
'class' : itemValidity ? 'valid' : undefined
};
scope.requirementSpec[nameStr].push(item);
}
else if(attrs.valueRequired){
var itemValidity = viewValue && viewValueStr && viewValueStr.length >= 1;
scope.valid = !itemValidity ? false : scope.valid;
var item = {
'msg' : $filter('translate')('FIELDREQUIRED'),
'class' : itemValidity ? 'valid' : undefined
};
scope.requirementSpec[nameStr].push(item);
}
if(attrs.maxLength){
var itemValidity = viewValue.length <= attrs.maxLength;
scope.valid = !itemValidity ? false : scope.valid;
var item = {
'msg' : $filter('translate')('MAXLENGTH', {PARAM: attrs.maxLength + ''}),
'class' : itemValidity ? 'valid' : undefined
};
scope.requirementSpec[nameStr].push(item);
}
if(attrs.minLetters){
var itemValidity = (viewValue && /[A-z]/.test(viewValue));
scope.valid = !itemValidity ? false : scope.valid;
var item = {
'msg' : $filter('translate')('MINLETTERS', {PARAM: attrs.minLetters + ''}),
'class' : itemValidity ? 'valid' : undefined
};
scope.requirementSpec[nameStr].push(item);
}
if(attrs.minNumbers){
var itemValidity = (viewValue && /\d/.test(viewValue));
scope.valid = !itemValidity ? false : scope.valid;
var item = {
'msg' : $filter('translate')('MINNUMBERS', {PARAM: attrs.minNumbers + ''}),
'class' : itemValidity ? 'valid' : undefined
};
scope.requirementSpec[nameStr].push(item);
}
if(attrs.validUrl){
// if(viewValue.indexOf('http') == -1){
// viewValue = 'http://' + viewValue;
// ctrl.$setViewValue(viewValue);
// }
// else if(viewValue.indexOf('http') != 0){
// var httpIndex = viewValue.indexOf('http');w
// viewValue = viewValue.substr(httpIndex);
// ctrl.$setViewValue(viewValue);
// }
// var urlPattern = new RegExp("(http|https)://[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?");
var urlPattern = new RegExp(/^(https?):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i);
//(http|https):\/\/([a-zA-Z0-9]+\.)?[a-zA-Z0-9][a-zA-Z0-9-]+\.[a-zA-Z]{2,6}?(.+){0, 100}$/i)
var itemValidity = !viewValue || viewValue.length == 0 || urlPattern.test(viewValue);
scope.valid = !itemValidity ? false : scope.valid;
// console.log(itemValidity);
var item = {
'msg' : $filter('translate')('VALIDURL'),
'class' : itemValidity ? 'valid' : undefined
};
scope.requirementSpec[nameStr].push(item);
}
}
if(scope.valid) {
ctrl.$setValidity(nameStr, true);
elm.removeClass('ng-required-invalid').removeClass('validatorError').removeClass('ng-invalid').addClass('ng-valid');
return viewValue;
}
else {
ctrl.$setValidity(nameStr, false);
return undefined;
}
}
if(!scope.requirementSpec){
scope.requirementSpec = {};
}
if(Boolean(attrs.valueRequired) || Boolean(attrs.minLength)){
ctrl.$setValidity(attrs.name, false);
// elm.removeClass('ng-valid').addClass('ng-invalid');
}
ctrl.$parsers.unshift(function(viewValue) {
return validator(viewValue);
});
ctrl.$formatters.unshift(function(viewValue) {
if(viewValue && viewValue != "" && viewValue.length > 0)
return validator(viewValue);
});
}
};
})
Added the following code to the bottom of my custom validation directive:
scope.$on('triggerValidator', function(e, val){
var viewValue = typeof $ === 'function' ? $('[name="' + attrs.name + '"]').val() : document.getElementsByName(attrs.name)[0].value;
if(viewValue && viewValue.length > 0){
try{
ctrl.$setViewValue(viewValue);
validator(viewValue);
}
catch(SUPPRESS){}
}
});
And the following to the bottom of my controller:
$timeout(function(){
$rootScope.$broadcast('triggerValidator');
}, 500)
Please notice that when using jQuery, a timeout of 200ms was sufficient, and when using native JS selectors, I had to increase the timeout to 500ms.
The try-catch block is there to catch a weird parser exception that Angular throws when trying to set a view value that contains the char '@', works fine though!.
It's also good to keep track of the official issue at the Angular repository: https://github.com/angular/angular.js/issues/1460
I suspect that this happens due to the fact that chrome fills the fields before angular even bootstraps
If the fields were set before Angular bootstraps -- it should work fine. I think the problem is other way round: Chorme sets the fields after Angular has bootstrapped but it did not informed Angular about state change.
There are a couple of approaches that could work, but none of them would consider as very elegant:
$digest
.I would probably go with option 3.
as it is easiest to implement and probably most reliable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With