Set custom input validation in angular form - angularjs

I have an AngularJS form.
I implemented typeahead script so when someone start typing into input field values are displayed above field. If defined values are banana, apple and beer and user starts to type "ba" list appears with value banana.
If user clicks on banana value from typeahead it's assigned into input field via javascript.
I want to make input field and entire form invalid if value selected isn't from typeahead list.
I have html:
<form id="form" name="form" ng-submit="formsubmit()" novalidate>
<input ng-model="food" ng-required="true" name="food" autocomplete="off" type="text" id="food" placeholder="Start typing" />
<p class="error validationerror" ng-show="form.food.$invalid && form.food.$touched">Required</p>
<div class="error validationerror" ng-messages="form.food.$error"><p ng-message="food">You must specify item from list</p></div>
<button type="submit" id="submit" class="btn large black" ng-disabled="form.$invalid">Submit</button>
</form>
I have controller:
var app = angular.module('app', ['ngRoute', 'ngMaterial', 'ngMessages', 'angular-loading-bar']);
app.controller('Food', ['$scope', '$http', '$routeParams', function ($scope, $http, $routeParams) {
$scope.formsubmit = function () {
console.log('submited');
};
$http.get('food.php')
.success(function (data) {
$scope.foods = data;
//typeahead script...
}]);
By now everything is working ok. Now we have to check if value of input field is defined in food list. No matter if it's pasted, typed or selected from typeahead list.
I defined directive:
app.directive('food', function (){
return {
require: 'ngModel',
link: function(scope, elem, attr, ngModel) {
function updateFoodInfo(scope, elem){
var food1 = $('#food').val();
var data = scope.foods;
if (data.indexOf(food1) < 0) {
ngModel.$parsers.unshift(function (value) {
ngModel.$setValidity('food', data.indexOf(value) === -1);
return value;
});
}
}
setInterval(function(){updateFoodInfo(scope,elem);}, 1000);
}
};
});
I have no console errors and whatever I enter into input field, form is valid. Only if I remove everything form is invalid and input field is set as invalid because it is empty. But I simply can't set custom validation.
Here is working plunker too.
plunker

Here is a working plunker
http://plnkr.co/edit/IlJJLduidBwUfb2EZWvV?p=preview
angular 1.6.5 http://plnkr.co/edit/fIRBuijd0xWDqZvHB24Z?p=preview
Hope it helps.

You only need to add the validation to your input field <input food></input> and you were checking for input that wasn't matching anything in the list. Just change to ngModel.$setValidity('food', data.indexOf(value) !== -1);.
Here is the plunker: http://plnkr.co/edit/FnuNFbGiCNOXwkkuDPJ7?p=preview

Related

AngularJS - Invoking function from a dynamically created element

I'm developing a query builder in angularJS. I'm customizing angular-query-builder .
The Code structure is (only the code relevant to the context is shown )
base.html
<query-builder group="filter.group"></query-builder>
querybuilder.html
<select ng-options="t.name as t.name for t in fields" ng-model="rule.field" execute-change="setValidation()" class="form-control input-sm"><option value="">--Select Filter--</option></select>
<select style="margin-left: 5px" ng-options="c.name as c.name for c in conditions" ng-model="rule.condition" class="form-control input-sm"></select>
<input type="text" ng-model="rule.data" class="form-control input-sm condition-inp" />
controller.js
$scope.setValidation = function () {
console.log('Triggred')
}
directive.js
.directive('queryBuilder', ['$compile', function ($compile) {
return {
restrict: 'E',
scope: {
group: '='
},
templateUrl: 'querybuilder.html',
compile: function (element, attrs) {
var content, directive;
content = element.contents().remove();
return function (scope, element, attrs) {
element.on('change', function (e) {
scope.$apply(attrs.executeChange);
});
}
}
}
}]);
What I want is When selecting an item in the first drop down mentioned in querybuilder.html I need to add validation to the corresponding input box.
ie, if the item selected is date, then the input box must be date picker and if it is phone number, then the input box should be integer.
I'm trying to do that using the setValidation function in controller.js But that function is not getting triggered.
I'm hoping that this is used with a parent of a form as $setValidity is called on a named form input myFrom.nameOfInput.$setValidity.
If so you can pass the form to the directive or pass the validitor function as a parameter to the directive like scope right now takes only group let it take parentValidator as well and have that be a function in the parent controller that sets the validity.

Cannot get form data with ng-model

So I have two forms inside the same controller.
<form name="myForm" id="myForm" class="form-horizontal" ng-submit="saveMyForm(myForm)" >
<input type="text" id="name" ng-model="name" />
//...etc
</form>
and another form
<form name="passForm" id="passForm" ng-submit="savePassForm(passForm)" >
<input type="password" id="oldpassword" name="oldpassword" ng-model="oldpassword" >
<input type="password" id="pw1" name="pw1" ng-model="pw1" >
<input type="password" id="pw2" name="pw2" ng-model="pw2" pw-check="pw1" >
</form>
<div class="msg-block" ng-show="passForm.$error">
<span class="msg-error loginError" ng-show="passForm.pw2.$error.pwmatch">
Passwords don't match.
</span>
</div>
To check if passwords match I have this directive
app.directive('pwCheck', [function () {
return {
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
var firstPassword = '#' + attrs.pwCheck;
elem.add(firstPassword).on('keyup', function () {
scope.$apply(function () {
var v = elem.val()===$(firstPassword).val();
ctrl.$setValidity('pwmatch', v);
});
});
}
}
}]);
So my first form works fine.
In my second form, the one for passwords, I cannot grab the passwords from the fields to send them to the server. I do
var passData = {
"oldpassword" : $scope.oldpassword,
"newpassword" : $scope.pw2
}
$scope.changepassword = function(form){
if(form.$valid) {
var promisePass = passwordFactory.changePass(passData);
promisePass.success(function (data, status) {
//handle
When I check my console, there are no data, passData is empty.
What am I missing here? Is it the fact that there are two forms inside the same controller? Does the directive messes things up?
Please help me fix this.
Thanks
I see a couple issues. First, the function name you have specified here:
<form name="passForm" id="passForm" ng-submit="savePassForm(passForm)" >
Does not match the name in your controller:
$scope.changepassword = function(form){
Second, you create your passData object outside of the submit function. This means it's going to have the values of the scope variables when the controller first loaded, likely undefined. Move the creation of passData inside your function and then it will be created with the current values of the scope variables.

Textbox validation onfous using Angularjs

I'm trying to build an Angular JS form. I'd like user to be able to set the focus on a text field when entering data in text field.
Thanks
If you want default autofocus to a particular text field then you can add a directive like below-
.directive('autoFocus', function($timeout) {
return {
restrict: 'A',
link: function($scope, $element) {
$timeout(function() {
$element[0].focus();
}, 0);
}
};
})
And use it with text box like -
You can use ng-focus
<input type="text" ng-focus="focus=true;blur=false;" ng-blur="blur=true;focus=false;"/>
<p>focus: {{focus}}</p>
<p>blur: {{blur}} </p>
if you want to set focus, or you can do like this
<input type="text" ng-focus="validate('somevalue')"/>
and
$scope.validate= function(text)
{
// validate here
}

asyncValidator dependent on other field's value

I'm trying to add a validation field to one of my inputs, it should request the server whether the inputted VAT number is valid, so I'm using an async validator for this. Works fine with this code:
myApp.factory('isValidVat', function($q, $http) {
return function(vat) {
var deferred = $q.defer();
console.log(vat);
$http.get('/api/vat/' + vat).then(function() {
deferred.resolve();
}, function() {
deferred.reject();
});
return deferred.promise;
}
});
myApp.directive('validVat', function(isValidVat) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$asyncValidators.vat = isValidVat;
}
};
});
<form name="form" novalidate ng-submit="check(form)">
<div class="vat-field">
<label>VAT number
<input type="text" ng-model="formModel.vat" name="vat" valid-vat="true">
</label>
<div ng-show="registerForm.$submitted || registerForm.vat.$touched">
<span ng-show="registerForm.vat.$error.vat">
<small class="error">Your VAT address is not valid, please correct.</small>
</span>
</div>
</div>
<div class="country-field">
<label>Country
<select ng-model="formModel.country" name="country">
<option value="{{country.iso_3}}" ng-repeat="country in countries()">{{country.name}}</option>
</select>
</label>
</div>
<button type="submit">Check</button>
</form>
However, I want to make this asyncValidator check conditional on the value of another field (country, more specifically whether the country is a EU country).
The country field is a combobox, populoated via a service which has a record of all countries and their vat information and eu status.
However, I do not know how to inject the value of the selected country into the factory function. One idea was to link the selected country to it's own service and use it from the factory, but then the validation doesn't run again when another country is selected. If a non-EU country is selected, I don't really care what is in the field.
Pasing value into derective is easy, you can pass it to same attribute or some other. Its better not to use isolated scope, because attribute directive should be able to work on every element. So You can pass your country information to the validation directive, and then pass it to function which is returned by factory.
The other thing is dependency. Validation is only run when model itself is changed. So you have to place watch / observation on it.
Stop talking ... its code time ...
Here is fully working example based on your code :
http://jsbin.com/yuqibajehe/edit?html,js,output
.factory('isValidVat', function($q, $http, $timeout) {
return function(vat, country) {
var deferred = $q.defer();
console.log(vat, country);
$timeout(function() {
if (vat === country) {
deferred.resolve();
}
deferred.reject();
},1000);
return deferred.promise;
};
})
.directive('validVat', function(isValidVat) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
attrs.$observe('validVat', function() {
ngModel.$validate();
});
ngModel.$asyncValidators.vat = function(vat) {
return isValidVat(vat, attrs.validVat);
};
}
};
});
Ass you can see I have little bit simplified the async validator, but enough for now. function has two arguments, so I call it from other function.
Directive is used like that:
<input type="text" ng-model="vm.vat" name="vat" valid-vat="{{formModel.country}}" />
So we pass the value of model straight to the attribute valid-vat.
In directive we can then send the value into validator.
Then we have to observe the content of validator and run validation if this is changed using $validate().
For this case it is probably enough, but if you want to pass model directly like: valid-vat="formModel.country" it wouldn't work, because value of attribute doesn't change. So you would have go to the scope, or better evaluate attr value and watch its changes - like in this example: http://plnkr.co/edit/296x2shAVSe7FRv3mJnp?p=preview

Share validation state for multiple fields against the same validator

I will start out stating that I have searched google and SO and I have not found an answer for this specific situation. Yes, there are other posts that sound the same but are more based on a "MoreThan / LessThan" mentality. This does not following that mentality at all so please do not mark this as a duplicate referring to them.
Check out the Plunker Example
I am attempting to make sure the user does not enter an address that already exists else where on the page. To do this I need to validate all the address fields since different locations may have the same street address. I need the validator to set all the related fields to valid if any are invalid once the address has been fixed to not be a duplicate. Currently it only sets the last field modified to valid and leaves the rest as invalid.
Plunker example demonstrates what is happening. I have tried many different approaches such as iterating through all fields and setting them to prestine and untouched and then setting them to dirty and touched to trigger validations again but I am having no luck getting this working.
Validator
angular.directive('ruleFunc', ['$parse', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, $element, $attrs, $ngModel) {
var validatorName = $attrs.ruleName;
var validatorFunc = $attrs.ruleFunc;
if (!angular.isDefined(validatorName)) {
throw Error("rule-name attribute must be defined.");
}
if (!angular.isDefined(validatorFunc)) {
throw Error("rule-func attribute must be defined.");
}
// in real code I passing a function call with the model as the param
// this example demonstrated the issue I am having though
var expressionHandler = $parse(validatorFunc);
// had to use viewChangeListener because changes to the model
// were not showing up correctly in the actual implementation
$ngModel.$viewChangeListeners.push(function() {
var valid = expressionHandler($scope);
$ngModel.$setValidity(validatorName, valid);
});
});
Form
<form name="AddressForm" novalidate>
<h1>Address Form</h1>
<div style="margin:20px">
<input id="Street" type="text" name="Street" placeholder="Street" data-ng-model="ctrl.address.street" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.Street.$error.profileHasContact}}
<br />
<input id="City" type="text" name="City" placeholder="City" data-ng-model="ctrl.address.city" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.City.$error.profileHasContact}}
<br />
<input id="State" type="text" name="State" placeholder="State" data-ng-model="ctrl.address.state" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.State.$error.profileHasContact}}
<br />
<input id="Zip" type="text" name="Zip" placeholder="Zip" data-ng-model="ctrl.address.zip" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.Zip.$error.profileHasContact}}
<br />
<div ng-if="(AddressForm.Street.$error.profileHasContact
|| AddressForm.City.$error.profileHasContact
|| AddressForm.State.$error.profileHasContact
|| AddressForm.Zip.$error.profileHasContact)">Address already exists in Main Contacts</div>
<button type="submit">Submit</button>
</div>
I did find a post that was close enough that I could hack together a solution.
Form validation - Required one of many in a group
Here is the updated plunker
Updated Validator
directive('ruleFunc', ['$parse', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, $element, $attrs, $ngModel) {
var validatorName = $attrs.ruleName;
var validatorFunc = $attrs.ruleFunc;
var groupName = $attrs.ruleGroup;
if (!angular.isDefined(validatorName)) {
throw Error("rule-name attribute must be defined.");
}
if (!angular.isDefined(validatorFunc)) {
throw Error("rule-func attribute must be defined.");
}
if(angular.isDefined(groupName)){
// setup place to store groups if needed
if (!$scope.__ruleValidationGroups) {
$scope.__ruleValidationGroups = {};
}
var groups = $scope.__ruleValidationGroups;
// setip group if needed
if(!groups[groupName]){
groups[groupName] = {};
}
var group = groups[groupName];
// assign model to group
group[$attrs.ngModel] = {
model: $ngModel
}
}
function updateValidity(valid){
if(angular.isDefined(groupName)){
// set all models in group to same validity
for(var prop in group){
if(group.hasOwnProperty(prop)){
group[prop].model.$setValidity(validatorName, valid);
}
}
}
else
{
// set this model validity if not in group
$ngModel.$setValidity(validatorName, valid);
}
}
var expressionHandler = $parse(validatorFunc);
$ngModel.$viewChangeListeners.push(function() {
var valid = expressionHandler($scope);
updateValidity(valid);
});
}
};
}]);

Resources