When an input field requires more info, the browser shows a message in a bubble about why the input is invalid. I would like to prevent the default for this in vue but I'm not sure how. Below is how I would do it in JavaScript but in Vue, there may be a way to do #invalid like how I know you can do #submit on a form as an eventListener. I'm also wondering if extra prevention is needed to prevent this in ios or android.
HTML
<form>
<input type="text" required>
<input type="submit">
</form>
JS
document.querySelector( "input" ).addEventListener( "invalid",
function( event ) {
event.preventDefault();
});
https://codepen.io/albert-anthony4962/pen/BajORVZ
If you want to completely disable validation, you can add novalidate="true" to your form element.
I suspect that you might only want to do that on the initial page load. If so, could you update your section and hopefully and add an example? I can update my answer after that 😀
A pattern I have (idea from Vuetify) is pretty easy:
new Vue({
el: "#app",
data: {
isFormValid: null,
form: {
input_1: {
text: null,
rules: ['required', 'min3'],
validateText: null
},
input_2: {
text: null,
rules: ['required'],
validateText: null
}
},
rules: {
required: v => !!v && !![...v].length || 'This field is required.',
min3: v => !!v && !!([...v].length > 2) || 'This field must be at least 3 characters long.'
}
},
methods: {
validateForm() {
const validated = []
for (let key in this.form) {
const v = this.form[key].rules.map(e => {
return this.rules[e](this.form[key].text)
})
if (v.some(e => e !== true)) {
this.form[key].validateText = v.filter(e => e !== true)[0]
validated.push(false)
} else {
this.form[key].validateText = "This field is valid."
validated.push(true)
}
}
return validated.every(e => e === true)
},
submitForm() {
if (this.validateForm()) {
// submit logic
this.isFormValid = "Yes, it's valid."
} else {
// not valid logic:
this.isFormValid = "No, it's not valid."
}
},
resetValidation() {
const form = JSON.parse(JSON.stringify(this.form))
for (let key in form) {
form[key].validateText = null
}
this.isFormValid = null
this.form = form
},
resetForm() {
const form = JSON.parse(JSON.stringify(this.form))
for (let key in form) {
form[key].validateText = null
form[key].text = null
}
this.isFormValid = null
this.form = form
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form ref="formRef">
<label for="input_1">
Input 1:
<input
id="input_1"
type="text"
v-model="form.input_1.text"
/>
</label>
<div>This field will validate if NOT EMPTY AND HAS AT LEAST 3 CHARS</div>
<div>{{ form.input_1.validateText || ' ' }}</div>
<br />
<label for="input_2">
Input 2:
<input
id="input_2"
type="text"
v-model="form.input_2.text"
/>
</label>
<div>This field will validate if NOT EMPTY</div>
<div>{{ form.input_2.validateText || ' ' }}</div>
<br />
<button type="submit" #click.prevent="submitForm">
SUBMIT
</button>
<div>Is the form valid? {{ isFormValid }}</div>
</form>
<button #click="resetValidation">RESET VALIDATION</button><br />
<button #click="resetForm">RESET FORM</button>
</div>
This way you don't have to put up with the HTML5 "bubbles", but can still validate your form - in any way you need. You can compose any validation scheme you want by using functions that go over your input text. You could even come up with regexp validation, pattern validation (like phone numbers), etc. It's not the greatest solution, but quite "pluggable".
This is also supposed to be cross-platform (if you use Vue).
Related
I'm trying to build a simple form to capture email and password for a new user signing up on Firebase, I'm using React with Typescript and I'm getting the error "Object is possibly 'null'. TS2531" on the following section of the code:
<form onSubmit={(event) => { this.handleSignUp({event, email: this._email.current.value, password: this._password.current.value})}}>
In particular it's the this._email.current.value and this._password.current.value that are throwing this error.
I've dug around about both the error code and type scripting and it's something to do with the "strictNullChecks" on the typescript, but I don't really want to turn that option off, and I don't think I have enough skill or understanding of coding to know how to get around this. Even though I do understand that a form can be submitted with empty values, I am checking later with the firebase auth to make sure that there are strings with more than 4 characters.
Below is the code for the whole react component.
interface IHandleSubmitNewUserFunc {
event: any,
email: any,
password: any
}
class NewUserSignup extends React.Component {
constructor(props: any) {
super(props);
this.handleSignUp = this.handleSignUp.bind(this);
}
handleSignUp(input: IHandleSubmitNewUserFunc) {
input.event.preventDefault();
const { email, password } = input;
if (email.length < 4 && email != null) {
alert('Please enter an email address.');
return;
}
if (password.length < 4 && password != null) {
alert('Please enter a password.');
return;
}
firebase.auth().createUserWithEmailAndPassword(email, password).catch(function (error) {
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode == 'auth/weak-password') {
alert('The password is too weak.');
} else {
alert(errorMessage);
}
console.log(error);
});
}
private _email = React.createRef<HTMLInputElement>();
private _password = React.createRef<HTMLInputElement>();
render() {
return <div>
<div className="signup-modal-container">
<div className="identity-panel">
<img src={logo}></img>
<form onSubmit={(event) => { this.handleSignUp({event, email: this._email.current.value, password: this._password.current.value})}}>
<div className="form-flex-container">
<div className="signup-item">
<h2>
Sign Up
</h2>
<label htmlFor="email" id="email">
Email:
</label>
</div>
<div className="signup-item">
<div className="input-container">
<input type="text" name="email" id="email" ref={this._email}/>
</div>
</div>
<div className="signup-item">
<label htmlFor="password">
Password:
</label>
</div>
<div className="signup-item">
<div className="input-container">
<input type="password" name="password" id="password" ref={this._password}/>
</div>
</div>
<div className="signup-item">
<button type="submit">
Sign Up
</button>
</div>
</div>
</form>
</div>
</div>
<p>
NewUserSignup is showing
</p>
</div>
}
}
export default NewUserSignup;
I'm not really sure what code I'd need to not throw this error, any advice is aprpeciated.
Pretty much as it says. The references are null by default, there's no guarantee that current is assigned by the time you access them (as far as the code knows).
You have 2 options:
Add a truthy check before access this._email.current and this._password.current.
const eCurrent = this._email.current;
const pCurrent = this._password.current;
if (!eCurrent || !pCurrent) {
// This will probably never happen, to respond to events these will be hooked up.
return;
}
Use the non-null assertion since you know it's a safe operation: this._email.current!.value.
// Assert that current won't be null.
const emailValue = this._email.current!.value;
const passwordValue = this._password.current!.value;
I have created a settings page where users can update their email addresses. Everything worked fine but suddenly the validation is not updating anymore. Only the first change of the input field triggers validateState().
Any further changes will not trigger this function so the status of that field stays as it is.
I have compared the code with other components that use the same code and they still work fine.
I am using bootstrap-vue components for the form.
<template>
<div class="row">
<div class="col-md-12">
<b-form #submit="onSubmit">
<b-form-group :label="$t('general.email')"
label-for="settingsEmail"
:invalid-feedback="errors.first('email')">
<b-form-input id="settingsEmail"
type="text"
v-model="form.email"
:disabled="saving"
name="email"
:state="validateState('email')"
v-validate="{required: true, email: true}">
</b-form-input>
</b-form-group>
<b-button type="submit" variant="primary" :disabled="saving || !hasChanged() || errors.any()"><i class="fa fa-refresh fa-spin fa-fw" v-if="saving"></i> {{$t('general.save')}}</b-button>
</b-form>
</div>
</div>
</template>
<script>
import {UPDATE_USER} from '../config/actions'
export default {
name: 'settingsAccount',
data() {
return {
form: {},
saving: false
}
},
computed: {
user: function() {
return this.$store.getters.getUser;
}
},
created() {
this.init();
},
methods: {
init() {
this.form.email = this.user.email;
},
hasChanged() {
if(this.form.email !== this.user.email) {
return true;
}
return false;
},
onSubmit(event) {
event.preventDefault();
this.saving = true;
this.$validator.validateAll().then((result) => {
if (result) {
let data = {};
if(this.form.email !== this.user.email) {
data.email = this.form.email;
}
this.$store.dispatch(UPDATE_USER, data).then(() => {
this.saving = false;
this.$validator.reset();
}).catch(() => {
this.saving = false;
});
} else {
this.saving = false;
}
});
},
validateState(ref) {
if (this.veeFields[ref] && (this.veeFields[ref].dirty || this.veeFields[ref].validated)) {
return !this.errors.has(ref)
}
return null
},
}
}
</script>
The problem you're having is that the form data element is an empty object, so it will only trigger reactivity when the whole object changes. Either you need to change your data to be this:
data() {
return {
form: {email:''},
saving: false
}
},
Or in your init function, explicitly add the email property as reactive:
methods: {
init() {
this.$set(form,'email',this.user.email)
},
//...
If you're not clear on why, you can read the details here: https://v2.vuejs.org/v2/guide/reactivity.html
A working example (minus vuex) here: https://codesandbox.io/s/x4kp93w3o
PS, when writing questions about vue, it's very helpful to boil it down to a simpler example. Get rid of vuex, remove your translation stuff. Sometimes the answer will jump out at you once you have it as simple as possible.
I'm new with Knockout.js, and I would like to check if a field of my form has a specific value. Actually, I only check if it is required or not. What should I do?
Here's what I have in my html page:
<div data-bind="visible: !Vm.isValid()" class="text-danger">Fill each field to send data, otherwise show this message</div>
<input data-bind="enable: Vm.isValid()" type="button" value="Send data!" />
That's what my vm.js file looks like:
window.Vm = ko.validatedObservable({
name : ko.observable().extend({ required: true })
});
I would make something like this, but I don't know how to do it:
var found = "found";
window.Vm = ko.validatedObservable({
name: ko.observable().extend({
required: true,
function: {
if (this.val() == found)
return true; // invalid value, can't submit my form
}
})
});
I would actually recommend against using the Knockout Validation library, as it hasn't been maintained for years. It's an outdated solution to a problem that doesn't really exist anymore. In 2019 you can just use the form validation that is native to every modern browser. Just put a required attribute on your form fields and the form will not submit if not all required fields have been filled out.
If you want it to be a little more dynamic, you could do something like this:
function ViewModel() {
var vm = this;
vm.name = ko.observable();
vm.required = ['name', 'email'];
vm.isRequired = isRequired;
function isRequired(field) {
return vm.required.indexOf(field) > -1;
}
}
And use the attr binding to set the required attribute based on the array of required elements in your viewmodel.
<input type="text" data-bind="textInput: name, attr: { required: isRequired('name') }">
You can use a custom validator like this (Documentation):
var found = "found";
var Vm = ko.validatedObservable({
name: ko.observable().extend({
required: {
message: "This is a required field",
},
validation: {
validator: (val, paramValue) => {
// "val" has the value entered in the field
// "paramValue" has the value set in "params"
return val === paramValue
},
message: "The value is not " + found,
params: found
}
})
});
ko.applyBindings(Vm)
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.3/knockout.validation.min.js"></script>
<input type="text" data-bind="value: name" />
I have taken data as ["A","B"], and search based upon the same data.
ko.extenders.required = function(target, overrideMessage) {
//add some sub-observables to our observable
target.hasError = ko.observable();
target.validationMessage = ko.observable();
target.data = ko.observableArray(["A","B"]);
target.found = ko.observable();
target.foundMessage = ko.observable();
//define a function to do validation
function validate(newValue) {
target.hasError(newValue ? false : true);
target.validationMessage(newValue ? "" : overrideMessage || "This field is required");
target.found(target.data().find(function(element){ return newValue==element;}));
target.found()?target.foundMessage("element has found"):target.foundMessage("element has not found");
}
//initial validation
validate(target());
//validate whenever the value changes
target.subscribe(validate);
//return the original observable
return target;
};
function AppViewModel(first) {
this.firstName = ko.observable(first).extend({ required: "" });
}
ko.applyBindings(new AppViewModel("C"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<p data-bind="css: { error: firstName.hasError }">
<input data-bind='value: firstName, valueUpdate: "afterkeydown"' />
<span data-bind='visible: firstName.hasError, text: firstName.validationMessage'> </span>
<span data-bind='visible: (!firstName.hasError()), text: firstName.foundMessage'> </span>
</p>
I am using formsy-react to handle validation of my input fields. Problem is that I can't update states. I followed example on formsy github page and validation is working but problem is that states are incorrect. They are always one step (or few) behind and I am not sure why...
I used callback on setState function to implement some custom logic on validation and that part doesn't work properly.
I have a situation where user enters email. After user enters email I check if email is already registrated. If user is already in system, I create new input component (password type) and if not I create new "input type email" component.
Since all forms elements are required I added one more validation check that checks if new password or email component is added and if there is any data.
To update states I used Forms form API call onChange() and this part is not working for unknown reason.
Does someone knows where is the problem?
This is code I am using:
Component Input (shorted version)
changeValue(event) {
this.setValue(event.currentTarget.value);
},
render() {
// Set a specific className based on the validation
// state of this component. showRequired() is true
// when the value is empty and the required prop is
// passed to the input. showError() is true when the
// value typed is invalid
const className = (this.props.className || "col-md-4" );
const classValidationName =this.isValid() ? 'valid' : this.showError() ? ' invalid' : null;
// An error message is returned ONLY if the component is invalid
// or the server has returned an error message
const errorMessage = this.getErrorMessage();
return (
<div className= {className}>
<div className="md-form">
<span className="prefix"><i className={this.props.icon}></i></span>
<input
className={classValidationName}
name={this.props.name}
id={this.props.id}
type={this.props.inputType}
value={this.getValue() || ""}
onChange={this.changeValue}
onBlur={this.props.controlFuncOnBlur}
placeholder={this.props.placeholder}
required={this.props.required}
pattern={this.props.pattern}
/>
<label id={this.props.name + 'Label'} htmlFor={this.props.name} data-error={errorMessage}
data-success={this.props.successMessage}>{this.props.title}
</label>
</div>
</div>
);
}
Container (shorted version)
handleEmailBlur(event) {
const self = this;
if (this.refs.email.isValid) {
axios.get('/api/checkIsUserRegistrated', {
params: {
email: this.state.email
}
})
.then(function (response) {
if (self.state.userExist !== response.data[0].userExist) {
self.setState({
userExist: response.data[0].userExist,
confirmEmail: "",
password: ""
});
self.forceUpdate();
}
})
.catch(function (error) {
console.log(error);
});
}
}
enableButton = () => {
this.setState({
formValid: true
});
}
disableButton = () => {
this.setState({
formValid: false
});
}
saveCurrentValuesToStates = (getCurrentValues, isChanged) => {
console.log(this);
this.setState(getCurrentValues, ()=> {
if (this.state.formValid && (this.state.password || this.state.confirmEmail)){
this.setState({
canSubmitForm: true
});
}
else{
this.setState({
canSubmitForm: false
});
}
});
}
<Formsy.Form className="booker-form" ref="form"
onChange={this.saveCurrentValuesToStates} onValid={this.enableButton} onInvalid={this.disableButton}>
<SingleInput
inputType={'email'}
icon={'icon-Email'}
id={'email'}
name={'email'}
title={'E-mail'}
ref="email"
controlFuncOnBlur={this.handleEmailBlur}
content={this.state.email}
errorMessage={'Incorect E-Mail address'}
required
validations="isEmail"
validationError="This is not a valid email"
/>
{(this.state.userExist === '0') ?
<SingleInput
inputType={'email'}
icon={'icon-Email'}
id={'confirmEmail'}
name={'confirmEmail'}
title={'Confirm your E-mail'}
content={this.state.confirmEmail}
required
validations="equalsField:email"
validationError="Emails don't match"
/>
: null}
{(this.state.userExist === '1') ?
<SingleInput
inputType={'password'}
icon={'icon-Padlock'}
id={'password'}
name={'password'}
title={'Enter your password'}
content={this.state.password}
required
/>
: null}
I have a form where I enter an email and confirm email and then continue to the next page and all is well. The validation works fine when the page initially loads and it's the user's first time, so the input field is not prepopulated from cookie data. However, when the user returns, the input field data is prepopulated from cookie data and that is fine but the submit button is still disabled even though the prepopulated text is valid format. I inspected the elements and it seems to think the field is ng-invalid even though it's valid format.
I noticed when I go to one of the fields and backspace to remove the last character and reinsert the same character as before for email and do the same for the next field, the form is valid again. Even though, it's the same text as before.
I'm wondering why validation fails when the form first loads with prepopulated data?
Here's my code:
export class EmailComponent implements OnInit {
public user : User;
Form : FormGroup;
displayErrors : boolean;
ngOnInit() {
// initialize model here
this.user = {
Email: '',
confirmEmail: ''
}
}
constructor(fb: FormBuilder, private cookieService: CookieService, private cryptoService: CryptoService) {
var encryptedEmail = this.cookieService.get(AppCookie.EmailAddress);
var Cookie = null;
if(encryptedEmail != null && encryptedEmail != 'undefined')
Cookie = this.cryptoService.Decrypt(encryptedEmail);
if(Cookie == null) {
this.Form = fb.group({
email: ['', [Validators.required, Validators.pattern(EMAIL_REGEXP)]],
confirmEmail: ['', [Validators.required, Validators.pattern(EMAIL_REGEXP)]]
},
{
validator: this.matchingEmailsValidator('email', 'confirmEmail')
});
}
else {
this.Form = fb.group({
email: [Cookie, [Validators.required, Validators.pattern(EMAIL_REGEXP)]],
confirmEmail: [Cookie, [Validators.required, Validators.pattern(EMAIL_REGEXP)]]
},
{
validator: this.matchingEmailsValidator('email', 'confirmEmail')
});
}
}
save(model: User, isValid: boolean)
{
model.Email = this.Form.get('email').value;
var encrypted = this.cryptoService.Encrypt(model.Email);
this.cookieService.put(AppCookie.EmailAddress, encrypted);
}
matchingEmailsValidator(emailKey: string, confirmEmailKey: string): ValidatorFn {
return (group: FormGroup): {[key: string]: any} => {
let email = group.controls[emailKey];
let confirmEmail = group.controls[confirmEmailKey];
if (email.value !== confirmEmail.value) {
return {
mismatch: true
};
}
};
}
}
and here's my view:
<form [formGroup]="Form" novalidate (ngSubmit)="Form.valid && save(Form.value, Form.valid)">
<div class="login-wrapper">
<div class="login-page">
<section class="login-form form-group">
<p>
<input id="email"
[class.email-address-entry]="!displayErrors"
[class.email-address-entry-text]="!displayErrors && this.Form.get('email').value !='' "
type="email"
placeholder="name#domain.com" formControlName="email" />
</p>
<p class="login-form__msg">Reenter your email to confirm</p>
<input id="reenteremail"
[class.email-address-entry]="!displayErrors"
[class.entry-border-invalid]="displayErrors && !Form.valid && Form.errors?.mismatch"
[class.email-address-entry-text]="!displayErrors && this.Form.get('email').value !='' "
(blur)="displayErrors=true"
type="email" placeholder="name#domain.com"
formControlName="confirmEmail"/>
<p class="error-msg" *ngIf="displayErrors && !Form.valid && Form.errors?.mismatch">The email you entered does not match.</p>
</section>
<p class="login-confirm">
<span>
<button type="submit" [disabled]="!Form.valid" (click)="Form.get('email').length > 0 ? save(Form.value, Form.valid) : NaN">Confirm</button>
</span>
</p>
</div>
</div>
</form>
EDIT: It's similar to this issue as well:
Angular 2 - Form is invalid when browser autofill
I tried adding this:
ngAfterViewChecked() {
if (Cookie) {
// enable to button here.
var element = <HTMLInputElement> document.getElementById("confirmBtn");
element.disabled = false;
}
But it won't work because fields are still invalid. I need a way to manually set re-validation or change ng-invalid to ng-valid.
If you keep a reference to the form instance (either by using reactive forms or by accessing it using #ViewChild) you should be able to write the following in ngAfterViewInit():
for (var i in this.form.controls) {
this.form.controls[i].updateValueAndValidity();
}
Or perhaps marking the fields as touched will be better in your case:
for (var i in this.form.controls) {
this.form.controls[i].markAsTouched();
}