Reputation: 1839
I am looking at the vuetify table example for CRUD operations. A codepen for it is here: https://codepen.io/uglyhobbitfeet/pen/oNvKaaL
I added a parent form that wraps around the table and another component. It validates that the table has more than 0 rows and validates the other component before the user can click the 'continue' button.
In the provided codepen, when the user clicks on the 'New Item' button (located at the top-right of the table), I would like to validate the popped up fields before the user can 'save' the data to the table. How would that be done? I have tried to wrap the v-dialog in a separate v-form from the parent form, but I couldn't get it to work and I'm not sure nesting forms is the way to go. Any suggestions?
Since SO requires code to be posted when providing a codepen link here's a small snippet.
<v-data-table
:headers="headers"
:items="desserts"
sort-by="calories"
class="elevation-1"
>
Upvotes: 2
Views: 3575
Reputation: 31
reassignFormInputs(form) {
const inputs = [];
// Copied from VForm's previous life* which had a getInputs() function
const search = (children, depth = 0) => {
for (let index = 0; index < children.length; index++) {
const child = children[index];
if (child.errorBucket !== undefined) inputs.push(child);
else search(child.$children, depth + 1);
}
if (depth === 0) return inputs;
};
search(form.$children);
form.inputs = inputs;
},
saveForm() {
this.reassignFormInputs(this.$refs.form);
if (this.valid) {
console.log(this.lessonPlanData);
} else {
this.$refs.form.validate();
}
},
Assign your child components to the parent input.
Source: https://github.com/vuetifyjs/vuetify/issues/4900#issuecomment-423600028
Upvotes: 3
Reputation: 1839
It looks like I did get it working with nested v-forms. I updated the codepen to show the solution. If you edit a row and remove the dessert name the save button will be disabled. If there are no table rows or the other v-text-field has no text the continue button will be disabled.
Upvotes: 0
Reputation: 460
I get your code and make some changes, I added the v-form inside de v-container to validate, then I created the Rules for the inputs to validate, and disabled the button if the form is not valid for both editing and new items. In the second part the script I added the rules to validate the fields and then in the save function I check if everything is ok.
In the other form (father form) you can just verify if some item was added since you already validated the data inside the form in the dialog.
this.desserts.length > 0 //And then proceed
For more info see the docs
Template
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
sort-by="calories"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>My CRUD</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<div class="flex-grow-1"></div>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on }">
<v-btn color="primary" dark class="mb-2" v-on="on">New Item</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<!-- Form Tag -->
<v-form
id="dessertForm"
ref="dessertForm"
v-model="isValid"
>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field :rules="nameRules" v-model="editedItem.name" label="Dessert name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field :rules="genericRules" v-model="editedItem.calories" label="Calories"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field :rules="genericRules" v-model="editedItem.fat" label="Fat (g)"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field :rules="genericRules" v-model="editedItem.carbs" label="Carbs (g)"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field :rules="genericRules" v-model="editedItem.protein" label="Protein (g)"></v-text-field>
</v-col>
</v-row>
</v-form>
</v-container>
</v-card-text>
<v-card-actions>
<div class="flex-grow-1"></div>
<v-btn color="blue darken-1" text @click="close">Cancel</v-btn>
<!-- Disable button if is not valid -->
<v-btn :disabled="!isValid" color="blue darken-1" text @click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.action="{ item }">
<v-icon
small
class="mr-2"
@click="editItem(item)"
>
edit
</v-icon>
<v-icon
small
@click="deleteItem(item)"
>
delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" @click="initialize">Reset</v-btn>
</template>
</v-data-table>
</v-app>
</div>
Script
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
dialog: false,
isValid: true,
// Rules for name (example)
nameRules: [
v => !!v || 'Name is required',
v => (v && v.length >= 10) || 'Name must be more than 10 characters',
],
// Rules for generic fields (example)
genericRules: [
v => (v && v > 0) || 'Value must be more than 0',
],
headers: [
{
text: 'Dessert (100g serving)',
align: 'left',
sortable: false,
value: 'name',
},
{ text: 'Calories', value: 'calories' },
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Actions', value: 'action', sortable: false },
],
desserts: [],
editedIndex: -1,
editedItem: {
name: '',
calories: 0,
fat: 0,
carbs: 0,
protein: 0,
},
defaultItem: {
name: '',
calories: 0,
fat: 0,
carbs: 0,
protein: 0,
},
}),
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
},
watch: {
dialog (val) {
val || this.close()
},
},
mounted () {
this.initialize()
},
methods: {
initialize () {
this.desserts = [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
},
{
name: 'Ice cream sandwich',
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
},
{
name: 'Eclair',
calories: 262,
fat: 16.0,
carbs: 23,
protein: 6.0,
},
{
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
},
{
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
},
{
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
},
{
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
},
{
name: 'Honeycomb',
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
},
{
name: 'Donut',
calories: 452,
fat: 25.0,
carbs: 51,
protein: 4.9,
},
{
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
},
]
},
editItem (item) {
this.editedIndex = this.desserts.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
const index = this.desserts.indexOf(item)
confirm('Are you sure you want to delete this item?') && this.desserts.splice(index, 1)
},
close () {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
save () {
if(this.$refs.dessertForm.validate()) {
if (this.editedIndex > -1) {
Object.assign(this.desserts[this.editedIndex], this.editedItem)
} else {
this.desserts.push(this.editedItem)
}
this.close()
}
},
},
})
Upvotes: 0