Form validation - Required one of many in a group - javascript

In the project I'm working on at the moment I currently have three textboxes and I need to validate that at least one of the text boxes has been populated.
I've been reading into custom validation with Angular directives and I understand you can set the validity of an input in a directive's link function using the following:
ctrl.$parsers.unshift(function(viewValue) {
// validation logic here
});
The problem I have is that I don't need to set an individual input's validity.. I need to invalidate the entire form if the criteria isn't met. I just wonder how to approach this?
I'm thinking maybe I should create a directive that's placed on the enclosing form itself and then make the form invalid?
I suppose I'm just looking for some guidance into how I should go about this because I'm a little unclear where to start - all the material I'm reading on custom validation seems to be for when you're validating a specific input as opposed to a set of conditions on a form.
I hope I've made myself clear! Thanks..

You can use ng-required to force the user to fill at least one field by checkingthe length attribute of the string.
You can do the following for example:
<form name="myForm">
<input type="text" ng-model="fields.one" name="firstField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
<br/>
<input type="text" name="secondField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" ng-model="fields.two" />
<br/>
<input type="text" ng-model="fields.three" name="thirdField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
<br/>
<button type="submit" ng-disabled="!myForm.$valid">Submit</button>
</form>
See this working fiddle example for more details.
You can have more details about required vs ng-required by reading this question

There are several approaches and the best option depends on your exact requirements.
Here is one approach that I found to be generic enough and flexible.
By "generic" I mean it doesn't only work for text-fields, but also for other kinds of inputs, such as check-boxes.
It's "flexible" because it allows any number of control-groups, such that at least one control of each group must be non-empty. Additionally, there is no "spacial" constraint - the controls of each group can be anywhere inside the DOM (if required, it is easy to constrain them inside a single form).
The approach is based on defining a custom directive (requiredAny), similar to ngRequired, but taking into account the other controls in the same group. Once defined, the directive can be used like this:
<form name="myForm" ...>
<input name="inp1" ng-model="..." required-any="group1" />
<input name="inp2" ng-model="..." required-any="group1" />
<input name="inp3" ng-model="..." required-any="group1" />
<input name="inp4" ng-model="..." required-any="group2" />
<input name="inp5" ng-model="..." required-any="group2" />
</form>
In the above example, at least one of [inp1, inp2, inp3] must be non-empty, because they belong to group1.
The same holds for [inp4, inp5], which belong to group2.
The directive looks like this:
app.directive('requiredAny', function () {
// Map for holding the state of each group.
var groups = {};
// Helper function: Determines if at least one control
// in the group is non-empty.
function determineIfRequired(groupName) {
var group = groups[groupName];
if (!group) return false;
var keys = Object.keys(group);
return keys.every(function (key) {
return (key === 'isRequired') || !group[key];
});
}
return {
restrict: 'A',
require: '?ngModel',
scope: {}, // An isolate scope is used for easier/cleaner
// $watching and cleanup (on destruction).
link: function postLink(scope, elem, attrs, modelCtrl) {
// If there is no `ngModel` or no groupName has been specified,
// then there is nothing we can do.
if (!modelCtrl || !attrs.requiredAny) return;
// Get a hold on the group's state object.
// (If it doesn't exist, initialize it first.)
var groupName = attrs.requiredAny;
if (groups[groupName] === undefined) {
groups[groupName] = {isRequired: true};
}
var group = scope.group = groups[groupName];
// Clean up when the element is removed.
scope.$on('$destroy', function () {
delete(group[scope.$id]);
if (Object.keys(group).length <= 1) {
delete(groups[groupName]);
}
});
// Update the validity state for the 'required' error-key
// based on the group's status.
function updateValidity() {
if (group.isRequired) {
modelCtrl.$setValidity('required', false);
} else {
modelCtrl.$setValidity('required', true);
}
}
// Update the group's state and this control's validity.
function validate(value) {
group[scope.$id] = !modelCtrl.$isEmpty(value);
group.isRequired = determineIfRequired(groupName);
updateValidity();
return group.isRequired ? undefined : value;
}
// Make sure re-validation takes place whenever:
// either the control's value changes
// or the group's `isRequired` property changes
modelCtrl.$formatters.push(validate);
modelCtrl.$parsers.unshift(validate);
scope.$watch('group.isRequired', updateValidity);
}
};
});
This might not be so short, but once included into a module, it is very easy to integrate into your forms.
See, also, this (not so) short demo.

It's too late but might be can save some one's time:
If there are only two fields, and want to make one of them required then
<input type="text"
ng-model="fields.one"
ng-required="!fields.two" />
<br/>
<input type="text"
ng-model="fields.two"
ng-required="!fields.one" />
If you have three like in question then
<input type="text"
ng-model="fields.one"
ng-required="!(fields.two || fields.three)" />
<br/>
<input type="text"
ng-model="fields.two"
ng-required="!(fields.one || fields.three)" />
<br/>
<input type="text"
ng-model="fields.three"
ng-required="!(fields.one|| fields.two)" />
If more than this, I will suggest to write a function on scope and watch it.
See the working example

modification to ExpertSystem's answer (https://stackoverflow.com/a/24230876/4968547) so that his code works in the latest angularjs.
i changed the updateValidity() to set parse also to true/false
function updateValidity() {
if (group.isRequired) {
modelCtrl.$setValidity('required', false);
modelCtrl.$setValidity('parse', false);
} else {
modelCtrl.$setValidity('required', true);
modelCtrl.$setValidity('parse', true);
}
}
now its working fine for me

Ran into this same problem last week; ExpertSystem's solution was a good start, but I was looking for a few enhancements to it:
Use Angular 1.4.3
Use ngMessages
I eventually wound up with this example on JSFiddle - hope that helps inspire others in the same boat! Relevant JS code from the Fiddle:
var app = angular.module('myApp', ['ngMessages']);
app.controller('myCtrl', function ($scope) {
$scope.sendMessage = function () {
$scope.myForm.$submitted = true;
if ($scope.myForm.$valid) {
alert('Message sent !');
}
};
});
app.directive('requiredAny', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function postLink(scope, elem, attrs, ctrl) {
// If there is no 'ngModel' or no groupName has been specified,
// then there is nothing we can do
if (!ctrl || !attrs.requiredAny) { return };
// If this is the first time we've used this directive in this scope,
// create a section for it's data. If you need / want to make use of
// an isolate scope you'll need to make 'var groups' scoped to the directive;
// but then you may want to look in to clearing out group entries yourself
if (!scope.__requiredAnyGroups) {
scope.__requiredAnyGroups = {}
}
var groups = scope.__requiredAnyGroups;
// Create a bucket for this group if one does not yet exist
if (!groups[attrs.requiredAny]) {
groups[attrs.requiredAny] = {};
}
var group = groups[attrs.requiredAny];
// Create the entry for this control
group[attrs.ngModel] = {
ctrl: ctrl,
hasValue: false
};
ctrl.$validators.requiredAny = function(view, value) {
var thisCtrl = group[attrs.ngModel],
ctrlValue = (typeof value !== 'undefined') && value,
oneHasValue = false;
thisCtrl.hasValue = ctrlValue;
// First determine if any field in the group has a value
for (var prop in group) {
if (group.hasOwnProperty(prop) && group[prop].hasValue) {
oneHasValue = true;
break;
}
}
// Set the validity of all other fields based on whether the group has a value
for (var prop in group) {
if (group.hasOwnProperty(prop) && thisCtrl != group[prop]) {
group[prop].ctrl.$setValidity('requiredAny', oneHasValue);
}
}
// Return the validity of this field
return oneHasValue;
};
}
};
});

Here is a refactored take on ExpertSystems great post. I didn't need the destroy method so I gutted it.
I also added a grayed out explanation that may help in your code. I use this directive for ALL my required fields. Meaning when I use this directive I no longer use ng-required, or required.
If you want a field required just pass in a unique group name. If you don't want the field required then pass in null, and if you want to have many different groups just pass in a matching group name.
I believe there is a little more refactoring that could be done here. Angularjs states that when using $setValidity, that instead you should use $validators pipeline instead, but I could not get that to work. I am still learning this complex animal. If you have more info, post it!
app.directive('rsPartiallyRequired', function () {
var allinputGroups = {};
return {
restrict: 'A',
require: '?ngModel',
scope: { },
link: function(scope, elem, attrs, ctrl) {
if( !ctrl || !attrs.rsPartiallyRequired ){ return } // no ngModel, or rsPartialRequired is null? then return.
// Initilaize the following on load
ctrl.$formatters.push( validateInputGroup ); // From model to view.
ctrl.$parsers.unshift( validateInputGroup ); // From view to model.
if ( ! allinputGroups.hasOwnProperty( attrs.rsPartiallyRequired )){ // Create key only once and do not overwrite it.
allinputGroups[ attrs.rsPartiallyRequired ] = { isRequired: true } // Set new group name value to { isRequired: true }.
}
scope.inputGroup = allinputGroups[ attrs.rsPartiallyRequired ] // Pass { isRequired: true } to form scope.
function validateInputGroup(value) {
scope.inputGroup[ scope.$id ] = !ctrl.$isEmpty( value ); // Add to inputGroup ex: { isRequired: true, 01E: false }.
scope.inputGroup.isRequired = setRequired( attrs.rsPartiallyRequired ); // Set to true or false.
updateValidity(); // Update all needed inputs based on new user input.
return scope.inputGroup.isRequired ? undefined : value
}
function setRequired(groupName) {
if( ! allinputGroups[ groupName ] ){ return false } // No group name then set required to false.
return Object.keys( allinputGroups[ groupName ] ).every( function( key ) { // Key is 'isRequired' or input identifier.
return ( key === 'isRequired' ) || ! allinputGroups[ groupName ][ key ]
});
}
scope.$watch('scope.inputGroup.isRequired', updateValidity); // Watch changes to inputGroup and update as needed.
function updateValidity() { // Update input state validity when called.
ctrl.$setValidity('required', scope.inputGroup.isRequired ? false : true );
}
}
}
});
// This directive sets input required fields for groups or individual inputs. If an object in the template is given
// to the directive like this:
// Object: { "name": "account_number", "attrs": { "required": { "group": "two" }}}.
// HTML: <input type="text" rs-partially-required="{{ field.attrs.required.group }}" />
// Or anything where the evaluation is a string, for example we could use "groupOne" like this...
// HTML: <input type="text" rs-partially-required="groupOne" />
// Then this directive will set that group to required, even if it's the only member of group.
// If you don't want the field to be required, simply give the directive a null value, like this...
// HTML: <input type="text" rs-partially-required="null" />
// However, when you want to use this directive say in an ngRepeat, then just give it a dynamic string for each input
// and link the inputs together by giving the exact matching string to each group that needs at least one field. ex:
// <input type="text" rs-partially-required="null" />
// <input type="text" rs-partially-required="one" />
// <input type="text" rs-partially-required="two" />
// <input type="text" rs-partially-required="one" />
// <input type="text" rs-partially-required="null" />
// <input type="text" rs-partially-required="three" />
// <input type="text" rs-partially-required="three" />
// <input type="text" rs-partially-required="three" />
// In the above example, the first and fifth input are not required and can be submitted blank.
// The input with group "two" is the only one in the group, so just that input will be required by itself.
// The 2 inputs with "one" will be grouped together and one or the other will require an input before
// the form is valid. The same will be applied with group "three".
// For this form to be valid, group "two" will be required, and 1 input from group one will be required,
// and 1 input from group three will be required before this form can be valid.

You can add required attribute for each of them , and at the end , you can rely your validation on each/all/or just one of them
<form name="form" novalidate ng-submit="submit()">
// novalidate is form disabling your browser's own validation mechanism
<input type="text" required ng-model="texts.text1">
<input type="text" required ng-model="texts.text2">
<input type="text" required ng-model="texts.text3">
// you can do validation in variety of ways , but one of them is to disable your submit button until one of the textboxes are filled correctly like this :
<button type="submit" ng-disabled="form.text1.$invalid && form.text2.$invalid && form.text3.$invalid"></button>
</form>
This way if just one of them is filled , button will be enable
I don't know how you're gonna show that form is not valid , but I think desabling the submit button is the general way

I had similar grouping requirement in my project and I wrote this.Interested people can use this
.directive('group',function(){
return {
require: '^form',
link : function($scope,element,attrs,formCtrl){
var ctrls =[];
element.find(".group-member").each(function(){
var member = angular.element($(this));
var mdlCtrl = member.data("$ngModelController");
if(!mdlCtrl){
throw "Group member should have ng-model";
}
ctrls.push(mdlCtrl);
});
var errKey = attrs['name']+"GrpReqd";
var min = attrs['minRequired'] || 1;
var max = attrs['maxRequired'] || ctrls.length;
$scope.validateGroup = function(){
var defined=0;
for(i=0;i<ctrls.length;i++){
if(ctrls[i].$modelValue){
defined++;
}
}
if(defined < min || defined > max){
formCtrl.$setValidity(errKey,false);
} else {
formCtrl.$setValidity(errKey,true);
}
};
//support real time validation
angular.forEach(ctrls,function(mdlCtrl){
$scope.$watch(function () {
return mdlCtrl.$modelValue;
}, $scope.validateGroup);
});
}
};
})
HTML usage :
<div name="CancellationInfo" group min-required="1" max-required="1">
<input type="text" class="form-control group-member" style="width:100%;" name="Field1" ng-model="data.myField" />
<input type="text" class="form-control group-member" style="width:100%;" name="Field1" ng-model="data.myField2" />
<input type="text" class="form-control group-member" style="width:100%;" name="Field2" ng-model="data.myField3" />
</div>
Here group directive identifies the logical grouping. This directive sits on an element without ng-model, a div in the above example. group directive receives 2 optional attribute min-required and max-required. Group members are identified using group-member class on individual fields. Group members are supposed to have an ng-model for binding. Since group directive doesn't have an ng-model error will be emitted under yourForm.$error.CancellationInfoGrpReqd in the above case. Unique Error key is generated from the element name on which group directive is sitting with GrpReqd appended to it.

Related

Angular: how to fire ngChange when input type="number" but not entered a number

I'm building an angular directive, where I have two number inputs and both together represent an age range.
What I want to achieve is being able to use this directive the following way:
<input-age-range
name="ageRange"
ng-model="filterUpdateVM.ageRange">
</input-age-range>
<span ng-if="myCtrlVM.form.ageRange.$error.ageRange">
Please, enter a valid age range.
</span>
and be able to show a custom error when the entered age range is not correct.
My directive html template looks this way:
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Minimum age"
name="ageMin"
min="18"
max="80"
ng-change="checkAndValidateAgeRange()"
ng-model="ageRange.ageMin"
ng-model-options="{allowInvalid: true}">
</div>
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Maximum age"
name="ageMax"
min="18"
max="80"
ng-change="checkAndValidateAgeRange()"
ng-model="ageRange.ageMax"
ng-model-options="{allowInvalid: true}">
</div>
Every time the user types anything in an input, I wish to check the if the entered range is correct:
Both ages should be numbers
Both ages should be between 18 and 80
ageMin <= ageMax
So far ng-model-options="{allowInvalid: true}" will let the ngChange function inside my directive to be triggered in case any of the ages entered is not between the desired age range (18-80) - this way I can do some checking and set an input error if there's any and show it.
My problem here is that if the age entered at first is not a number, ngChange is not called: it won't be able to do the error checking so there won't be any errors to be shown. How can I have my ngChange function called in this case, without changing my input type="number"?
EDIT: Jsfiddle added: http://jsfiddle.net/afkf96qh/
Finally after a day of trying to figure out what was going on, I achieved the behavior I was looking for.
First of all, ng-model-options="{allowInvalid: true}" had nothing to do in solving my problem, so I removed that out of my input. My directive html template looks this way now:
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Minimum age"
name="ageMin"
min="18"
max="80"
ng-model="ageRange.ageMin"
ng-change="checkAndValidateAgeRange()">
</div>
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Maximum age"
name="ageMax"
min="18"
max="80"
ng-model="ageRange.ageMax"
ng-change="checkAndValidateAgeRange()">
</div>
The problem that caused not triggering ng-change="checkAndValidateAgeRange()" was the way I was initializing the ageRange variable at the ngModel $render function:
angular
.module('myModule')
.directive('myDirective', myDirective);
function myDirective() {
var directive = {
templateUrl: 'myTemplate.html',
restrict: 'E',
require: ['^form', 'ngModel'],
link: linkFunc
};
return directive;
/////////
function linkFunc(scope, element, attrs, ctrls) {
scope.form = ctrls[0];
scope.ageRange = {};
scope.checkAndValidateAgeRange = checkAndValidateAgeRange;
var ngModel = ctrls[1];
ngModel.$render = getAgeRange;
function checkAndValidateAgeRange() {
// fired when the model changes
};
// $render function
function getAgeRange() {
scope.ageRange.ageMin = ngModel.$viewValue.ageMin;
scope.ageRange.ageMax = ngModel.$viewValue.ageMax;
};
};
};
In some cases, both of the ngModel object's properties passed to the directive - ageMin and ageMax - could be undefined.
And we all know what happens when we have an input with restrictions such as type="[whatever]", min="18" or max="80" and the data entered in that input does not follow those requirements: it is set to undefined in the model.
So, if the above properties were initially passed to the directive set to undefined and you entered a value in the <input type="number ...> that, for example, was not numeric... the binded model's value would still be undefined, so there wouldn't be any changes in the model making the ng-change not trigger.
What I finally did to solve this, was to initialize both ageRange.ageMin and ageRange.ageMax to null if they are passed as undefined, at my $render function:
ngModel.$render = getAgeRange;
// $render function
function getAgeRange() {
scope.ageRange.ageMin = ngModel.$viewValue.ageMin || null;
scope.ageRange.ageMax = ngModel.$viewValue.ageMax || null;
};
This way, when entering an invalid input the model's property value will change from null to undefined causing the ng-change to trigger.
checkout this fiddle
$scope.checkAndValidateAgeRange = function(name, model) {
if (name === 'ageMin') {
if (angular.isNumber(model)) {
if (model < $scope.age.min) {
$scope.minAgeMessage = 'You are under age';
} else if (model > $scope.age.max) {
$scope.minAgeMessage = 'You are too old';
} else {
$scope.minAgeMessage = '';
}
} else {
$scope.minAgeMessage = 'Enter Only Numbers';
$scope.ageRange.ageMin = null;
}
}
if (name === 'ageMax') {
if (angular.isNumber(model)) {
if (model <= $scope.ageRange.ageMin) {
$scope.maxAgeMessage = 'You are under age';
} else if (model > $scope.age.max) {
$scope.maxAgeMessage = 'You are too old';
} else {
$scope.maxAgeMessage = '';
}
} else {
$scope.maxAgeMessage = 'Enter Only Numbers';
$scope.ageRange.ageMax = null;
}
}
}
you can't use allowInvalid on type='number' as given here. You can set type='text' and then handle validation inside the ng-change handler if your intent if using type='number' is showing validation messages.
To set validity of form inside a controller refer to this link.

AngularJS: Dynamic inputs with form validation

I'm creating inputs inside a form dynamically. I created this directive for the purpose:
// Generate inputs on the fly using BE data.
.directive('inputGenerator', ['$compile', function ($compile) {
return {
restrict: 'E',
scope: {
id: '#',
type: '#',
name: '#',
attributes: '=',
ngModel: '=',
ngDisabled: '='
},
link: function (scope, iElement, iAttrs) {
// Get attributes
var id = iAttrs.id,
type = iAttrs.type,
name = iAttrs.name;
scope.ngModel[name] = {};
var extended_attributes = {
"type": type,
"id": id,
"data-ng-model": 'ngModel.' + name + '[\'value\']',
"name": name,
"data-ng-disabled": "ngDisabled"
};
if ( ! scope.attributes) {
scope.attributes = {};
}
// Append extra attributes to the object
angular.extend(scope.attributes, extended_attributes);
// Generate input
var input = '<input ';
angular.forEach(scope.attributes, function (value, key) {
input += key + '="' + value + '" ';
});
input += '/>';
// Compile input element using current scope (directive) variables
var compiledElement = $compile(input)(scope);
// Set the file selected name as the model value
compiledElement.on('change', function () {
if (this.files && this.files[0].name) {
var that = this;
scope.$apply(function () {
scope.ngModel[name] = {};
scope.ngModel[name]['value'] = that.files[0].name;
});
}
});
// Replace directive element with generated input
iElement.replaceWith(compiledElement);
}
};
}]);
This html line will trigger the directive:
<input-generator data-name="{{ item.name }}" data-ng-model="inputs.sources" data-attributes="item.attrs" data-type="{{ item.type }}" data-id="inputFile_{{ $index }}" data-ng-disabled="inputs.sources[item.name].selected" />
I'm running on Angular 1.4.3.
Problem
The model and pretty much everything works fine in the directive, but for some reason the form remains valid when the input added is invalid as you can see in this image.
I already tried:
Any of the Angular features of form validation works
I debugged Angular and seems to be that the input attached to the form is different from the input compiled inside the directive.
I already called formName.$setPristine() after each input was created, but it didn't work.
I couldn't access the form from the directive, but I think is not a good idea either.
I already wrapped the input with a ng-form tag, but nothing useful comes out of that.
I tried to use the directive compile method, but this is just triggered once when the app loads and I've a select input that loads different inputs on change.
Any help on this is much appreciated! :)
Thank you to everyone for contribute anyways!!
You should definitely take a look at my Angular-Validation Directive/Service. It as a ton of features and I also support dynamic inputs validation, you can also pass an isolated scope which helps if you want to not only have dynamic inputs but also have dynamic forms, also good to use inside a modal window.
For example, let's take this example being a dynamic form and inputs defined in the Controller:
$scope.items.item1.fields = [
{
name: 'firstName',
label:'Enter First Name',
validation:"required"
},
{
name: 'lastName',
label: 'Enter Last Name',
validation:"required"
}
];
$scope.items.item2 = {
heading:"Item2",
formName:"Form2"
};
$scope.items.item2.fields = [
{
name: 'email',
label:'Enter Email Id',
validation:"required"
},
{
name: 'phoneNo',
label: 'Enter Phone Number',
validation:"required"
}
];
It will bind the validation to the elements and if you want to easily check for the form validity directly from the Controller, simply use this
var myValidation = new validationService({ isolatedScope: $scope });
function saveData() {
if(myValidation.checkFormValidity($scope.Form1)) {
alert('all good');
}
}
You can also use interpolation like so
<input class="form-control" type="text" name="if1"
ng-model="vm.model.if1"
validation="{{vm.isRequired}}" />
Or using a radio/checkbox to enable/disable a field that you still want to validate when it becomes enable:
ON <input type="radio" ng-model="vm.disableInput4" value="on" ng-init="vm.disableInput4 = 'on'">
OFF <input type="radio" ng-model="vm.disableInput4" value="off">
<input type="text" name="input4"
ng-model="vm.input4"
validation="alpha_dash|min_len:2|required"
ng-disabled="vm.disableInput4 == 'on'" />
It really as a lot of features, and is available on both Bower and NuGet (under the tag name of angular-validation-ghiscoding). So please take a look at my library Angular-Validation and a live demo on PLUNKER.
It's loaded with features (custom Regex validators, AJAX remote validation, validation summary, alternate text errors, validation on the fly with the Service, etc...). So make sure to check the Wiki Documentation as well... and finally, it's fully tested with Protractor (over 1500 assertions), so don't be afraid of using in production.
Please note that I am the author of this library
I ran into this issue as well with Angular v1.5.9. The main issue here is that you are compiling the HTML template before it is in the DOM, so Angular doesn't know it's part of the form. If you add the HTML first, then compile, Angular will see your input as a form child, and it will be used in the form validation.
See similar answer to Form Validation and fields added with $compile
Don't do:
var myCompiledString = $compile(myHtmlString)(scope);
// ...
element.replaceWith(myCompiledString);
Do this instead:
var myElement = angular.element(myHtmlString)
element.replaceWith(myElement) // put in DOM
$compile(myElement)(scope) // $compile after myElement in DOM
Note: I swapped the more conventional element for OP's iElement, which is a jQLite reference of the directive's HTML element
you need to use ng-form directive as a wrapper for your input.
you can see an example of this here
but it works for me. You can pass the form reference to the directive and use it directly.
in the code below, the scope.form is used to know the general state of the form, and the scope.name to access the input field state.
< ng-form name="{{name}}" ng-class="{error: this[name][name].$invalid && form.$submitted}" >
i hope it helps
you need to set name of control dynamic and use this dynamic name for form validation. in the following e.g. you see the dynamic name and id of control and used for the validation of angular (using ng-massages)
more details see http://www.advancesharp.com/blog/1208/dynamic-angular-forms-validation-in-ngrepeat-with-ngmessage-and-live-demo
Field Num Is Required.

AngularJS how to submit untouched, empty form fields?

I've got a problem with a form. I'm submitting the form and some of the fields don't have to be filled, when the data is send to a restful API I see that angular doesn't pick up the empty, never touched, never changed, input fields.
But on the server side the field is expected to be present. How can I get angular to send these fields as well?
I have two inputs (data.Record.one, data.Record.two), when I fill one and submit the form I'm ending up with this:
{
"Record": {
"one": "test"
}
}
I guess this is because angular doesn't "see" untouched and empty fields.
But I want that:
{
"Record": {
"one": "test",
"two": ""
}
}
Here is a demo of what I try: http://plnkr.co/edit/Ky0PQ9U5o1V7IyU9QgkK
I'm well aware that I could initialize the form with an empty skeleton object but I would really prefer to not have to do that. If I need to do that it's just additional code that as a bonus will just introduce an area of potential fail if another dev adds a field in the form but forgets / doesn't know about the skeleton in the JS code.
Angular treats empty fields as empty/null by default. You can disable this by adding ng-trim="false" on your input fields.
This will not add extra code, only definition on your markup.
https://docs.angularjs.org/api/ng/input/input%5Btext%5D
EDIT: I see now what you want. Angular takes good care of your model (data). Just because you bind an input field to a sub,sub property won't initialize the property with empty string. The input fields does not contain empty string - they contain null which is what you put in them in the first place (by binding them to an uninitalized property).
The feature you want, is for angular to initialize your data.Record with the properties you need. You could make a custom directive for that called initialize-property that would set its model to an empty string.
EDIT: I created the directive but plunker won't let me access due to some cross domain problems. You should be able to use it in your own work though
myApp.directive('initializeProperty', function() {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.val('');
}
};
});
And then on your input fields use this:
One <input name="one" ng-model="data.Record.one" initialize-property ng-trim="false" /><br />
Two <input name="one" ng-model="data.Record.two" initialize-property ng-trim="false" /><br />
replace your app.js code to this one
var myApp = angular.module('myApp', []);
myApp.controller('TestFormController', ['$scope', '$log', function($scope, $log) {
$scope.data = {
Record : {
"one": "",
"two": ""
}
}
$scope.add = function() {
$log.log($scope.data);
}
}]);
DEMO
UPDATE !
change your view with this html
One <input name="one" ng-init="data.Record.one = ''" ng-model="data.Record.one" /><br />
Two <input name="one" ng-init="data.Record.two = ''" ng-model="data.Record.two" /><br />
i have added extra ng-init directive which will initilize your data
DEMO
I fixed it simply using ng-init="inputfieldname=''"
I had the same problem, so then in my research I find this:
http://blog.fernandomantoan.com/angularjs-forcando-bind-de-todos-os-campos-no-submit/
Solve my problem, but I had to edit, thus:
.directive('forceBind', function() {
return {
require: '^form',
priority: -1,
link: function (scope, element, attrs, form) {
element.bind('submit', function() {
angular.forEach(form, function(value, key) {
if(typeof value == 'object'){
if (value.hasOwnProperty('$modelValue')) {
if (!value.$viewValue) {
value.$setViewValue("");
}
}
}
});
});
}
};
});
So, when the event submit runs, before entering in method the directive force bind.
I recommend using ng-submit for validation.
Sorry my english.

Angularjs Chrome autocomplete dilemma

I have a simple login form which works just peachy unless you use Chrome's auto complete feature.
If you start typing and use the auto complete feature and it auto populates your password, my angularjs model does not have any value for the password.
I tried to turn autocomplete off by setting the attribute on the form autocomplete="off" but that doesn't seem to have any effect.
How can I either:
1. Ensure that I can get the value if someone uses Chrome's auto-complete feature?
2. Disable Chrome's auto-complete feature?
<form class="form-signin" name="form" ng-submit="login()" autocomplete="off">
<h3>Login</h3>
<input type="email" name="email" class="form-control" placeholder="Email address" ng-model="user.email" required autofocus>
<input type="password" name="password" class="form-control" placeholder="Password" ng-model="user.password" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
From the link added in the comment:Github Issue's
// Due to browsers issue, it's impossible to detect without a timeout any changes of autofilled inputs
// https://github.com/angular/angular.js/issues/1460
// https://github.com/angular/angular.js/issues/1460#issuecomment-28662156
// Could break future Angular releases (if use `compile()` instead of `link())
// TODO support select
angular.module("app").config(["$provide", function($provide) {
var inputDecoration = ["$delegate", "inputsWatcher", function($delegate, inputsWatcher) {
var directive = $delegate[0];
var link = directive.link;
function linkDecoration(scope, element, attrs, ngModel){
var handler;
// By default model.$viewValue is equals to undefined
if(attrs.type == "checkbox"){
inputsWatcher.registerInput(handler = function(){
var value = element[0].checked;
// By default element is not checked
if (value && ngModel.$viewValue !== value) {
ngModel.$setViewValue(value);
}
});
}else if(attrs.type == "radio"){
inputsWatcher.registerInput(handler = function(){
var value = attrs.value;
// By default element is not checked
if (element[0].checked && ngModel.$viewValue !== value) {
ngModel.$setViewValue(value);
}
});
}else{
inputsWatcher.registerInput(handler = function(){
var value = element.val();
// By default value is an empty string
if ((ngModel.$viewValue !== undefined || value !== "") && ngModel.$viewValue !== value) {
ngModel.$setViewValue(value);
}
});
}
scope.$on("$destroy", function(){
inputsWatcher.unregisterInput(handler);
});
// Exec original `link()`
link.apply(this, [].slice.call(arguments, 0));
}
// Decorate `link()` don't work for `inputDirective` (why?)
/*
directive.link = linkDecoration;
*/
// So use `compile()` instead
directive.compile = function compile(element, attrs, transclude){
return linkDecoration;
};
delete directive.link;
return $delegate;
}];
$provide.decorator("inputDirective", inputDecoration);
$provide.decorator("textareaDirective", inputDecoration);
//TODO decorate selectDirective (see binding "change" for `Single()` and `Multiple()`)
}]).factory("inputsWatcher", ["$interval", "$rootScope", function($interval, $rootScope){
var INTERVAL_MS = 500;
var promise;
var handlers = [];
function execHandlers(){
for(var i = 0, l = handlers.length; i < l; i++){
handlers[i]();
}
}
return {
registerInput: function registerInput(handler){
if(handlers.push(handler) == 1){
promise = $interval(execHandlers, INTERVAL_MS);
}
},
unregisterInput: function unregisterInput(handler){
handlers.splice(handlers.indexOf(handler), 1);
if(handlers.length == 0){
$interval.cancel(promise);
}
}
}
}]);
From: Developer.mozilla.org docs Turning_off_form_autocompletion
If an author would like to prevent the auto-filling of password fields
in user management pages where a user can specify a new password for
someone other than themselves, autocomplete="new-password" should be
specified, though support for this has not been implemented in all
browsers yet.
So, what makes it work for me:
set autocomplete="new-password" on the password field
set autocomplete="off" in the username field.
I hope that it works for you too :)
As said here, https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form
The Google Chrome UI for auto-complete requests varies, depending on
whether autocomplete is set to off on input elements as well as their
form. Specifically, when a form has autocomplete set to off and its
input element's autocomplete field is not set, then if the user asks
for autofill suggestions for the input element, Chrome might display a
message saying "autocomplete has been disabled for this form." On the
other hand, if both the form and the input element have autocomplete
set to off, the browser will not display that message. For this
reason, you should set autocomplete to off for each input that has
custom auto-completion.
You need to set autocomplete="off" on both form and input
I don't think this is related to AngularJS
I had the same issue and found a very simple solution that just uses jQuery to grab the value on submit. In my controller I have the following:
$scope.username = "";
$scope.password = "";
$scope.login = function(){
$scope.username = $("#username").val();
$scope.password = $("#password").val();
// Proceed as normal
};
There are some downsides, if you need to do validation etc but otherwise it's fine for smaller forms like this.
You could watch the email field value and everytime the value in that field is changing, you could trigger a "change"-event on the password field. This events trigger all the ng-model magic on that field and updates the model.
module.directive("autocompleteFor", function () {
return {
restrict: "A",
link: function ($scope, $element, $attrs) {
$scope.$watch($attrs.autocompleteFor, function () {
$element.triggerHandler("change");
})
}
}
});
With this directive you could handle that scenario like this:
<input type="email" name="email" ng-model="user.email">
<input type="password" autocomplete-for="user.email" name="password" ng-model="user.password" required>
-----------------------------
To disable the autocomplete/autofill from a input, just type:
- autocomplete="false" instead of autocomplete="off"!
Below directive worked for me. It's simple and clean fix. Hope that helps!
Ref: AngularJS browser autofill workaround by using a directive
Here is a solution that is far less hacky than other solutions presented and is semantically sound AngularJS: VictorBlog.com
myApp.directive('formAutofillFix', function() {
return function(scope, elem, attrs) {
// Fixes Chrome bug: https://groups.google.com/forum/#!topic/angular/6NlucSskQjY
elem.prop('method', 'POST');
// Fix autofill issues where Angular doesn't know about auto-filled inputs
if(attrs.ngSubmit) {
setTimeout(function() {
elem.unbind('submit').submit(function(e) {
e.preventDefault();
elem.find('input, textarea, select').trigger('input').trigger('change').trigger('keydown');
scope.$apply(attrs.ngSubmit);
});
}, 0);
}
};
});
Then you simply attach the directive to your form:
<form ng-submit="submitLoginForm()" form-autofill-fix>
<div>
<input type="email" ng-model="email" ng-required />
<input type="password" ng-model="password" ng-required />
<button type="submit">Log In</button>
</div>
</form>
alternative solution is just to get rid off form element and use ng-form instead, it disables all browser interferings
<div ng-form="yourFormName" class="form-signin" ng-submit="login()">
Old question, but whatever
I came across the same problem and I've got a small "hack" solution. This problem happened at many different places in my app, so I created a directive for reusability.
module.directive("fakeAutocomplete", [
function () {
return {
restrict: "EA",
replace: true,
template: "<div><input/><input type=\"password\"/></div>",
link: function (scope, elem, attrs) {
elem.css({
"overflow": "hidden",
"width": "0px",
"height": "0px"
});
}
}
}
]);
And simply add
<fake-autocomplete></fake-autocomplete>
At the beginning of your form and the browser will detect the fake fields as the ones that should autocomplete. Simply putting display:none on the fields also does not seem to work anymore, I've tested it.
In my case, i set property autocomplete="off" in form and input.
<form autocomplete="off">
<input type="text" autocomplete="off">
</form>
It could be much simpler solution to the problem.
Angularjs couldn't "see" the value
Take the value via DOM (jQuery) then put it back into Angularjs.
```
angular.module('someModule').directive('chromeAutofillHack', function()
{
return {
require: '^ngModel',
restrict: 'A',
priority: 500, // set higher priority then other custom directives
link: function(scope, element, attrs , ngModelCtrl)
{
ngModelCtrl.$parsers.unshift(function(email)
{
if (!email) { // only do this when angular think there is no value
email = $(element).val();
ngModel.$setViewValue(email);
}
return email;
});
}
};
});
```
--- NO LONGER RELEVANT ---
I was able to disable autocomplete (weirdly enough) by adding the following.
<form ... novalidate>
<input ... formnovalidate />
Reference this Plunker
My solution for Chrome 35.0, Firefox 30.0, angular 1.2.18 (login page with password manager, autofill, angular method and redirect):
How does browser know when to prompt user to save password?
I ended up with a different solution that I don't see here yet. From what I found, the password value isn't exposed to the model (or possibly even the js api) until the user interacts with the page. Clicking the login button is enough interaction to make the value available, and the data binding will succeed early enough for the click handler on the button to access the password from the model. So if I could detect that the browser has auto-filled, I could enable the login button even though my model hadn't been updated yet. So I wrote a simple helper service to see if Chrome has auto-filled any inputs:
utilApp.service('autoFillDetectionService', [function () {
return {
hasAutoFillInputs: function () {
try{
return !!$(':-webkit-autofill').length;
}
catch (x) {
// IE gets here, it/jquery complains about an invalid pseudo-class
return false;
}
}
};
}]);
From the login controller, I have an interval checking if any input fields are marked as autofill and if so enable the login button.
Just Replace autocomplete="off" with autocomplete="new-password".

jQuery Validation conditional dependency: ensure text input matches value if radio button checked

I have a some form elements that follow a format like this:
<input type="radio" name="test" value="A"> <input type="text" size="3" name="weightA" id="A"><br>
<input type="radio" name="test" value="B"> <input type="text" size="3" name="weightB" id="B"><br>
I am using the jQuery Validation plugin to conduct client-side validation. What I would like to do with these fields is to ensure that the text input corresponding to the selected radio button equals 100. I have successfully implemented this on the server-side using PHP, but would like to add a JS method to give immediate feedback before the form is submitted. I have already included a jQuery range: rule to constrain user inputs in the two text fields within the numeric range [1-100].
How would I go about making this work? Would jQuery.validator.addMethod be the way to do it?
Edit: in response to Sparky's comment, I have attempted an addMethod, below:
$.validator.addMethod(
"selectWt", function(value, element) {
var selA = $('[name="test"]').val() === "A";
var selB = $('[name="test"]').val() === "B";
if (selA && ($("#A").val() !== "100")) {
return false;
} else if (selB && ($("#B").val() !== "100")) {
return false;
} else return true;
}, "Selected option must equal 100."
);
This seems to trigger the validation for #A but not #B, and the error message displayed is the one specified by the message: rule rather than the one specified by addMethod. Please bear in mind I have minimal programming background.
Try this:
DEMO: http://jsfiddle.net/maqZe/
$.validator.addMethod("selectWt", function (value, element) {
if ($(element).prev().is(':checked')) {
return ($(element).val() === 100);
} else {
return true;
}
}, "Selected option must equal 100.");
This rule can be applied generically. It simply checks to see if the radio element placed previous to element is checked. If so, it then returns true only if the text element's value is 100.
The way it's written, it only works if your type=text element immediately follows the type=radio element. It will need to be tweaked if you change the HTML arrangement.
It can also be made more flexible by passing in the 100 value as a parameter.
DEMO: http://jsfiddle.net/NFJUN/
$.validator.addMethod("selectWt", function (value, element, param) {
if ($(element).prev().is(':checked')) {
return ($(element).val() === param);
} else {
return true;
}
}, "Selected option must equal {0}.");
...
$('#myform').validate({ // initialize the plugin
rules: {
myfield: {
selectWt: 100 // use a parameter instead of "true"
},
}
});

Categories

Resources