I am using reactive forms.
I have a submit button that should be disabled until the form is valid:
<button type="submit" [disabled]="!_searchForm.valid && !_searchForm.pristine">Submit</button>
My fields can be toggled on/off using bools:
showName: boolean = true;
showPhone: boolean = true;
showCellphone: boolean = true;
And this is my validation rules:
this._searchForm = this._formBuilder.group({
name: [{value: '', disabled: !this.showName}, Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[a-zA-Z]+$')])],
phone: [{value: '', disabled: !this.showPhone}, Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[0-9-]+$')])],
cellphone: [{value: '', disabled: !this.showCellphone}, Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[0-9]+$')])]
});
And finally, this is how each field is displayed in the HTML:
<div class="form-group" [ngClass]="{'has-danger': _searchForm.controls.name.errors && !_searchForm.controls.name.pristine}">
<label for="name">Name:</label>
<div class="input-group">
<span class="input-group-addon">
<div class="onofwrapper">
<div class="onoffswitch">
<input id="toggleName" type="checkbox" class="onoffswitch-checkbox" (click)='toggleName()' [checked]="showName">
<label class="onoffswitch-label" for="toggleName"></label>
</div>
</div>
</span>
<input type="text" formControlName="name" class="form-control" [ngClass]="{'form-control-danger': _searchForm.controls.name.errors && !_searchForm.controls.name.pristine}" autocomplete="off" spellcheck="false">
</div>
<div *ngIf="_searchForm.controls.name.errors && !_searchForm.controls.name.pristine" class="form-control-feedback">Error message</div>
If I don't touch the form I can submit, so pristine seems to be working.
The problem is, I can't enter text in just a single field and submit. If I enter text in one field, I have to enter text in all of them to be able to submit, or else _searchForm.valid won't return true even though I am not using Validators.required on all fields.
I have verified that each input "ships its own value", by removing the [disabled="!_searchForm.valid" line, and then just dumping out the values in my submit function like this:
console.log('Name: ' + this._searchForm.value.name);
console.log('Phone: ' + this._searchForm.value.phone);
console.log('Cellphone: ' + this._searchForm.value.cellphone);
What am I doing wrong?
Why does .valid require all fields in the form?
If you disable or enable an input, then, you need a function:
enableDisableInput(inputName: string): void {
if(!this._searchForm.controls[inputName].disabled) {
this._searchForm.controls[inputName].clearValidators();
this._searchForm.controls[inputName].disable();
} else {
this._searchForm.controls[inputName].setValidators(Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[0-9]+$')])]);
}
this._searchForm.controls[inputName].updateValueAndValidity();
}
Invoked <input id="toggleName" type="checkbox" class="onoffswitch-checkbox" (click)='enableDisableInput("name")' [checked]="showName">
The manner in which the form building and validation is done with Reactive Forms means that you must manually clear and add validation even on disabled items (there may be plans to change this as it is a not uncommon complaint on the angular github). This is a code oriented and driven manner of forms and needs to be treated as such for the time being.
Whether or not the inputs are required is moot if they have an unmet minimum length. Reference https://github.com/angular/angular/pull/11450 which was recently included in Angular 2.0.2 https://github.com/angular/angular/blob/master/CHANGELOG.md
For forms driven it looks like they have a correction in 2.1.0 where those fields are optional with pattern and minlength but I don't know if that's also in Reactive Forms or not.
It turns out that this was a problem with input type="number". The fields containing phone and cellphone values was only supposed to contain numbers, so in addition to the Validators.pattern('^[0-9-]+$') validation I also gave them the input type of number.
As soon as that was changed into text, everything worked like expected.
I had the same issue with the Angular reactive forms. I was disabling and enabling my form controls based on some logic. It turns out I was disabling, but not enabling back the form controls, so I was getting a form.valid as false. Apparently in Angular's reactive forms a form with disabled fields is invalid and the docs are not mentioning this default behavior.
I have tried multiple options that I have found on SO and elsewhere for clearing form inputs, all listed below in the code, but nothing seems to work. Is there anything specific about this form that would determine which one I should use?
<template name="CompanyAdd">
<div>
<form class="form-inline">
<div class="form-group">
{{> inputAutocomplete settings=companySettings id="companyAdd" name="companyAdd" class="input-xlarge" autocomplete="off" placeholder="Add Company"}}
</div>
<button type="submit" class="btn btn-default company-add">Add</button>
</form>
</div>
</template
Template.CompanyAdd.events({
'submit form': function(e) {
e.preventDefault();
var selection = $(e.target).find('[id=companyAdd]').val();
var company = {
ticker: selection
};
if(Companies.findOne({ticker:selection})) {
console.log("Do nothing");
} else {
Meteor.call('companyAdd', company, function(error, result) {
});
}
//event.target.reset();
//e.target.reset();
//target.text.value = '';
//template.find("form").reset();
//document.getElementById("companyAdd").reset();
}
});
Given that you have
var selection = $(e.target).find('[id=companyAdd]').val();
That is the input you want to clear and that - I assume - works, I would do:
var field = $(e.target).find('[id=companyAdd]');
var selection = field.val();
...
field.val('')
Otherwise if you wish to reset all form, go for #JeremyK`s #reset.
Your second attempt:
e.target.reset();
should work fine. If it is not working, check if there are any errors in the console and report back here.
The handler function receives two arguments: event, an object with
information about the event, and template, a template instance for the
template where the handler is defined.
In your code above you define your handler like this:
'submit form': function(e) {
You have named the event argument e, and discarded the template argument.
e is has information about the event
e.target is the form element (The event was defined on 'submit form')
e.target.reset succeeds because reset is a valid function to call on a form.
Briefly, your other attempts failed because:
event.target.reset(); event is not defined or passed in, at least not with the name event (you used e)
target.text.value = ''; target is an undefined variable
template.find("form").reset(); this fails because template is undefined. If you change your handler definition to receive the template variable, this will work (change 'submit form': function(e) to 'submit form': function(e, template)
document.getElementById("companyAdd").reset(); This fails because the element with the id companyAdd is the input element, not the form, so .reset() is undefined. You could change this to document.getElementById("companyAdd").text.value = ''
I am trying to build a filter for my portfolio website. Checkboxes that let you pick a technology (react, redux, jquery etc.) to display a piece of work(s) that contain(s) that/those technologies. So every time the user clicks on a box, I want to add the value (JavaScript, Redux, React etc.) to an array that I use in another function to check against my portfolio pieces and filter out what isn't there.
I am finding this very difficult and I think it should be quite simple. Can someone point me in the right direction? Is there a way to simply have a function trigger (onChange callback?) that reads the checked/unchecked status of my form input elements and then updates my state array accordingly? Can I get the status of all the checkboxes simply in React? Do I need to have individual state of checked/unchecked for my checkboxes?
It seems that jQuery makes it pretty possible with selectors with:
$('input[type="checkbox"]:checked').each(function () {}
If you don't care about the order and you just want to append the items to the array as they appear we could definitely do exactly what you suggest in your question. On the change event of the checkbox check if the box is checked or or unchecked (event.target.checked returns true if checked or false if unchecked) and handle the array logic accordingly. this is a simple representation of how that could work:
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Portfolio extends Component {
constructor() {
super()
// initialize your options array on your state
this.state = {
options: []
}
}
onChange(e) {
// current array of options
const options = this.state.options
let index
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(+e.target.value)
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(+e.target.value)
options.splice(index, 1)
}
// update the state with the new array of options
this.setState({ options: options })
}
render() {
return (
<main className='portfolio'>
<form>
<div className="input-group">
<label>cb1</label>
<input type="checkbox" value={1} onChange={this.onChange.bind(this)} />
</div>
<div className="input-group">
<label>cb2</label>
<input type="checkbox" value={2} onChange={this.onChange.bind(this)} />
</div>
<div className="input-group">
<label>cb3</label>
<input type="checkbox" value={3} onChange={this.onChange.bind(this)} />
</div>
</form>
<div className="selected-items">
{this.state.options.map(number =>
<p key={number}>item: {number}</p>
)}
</div>
</main>
)
}
}
if you DO care about order, if you can append numerical values to the array like I did in this example you could easily give your checkboxes sorted numerical values and you could sort the array before updating your state so it's always in a certain order regardless of the order they are checked.
onChange(e) {
// current array of options
const options = this.state.options
let index
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(+e.target.value)
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(+e.target.value)
options.splice(index, 1)
}
// sort the array
options.sort()
// update the state with the new array of options
this.setState({ options: options })
}
Here's how I'm doing it:
// util.js
import getPath from 'lodash/get';
import setIn from 'lodash/fp/set';
export function linkMultiCheck(name, value) {
return {
checked: getPath(this.state, name, []).includes(value),
onChange: ev => {
let values = getPath(this.state, name, []);
if(ev.target.checked) {
values = [...values, value];
} else {
values = values.filter(v => v !== value);
}
this.setState(setIn(name, values));
},
}
}
// form.js
<ul>
{options.branches.map(branch => (
<li key={branch.id} className="checkbox">
<label>
<input type="checkbox" name={this.id} {...this::linkMultiCheck('formData.branchIds',branch.id)}/>
{branch.id}
</label>
</li>
))}
</ul>
i.e., if a checkbox is checked, append it to the current array of values. If it's unchecked, filter it out.
I'm using lodash here so that we can set deeply nested state values using dot notation.
Taken this form as example http://plnkr.co/edit/fHEBw6dDdG3IVgnmCLb7?p=preview
How can I put the $pristine state of the form to true after the SAVE DRAFT button is pressed?
You can call $setPristine on the form: http://plnkr.co/edit/wXaFXtuhNH6d4SP2uArm?p=preview
<button ng-click="reset(); form.$setPristine()">RESET</button>
<button ng-click="update(user); form.$setPristine()">SAVE</button>
Or you can call the method in your controller (after ensuring that the form exists):
$scope.update = function(user) {
$scope.master= angular.copy(user);
if ($scope.form) $scope.form.$setPristine();
};
$scope.reset = function() {
$scope.user = angular.copy($scope.master);
if ($scope.form) $scope.form.$setPristine();
};
Demo: http://plnkr.co/edit/Mau7uuDfPlzcn418OdWh?p=preview
I've noticed that the reset() won't clear the email input unless it's valid.
I've tried another approach instead:
<button type="reset" ng-click="form.$setPristine()">RESET</button>
<button ng-click="update(user); form.$setPristine()">SAVE</button>
With AngularJS, I can use ng-pristine or ng-dirty to detect if the user has entered the field. However, I want to do client-side validation only after the user has left the field area. This is because when a user enters a field like e-mail or phone, they will always get an error thrown until they've completed typing out their full e-mail, and this is not an optimal user experience.
Example
UPDATE:
Angular now ships with a custom blur event:
https://docs.angularjs.org/api/ng/directive/ngBlur
From version 1.3.0 this can easily be done with $touched, which is true after the user has left the field.
<p ng-show="form.field.$touched && form.field.$invalid">Error</p>
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
Angular 1.3 now has ng-model-options, and you can set the option to { 'updateOn': 'blur'} for example, and you can even debounce, when the use is either typing too fast, or you want to save a few expensive DOM operations (like a model writing to multiple DOM places and you don't want a $digest cycle happening on every key down)
https://docs.angularjs.org/guide/forms#custom-triggers and https://docs.angularjs.org/guide/forms#non-immediate-debounced-model-updates
By default, any change to the content will trigger a model update and
form validation. You can override this behavior using the
ngModelOptions directive to bind only to specified list of events.
I.e. ng-model-options="{ updateOn: 'blur' }" will update and validate
only after the control loses focus. You can set several events using a
space delimited list. I.e. ng-model-options="{ updateOn: 'mousedown
blur' }"
And debounce
You can delay the model update/validation by using the debounce key
with the ngModelOptions directive. This delay will also apply to
parsers, validators and model flags like $dirty or $pristine.
I.e. ng-model-options="{ debounce: 500 }" will wait for half a second
since the last content change before triggering the model update and
form validation.
I solved this by expanding on what #jlmcdonald suggested. I created a directive that would automatically be applied to all input and select elements:
var blurFocusDirective = function () {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, elm, attr, ctrl) {
if (!ctrl) {
return;
}
elm.on('focus', function () {
elm.addClass('has-focus');
scope.$apply(function () {
ctrl.hasFocus = true;
});
});
elm.on('blur', function () {
elm.removeClass('has-focus');
elm.addClass('has-visited');
scope.$apply(function () {
ctrl.hasFocus = false;
ctrl.hasVisited = true;
});
});
elm.closest('form').on('submit', function () {
elm.addClass('has-visited');
scope.$apply(function () {
ctrl.hasFocus = false;
ctrl.hasVisited = true;
});
});
}
};
};
app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);
This will add has-focus and has-visited classes to various elements as the user focuses/visits the elements. You can then add these classes to your CSS rules to show validation errors:
input.has-visited.ng-invalid:not(.has-focus) {
background-color: #ffeeee;
}
This works well in that elements still get $invalid properties etc, but the CSS can be used to give the user a better experience.
I managed to do this with a pretty simple bit of CSS. This does require that the error messages be siblings of the input they relate to, and that they have a class of error.
:focus ~ .error {
display:none;
}
After meeting those two requirements, this will hide any error message related to a focused input field, something that I think angularjs should be doing anyway. Seems like an oversight.
This seems to be implemented as standard in newer versions of angular.
The classes ng-untouched and ng-touched are set respectively before and after the user has had focus on an validated element.
CSS
input.ng-touched.ng-invalid {
border-color: red;
}
Regarding #lambinator's solution... I was getting the following error in angular.js 1.2.4:
Error: [$rootScope:inprog] $digest already in progress
I'm not sure if I did something wrong or if this is a change in Angular, but removing the scope.$apply statements resolved the problem and the classes/states are still getting updated.
If you are also seeing this error, give the following a try:
var blurFocusDirective = function () {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, elm, attr, ctrl) {
if (!ctrl) {
return;
}
elm.on('focus', function () {
elm.addClass('has-focus');
ctrl.$hasFocus = true;
});
elm.on('blur', function () {
elm.removeClass('has-focus');
elm.addClass('has-visited');
ctrl.$hasFocus = false;
ctrl.$hasVisited = true;
});
elm.closest('form').on('submit', function () {
elm.addClass('has-visited');
scope.$apply(function () {
ctrl.hasFocus = false;
ctrl.hasVisited = true;
});
});
}
};
};
app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);
It might work for you to write a custom directive that wraps the javascript blur() method (and runs a validation function when triggered); there's an Angular issue that has a sample one (as well as a generic directive that can bind to other events not natively supported by Angular):
https://github.com/angular/angular.js/issues/1277
If you don't want to go that route, your other option would be to set up $watch on the field, again triggering validation when the field is filled out.
To pick up further on the given answers, you can simplify input tagging by using CSS3 pseudo-classes and only marking visited fields with a class to delay displaying validation errors until the user lost focus on the field:
(Example requires jQuery)
JavaScript
module = angular.module('app.directives', []);
module.directive('lateValidateForm', function () {
return {
restrict: 'AC',
link: function (scope, element, attrs) {
$inputs = element.find('input, select, textarea');
$inputs.on('blur', function () {
$(this).addClass('has-visited');
});
element.on('submit', function () {
$inputs.addClass('has-visited');
});
}
};
});
CSS
input.has-visited:not(:focus):required:invalid,
textarea.has-visited:not(:focus):required:invalid,
select.has-visited:not(:focus):required:invalid {
color: #b94a48;
border-color: #ee5f5b;
}
HTML
<form late-validate-form name="userForm">
<input type="email" name="email" required />
</form>
based on #nicolas answer.. Pure CSS should the trick, it will only show the error message on blur
<input type="email" id="input-email" required
placeholder="Email address" class="form-control" name="email"
ng-model="userData.email">
<p ng-show="form.email.$error.email" class="bg-danger">This is not a valid email.</p>
CSS
.ng-invalid:focus ~ .bg-danger {
display:none;
}
Here is an example using ng-messages (available in angular 1.3) and a custom directive.
Validation message is displayed on blur for the first time user leaves the input field, but when he corrects the value, validation message is removed immediately (not on blur anymore).
JavaScript
myApp.directive("validateOnBlur", [function() {
var ddo = {
restrict: "A",
require: "ngModel",
scope: {},
link: function(scope, element, attrs, modelCtrl) {
element.on('blur', function () {
modelCtrl.$showValidationMessage = modelCtrl.$dirty;
scope.$apply();
});
}
};
return ddo;
}]);
HTML
<form name="person">
<input type="text" ng-model="item.firstName" name="firstName"
ng-minlength="3" ng-maxlength="20" validate-on-blur required />
<div ng-show="person.firstName.$showValidationMessage" ng-messages="person.firstName.$error">
<span ng-message="required">name is required</span>
<span ng-message="minlength">name is too short</span>
<span ng-message="maxlength">name is too long</span>
</div>
</form>
PS. Don't forget to download and include ngMessages in your module:
var myApp = angular.module('myApp', ['ngMessages']);
ng-model-options in AngularJS 1.3 (beta as of this writing) is documented to support {updateOn: 'blur'}. For earlier versions, something like the following worked for me:
myApp.directive('myForm', function() {
return {
require: 'form',
link: function(scope, element, attrs, formController) {
scope.validate = function(name) {
formController[name].isInvalid
= formController[name].$invalid;
};
}
};
});
With a template like this:
<form name="myForm" novalidate="novalidate" data-my-form="">
<input type="email" name="eMail" required="required" ng-blur="validate('eMail')" />
<span ng-show="myForm.eMail.isInvalid">Please enter a valid e-mail address.</span>
<button type="submit">Submit Form</button>
</form>
Use field state $touched The field has been touched for this as shown in below example.
<div ng-show="formName.firstName.$touched && formName.firstName.$error.required">
You must enter a value
</div>
You can dynamically set the has-error css class (assuming you're using bootstrap) using ng-class and a property on the scope of the associated controller:
plunkr: http://plnkr.co/edit/HYDlaTNThZE02VqXrUCH?p=info
HTML:
<div ng-class="{'has-error': badEmailAddress}">
<input type="email" class="form-control" id="email" name="email"
ng-model="email"
ng-blur="emailBlurred(email.$valid)">
</div>
Controller:
$scope.badEmailAddress = false;
$scope.emailBlurred = function (isValid) {
$scope.badEmailAddress = !isValid;
};
If you use bootstrap 3 and lesscss you can enable on blur validation with the following less snippet:
:focus ~ .form-control-feedback.glyphicon-ok {
display:none;
}
:focus ~ .form-control-feedback.glyphicon-remove {
display:none;
}
.has-feedback > :focus {
& {
.form-control-focus();
}
}
outI used a directive. Here is the code:
app.directive('onBlurVal', function () {
return {
restrict: 'A',
link: function (scope, element, attrs, controller) {
element.on('focus', function () {
element.next().removeClass('has-visited');
element.next().addClass('has-focus');
});
element.on('blur', function () {
element.next().removeClass('has-focus');
element.next().addClass('has-visited');
});
}
}
})
All my input control has a span element as the next element, which is where my validation message is displayed and so the directive as an attribute is added to each input control.
I also have (optional).has-focus and has-visited css class in my css file which you see being referenced in the directive.
NOTE: remember to add 'on-blur-val' exactly this way to your input control without the apostrophes
By using ng-focus you can achieve your goal. you need to provide ng-focus in your input field. And while writing your ng-show derivatives you have to write a logic not equal too. Like the below code:
<input type="text" class="form-control" name="inputPhone" ng-model="demo.phoneNumber" required ng-focus>
<div ng-show="demoForm.inputPhone.$dirty && demoForm.inputPhone.$invalid && !demoForm.inputPhone.$focused"></div>
We can use onfocus and onblur functions. Would be simple and best.
<body ng-app="formExample">
<div ng-controller="ExampleController">
<form novalidate class="css-form">
Name: <input type="text" ng-model="user.name" ng-focus="onFocusName='focusOn'" ng-blur="onFocusName=''" ng-class="onFocusName" required /><br />
E-mail: <input type="email" ng-model="user.email" ng-focus="onFocusEmail='focusOn'" ng-blur="onFocusEmail=''" ng-class="onFocusEmail" required /><br />
</form>
</div>
<style type="text/css">
.css-form input.ng-invalid.ng-touched {
border: 1px solid #FF0000;
background:#FF0000;
}
.css-form input.focusOn.ng-invalid {
border: 1px solid #000000;
background:#FFFFFF;
}
</style>
Try here:
http://plnkr.co/edit/NKCmyru3knQiShFZ96tp?p=preview