Dynamically update class based on individual form elements validation in Vuetify - vue.js

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>

Related

Check input filed that is looped over and set status for each element accordingly

I am working in a vue component and have a v-for loop in the html that creates some v-text-fields. I want to be able to verify that the v-text-field matches one of the elements in an answer array. I have it set up like this below right now.
<v-expansion-panel
v-for="(element, index) in prompts"
:key="'bucket-a-' + index"
>
<v-expansion-panel-header>
<v-container>
<v-expansion-panel-content>
<v-text-field
label="Answer must match one of the answer values from above"
clearable
v-model="element.answer"
:error="validator"
#input="answerInputValidator($event, element)"
></v-text-field>
</v-expansion-panel-content>
</v-container>
</v-expansion-panel-header>
</v-expansion-panel>
The answer input validator function is set up like this below:
answerInputValidator(evt, prompt) {
this.answerObjects.forEach(element => {
if(element.value === evt){
prompt.answer = evt;
return this.validator = false;
}else{
return this.validator = true;
}
});
}
The function works to validate the v-text-field and links to :error with the property this.validator. However, the issue I am having is that this.validator is a variable declared on the whole of the component so if one input area is found to be valid and the user moves onto the next one and starts inputting and invalid response the previous input area will also be set to invalid. Because this.validtor then gets set to true because the input is invalid on the element being manipulated. But I don't want it to affect all other v-text-fields. I need to be able to check the input against an array of information but treat each field differently. Any ideas on how to achieve this?
You'll need as many error validator flags as you have v-text-field elements. I don't know the structure of the prompts array but maybe each element of it can have its own isError property initialized to false.
Then each v-text-field can have the error prop set as :error="element.isError" Then the validator method can receive each element and toggle that element's individual isError flag on or off without affecting any others.
I don't know how v-text-field works since I have never user Vuetify, but as another answer says each of the prompt could have a property to check is the answer match.
Here is a snippet of how I would do it using plain vue.
Template
<template>
<main>
<div v-for="option in options" :key="option.id">
<input
type="text"
v-model="option.userAnswer"
#input="handleAnswer(option)"
/>
</div>
</main>
Data
data() {
return {
options: [
{
id: 1,
question: "question 1",
userAnswer: "",
rightAnswer: "hello 1",
isAnswerCorrect: false,
},
{
id: 2,
question: "question 2",
userAnswer: "",
rightAnswer: "hello 2",
isAnswerCorrect: false,
},
{
id: 3,
question: "question3",
userAnswer: "",
rightAnswer: "hello3",
isAnswerCorrect: false,
},
],
};
},
Methods
methods: {
handleAnswer(option) {
if (option.userAnswer === option.rightAnswer) {
console.log("right");
option.isAnswerCorrect = true;
} else {
console.log("error");
option.isAnswerCorrect = false;
}
},
},
I hope it helps!

Pass one string piece of data from parent component to child. Vue.js 2

Hoping someone can see a simple mistake I'm making and help me correct it.
I'm trying to pass one string variable as a prop from a parent to a child. This string reveals itself on a checkbox select. There are three and depending which is selected, the name associated will be passed.
Basically, showing some components if different checkboxes are checked. I can clearly see the variable I'm passing shows up in the parent component just fine. I log it out in the method provided. However, it doesn't pass to the child component. I log out there and get undefined as the result.
I've tried multiple solutions, and read about passing props, but nothing is working.
Parent component looks like this.
<template>
<v-col>
<v-checkbox
label="LS"
color="primary"
value="ls"
v-model="checkedSensor"
#change="check($event)"
hide-details
/></v-checkbox>
</v-col>
<v-col cols="2" class="mx-auto">
<v-checkbox
label="DG"
color="primary"
value="dg"
v-model="checkedSensor"
#change="check($event)"
hide-details
/>
</v-col>
<v-col cols="2" class="mx-auto">
<v-checkbox
label="CR"
color="primary"
value="cr"
v-model="checkedSensor"
#change="check($event)"
hide-details
/>
<ls-sensor v-if="lsSensor" :sensorType="checkedSensor" />
</template>
<script>
export default{
data() {
return {
checkedSensor: " ",
lsSensor: false,
dgSensor: false,
crSensor: false,
};
},
methods: {
check: function (e) {
this.showDeviceComponent(e);
},
showDeviceComponent(e) {
if (e == "ls") {
this.lsSensor = true;
this.crSensor = false;
this.dgSensor = true;
console.log("file input component", this.checkedSensor)
} else if (e == "dg") {
this.dgSensor = true;
this.lsSensor = false;
this.crSensor = false;
} else {
this.crSensor = true;
this.dgSensor = false;
this.lsSensor = false;
}
},
},
}
</script>
Child component (just putting the script here as I don't have it's place in the template yet. In the mounted method, I should be seeing the value from the prop. It's showing up as undefined. Gahh.. what silliness I have made a mistake on? Your help is greatly appreciated.
export default {
props:['sensorType'],
data() {
return {
coating: false,
};
},
mounted() {
console.log("required input component, sensor type", this.sensorType)
},
components: {
coatings: require("#/components/shared/FormData/Coatings.vue").default,
"ls-sensor-panel": require("#/components/shared/FormData/LS_SensorPanel.vue").default,
},
};
The only thing that stands out to me is when you create camel cased property names, I am pretty sure that those have to be written as dash (-) separated attributes on a component?
<ls-sensor v-if="lsSensor" :sensor-type="checkedSensor" />
So prop: ['sensorType'] is passed as :sensor-type=""

how to detect change of actual value not just OnChange nuxt vuetify

As a result of
export default {
name: "Details",
async asyncData({ redirect, params, store }) {
if (
!store
I am returning a few values in which one of them is
return {
camera: c,
thumbnail_url: thumbnail_url,
camera, and then in my form fields where I am populating a Vuetify dialog, Text Field inputs
such as
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-card-text>
<v-layout class="model-container">
<v-row>
<v-col cols="12" lg="7" md="7" sm="12" xs="12">
<v-text-field
v-model="camera.name"
class="caption bottom-padding"
required
>
<template v-slot:label>
<div class="caption">
Name
</div>
</template>
</v-text-field>
my issue is, I have a button as
<v-btn color="primary" text #click="updateCamera">
Save
</v-btn>
which I only want to make disable false, only if there is an actual change occurs to, this.camera, in updateCamera method, I can use the updated values as
async updateCamera() {
let payload = {
name: this.camera.name,
but I want to enable or disable the button on when change occurs,
I had tried #input, I have also tried to watch camera object
<v-text-field
v-model="camera.name"
class="caption bottom-padding"
required
#input="up($event, camera)"
>
This way I tried to get some info about event, such as which text field it is, so I can compare, but in up method it only passes input value.
in watch
camera: function() {
this.$nextTick(() => {
console.log(this.camera)
})
}
camera: {
handler: function(val) {
this.$nextTick(() => {
console.log(val)
})
/* ... */
},
immediate: true
}
I have tried this but nothing worked.
Of course, we can enable or disable a button on change but not just if the user places an A and then deletes it, not such change.
Any help would be wonderful
Update:
Even after using this
camera: {
handler: function(newValue) {
if (newValue === this.dumpyCamera) {
console.log(this.dumpyCamera)
console.log(newValue)
console.log("here")
this.updateButton = true
} else {
this.updateButton = false
}
},
deep: true
}
both new and old values are the same.
I have tried to add new variable dumyCamera and on mount I have assigned this.camera value to this.dumyCamera but when something changes in camera, it changes this.dumyCamera as well? why is this the case?
You should be able to recognize any changes made to this.camera by using a watcher
watch: {
camera: {
handler (newValue, oldValue) {
// do something here because your this.camera changed
},
deep: true
}
}

force a new validation if another related field changed

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

How to Create Component on the fly?

First of all here is my structure
CHILD COMPONENT
// HTML
<v-select
v-bind:items="selectItems"
v-model="selectedItemModel"
label="Category"
item-value="text"
></v-select>
<v-text-field
label="Enter Value"
type="number"
v-model="compValModel"
></v-text-field>
// REMOVE BUTTON for deleting this component in render.
<v-btn icon>
<v-icon>cancel</v-icon>
</v-btn>
// JS
props: {
selectItems: {
type: Array,
required: true
},
selectedItem: {
type: String
},
compVal: {
type: Number
},
}
data () {
return {
selectedItemModel: this.selectedItem,
compValModel: this.compVal
}
},
watch: {
selectedItemModel(value) {
this.$emit('selectedItemInput', value);
},
compValModel(value) {
this.$emit('compValInput', value);
}
}
PARENT COMPONENT
// HTML
<component
:selecItems="selectItems"
:selectedItem="selectOneItem"
:compVal="compOneVal"
#selectedItemInput="selectOneItem = $event"
#compValInput="compOneVal = $event"
></component>
// ADD BUTTON for adding the above component more.
<v-btn icon>
<v-icon>cancel</v-icon>
</v-btn>
My Case
When i click on that plus button, it should create new component. and when i click on that REMOVE button from that component, it should delete that component.
Right now i have followed this. The problem in this approach is, whenever a new component is created. the value of the exsisting dynamically created values got refereshed.
My Question
Whats the good way to duplicate the component dynamically?
Since we are going to create components dynamically, how to create data values also dynamically?