force a new validation if another related field changed - vue.js

I want to validate two textfields which are related to each other. The first one must be smaller than the second one (e.g. min/max, start date/end date).
So for the coding part I created this
HTML:
<div id="app">
<v-app id="inspire">
<v-text-field
v-model="values[0]"
:rules="firstValidation"
></v-text-field>
<v-text-field
v-model="values[1]"
:rules="secondValidation"
></v-text-field>
</v-app>
</div>
JS:
new Vue({
el: '#app',
data () {
return {
values: [1, 2]
}
},
computed: {
firstValidation: function () {
return [value => parseFloat(value) < this.values[1] || "Must be less than second value"]
},
secondValidation: function () {
return [value => parseFloat(value) > this.values[0] || "Must be greater than first value"]
}
}
})
I will also provide a snippet here
https://codepen.io/anon/pen/NZoaew?editors=1010
When I change the value of one field the other one will not revalidate. Steps to reproduce:
change the value of the first field to 12
the second field has a value of 2 so you will get an error
change the value of the second field to 22
now the form is valid but the first field still throws the error because it didn't revalidate.
remove one character from the first field
now this field revalidates and you can submit it. Is there a mechanism to revalidate the other field on changes and vice versa?
I think a possible solution would be to listen to the #input event of a field but how would you force the other field to revalidate then?

This will validate them both but only show an error in the field once the user has typed in that field:
new Vue({
el: '#app',
data () {
return {
values: [1, 2],
firstValidation: [true],
secondValidation: [true]
}
},
methods: {
validate: function (index) {
const valid = (this.values[0] - this.values[1]) < 0
if (valid) {
this.firstValidation = [true];
this.secondValidation = [true];
return;
}
if (index > 0)
this.secondValidation = ["Must be greater than first value"];
else
this.firstValidation = ["Must be less than second value"];
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.16/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-text-field
v-model="values[0]"
#input="validate(0)"
:rules="firstValidation"
></v-text-field>
<v-text-field
v-model="values[1]"
#input="validate(1)"
:rules="secondValidation"
></v-text-field>
</v-app>
</div>
(I'm not sure why the styles aren't rendering but it uses the same scripts as your codepen)

Any reason why your data model is an array? Be mindful of this. From the docs
Due to limitations in JavaScript, Vue cannot detect the following
changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue
i.e. when values[1] = parseFloat($event) is called, it is NOT reactive. I would avoid this if possible, otherwise use $set. Then to force validation of the field when another field changes, you will have to watch for the change and then manually call validation.
Template
<div id="app">
<v-form ref="form"> <!-- Need a reference to the form to validate -->
<v-app id="inspire">
<v-text-field
:value="values[0]"
:rules="firstValidation"
#input="$set(values, 0, parseFloat($event))"
></v-text-field>
<v-text-field
:value="values[1]"
:rules="secondValidation"
#input="$set(values, 1, parseFloat($event))"
></v-text-field>
<v-btn #click="submit">Submit</v-btn>
</v-app>
</form>
</div>
Code
Add this to your component
methods: {
submit: function(){
console.log(this.values);
},
validate: function() {
// manually call validation
this.$refs.form.validate();
}
},
watch: {
// watch for change and then validate
values: 'validate'
}
See updated working codepen

Related

Hide one item from input list that is dynamically generated unless other input from list has value in vue / vuetify

So the process is like this. I have a form that is created from api. I want to show all the inputs except for one. That one will only be shown if the user adds value to another specific input from the form.
Something like this is the idea.
<Form>
<div-for="formItem in state.formItemData">
<template v-if="formItem.one !== ''">
<form-input
v-model="invoiceForm.two"
:key="formItem.id"
ref="two"
></form-input>
</template>
</div>
</Form>
const invoiceForm = computed({
get: () => state.forms.formData,
set: (value) => {
state.forms.formData= value
}
})
The concept in your problem is very simple, you just must make use of a computed property that evaluates if there are values in input 1 and it will rerender the component showing input2.
new Vue({
el: '#app',
data: {
input1: '',
input2: ''
},
methods: {
verification() {
console.log(this.input1);
}
},
computed: {
notEmpty() {
return this.input1 !== '' && this.input1.length > 3;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input #change="verification" type="text" v-model="input1"> <input v-if="notEmpty" type="text" v-model="input2">
</div>
You should use a v-show if you're eventually going to show/hide it when rendering, especially if it's going to be multiple times. The Vue documentation says that the v-if will not be rendered when the initial condition is false.
Source: https://v3.vuejs.org/guide/conditional.html#v-if-vs-v-show

Checking validation of VTextField

I have a CodePen demonstrating the issue at Checking validation of VTextField. The CodePen code is below as well.
Inside of the updateTitle method, I would like to be able to check to see whether or not the value is valid, because I need to run some custom logic when the value changes and what is executed will depend upon whether or not the value is valid.
I can see that v-text-field has a valid and validationState property. Is it possible to access either property inside of updateTitle?
HTML
<div id="app">
<v-app id="inspire">
<v-form>
<v-container>
<v-row>
<v-col cols="12" sm="6">
<v-text-field
:value = "title"
:rules="[rules.required, rules.counter]"
label="Title"
counter
maxlength="20"
#input="updateTitle"
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-form>
</v-app>
</div>
JS:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
title: 'Preliminary report',
rules: {
required: value => !!value || 'Required.',
counter: value => value.length <= 10 || 'Max 10 characters'
}
}
},
methods: {
updateTitle( value ) {
console.log( "update", value );
}
}
})
The answer is to provide a ref to the v-text-field. This allows one to access the field via the special $refs property and call the validate function. The changes to the CodePen are:
<v-text-field
ref = "theField"
:value = "title"
:rules="[rules.required, rules.counter]"
label="Title"
counter
maxlength="20"
#input="updateTitle"
></v-text-field>
updateTitle( value ) {
const isValid = this.$refs.theField.validate();
console.log( "update", value, isValid );
}

Dynamically update class based on individual form elements validation in Vuetify

Take a look at the official Vuetify form validation example.
The very first example, if you click in a field and then outside it, it is automatically validated. The entire field becomes red and you get a hint in red text.
What I would like is based on that built-in/native validation to add or remove a class (that turns the text red) on a completely separate HTML element.
It would be ideal if something like hint-for="" exists. Some way to connect a separate HTML element with the form field validation.
I have tried to condition the class with the "valid" property of the form element, something like this: this.$refs.form.$children[1].valid but this doesn't exist on page load and throws errors.
Right now I have some results by basically having double validation, the normal one that validates automatically based on the "rules" property on the form field, and a custom one that I call my self on #input and on #blur of the form field, but this is largely inefficient so I'm hoping there's a better way.
You can use the value of the v-form to track the validity of your form. In order to listen to changes you can use the input event like this
<template>
<div>
<v-form lazy-validation v-model="valid" #input="updateOtherElement">
<v-text-field
v-model="email"
:rules="emailRules"
label="Email"
required
></v-text-field>
</v-form>
</div>
</template>
<script>
export default {
data () {
return {
valid: true,
email: "",
emailRules: [
v => /.+#.+/.test(v) || 'E-mail must be valid',
],
}
},
methods: {
updateOtherElement(valid) {
// update other elements css
}
}
}
</script>
An alternative would be to track the changes with a watcher
This is what I came up with.
I had some trouble with validation being active immediately and nonexistent text fields on page load but with this setup, it works.
So once validation kicks in the fields will turn red by the native Vuetify validation if they are not valid, and I toggle the "invalid" class on a completely separate piece of HTML with custom functions. What is important here that each text field has it's own "subheader" which will turn red only if that single connected text-filed is invalid, not the entire form.
<template>
<v-form
ref="form"
lazy-validation
>
<v-subheader v-bind:class="passwordValid()">
Password *
</v-subheader>
<v-text-field
:rules="rules.password"
ref="password"
></v-text-field>
<v-subheader v-bind:class="passwordAgainValid()">
Password Again *
</v-subheader>
<v-text-field
:rules="rules.passwordAgain"
ref="passwordAgain"
></v-text-field>
</v-form>
<v-btn
v-on:click="save"
>
Save
</v-btn>
</template>
<script>
export default {
methods: {
save() {
let self = this
self.activateRules()
self.$nextTick(function () {
if (self.$refs.form.validate()) {
self.rules = {}
// submit...
}
})
},
activateRules () {
this.rules = {
password: [
v => v.length > 0 || ''
],
passwordAgain: [
v => v.length > 0 || ''
]
}
},
passwordValid: function () {
let passwordValid = true
if (this.$refs.password) {
passwordValid = this.$refs.password.valid
}
return {
'error--text': !passwordValid
}
},
passwordAgainValid: function () {
let passwordAgainValid = true
if (this.$refs.passwordAgain) {
passwordAgainValid = this.$refs.passwordAgain.valid
}
return {
'error--text': !passwordAgainValid
}
}
}
}
</script>

How to bind v-model in VueJS component

I'm new with Vue, so I trying to create a Input Date Picker using Vuetify.
This is my code:
Codepen
The problem is, when I select a date in the picker, I get an error in console:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: '$value'
Is that the correct way to create that component?
As the error states, you cannot change a prop from a child. This is because Vue uses this parent - child flow:
With this structure, you assure that the parent's data is the only source of truth.
In order to change data, you need to emit it up to the parent. You almost had it in your example. Instead of emitting a change event, you need to emit input for v-model to sync the changes.
This is more clear when you see the example from the docs:
<input v-model="searchText">
Is indeed the same as:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
However, since vue 2.3.0, you can now do changes to props via the .sync modifier
Vue.component('date-picker', {
props: ['label'],
data: () => ({
date: null,
dateFormatted: null,
open: false
}),
created() {
this.date = this.$value;
},
methods: {
formatDate (date) {
if (!date) return null
const [year, month, day] = date.split('-')
return `${day}/${month}/${year}`
},
parseDate (date) {
if (!date) return null
const [day, month, year] = date.split('/')
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
},
update() {
this.$emit('input', this.dateFormatted);
}
},
watch: {
date (val) {
this.dateFormatted = this.formatDate(this.date)
},
dateFormatted(val) {
this.update();
}
},
template:`
<v-menu
ref="open"
:close-on-content-click="false"
v-model="open"
:nudge-right="40"
lazy
transition="scale-transition"
offset-y
full-width
>
<v-text-field
slot="activator"
v-model="dateFormatted"
:label="label"
placeholder="dia/mês/ano"
#blur="date = parseDate(dateFormatted)"
></v-text-field>
<v-date-picker v-model="date" no-title #input="open = false"></v-date-picker>
</v-menu>
`
});
Vue.config.productionTip = false;
Vue.config.devtools=false
new Vue({
el: '#app',
data: () => ({
myDate: '2018-09-01'
})
});
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.1.4/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.2.0/vuetify.min.js"></script>
<div id="app">
<v-app>
<v-flex xs24>
<date-picker label="Select a date" v-model="myDate"></date-picker>
<br>
Current date: {{myDate}}
</v-flex>
</v-app>
</div>

How to prevent Vue input from setting value?

I have the following Vue code:
// HTML
<div id="app">
Value: <pre>{{ value }}</pre>
<input type="text" :value="value" #input="setValue">
</div>
// JS
new Vue({
el: "#app",
data: {
value: '',
},
methods: {
setValue(event){
/* Remove non-numeric values */
this.value = event.target.value.replace(/[^\d]/g, '');
}
}
});
I have it set up on JSFiddle here: http://jsfiddle.net/eywraw8t/353729/.
Why does the input allow me to enter non-numeric values?
If you run the code above, and enter non-numeric gibberish into the input element (e.g. asdasfa), you'll see that the input element will contain your entered text (asdasfa), but the element above the input will be empty!
I would like to restrict users to only being allowed to enter numbers into the input. I would like to do this using only Vue, no 3rd party libraries or type="number".
because the value of this.value doesn't change (always ='') so it will not trigger re-render.
The solution:
you can use this.$forceUpdate() to force re-render.
or use bind key with different value.
new Vue({
el: "#app",
data: {
value: '',
errorDescription: ''
},
methods: {
setValue(event){
/* Remove non-numeric values */
this.value = event.target.value.replace(/[^\d]/g, '')
this.errorDescription = 'Only Number allow'
this.$forceUpdate()
}
}
})
.error {
background-color:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
Value: <pre>{{ value }}</pre><span class="error">{{errorDescription}}</span>
<input type="text" :value="value" #input="setValue">
</div>
The issue is Vue doesn't see a change to your value data property because when you filter out non-numbers, you are essentially assigning the same string value back to it. Since strings are immutable and identical when their contents are the same, this doesn't trigger Vue's reactivity.
An easy solution is to manually set the <input> value to the new, number-only value.
this.value = event.target.value = event.target.value.replace(/\D/g, '')
http://jsfiddle.net/9o2tu3b0/