Add/Edit list item at parent from child component form - vue.js

I am developing a recipe app. At my CreateRecipe component, I have child component to add ingredients to the recipe or edit existing ingredients. Ill start by showing the code and what i want to achieve and then the problem
Parent component:
<template>
...
<v-dialog v-model="AddIgredientsDialog" max-width="800px">
<template v-slot:activator="{ on, attrs }">
<v-btn color="secondary" dark v-bind="attrs" v-on="on">
Add addIngredients
</v-btn>
</template>
<AddItemsForm
#addIngredient="SaveNewIgredient"
:newIngredientsItem="editedIgredient"
/>
</v-dialog>
</template>
<script>
import AddItemsForm from "./AddItemsForm"; //Child Component
data: () => ({
AddIgredientsDialog:false,
article: {
headline: "",
img: "",
content: "",
subHeader: "",
description: "",
igredients: [], //List to add/edit item at AddItemsForm child component
preperation: []
},
editedIgredient: { //Item to use for editing or adding new item to article.igredients
title: "",
subIgredients: []
},
defaultItem: { //Item used for resetting editedIgredient item
title: "",
subIgredients: []
},
editedIndex: -1, helper variable for knowing whether i need to add new item or edit exiting item
}),
methods:{
editIngredients(item) {
this.editedIndex = this.article.igredients.indexOf(item);
this.editedIgredient = Object.assign({}, item);
this.AddIgredientsDialog = true;
},
SaveNewIgredient(newItem) { //Triggered on #click of save button at child component New item is the
//item passed from children
if (this.editedIndex > -1) {
this.editedIgredient = Object.assign({}, newItem);
Object.assign(
this.article.igredients[this.editedIndex],
this.editedIgredient
);
} else {
this.article.igredients.push(this.editedIgredient);
}
this.AddIgredientsDialog = false;
this.$nextTick(() => {
this.editedIgredient = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
}
</script>
Child Component:
<template>
<v-card>
<v-card-title>
<span class="headline">Add Ingredients</span>
</v-card-title>
<v-card-text>
<v-text-field v-model="newIngredientsItem.title" placeholder="כותרת">
</v-text-field>
<v-row align="center">
<v-col sm="11">
<v-text-field
v-model="newIgredient"
placeholder="New Igredient"
#keyup.enter="addNewIgredient"
>
</v-text-field>
</v-col>
<v-col sm="1">
<v-btn icon #click="addNewIgredient">
<v-icon>
mdi-plus
</v-icon>
</v-btn>
</v-col>
<v-col class="mt-0 pt-0" cols="12">
<v-row no-gutters>
<v-col cols="12">
<v-card flat tile>
<template
v-for="(item, index) in newIngredientsItem.subIgredients"
>
<v-list-item :key="index" class="mr-0 pr-0">
<v-list-item-content>
<v-list-item-title>
<v-edit-dialog #click.native.stop>
{{ item }}
<v-text-field
slot="input"
v-model="newIngredientsItem.subIgredients[index]"
></v-text-field>
</v-edit-dialog>
</v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn icon #click="removeIgredient(index)">
<v-icon small>
mdi-delete
</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
<v-divider
v-if="index + 1 < newIngredientsItem.subIgredients.length"
:key="item + index"
></v-divider>
</template>
</v-card>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-btn color="primary" #click="AddIngredients">
Save
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
props: {
newIngredientsItem: {
type: Object,
default() {
return {
title: "",
subIgredients: [ ]
};
}
}
},
data: () => ({
newIgredient: ""
}),
methods: {
addNewIgredient() {
this.newIngredientsItem.subIgredients.push(this.newIgredient);
this.newIgredient = "";
},
AddIngredients() {
this.$emit("addIngredient", this.newIngredientsItem);
},
removeIgredient(index) {
this.newIngredientsItem.subIgredients.splice(index, 1);
}
}
};
</script>
My Problem:
At the moment im only trying to use the SaveNewIgredient() method.
After 1st time of adding item the item is added as it should and the parent defaultItem state remain as is which is good:
defaultItem: {
title: "",
subIgredients: []
},
After adding a second item the defaultItem changes and takes the editedItem properties.
For example if i add at the second time
{
title:'Test 1',
subIgredients: [
'Test 1 - 1',
'Test 1 - 2',
'Test 1 - 3',
]
}
That is what the defaultItem will be and then this assignment causes a bug
this.editedIgredient = Object.assign({}, this.defaultItem);
because editedItem should be:
{
title: "",
subIgredients: []
}

I tried to solve your problem. To do this I modified and in some places simplified your code to keep only what was close to the SaveNewIgredient() function. So here is my code.
Parent Component (for me App.vue):
<template>
<AddItemsForm #addIngredient="SaveNewIgredient" />
</template>
<script>
import AddItemsForm from "./AddItemsForm"; //Child Component
export default {
name: "App",
components: { AddItemsForm },
data() {
return {
article: {
igredients: [], //List to add/edit item at AddItemsForm child component
},
editedIgredient: {
//Item to use for editing or adding new item to article.igredients
title: "",
subIgredients: [],
},
defaultItem: {
//Item used for resetting editedIgredient item
title: "",
subIgredients: [],
},
};
},
methods: {
SaveNewIgredient(newItem) {
console.log("Received: ", newItem);
this.editedIgredient = newItem;
this.article.igredients.push({ ...this.editedIgredient });
console.log("defaultClear: ", this.defaultItem);
console.log("infoItem: ", this.editedIgredient);
this.editedIgredient = this.defaultItem;
console.log("defaultClear: ", this.defaultItem);
console.log("editedWillClear: ", this.editedIgredient);
console.log("listFinal: ", this.article.igredients);
},
},
};
</script>
Child Component (for me AddItemsForm.vue):
<template>
<div>
<input v-model="newIgredient" placeholder="New Igredient" />
<button #click="addNewIgredient">ADD</button>
<div>
<button color="primary" #click="AddIngredients">Save</button>
</div>
</div>
</template>
<script>
export default {
props: {
IngredientsItem: {
type: Object,
default() {
return {
title: "",
subIgredients: [],
};
},
},
},
data() {
return {
newIgredient: "",
title: "TEST",
titleNbr: 0,
resetIgredient: false,
};
},
computed: {
newIngredientsItem() {
return this.IngredientsItem;
},
},
methods: {
addNewIgredient() {
if (this.resetIgredient === true) {
this.newIngredientsItem.subIgredients = [];
}
this.newIngredientsItem.subIgredients.push(this.newIgredient);
this.newIgredient = "";
this.resetIgredient = false;
console.log("ADD: ", this.newIngredientsItem.subIgredients);
},
AddIngredients() {
this.newIngredientsItem.title = this.title + this.titleNbr;
this.titleNbr++;
console.log("EMIT: ", this.newIngredientsItem);
this.$emit("addIngredient", this.newIngredientsItem);
this.resetIgredient = true;
},
},
};
</script>

Related

I want to add editing functions using Nuxt.js

What I want to come true
I am creating TodoLists.
I tried to implement the following editing features, but it didn't work and I'm having trouble.
Click the edit button to display the edit text in the input field
If you click the save button after entering the changes in the input field, the changes will be reflected in the first position.
Code
<v-row v-for="(todo,index) in todos" :key="index">
<v-text-field
filled
readonly
:value="todo.text"
class="ma-3"
auto-grow
/>
<v-menu
top
rounded
>
<template #activator="{ on, attrs }">
<v-btn
v-bind="attrs"
icon
class="mt-6"
v-on="on"
>
<v-icon>
mdi-dots-vertical
</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item
link
>
<v-list-item-title #click="toEdit(todos)">
<v-icon>mdi-pencil</v-icon>
Edit
</v-list-item-title>
</v-list-item>
</v-list>
<v-list>
<v-list-item
link
>
<v-list-item-title #click="removeTodo(todo)">
<v-icon>mdi-delete</v-icon>
Delete
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-row>
<v-text-field
v-model="itemText"
filled
color="pink lighten-3"
auto-grow
#keyup.enter="addTodo"
/>
<v-btn
:disabled="disabled"
#click="addTodo"
>
Save
</v-btn>
data () {
return {
editIndex: false,
hidden: false,
itemText: '',
items: [
{ title: 'Edit', icon: 'mdi-pencil' },
{ title: 'Delete', icon: 'mdi-delete' }
]
}
},
computed: {
todos () {
return this.$store.state.todos.list
},
disabled () {
return this.itemText.length === 0
}
},
methods: {
addTodo (todo) {
if (this.editIndex === false) {
this.$store.commit('todos/add', this.itemText)
this.itemText = ''
} else {
this.$store.commit('todos/edit', this.itemText, todo)
this.itemText = ''
}
},
toEdit (todo) {
this.editIndex = true
this.itemText = this.todos
},
removeTodo (todo) {
this.$store.commit('todos/remove', todo)
}
}
}
</script>
export const state = () => ({
list: []
})
export const mutations = {
add (state, text) {
state.list.push({
text
})
},
remove (state, todo) {
state.list.splice(state.list.indexOf(todo), 1)
},
edit (state, text, todo) {
state.list.splice(state.list.indexOf(todo), 1, text)
}
}
Error
Click the edit button and it will look like this
What I tried myself
//methods
toEdit (todo) {
this.editIndex = true
this.itemText = this.todos.text //add
},
// Cannot read property 'length' of undefined
For some reason I get an error that I couldn't see before
The properties/data types in your code are a bit mixed up.
Here you're accessing state.todos.list...
todos () {
return this.$store.state.todos.list
},
...but in your store the const state doesn't include todos:
export const state = () => ({
list: []
})
Furthermore, you're writing to itemText the content of todos, which should be a string but actually is an object - which leads to the output of [object Object].
toEdit (todo) {
this.editIndex = true
this.itemText = this.todos
},
Also, please check out kissu's comment about the mutations.

Vuetify v-dialog not showing the second time

I'm trying to to conditionally show a dialog. The dialog shows up the first time just fine.
But when second time I try to remount the component I can't see the dialog.
The dialog also fetches some data when it is mounted. I can see the logs that the dialog is being mounted and unmounted and also the network request on chrome devtools network tab but I can't see the dialog.
index.vue
<v-list-item-title #click="changeDialogState">
<TestDialog
v-if="showEditDialog"
:id="item.id"
#editDone="changeDialogState"/>
Edit
</v-list-item-title>
------------------------
//This is defined inside methods
changeDialogState() {
this.showEditDialog = !this.showEditDialog // this.showEditDialog is just a boolean value define inside data()
},
Testdialog.vue
<template>
<v-dialog v-model="dialog" width="700">
<v-progress-circular
v-if="fetching"
:size="70"
:width="7"
color="purple"
indeterminate
></v-progress-circular>
<v-card v-else>
<v-card-title> New Customer </v-card-title>
<v-divider></v-divider>
<v-card-text>
<customer-form
v-model="customer"
:errors="errors"
:initial-customer="customer"
#submit="editCustomer"
>
<template v-slot:footer>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text #click="$emit('editDone')"> Cancel </v-btn>
<v-btn color="primary" type="submit" class="ma-1">
Save Customer
</v-btn>
</v-card-actions>
</template>
</customer-form>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
import { formHandling, paginatedResponse } from '~/mixins'
export default {
mixins: [formHandling, paginatedResponse],
props: {
value: {
type: Object,
default: () => ({
//some stuff here
}),
},
error: {
type: Array,
required: true,
default: () => {
return []
},
},
id: {
type: String,
required: true,
},
},
async fetch() {
this.fetching = true
this.customer = await this.$axios.$get(`/customer/${this.id}/`)
this.fetching = false
},
data() {
return {
dialog: true,
customer: {
...
},
errors: {
...
},
fetching: true,
}
},
mounted() {
console.log('TestDialog - mounted')
},
beforeDestroy() {
console.log('TestDialog - unmounted')
},
methods: {
async editCustomer(customer) {
try {
await this.$axios.$put(`/customer/${this.id}/`, customer)
this.dialog = false
} catch (err) {
this.setErrors(err)
}
},
},
}
</script>

Vue Passing Data from component to another component

I am making a "create account" flow for user and I am not able to pass data from one component to another. The first component has radio buttons with options of "tenant", "landlord", "contractor". Once the user selects "tenant", then the data should pass to the next step where they fill out a form with name and all that good stuff.. once they submit, it should all go together to the back end.
acc-for.vue with radio buttons component below.
<template>
<div>
<v-app
style="background-image: url('https://blog.modsy.com/wp-content/uploads/2019/06/D2_Full.jpg')"
>
<v-container class="pa-12">
<v-row>
<v-card class="pa-16">
<v-card-title>
Are you a?
</v-card-title>
<v-radio-group v-model="selectedValue" #change="selectedAcc">
<v-radio
v-for="account in accountType"
:key="account.name"
:value="account.name"
:label="account.name"
></v-radio>
</v-radio-group>
<v-row>
<v-btn rounded color="black" class="white--text" href="/login"
>Back</v-btn
>
<v-btn
rounded
color="black"
class="white--text"
#click="selected(accountSelected)"
>Next</v-btn
>
<!-- href="/create-acc" -->
</v-row>
</v-card>
</v-row>
</v-container>
</v-app>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
data() {
return {
selectedValue: false,
};
},
computed: {
accountType() {
return this.$store.state.accountType;
},
selected() {
return this.$store.state.selectedAccType;
},
},
methods: {
...mapMutations(["SELECTED_ACCOUNT_TYPE"]),
selectedAcc(e) {
this.$emit("selected-accountType", e);
},
},
};
</script>
<style></style>
createAccount.vue this component has the form for the fName and lName and all that good stuff..
<template>
<div>
<v-app
style="background-image: url('https://blog.modsy.com/wp-content/uploads/2019/06/D2_Full.jpg')"
>
<v-container class="pa-12">
<v-row>
<v-card class="pa-16">
<v-card-title>
{{ selectedTypeUser }}
</v-card-title>
<v-form>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_fName"
label="First Name"
/>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_lName"
label="Last Name"
/>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_email"
label="Email"
/>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_password"
label="Password"
/>
<v-row>
<v-btn rounded color="black" class="white--text" href="/acc-for"
>Back</v-btn
>
<v-btn
#click="registerUser"
rounded
color="black"
class="white--text"
>Next</v-btn
>
</v-row>
</v-form>
</v-card>
</v-row>
</v-container>
</v-app>
</div>
</template>
<script>
import { mapMutations } from "vuex";
import axios from "axios";
export default {
data() {
return {
newUser_fName: "",
newUser_lName: "",
newUser_email: "",
newUser_password: "",
};
},
methods: {
...mapMutations(["ADD_USER"]),
registerUser() {
let config = {
headers: {
"Content-Type": "application/json",
},
};
axios
.post(
"http://localhost:7876/createUser",
{
fName: this.newUser_fName,
lName: this.newUser_lName,
email: this.newUser_email,
password: this.newUser_email,
},
config
)
.then((response) => {
console.log(response.statusText);
})
.catch((e) => {
console.log("Error: ", e.response.data);
});
},
},
};
</script>
store.js (state) is below
// import vue from "vue";
import vuex from "vuex";
import axios from "axios";
import Vue from "vue";
Vue.use(vuex, axios);
export default new vuex.Store({
state: {
users: [],
accountType: [
{ name: "Tenant" },
{ name: "Landlord" },
{ name: "Contractor" }
],
selectedAccType: [],
},
mutations: {
ADD_USER: (state, payload) => {
state.users.push(payload.user);
},
SELECTED_ACCOUNT_TYPE: (state, payload) => {
state.selectedAccType.push(payload)
}
},
actions: {
addUser: ({ commit }, payload) => {
commit("ADD_USER", payload);
},
selectAcc: ({ commit }, payload) => {
commit("SELECTED_ACCOUNT_TYPE", payload)
}
},
// getters: {
// addAccountType(state, e) {
// state.accountType.push(e)
// },
// },
});
I see this on the button:
<v-btn rounded
color="black"
class="white--text"
#click="selected(accountSelected)"
>Next</v-btn
>
But I'm not finding a method with that name in your attached code. Double check that your names are all matched up and that you are calling the correct function on the click event.

Problem in POST and PUT request, with axios, vuetify datatable, vuejs

I'm trying to adapt a sample datatable from the vuetify website itself according to my needs by implementing axios to consume my api. The GET AND DELETE method is working perfectly, however I am very confused about the POST AND PUT method, I am using 2 models as a client and the relationship with the genre, follows part of the code:
<template>
<v-data-table
:headers="headers"
:items="clients"
sort-by="firstName"
class="elevation-2"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-icon medium>mdi-account-supervisor</v-icon>
<v-toolbar-title> Clients</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on }">
<v-btn
color="blue"
dark class="mt-6 mb-4"
v-on="on"
rounded
><v-icon medium>mdi-plus</v-icon>Add new</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-form>
<v-row>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.firstName" label="First Name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.lastName" label="Last Name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.email" label="E-Mail"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.phone" label="Phone"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.mobilePhone" label="Mobile Phone"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<!-- select options-->
<v-select
label='Gender'
v-model='editedItem.gender.name'
:items='genders'
item-value='name'
item-text='name'
>
</v-select>
</v-col>
</v-row>
</v-form>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" rounded #click="close">Cancel</v-btn>
<v-btn color="primary" rounded #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.action="{ item }">
<v-icon
small
color="green"
class="mr-2"
#click="editItem(item)"
>
mdi-pencil
</v-icon>
<v-icon
small
color="red"
#click="deleteItem(item)"
>
mdi-delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" #click="initialize">Reset</v-btn>
</template>
</v-data-table>
</template>
<script>
import axios from 'axios'
import Client from '../../services/clients';
import Gender from '../../services/genders';
export default {
data: () => ({
dialog: false,
headers: [
{
text: 'First Name',
align: 'start',
sortable: false,
value: 'firstName',
},
{ text: 'Last Name', value: 'lastName' },
{ text: 'Email', value: 'email' },
{ text: 'Phone', value: 'phone' },
{ text: 'Mobile Phone', value: 'mobilePhone' },
{ text: 'Gender', value: 'gender.name' },
{ text: 'Actions', value: 'action', sortable: false },
],
clients: [],
genders: [],
errors: [],
editedIndex: -1,
editedItem: {
firstName: '',
lastName: '',
email: '',
phone: '',
mobilePhone: '',
gender: '',
},
defaultItem: {
firstName: '',
lastName: '',
email: '',
phone: '',
mobilePhone: '',
gender: '',
},
}),
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
},
watch: {
dialog (val) {
val || this.close()
},
},
created () {
this.initialize()
},
methods: {
initialize () {
Client.list().then(response => {
this.clients = response.data
}).catch(e => {
console.log(e)
});
Gender.list().then(response => {
this.genders = response.data
}).catch(e => {
console.log(e)
});
},
editItem (item) {
axios.put('http://192.168.26.130:3000/client/' + item.id)
.then(response => {
this.editedIndex = this.clients.indexOf(item)
this.editedItem = Object.assign({}, item)
this.editedID = this.editedItem.id
this.dialog = true
this.response = response
}).catch(e => {
console.log(e)
});
},
deleteItem (item) {
if (confirm("Do you really want to delete?")) {
axios.delete('http://192.168.26.130:3000/client/' + item.id)
.then(response => {
const index = this.clients.indexOf(item)
this.deletedItem = Object.assign({}, item)
this.deletedID = this.deletedItem.id
this.clients.splice(index, 1);
this.response = response
}).catch(e => {
console.log(e)
});
}
},
close () {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
save () {
if (this.editedIndex > -1) {
axios.post('http://192.168.26.130:3000/client/')
.then(response => {
Object.assign(this.clients[this.editedIndex], this.editedItem)
this.response = response.data
}).catch(e => {
console.log(e)
});
} else {
this.clients.push(this.editedItem)
}
this.close()
},
},
}
</script>
When opening the modal to add item, only when opening the select and modifying the genre this error already appears before even saving, as shown in the image:
When clicking on save it is saved only on the front, and when updating the page the record disappears, could someone give me a light?
Update Edit.
After some changes, I think I am closer to the solution but I came to the following obstacle, When saving the client item, the gender is stored empty.
of console.log and the item saved in the front end but in the database the gender is empty
The file DataTable.vue:
<template>
<v-data-table
:headers="headers"
:items="clients"
sort-by="firstName"
class="elevation-2"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-icon medium>mdi-account-supervisor</v-icon>
<v-toolbar-title> Clients</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on }">
<v-btn
color="blue"
dark class="mt-6 mb-4"
v-on="on"
rounded
><v-icon medium>mdi-plus</v-icon>Add new</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-form>
<v-row>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.firstName" label="First Name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.lastName" label="Last Name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.email" label="E-Mail"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.phone" label="Phone"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<v-text-field v-model="editedItem.mobilePhone" label="Mobile Phone"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="12">
<!-- select options-->
<v-select
label='Gender'
v-model='editedItem.gender'
:items='genders'
item-value='name'
item-text='name'
>
</v-select>
</v-col>
</v-row>
</v-form>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="error" rounded #click="close">Cancel</v-btn>
<v-btn color="primary" rounded #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.action="{ item }">
<v-icon
small
color="green"
class="mr-2"
#click="editItem(item)"
>
mdi-pencil
</v-icon>
<v-icon
small
color="red"
#click="deleteItem(item)"
>
mdi-delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" #click="initialize">Reset</v-btn>
</template>
</v-data-table>
</template>
<script>
import axios from 'axios'
import Client from '../../services/clients';
import Gender from '../../services/genders';
export default {
data: () => ({
dialog: false,
headers: [
{
text: 'First Name',
align: 'start',
sortable: false,
value: 'firstName',
},
{ text: 'Last Name', value: 'lastName' },
{ text: 'Email', value: 'email' },
{ text: 'Phone', value: 'phone' },
{ text: 'Mobile Phone', value: 'mobilePhone' },
{ text: 'Gender', value: 'gender.name' },
{ text: 'Actions', value: 'action', sortable: false },
],
clients: [],
genders: [],
errors: [],
editedIndex: -1,
editedItem: {
firstName: '',
lastName: '',
email: '',
phone: '',
mobilePhone: '',
gender: '',
},
defaultItem: {
firstName: '',
lastName: '',
email: '',
phone: '',
mobilePhone: '',
gender: '',
},
}),
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
},
watch: {
dialog (val) {
val || this.close()
},
},
created () {
this.initialize()
},
methods: {
initialize () {
Client.list().then(response => {
this.clients = response.data
}).catch(e => {
console.log(e)
});
Gender.list().then(response => {
this.genders = response.data
}).catch(e => {
console.log(e)
});
},
editItem (item) {
axios.put('http://192.168.26.130:3000/client/' + item.id)
.then(response => {
this.editedIndex = this.clients.indexOf(item)
this.editedItem = Object.assign({}, item)
this.editedID = this.editedItem.id
this.dialog = true
this.response = response
}).catch(error => {
console.log(error.response)
});
},
deleteItem (item) {
if (confirm("Do you really want to delete?")) {
axios.delete('http://192.168.26.130:3000/client/' + item.id)
.then(response => {
const index = this.clients.indexOf(item)
this.deletedItem = Object.assign({}, item)
this.deletedID = this.deletedItem.id
this.clients.splice(index, 1);
this.response = response
}).catch(error => {
console.log(error.response)
});
}
},
close () {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
save () {
if (this.editedIndex > -1) {
Object.assign(this.clients[this.editedIndex], this.editedItem)
} else {
this.clients.push(this.editedItem)
axios.post('http://192.168.26.130:3000/client/', this.editedItem)
.then(response => {
console.log(response)
}).catch(error => {
console.log(error.response)
});
}
this.close()
},
},
}
</script>
Please would someone help me?
There are a couple of issues. First, you aren't passing any data to your PUT and POST requests. They should look something like:
editItem (item) {
// YOU NEED TO PASS AN OBJECT TO THE PUT REQUEST ▼▼HERE▼▼
axios.put('http://192.168.26.130:3000/client/' + item.id , item)
.then(response => {
// handle response...
})
.catch(err => { console.log(error) })
},
save () {
if (this.editedIndex > -1) {
// YOU NEED TO PASS AN OBJECT TO THE POST REQUEST ▼▼HERE▼▼
axios.post('http://192.168.26.130:3000/client/', this.editedItem)
.then(response => {
// handle response...
})
.catch(err => { console.log(error) })
} else { /* ... */ }
},
Second, under the hood, <v-select> uses a v-for to iterate over all of the options that are supposed to go into the dropdown menu. If this were a plain HTML <select> element, it would look something like this:
<select name="gender">
<option value="">Select a gender...</option>
<option
v-for="gender in genders"
:key="gender"
value="gender.value"
>
{{ gender.text }}
</option>
</select>
Vuetify expects the array of genders to be in one of two formats, either an array of strings, or an array of objects with text and value properties:
const genders = ['male', 'female', 'other']
// OR
const genders = [
{ value: 1, text: 'male' }, // `value` can be anything you want
{ value: 2, text: 'female' },
{ value: 3, text: 'other' },
]
Alternatively, if your genders array has a different data structure, you can tell Vuetify what properties to use for the value and text properties (this is what it looks like you did). So if your genders array looks like this:
const genders = [
{ name: 'male' },
{ name: 'female' },
{ name: 'other' },
]
Your <v-select> should look like this (in your case you used the SAME property for both text and value, which is perfectly fine to do):
<v-select
v-model="editedItem.gender"
:items="genders"
item-text="name"
return-object
/>
What I'm guessing, based on the image you attached, is that the genders array is NOT in one of these formats, and this is causing an error when Vuetify tries to turn it into a dropdown. Also, I think you intend for the value selected to be assigned to editedItem.gender and not editedItem.gender.name. Here's a codepen showing how to use objects for v-select items.
If the items array is in one of the two formats I showed before, you do NOT need to specify the item-text and item-value props. They will be detected automatically.
Hope this helps!

Vuetify Data Table does not reload on state update

I would like to kindly ask you about Vuetify and its Data Table. I am using Vuex for state managing. When I open the page Vuex loads all the data from API. It works perfectly. Then I have the Data Table which also works (update, add, delete), but only one column does not... Data Table is filled with GET_CENTERS and the "one column" is an object (center.customer). When I change center.customer.name state is updated, database is updated, but the value in Data Table is not. (I have tried to just create a list of customers on same page and this list changes if :key is set).
CentersTable
<template>
<div>
<v-data-table
:headers="headers"
:items="GET_CENTERS"
sort-by="name"
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">Nový Záznam</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.name" label="Název"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.address" label="Adresa"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.city" label="Město"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.zip" label="PSČ"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.email" label="Email"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.phone" label="Telefon"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="12">
<v-combobox
v-model="editedItem.customer"
:items="GET_CUSTOMERS"
:search-input.sync="search"
item-text="name"
item-value="_id"
:hide-selected="hideSelected"
label="Zákazník"
persistent-hint
:clearable="clearable"
return-object
>
<template v-if="noData" v-slot:no-data>
<v-list-item>
<v-list-item-content>
<v-list-item-title>
No results matching "<strong>{{ search }}</strong>". Press <kbd>enter</kbd> to create a new one
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
</v-combobox>
</v-col>
</v-row>
</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>
<v-btn 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>
</v-data-table>
<template>
<v-list-item v-for="item in GET_CUSTOMERS" :key="item._id">
<v-list-item-content>{{item}}</v-list-item-content>
</v-list-item>
</template>
</div>
</template>
<script>
import {mapActions, mapGetters} from 'vuex'
export default {
name: "CentersTable",
data: () => ({
dialog: false,
editedItem: {},
headers: [
{
text: 'Název',
align: 'left',
sortable: true,
value: 'name',
},
{
text: 'Adresa',
sortable: true,
value: 'address',
},
{
text: 'Město',
sortable: true,
value: 'city',
},
{
text: 'PSČ',
sortable: true,
value: 'zip',
},
{
text: 'Email',
sortable: true,
value: 'email',
},
{
text: 'Telefon',
sortable: true,
value: 'phone',
},
{
text: 'Zákazník',
sortable: true,
value: 'customer.name',
},
{ text: 'Akce', value: 'action', sortable: false, align: 'right' },
],
search: null,
chips: true,
multiple: true,
hideSelected: true,
noData: true,
clearable: false,
}),
watch: {
dialog (val) {
val || this.close()
},
},
computed: {
formTitle() {
return this.editedItem._id === undefined ? 'Nový Záznam' : 'Upravit Záznam'
},
...mapGetters([
'GET_CUSTOMERS',
'GET_CENTERS'
]),
},
methods: {
...mapActions([
'createCenter',
'updateCenter',
'deleteCenter',
]),
editItem (item) {
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
confirm('Opravdu chcete smazat tento záznam?') && this.deleteCenter(item).then(() => {
this.$noty.success('Záznam byl smazán')
}).catch(() => {
this.$noty.alert('Při mazání záznamu došlo k chybě')
})
},
close () {
this.dialog = false
this.editedItem = {}
setTimeout(() => {
}, 300)
},
save () {
if (this.editedItem._id === undefined) {
this.createCenter(this.editedItem).then(() => {
this.$noty.success('Nový zákazník byl vytvořen')
}).catch(() => {
this.$noty.alert('Při vytváření zákazníka došlo k chybě')
})
} else {
this.updateCenter(this.editedItem).then(() => {
this.$noty.success('Záznam byl upraven')
}).catch(() => {
this.$noty.alert('Při ukládání záznamu došlo k chybě')
})
}
this.close()
},
}
}
</script>
<style scoped>
</style>
And Vuex store
import axios from "axios";
import Vue from "vue";
const state = {
customers: []
};
const mutations = {
SET_CUSTOMERS(state, payload) {
state.customers = payload;
},
UPDATE_CUSTOMER(state, payload) {
const customer = state.customers.findIndex(x => x._id === payload._id);
Vue.set(state.customers, customer, payload);
},
ADD_CUSTOMER(state, payload) {
state.customers.push(payload);
},
REMOVE_CUSTOMER(state, payload) {
const customer = state.customers
.map(customer => customer._id)
.indexOf(payload);
state.customers.splice(customer, 1);
}
};
const actions = {
loadCustomers({ commit }) {
axios.get("/api/customers").then(data => {
commit("SET_CUSTOMERS", data.data);
});
},
updateCustomer({ commit }, payload) {
axios.put(`/api/customers/${payload._id}`, payload).then(response => {
commit("UPDATE_CUSTOMER", response.data);
});
},
createCustomer({ commit }, payload) {
axios.post("/api/customers", payload).then(data => {
commit("ADD_CUSTOMER", data.data);
});
},
deleteCustomer({ commit }, payload) {
axios.delete(`/api/customers/${payload._id}`).then(() => {
commit("REMOVE_CUSTOMER", payload);
});
}
};
const getters = {
GET_CUSTOMERS: state => state.customers
};
export default {
state,
mutations,
actions,
getters
};