I'm currently learning VueJS for work and I'm trying to build a CRUD app that displays items from an API to a <v-data-table>
, and I want to edit, delete, and create new items using <v-dialog>
.
This is my main screen:
<template>
<v-app>
<v-main>
<div>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
single-line
hide-details
label="search">
</v-text-field>
<v-data-table
:items="movies"
:headers="headers"
:search="search"
>
<template v-slot:[`item.actions`]>
<edit-movie></edit-movie>
<delete-movie></delete-movie>
<details-movie></details-movie>
</template>
</v-data-table>
</div>
</v-main>
</v-app>
</template>
<script>
import {mapState} from 'vuex';
import DeleteMovie from './components/deleteMovie.vue';
import DetailsMovie from './components/detailsMovie.vue';
import EditMovie from './components/editMovie.vue';
export default {
name: 'App',
components: {
EditMovie,
DeleteMovie,
DetailsMovie
},
mounted(){
this.$store.dispatch('getMovies');
},
data: () => ({
search: ''
}),
computed: {
headers() {
return [
{text: "Title", value: "title"},
{text: "Overview", value: "overview"},
{text: "Votes", value:"vote_average"},
{text: 'Actions', value: 'actions', sortable: false },
{text: '', value: 'details'},
]
},
...mapState({
movies:state => state.movies
})
},
}
</script>
and I call the API like this:
export default new Vuex.Store({
state: {
movies: [],
},
mutations: {
async getMovies(state){
let response = await axios.get(`https://api.themoviedb.org/3/movie/now_playing?api_key=${public_key}&language=en-US`)
.then((result) => {
result.data.results.forEach(item => {
console.log(item)
state.movies.push(item)
});
})
.catch((error) => {
console.log(error)
})
}
},
actions: {
getMovies: context => {
context.commit('getMovies')
},
},
})
Now, my main concern is how to call a single item and display all the details in inside this dialog: (It has to be in a different component)
<template>
<v-dialog>
<template v-slot:activator="{ on, attrs }">
<v-btn
small
class="mr-2"
v-on="on"
v-bind="attrs"
>
Read More
</v-btn>
</template>
<v-card>
<v-card-text>
{{THIS IS WHERE IT SHOULD BE DISPLAYED}}
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
export default {
data: () => ({
}),
}
</script>
I also don't know how to edit/delete the items from a dialog in a different component.
Anyway, thank you in advance for any help
Here is a way that doesn't use an activator, with a demo. I'll show how to use just an Edit modal, for example, and you can create others by duplicating these steps.
1. Place the modal component outside of the table. Use a button in the slot that sets a v-model
value for the modal component when clicked. It will pass the id of the row's item:
Parent (Data Table)
<div>
<v-data-table :headers="headers" :items="movies">
<template v-slot:[`item.actions`]="{ item }">
<v-btn @click.stop="idEdit = item.id">Edit</v-btn>
</template>
</v-data-table>
<edit-movie v-model="idEdit"></edit-movie>
</div>
data: {
return {
idEdit: null,
}
}
2. The modal component uses a computed setter to show the modal if an id value has been passed and emits an event to clear the id when closed:
Child (Modal)
<template>
<v-dialog v-model="show">
<v-card>ID: {{ value }}</v-card>
</v-dialog>
</template>
export default {
props: ['value'], // References the `v-model` prop
computed: {
// Computed getter / setter
show: {
get() { return this.value !== null },
set(value) { this.$emit('input', null) } // Clear the `v-model` to close
}
}
}
3. The modal component can use a watch
to load data from the api when the id changes:
watch: {
value(newValue) {
if (!newValue) return;
console.log(`Load data here with ID: ${newValue}`);
}
}
Here's a demo: https://codepen.io/sh0ber/pen/poNLVvz
The benefit of no activator is creating only one component per action type instead of one per row, per type. It could be further improved to just one total modal component.
Also, use actions for async calls, not mutations.