I am trying to create a directive that will skip validation if a field is disabled.
I am using this approach :
implementing a directive to exclude a hidden input element from validation ($addControl issue)
My directive is :
dashboardApp.directive('shownValidation', function() {
return {
require: '^form',
restrict: 'A',
link: function(scope, element, attrs,form) {
var control;
scope.$watch(attrs.disabled,function(value){
if (!control){
control = form[element.attr("name")];
}
if (value == false){
form.$addControl(control);
angular.forEach(control.$error, function(validity, validationToken) {
form.$setValidity(validationToken, !validity, control);
});
} else {
console.log("Before remove control." + control.$name + " Form valid = " + form.$valid);
form.$removeControl(control);
console.log("After remove control." + control.$name + " Form valid = " + form.$valid);
}
});
}
};
});
In my app controller I have an watch to my $valid state of the form
$scope.$watch("userProfileForm.$valid", function(newValue) {
console.log("Log in controller for userProfileForm.$valid= " + newValue);
});
My input declaration in jsp is:
<input type = "text" name = "title" ng-disabled = "PROFILE_DISABLED_FIELDS_TITLE" required shown-validation />
I am displaying in the page the valid state of the form in a div <div>{{userProfileForm.$valid}}</div> to check it because based on the $valid state I am disable / enable the save button.
Here are the logs when I am open the page:
profile-editor-controller.js:164 Log in controller for userProfileForm.$valid= true
pc-directives.js:460 Before remove control.title Form valid = false
pc-directives.js:462 After remove control.title Form valid = true
profile-editor-controller.js:164 Log in controller for userProfileForm.$valid= false
There are other input fields in the form that are having validators but I made sure that they are all valid for the purpose of example.
If I put a breakpoint in the controller after the page is fully loaded and the message on the console are printed,I can see that $scope.userProfileForm.$error.required[0].$name == "title"
but if I look on the field is not on the form anymore:
$scope.userProfileForm.title == undefined
Any ideas where I am wrong or where is the trick ?
Later Edit:
After I read some articles about $watch and $observe I found that these two functions are working different based on your angular version and made me try a different approach and I found a solution for my case:
app.directive('validIfDisabled', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, element, attrs,model) {
var disabled=false;
function makeModelValid (model){
angular.forEach(model.$validators, function(validatorValue, validatorKey) {
model.$setValidity(validatorKey,true);
});
}
attrs.$observe( "disabled", function (value) {
disabled = value;
if(disabled){
makeModelValid(model);
}else{
model.$validate();
}
});
scope.$watch(function(){return model.$invalid;},function(){
if(disabled){
makeModelValid(model);
}
});
}
};
});
Related
I'm trying to implement an asynchronous validation by using a custom directive.
This is the directive
moduloArea.directive('uniqueName', function($http, $q) {
return {
require : 'ngModel',
link: function($scope, element, attrs, ngModel) {
ngModel.$asyncValidators.nombre = function(modelValue, viewValue) {
return $http.get('/checkUsernameAvailability/'+viewValue).then(
function(response) {
if (!response.data.validUsername) {
return $q.reject(response.data.errorMessage);
}
return true;
}
);
};
}
};
});
The result in console
As you can see when the root username is typed the return is an JSON object because this username is already taken.
But in the HTML the form in $invalid when the directive unique-name is inserted.
<form name="registerUsernameForm" novalidate="novalidate">
<input type="text" name="username" data-ng-model="person.userName" data-unique-name="" required="required"/>
<span data-ng-show="registerUsernameForm.username.$error.uniqueName">This username is already taken.</span>
<button type="submit" data-ng-disabled="registerUsernameForm.$invalid || registerUsernameForm.$pending" data-ng-click="registerPerson(person)"> Save Person </button>
</form>
I'm using the data-unique-name="" (="") because if I don't then thymeleaf generate the following error:
Attribute name "data-unique-name" associated with an element type
"input" must be followed by the ' = ' character.
What do you think can be wrong?
You are validating nombre not uniqueName. For this reason, uniqueName is still stay invalid.
moduloArea.directive('uniqueName', function($http, $q) {
return {
require : 'ngModel',
link: function($scope, element, attrs, ngModel) {
ngModel.$asyncValidators.uniqueName= function(modelValue, viewValue) {
var value = modelValue || viewValue;
return $http.get('/checkUsernameAvailability/'+value ).then(
function resolved(response) {
if (response.data && !response.data.validUsername) {
return $q.reject(response.data.errorMessage);
}
return true;
}, function rejected() {
//username does not exist, therefore this validation passes
return true;
}
);
};
}
};
});
I'm trying to unite the AngularJS validation model with the Bootstrap form validation display.
If a user loads an empty form, I don't want the form to display error message right away. I want to wait until the user interacts with the form.
If a user submit the form with required fields not filled out, I also want to display an error message.
If a user starts typing in the field, I want error messages to show up right away.
So I have to check myForm.$submitted, myForm.<fieldName>.$dirty as well as myForm.<fieldName>.$touched.
However, it makes a lot of duplicated code with very few variation.
I've tried to make a directive to fix this issue but I can't seem to find the right way to wrap this complexity away.
HTML:
<div class="form-group required" ng-class="{ 'has-error': myForm.firstname.$invalid && (myForm.firstname.$dirty || myForm.$submitted || myForm.firstname.$touched) }">
<label for="firstname" class="control-label" translate>Profile.FirstName</label>
<input type="text" class="form-control" id="firstname" name="firstname" required ng-model="vm.profile.firstName"/>
<p class="help-block" ng-if="myForm.firstname.$error.required" translate>Forms.Default.Required</p>
</div>
I want to take the whole ng-class attribute and replace it by something more succinct. The directive seemed like the way to go so tried this:
(function(){
'use strict';
angular.module('app')
.directive('hasError', [function(){
return {
restrict: 'A',
scope: {
form: '=bsForm',
control: '=bsControl'
},
link: function(scope, element){
scope.$watch('form', function(){
var isInvalid = scope.control.$invalid && scope.control.$dirty;
element.toggleClass('has-error', isInvalid);
});
}
};
}]);
})();
Usage:
<div class="form-group required" has-error bs-form="myForm" bs-control="myForm.firstname">
...
</div>
This however was not refreshing when properties of form changed.
What am I missing?
So... I managed to make a directive work properly for exactly my usage.
If there is a better way, please prove me wrong.
(function(){
'use strict';
angular.module('app')
.directive('hasError', [function(){
return {
restrict: 'A',
scope: {
form: '=bsForm',
control: '=bsControl'
},
link: function(scope, element){
scope.$watchGroup(['control.$invalid', 'control.$dirty', 'control.$touched', 'form.$submitted'], function(){
var isInvalid = scope.control.$invalid && (scope.control.$dirty || scope.form.$submitted || scope.control.$touched);
element.toggleClass('has-error', isInvalid);
});
}
};
}]);
})();
I did something like this one. My solution took a slightly different approach, but it may be helpful here (you can view the gist on Github).
Essentially, what I do is wrap all my form data inside a single object and I assign that object to a <form> attribute. I then watch that object and any time it changes, I select all elements with the ng-dirty and ng-invalid classes (this selector could be changed to whatever you like). I then loop through each of these elements and update messages for each of them.
Here's the code:
(function() {
"use strict"
angular.module('app')
.directive('formValidator', function() {
return {
require: '^form',
scope: {
formData: '=',
validateAll: '='
},
link: function(scope, element, attrs, ctrls) {
window.frm = ctrls;
var selector = '.ng-dirty.ng-invalid';
function validate() {
$(".formValidator-input-validation-error-message").remove();
element.find(selector).each(function(index, el) {
$el = $(el);
var messages = [];
var classes = $el.attr('class').match(/[\d\w-_]+/g);
for (var i in classes) {
var lastIndex = classes[i].lastIndexOf('-invalid-');
if (lastIndex != -1) {
var validationMessageAttr = "data-" + classes[i].substr(lastIndex + 9) + "-validation-message";
var msg = $el.attr(validationMessageAttr);
if (!msg) {
msg = element.attr(validationMessageAttr);
if (!msg) {
msg = "Invalid!";
}
}
messages.push("<div class='validator'>" + msg + "</div>");
}
}
$(el).after("<div style='position:absolute;' class='formValidator-input-validation-error-message'>" + messages.join() + "</div>");
});
}
scope.$watch(function() {
return scope.formData;
}, function() {
validate();
}, true);
scope.$watch('validateAll', function(newValue, oldValue) {
selector = !!newValue ? '.ng-invalid' : '.ng-dirty.ng-invalid';
validate();
});
}
};
})
})();
I'm facing an issue which I can't seem to solve.
I have several inputs with each a directive to validate the input value, like this:
<div class="row form-group">
<div class="col-sm-6">last name</div>
<div class="col-sm-6">
<div class="input-group" ng-class="{'has-error': form.lastname.$invalid && (form.lastname.$touched || form.$submitted)}">
<input type="text" name="lastname" class="form-control"
model-blur
validator-lastname
ng-trim="true"
ng-model="fields.lastname.value"
ng-maxlength="fields.lastname.validation.maxLength">
<input-group-addon class="input-group-addon"
iga-char=""
iga-form="form"
iga-field="form.lastname"
iga-if-touched="true">
</input-group-addon>
</div>
<form-message-list fml-form="form"
fml-field="form.lastname"
fml-label="Last name"
fml-fieldData="fields.lastname">
</form-message-list>
</div>
</div>
This field required the following pattern: /^[\'a-zA-Z_]+( [\'a-zA-Z_]+)*$/
My issue is this:
When I add an invalid value to my input, like this: / , my invalid message remains and ng-invalid-pattern remains on my field.
When I add this pattern to my field like this: ng-pattern="/^[\'a-zA-Z_]+( [\'a-zA-Z_]+)*$/" I don't have any issues. But when I try to validate via my directive validator-lastname it only checks one time. When I fill the input with an invalid value and then change it to empty, which is allowed, the ng-invalid-pattern error remains.
This is my directive:
angular.module('app')
.directive('validatorLastname', validatorLastname);
/* #ngInject */
function validatorLastname() {
var directive = {
require: 'ngModel',
link: link
};
return directive;
function link(scope, element, attrs, modelCtrl) {
var valid = false;
var formatter = function (inputValue) {
if (inputValue) {
var res = inputValue.match(/^[\'a-zA-Z_]+( [\'a-zA-Z_]+)*$/);
if (res && res.length > 0) {
valid = true;
}
modelCtrl.$setValidity('pattern', valid);
valid = false;
}
return inputValue;
};
modelCtrl.$parsers.push(formatter);
if (scope[attrs.ngModel] && scope[attrs.ngModel] !== '') {
formatter(scope[attrs.ngModel]);
}
}
}
I made a JSFiddle to reproduce the problem: http://jsfiddle.net/sZZEt/537/
I hope someone can point me in the right direction.
Thanks in advance.
You should update your directive code to make everything work fine.
angular.module('app')
.directive('validatorLastname', validatorLastname);
/* #ngInject */
function validatorLastname() {
var directive = {
require: 'ngModel',
link: link
};
return directive;
function link(scope, element, attrs, modelCtrl) {
var valid = false;
var formatter = function (inputValue) {
if (inputValue) {
var res = inputValue.match(/^[\'a-zA-Z_]+( [\'a-zA-Z_]+)*$/);
if (res && res.length > 0) {
valid = true;
}
modelCtrl.$setValidity('pattern', valid);
valid = false;
}else{
modelCtrl.$setValidity('pattern', true);
}
return inputValue;
};
modelCtrl.$parsers.push(formatter);
if (scope[attrs.ngModel] && scope[attrs.ngModel] !== '') {
formatter(scope[attrs.ngModel]);
}
}
}
I have created a plunk for your problem...
It is because if inputValue is null then your $setValidity method will not invoke and could not perform validation again. You should set pattern validity to true inside else part. if you want to make field valid for no-input.
You can now refer to updated plunk https://plnkr.co/edit/N3DrsB?p=preview
Hi i'm using an User Name validator in my project.But when I started writing some thing it's requesting for every characters after reach min length.
Here you can see in pic.
Here is my soruces;
Input parse in page:
<input type="text" id="regUserName" name="regUserName" required minlength="5" maxlength="15" class="form-control" placeholder="Kullanıcı Adı" data-ng-model="regUserName" username-validator />
And here validator in ValidationService.js:
angular.module('BlogAppModule').directive('usernameValidator', function ($http, $q) {
return {
require: 'ngModel',
link: function ($scope, element, attrs, ngModel) {
ngModel.$asyncValidators.usernameAvailable = function (username) {
return $http.get('/api/CheckUserName/' + username)
.then(function (response) {
var warning = document.getElementById("regUserName");
if (response.data != null) {
warning.style.backgroundColor = "red";
}
else {
warning.style.backgroundColor = "white";
}
})
}
}
};
});
Now here my some questions about this:
Will be a problem about this requests?(Like performance problem)
Can I set this validation like when I leave the textbox then check
username?If this is possible, what should I do?
When user write "." requesting don't work.How can I disable in
textinput special chars like : " . - , ! # " etc.
By default, changes will be written back to the model on every change.
Use ng-model-options="{ updateOn: 'blur' }" on the input element to update the binding when leaving the input field.
If you want to add client side validation as well, you can add a synchronous validator. $validators will be executed before $asyncValidatorss.
link: function ($scope, element, attrs, ngModel) {
ngModel.$asyncValidators.usernameAvailable = function (username) {
// your server side validation
};
ngModel.$validators.userNameOk = function(username) {
return username && username.indexOf('.') < 0;
};
}
http://plnkr.co/edit/Yd8C8Ut9VWPXEvDEMjFJ?p=preview
I am having a form page which is used for both add and edit form . So at the time of adding data into form , i need to check input data is duplicated or not, so i created a directive which will check with the data base weather data is existed or not.
<input name="divId" type="text" class="form-control fL w250" ng-model="divisionData.DivisionID" ng-disabled="Disabled()" unique-divid required /></div>
at the time of adding new data its works fine . but at the time of edit also using the same form so the unique-divid directive is running. I don't want this to happen in edit page
.directive('uniqueDivid', function($http) {
var toId;
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.ngModel, function(value) {
if(toId) clearTimeout(toId);
toId = setTimeout(function(){
console.log(value);
$http({
method : 'POST',
url : 'http://testurl',
data : $.param({'DivisionID' : value}),
headers : { 'Content-Type': 'application/x-www-form-urlencoded' }
})
.success(function(data) {
console.log(data);
if(data.status == 'success'){
ctrl.$setValidity('uniqueDivid', true);
}else if(data.status == 'fail'){
ctrl.$setValidity('uniqueDivid', false);
}
if (!data.success) {
} else {
}
});
}, 200);
})
}
}
});
Please suggest a solution either disable the directive or stop the scope.$watch function at the time of edit .
Can you add additional attribute to the input field indicating type of form action (create/edit) it is serving?
For example:
<input name="divId" type="text" class="form-control fL w250" ng-model="divisionData.DivisionID" ng-disabled="Disabled()" unique-divid form-action="edit" required />
If action type matches the one you want to ignore, just return from linking function.
link: function(scope, elem, attr, ctrl) {
if (attr.formAction === "edit") return;
..
}
In case you do not want to modify template, what is the indication in controller or scope that could be used to distinguish between new and existing divisionData? You could use that property as a flag to return from the unique-divid directive:
link: function(scope, elem, attr, ctrl) {
if (!scope.divisionData.isNew) return;
..
}