I'm trying to create a contact form. The form looks like this:
<form novalidate [formGroup]="contact" (ngSubmit)="send()">
<p>
<label>Name
<br>
<input type="text" class="input" value="" formControlName="name">
<span class="error">Enter your name</span>
</label>
</p>
<p>
<label>E-mail
<br>
<input type="email" class="input" value="" formControlName="email">
<span class="error">It looks like this email is invalid</span>
</label>
</p>
<p>
<label>Phone
<br>
<input type="text" class="input" value="" formControlName="telefone">
<span class="error">It looks like this phone number is invalid</span>
</label>
</p>
<p>
<label>Message
<br>
<textarea type="text" class="input" value="" formControlName="message"></textarea>
<span class="error">The message can't be empty</span>
</label>
</p>
<p class="submit">
<input type="submit" name="submit" class="bt" value="Send">
</p>
</form>
In this form only the message and the name and email or the phone number fields should be required.
I'm using a formBuilder class, so here's the TypeScript code:
this.contact = this.formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.compose([/*Use custom validador??*/])],
phone: ['', Validators.compose([/*Use custom validador??*/]],
message: ['', Validators.required]
});
I tried using custom validators as a solution, but I could not figure out a solution. Any suggestions?
I created a custom validator directive:
import {
FormGroup,
ValidationErrors,
ValidatorFn,
Validators,
} from '#angular/forms';
export const atLeastOne = (validator: ValidatorFn, controls:string[] = null) => (
group: FormGroup,
): ValidationErrors | null => {
if(!controls){
controls = Object.keys(group.controls)
}
const hasAtLeastOne = group && group.controls && controls
.some(k => !validator(group.controls[k]));
return hasAtLeastOne ? null : {
atLeastOne: true,
};
};
To use it, you just do this:
this.form = this.formBuilder.group({
name: ['', Validators.required],
email:[''],
telefone:[''],
message:['', Validators.required],
}, { validator: atLeastOne(Validators.required, ['email','telefone']) });
So email or telefone would be required here. If you leave it empty then any control with a value is fine and you can use it with any type of validator, not just Validators.required.
This is reusable in any form.
Yes, a custom validator is the way to go.
Make your form group like this:
this.contact = this.formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.required],
phone: ['', Validators.required],
message: ['', Validators.required]
}, {validator: this.customValidationFunction})
Then have the customValidationFunction check for validation. Made up validation just for example:
customValidationFunction(formGroup): any {
let nameField = formGroup.controls['name'].value; //access any of your form fields like this
return (nameField.length < 5) ? { nameLengthFive: true } : null;
}
Change each input like this (changing your p tags to divs. Substitute the control name for each and change syntax for the hidden span tag validation where appropriate):
<div [ngClass]="{'has-error':!contact.controls['name'].valid && contact.controls['name'].touched}">
<label>Name</label>
<input class="input" type="text" [formControl]="contact.controls['name']">
<span [hidden]="!contact.hasError('nameLengthFive')" class="error">Enter your name</span>
</div>
I have updated my validator snippet to support string and number types
I was inspired by Todd Skelton. This is a very simple Validator that does just what you ask for and nothing else:
/**
* Validates if at least one of the provided fields has a value.
* Fields can only be of type number or string.
* #param fields name of the form fields that should be checked
*/
export function atLeastOne(...fields: string[]) {
return (fg: FormGroup): ValidationErrors | null => {
return fields.some(fieldName => {
const field = fg.get(fieldName).value;
if (typeof field === 'number') return field && field >= 0 ? true : false;
if (typeof field === 'string') return field && field.length > 0 ? true : false;
})
? null
: ({ atLeastOne: 'At least one field has to be provided.' } as ValidationErrors);
};
}
And here is how I use it:
ngOnInit(): void {
this.form = this.formBuilder.group(
{
field: [this.field],
anotherField: [this.anotherField],
},
{ validator: atLeastOne('field','anotherField') },
);
}
Slightly refactored Florian's answer for the lazy as it was making ts-sonar angry (cognitive-complexity rule):
const isFieldEmpty = (fieldName: string, fg: FormGroup) => {
const field = fg.get(fieldName).value;
if (typeof field === 'number') { return field && field >= 0 ? true : false; }
if (typeof field === 'string') { return field && field.length > 0 ? true : false; }
};
export function atLeastOne(...fields: string[]) {
return (fg: FormGroup): ValidationErrors | null => {
return fields.some(fieldName => isFieldEmpty(fieldName, fg))
? null
: ({ atLeastOne: 'At least one field has to be provided.' } as ValidationErrors);
};
}
Related
I have an Input field in my angular template-driven form. I want to configure that fields so that it can only take an 11bits number like 01001111100
<input
class="form-control"
id="bitsValue"
[placeholder]="field.templateOptions.placeholder"
pattern="[0-1]{1}"
[(ngModel)]="ABC"
>
How can I do that?
If you were to use reactive forms, create you form for example:
this.myForm = this._sb.fb.group(
{
bitsValue: this._sb.fb.control('', [Validators.required, YourValidatorClass.elevenBitsOnly])
};
export class YourValidatorClass {
static elevenBitsOnly(control: AbstractControl): ValidationErrors | null {
const value = control.value;
//... validate your value meets your 11 bit requirement
return { bitValueReq: 'You must provide value as 11 bits number' };
//... if it doesn't meet the requirement return null
return null;
}
}
Then in your html template:
<div [formGroup]="myForm">
<mat-form-field>
<input matInput type="text" formControlName="bitsValue" />
<mat-error *ngIf="myForm.hasError('bitValueReq'))"> {{ getErrorMessage(myForm.get('bitsValue')) }}</mat-error>
</mat-form-field>
</div>
....component.ts file:
getErrorMessage(control: AbstractControl): string{
const error = control.getError('bitValueReq');
if(error){
return error['bitValueReq'];
}
}
I am a newible to VueJs
I would like to use Vue2 to create a Vue validation form
Here is the code I have written to perform a Vue validation form
https://jsfiddle.net/vzx07pk3/
index
<div id="app">
<label for="username">Name</label>
<input class="form-control" type="text" name="username" v-bind:class="{ 'is-invalid': usernameError }" v-model="username" placeholder="Username"/>
<div class="invalid-feedback" style="color:red">{{ userErrMsg }}</div>
<br><br>
<label for="">E-Mail</label>
<input class="form-control" type="text" name="email" v-bind:class="{ 'is-invalid': emailError }" v-model="email" placeholder="Email"/>
<div class="invalid-feedback" style="color:red">{{ emailErrMsg }}</div>
<br><br>
</div>
Js file:
var app = new Vue({
el: '#app',
data: {
username: '',
usernameError: false,
userErrMsg: '',
email: '',
emailError: false,
emailErrMsg: ''
},
watch: {
username: function () {
var isText = /^[a-zA-Z0-9]+$/;
if (!isText.test(this.username)) {
this.usernameError = true;
this.userErrMsg = 'Only letters and white space allowed ';
}
else if (this.username.length > 10) {
this.usernameError = true;
this.userErrMsg = 'MaxLength 10';
}
else {
this.usernameError = false;
this.userErrMsg = '';
}
},
email: function () {
var isMail = /^\w+((-\w+)|(\.\w+))*\#[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z]+$/;
if (!isMail.test(this.email)) {
this.emailError = true;
this.emailErrMsg = 'Invalid email format';
}
else {
this.emailError = false;
this.emailErrMsg = '';
}
}
}
});
but When I click the previous or next page, then back to index.html form page
The input field is auto-remove.
How to remember input data in the forms even after the previous/next page?
Should I use localStorage to remember the input value????
such as
mounted:function() {
if (!!window.MSInputMethodContext && !!document.documentMode) {
window.addEventListener('hashchange', this.hashChangeHandler);
}
if (localStorage.username) {
this.username = localStorage.username;
}
if (localStorage.email) {
this.email = localStorage.email;
}
}
Is it the best way to remember input data in the forms even after the previous /next page then back/return to index.html form page????
Thank you very much
You can use keep-alive to remember your input data like this:
<keep-alive>
your Component
</keep-alive>
I'm using vuelidate to validate a form in VUE build. I have a phone number input that needs to be formatted as 222-222-2222 which I have setup using a phone mask. My issue is now the input will not validate when the phone is filled in. My error message is still displayed. Is there a way to allow for numbers only along with a dash "-"???
<div class="form-field">
<label for="phone">
Phone Number
</label>
<input type="text" class="form-input text-input" name="phone" id="phone" #input="phoneNumberMask()"
v-model.trim="$v.formData.phone.$model">
<label v-if="($v.formData.phone.$dirty && !$v.formData.phone.required)
|| ($v.formData.phone.$dirty && !$v.formData.phone.numeric)
|| ($v.formData.phone.$dirty && !$v.formData.phone.minLength)
|| ($v.formData.phone.$dirty && !$v.formData.phone.maxLength)" class="error">
Please enter a valid phone number.
</label>
</div>
validations: {
formData: {
phone: {
required,
numeric,
minLength: minLength(12),
maxLength: maxLength(12)
}
}
}
methods: {
phoneNumberMask(){
const x = this.formData.phone.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
this.formData.phone = !x[2] ? x[1] : x[1] + '-' + x[2] + (x[3] ? '-' + x[3] : '');
},
}
As pointed out in the comments, you can create a regular expression that validates you phone number format, in this case /^[0-9]{3}-[0-9]{3}-[0-9]{4}$/ could be a starting point.
Here a complete example.
<template>
<div class="">
<label
:class="{
error: $v.number.$error,
green: $v.number.$dirty && !$v.number.$error
}"
>
<input
class=""
v-model="number"
#change="$v.number.$touch()"
placeholder="Phone number"
/>
</label>
<div class="" v-if="$v.number.$dirty && $v.number.$error">
<p class="">NOT VALID</p>
</div>
<button
class="
"
:disabled="$v.$invalid"
#click="saveClick()"
>
<span class="">Save</span>
</button>
</div>
</template>
Script part
import { required, helpers } from "vuelidate/lib/validators"
const number = helpers.regex(
"serial",
/^[0-9]{3}-[0-9]{3}-[0-9]{4}$/
)
export default {
data() {
return {
number: null
}
},
computed: {
serial: {
get() {
return this.number
},
set(value) {
this.number = value
}
}
},
methods: {
saveClick() {
//TODO
}
},
validations: {
number: {
required,
number
}
}
}
Note
This is for Vue 2.x and vuelidate ^0.7.6
I have the following React Component, which holds a form with two inputs and a button.
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: null,
password: null
}
}
emailInputChanged = (e) => {
this.setState({
email: e.target.value.trim()
});
};
passwordInputChanged = (e) => {
this.setState({
password: e.target.value.trim()
});
};
loginButtonClicked = (e) => {
e.preventDefault();
if (!this.isFormValid()) {
//Perform some API request to validate data
}
};
signupButtonClicked = (e) => {
e.preventDefault();
this.props.history.push('/signup');
};
forgotPasswordButtonClicked = (e) => {
e.preventDefault();
this.props.history.push('/forgot-password');
};
isFormValid = () => {
const {email, password} = this.state;
if (email === null || password === null) {
return false;
}
return isValidEmail(email) && password.length > 0;
};
render() {
const {email, password} = this.state;
return (
<div id="login">
<h1 className="title">Login</h1>
<form action="">
<div className={(!isValidEmail(email) ? 'has-error' : '') + ' input-holder'}>
<Label htmlFor={'loginEmail'} hidden={email !== null && email.length > 0}>email</Label>
<input type="text" className="input" id="loginEmail" value={email !== null ? email : ''}
onChange={this.emailInputChanged}/>
</div>
<div className={(password !== null && password.length === 0 ? 'has-error' : '') + ' input-holder'}>
<Label htmlFor={'loginPassword'}
hidden={password !== null && password.length > 0}>password</Label>
<input type="password" className="input" id="loginPassword"
value={password !== null ? password : ''}
onChange={this.passwordInputChanged}/>
</div>
<button type="submit" className="btn btn-default" id="loginButton"
onClick={this.loginButtonClicked}>
login
</button>
</form>
<div className="utilities">
<a href={'/signup'} onClick={this.signupButtonClicked}>don't have an account?</a>
<a href={'/forgot-password'} onClick={this.forgotPasswordButtonClicked}>forgot your password?</a>
</div>
</div>
)
}
}
export function isValidEmail(email) {
const expression = /\S+#\S+/;
return expression.test(String(email).toLowerCase());
}
The values of the inputs are stored in the component's state. I have given them initial value of null and update them using setState() in the onChange event.
In the render() method I use the state to colour inputs with invalid values. Currently I am only checking the email value against a simple regex and the password to be at least 1 character in length.
The reason I have set the initial values of the state variables to null so I can check in the layout and the initial style on the input to not be marked as "has-errors". However I need to extend the check to:
this.state.email !== null && this.state.email.length === 0
in order to work, because null has no length property.
Is there a cleaner and "React-ish" way to achieve this:
- initial state of the div holding the inputs has no class has-errors
- less checks when setting the value attribute on the input (because React doesn't accept null as value of the attribute)
Edit:
If the initial value of this.state.email and this.state.password are empty strings, I get has-error applied to the div holding the inputs. In my opinion this is bad UX, because the user hasn't done anything and he is already wrong.
The hidden attribute is used by a custom component I made, which acts like "placeholder" (gets erased if something is typed in the input).
Video below showing how my form looks like when this.state.email and this.state.password are empty strings as well how my <Label> component works:
You could create a validate function which return an errors object to know which fields are in error, and use empty strings as initial values. I don't understand what you are trying to do with the hidden attribute.
Edit: add a touched property in the state, to know which field have been touched.
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
touched: {},
};
}
emailInputChanged = e => {
this.setState({
email: e.target.value.trim(),
touched: {
...this.state.touched,
email: true,
},
});
};
passwordInputChanged = e => {
this.setState({
password: e.target.value.trim(),
touched: {
...this.state.touched,
password: true,
},
});
};
loginButtonClicked = e => {
e.preventDefault();
if (!this.isFormValid()) {
//Perform some API request to validate data
}
};
isFormValid = () => {
const errors = this.validate();
return Object.keys(errors).length === 0;
};
validate = () => {
const errors = {};
const { email, password } = this.state;
if (!isValidEmail(email)) {
errors.email = true;
}
if (password.length === 0) {
errors.password = true;
}
return errors;
};
render() {
const { email, password, touched } = this.state;
const errors = this.validate();
return (
<div id="login">
<h1 className="title">Login</h1>
<form action="">
<div
className={
(errors.email && touched.email ? 'has-error' : '') +
' input-holder'
}
>
<Label htmlFor={'loginEmail'}>email</Label>
<input
type="text"
className="input"
id="loginEmail"
value={email}
onChange={this.emailInputChanged}
/>
</div>
<div
className={
(errors.password && touched.password ? 'has-error' : '') +
' input-holder'
}
>
<Label htmlFor={'loginPassword'}>password</Label>
<input
type="password"
className="input"
id="loginPassword"
value={password}
onChange={this.passwordInputChanged}
/>
</div>
<button
type="submit"
className="btn btn-default"
id="loginButton"
onClick={this.loginButtonClicked}
>
login
</button>
</form>
</div>
);
}
}
export function isValidEmail(email) {
const expression = /\S+#\S+/;
return expression.test(String(email).toLowerCase());
}
i am using number type here so that it accepts only number.But i don't want this number type like it's gives increments and decrements selector at the right side of the field.What i need is i need to accept only numbers not any characters.How can i do that?
these are my form elements:
<label className="w3-label w3-text-blue w3-large">
Phone Number:
<input type="number" className="w3-input w3-border" value={this.state.phoneno}
onChange={e => this.setState({ phoneno: e.target.value })} required placeholder="Enter Father's Phone No. in Numbers"/>
</label><br/>
<label className="w3-label w3-text-blue w3-large">
Mobile Number:
<input type="number" className="w3-input w3-border" value={this.state.mobileno} pattern="[0-9]{10}" title="Please enter 10 digit mobile Number"
onChange={e => this.setState({ mobileno: e.target.value })} required placeholder="Enter Father's Mobile No. in Numbers"/>
</label></div><br/>
Try this
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
https://css-tricks.com/snippets/css/turn-off-number-input-spinners/
You can use regular expressions in javascript to check whether given number is valid or not.
function phonenumber(inputtxt)
{
var phoneno = /^\d{10}$/;
if((inputtxt.value.match(phoneno))
{
return true;
}
else
{
alert("message");
return false;
}
}
This kind of tasks is where React really shines. Just use <input>s with type="text" and let your onChange handlers do the job. I would suggest using String.replace with \D (not a digit) regexp to filter out unwanted chars, and then String.substr to control the length of the entered value. Something like:
const filterNonDigits = value => value ? value.replace(/\D+/, '') : '';
const takeLast = (amount, value) => value ? value.substr(0, amount) : '';
class NumbersInput extends React.Component {
constructor() {
super();
this.state = {};
}
handlePhone(value) {
this.setState({ phoneno: filterNonDigits(value) });
}
handleMobile(value) {
this.setState({ mobileno: takeLast(10, filterNonDigits(value)) });
}
render() {
return (
<div>
<label>
Phone Number:
<input
value={this.state.phoneno}
onChange={e => this.handlePhone(e.target.value)}
/>
</label>
<br/>
<label>
Mobile Number:
<input
value={this.state.mobileno}
onChange={e => this.handleMobile(e.target.value)}
/>
</label>
</div>
);
}
}