Catatan Belajar Vue : Vuex
Target tulisan kali ini adalah bisa membuat satu buah fitur crud
menggunakan vuex. Lalu apa itu vuex ? Kalau dibaca dari situs ofisialnya sih vuex itu state management pattern + library, tapi setelah itu ada pertanyaan lagi state management itu apa ? Kalau yang saya pahami sih state management atau vuex ini berfungsi sebagai “sumber kebenaran” ( source of truth ) sehingga data yang sama dapat dimanfaatkan lintas komponen. Mohon koreksinya jika pemahaman saya salah.
Persiapan
Di sini saya akan buat aplikasi crud
dari vue, maka dari itu akan dibutuhkan beberapa halaman, diantaranya :
List.vue
sebagai halaman daftar data yang tersedia
<template> <v-layout row wrap> <v-btn light to="peserta/tambah">Tambah Peserta </v-btn> <v-data-table v-bind:headers="headers" :items="items" hide-actions class=""> <template slot="items" scope="props"> <td class="text-xs-right">{{ props.item.tim }}</td> <td class="text-xs-right">{{ props.item.qualified | masklabel}}</td> <td class="text-xs-right"> <v-btn color="primary" :to="{ name: 'edit', params: { id: props.item.id }}">Ubah</v-btn> <v-dialog v-model="dialog" max-width="600px"> <v-btn color="error" dark slot="activator" >Hapus</v-btn> <v-card> <v-card-title> <span class="headline">Anda yakin untuk menghapus data ini?</span> </v-card-title> <v-card-text><h3>Hapus</h3></v-card-text> <v-card-actions> <v-spacer></v-spacer> <v-btn color="green darken-1" flat="flat" @click="dialog = false">Tidak</v-btn> <v-btn color="green darken-1" flat="flat" >Ya</v-btn> </v-card-actions> </v-card> </v-dialog> </td> </template> </v-data-table> <v-flex xs12 > <!-- <v-pagination :length="length" v-model="page" ></v-pagination> --> </v-flex> </v-layout> </template> <script> export default { data () { return { dialog: false, selected: [], headers: [ { text: 'Tim', value: 'tim' }, { text: 'Status', value: 'status' }, { text: 'Action', value: 'action' } ], items: [ { id : 1, tim: 'Ac Milan', qualified: true }, { id: 2, tim: 'Parma', qualified: false } ] } }, filters: { masklabel(value) { return (value) ? 'Lolos' : 'Tidak lolos' } } } </script>
Add.vue
sebagai komponen menambah data
<template> <v-form v-model="valid" ref="form" lazy-validation> <v-text-field label="Team" v-model="team" :rules="teamRules" required ></v-text-field> <v-select label="Status" v-model="select" :items="items" :rules="[v => !!v || 'Item is required']" required ></v-select> <v-btn @click="submit" :disabled="!valid" > submit </v-btn> <v-btn @click="clear">clear</v-btn> </v-form> </template> <script> export default { data: () => ({ valid: true, team: '', teamRules: [ (v) => !!v || 'Team is required', ], select: null, items: [ 'Lolos', 'Tidak Lolos' ] }), methods: { submit () { if (this.$refs.form.validate()) { } }, clear () { this.$refs.form.reset() } } } </script>
Edit.vue
sebagai komponen ubah data.
<template> <v-form v-model="valid" ref="form" lazy-validation> <v-text-field label="Team" v-model="team" :rules="teamRules" required ></v-text-field> <v-select label="Status" v-model="select" :items="items" :rules="[v => !!v || 'Item is required']" required ></v-select> <v-btn @click="submit" :disabled="!valid" > submit </v-btn> <v-btn @click="back">Back</v-btn> </v-form> </template> <script> export default { data: () => ({ valid: true, team: '', teamRules: [ (v) => !!v || 'Team is required', ], select: '', items: [ 'Lolos', 'Tidak Lolos' ], checkbox: false }), created() { }, methods: { fetch(value) { }, submit () { if (this.$refs.form.validate()) { } }, back () { } } } </script>
Komponen yang dibutuhkan sudah disiapkan, sekarang saatnya mengatur router, buka index.js
dalam folder router
, lalu ubah menjadi seperti berikut :
import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/Home' import List from '@/components/List' import Add from '@/components/Add' import Edit from '@/components/Edit' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Root', component: Home }, { path: '/list', name: 'List', component: List }, { path: '/peserta/tambah', name: 'add', component: Add }, { path: '/peserta/:id/edit', name: 'edit', component: Edit } ], mode: 'history' })
Seharusnya saat aplikasi diakses akan menghasilkan seperti berikut
Vuex
Gunakan perintah berikut untuk mengunduh paket vuex
npm install --save vuex
Sejajar dengan berkas main.js buat satu berkas baru namanya store.js ( lokasi nama bebas sih sesuaikan dengan keinginan), dalam struktur dasar store.js sendiri sebagai berikut
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export const store = new Vuex.Store({ state: { }, getters: { }, mutations: { } })
State : Ini adalah “sumber kebenaran” itu, konten state di sini bisa disalin dari data di komponen, maka hasilnya akan seperti konten di bawah ini.
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export const store = new Vuex.Store({ state: { participants: [{ id: 1, tim: 'Ac Milan', qualified: true }, { id: 2, tim: 'Parma', qualified: false } ] }, getters: { }, mutations: { } })
Di state saya tambahkan properti ‘participants’ yang isinya itu hasil salin dari properti `items‘ di komponen List.vue. Dan vuex menganut single state tree yang artinya vuex hanya boleh menyediakan punya satu sumber saja.Kalau mau punya banyak sumber bisa menggunakan vuex module, tapi itu dibahas nanti.
Getters : Sesuai namanya Get (mengambil) Getters berfungsi untuk mengambil data dari state, dengan getters kita bisa mengatur data yang akan dimunculkan, mari tambahkan dulu getters pada store.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export const store = new Vuex.Store({ state: { participants: [{ id: 1, tim: 'Ac Milan', qualified: true }, { id: 2, tim: 'Parma', qualified: false } ] }, getters: { all(state) { return state.participants }, isQualified(state) { return state.participants.filter(participant => { return participant.qualified }) }, notQualified(state) { return state.participants.filter(participant => { return !participant.qualified }) }, detail: (state, getters) => (id) => { return state.participants.find(participant => participant.id == id) } }, mutations: { } })
Di sana ada 4 buah method di dalam getters,
all : Mengambil semua data participant ( sebenarnya ini gak perlu sih, hanya sebagai contoh maka saya tambahkan )
isQualified : Mengambil semua data participants yang qualified nya true
notQualified : Mengambil semua data participants yang qualified nya false
Detail : Yang ini agak berbeda karena ini akan mampu menerima parameter yang dikirim dari komponen.
Terakhir, Mutations : Seperti disinggung di atas saya akan mencoba membuat aplikasi crud
yang berarti di sini saya akan memanipulasi ( menambah, mengubah, dan menghapus ) data dari state. Menurut peraturan yang ada kita tidak boleh mengubah langsung kepada state, kita harus melalui mutations
terlebih dahulu. Berikut mutations
yang saya buat untuk mengakomodir kebutuhan crud
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export const store = new Vuex.Store({ state: { participants: [{ id: 1, tim: 'Ac Milan', qualified: true }, { id: 2, tim: 'Parma', qualified: false } ] }, getters: { all(state) { return state.participants }, isQualified(state) { return state.participants.filter(participant => { return participant.qualified }) }, notQualified(state) { return state.participants.filter(participant => { return !participant.qualified }) }, detail: (state, getters) => (id) => { return state.participants.find(participant => participant.id == id) } }, mutations: { register(state, data) { state.participants.push(data) }, update(state, data) { var participant = state.participants.find(o => o.id == data.id) var index = state.participants.indexOf(participant) state.participants[index].tim = data.tim state.participants[index].qualified = data.qualified }, delete(state, value) { var participant = state.participants.find(o => o.id == value) state.participants.splice(state.participants.indexOf(participant), 1) } } })
Di sana ada 3 mutation
strukturnya mirip dengan getters
namun mutation
bisa dikirimi parameter dengan mudah ( tidak seperti getter
yang struktur nya berubah ketika ingin membaca parameter), Baik getters
dan mutations
wajib menyertakan parameter state
agar bisa mengakses state
yang tersedia.
Adapun fungsi di atas :
register
: Bertanggung jawab menambahkan data ke state
update
: Bertanggung jawab mengubah data ke state
delete
: Bertanggung jawab menghapus data dari state
Setelah store
disiapkan maka panggil store.js
di main.js
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import Vuetify from 'vuetify' import './stylus/main.styl' import App from './App' import router from './router' import {store} from './store' Vue.use(Vuetify) Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', template: '<App/>', router, store, components: { App } })
Persiapan vuex selesai.
CRUD
Setelah siap semuanya sekarang tinggal memanfaatkan saja, ubah List.vue
menjadi seperti berikut
List.vue
<template> <v-layout row wrap> <v-btn light to="peserta/tambah">Tambah Peserta </v-btn> <v-data-table v-bind:headers="headers" :items="items" hide-actions class=""> <template slot="items" scope="props"> <td class="text-xs-right">{{ props.item.tim }}</td> <td class="text-xs-right">{{ props.item.qualified | masklabel}}</td> <td class="text-xs-right"> <v-btn color="primary" :to="{ name: 'edit', params: { id: props.item.id }}">Ubah</v-btn> <v-dialog v-model="dialog" max-width="600px"> <v-btn color="error" dark slot="activator" @click="modalConfirm(props.item.id)" >Hapus</v-btn> <v-card> <v-card-title> <span class="headline">Anda yakin untuk menghapus data ini?</span> </v-card-title> <v-card-text><h3>{{ selectname }}</h3></v-card-text> <v-card-actions> <v-spacer></v-spacer> <v-btn color="green darken-1" flat="flat" @click="dialog = false">Tidak</v-btn> <v-btn color="green darken-1" flat="flat" @click="confirmDelete(selectedId)">Ya</v-btn> </v-card-actions> </v-card> </v-dialog> </td> </template> </v-data-table> <v-flex xs12 > <!-- <v-pagination :length="length" v-model="page" ></v-pagination> --> </v-flex> </v-layout> </template> <script> export default { data () { return { search : '', dialog: false, selectname: '', selectedId: '', selected: [], headers: [ { text: 'Tim', value: 'tim' }, { text: 'Status', value: 'status' }, { text: 'Action', value: 'action' } ], items: [ ] } }, created() { this.fetch() }, methods: { fetch() { this.items = this.$store.getters.all //bisa juga langsung ke state // this.$store.state.participants // kalau untuk getter strkturnya : this.$store.getters.namagetter }, modalConfirm(value) { var participant = this.items.find(o => o.id === value); this.selectname = participant.tim this.selectedId = participant.id }, confirmDelete(value) { this.$store.commit('delete',value) this.dialog = false } }, filters: { masklabel(value) { return (value) ? 'Lolos' : 'Tidak lolos' } } } </script>
Add.vue
<template> <v-form v-model="valid" ref="form" lazy-validation> <v-text-field label="Team" v-model="team" :rules="teamRules" required ></v-text-field> <v-select label="Status" v-model="select" :items="items" :rules="[v => !!v || 'Item is required']" required ></v-select> <v-btn @click="submit" :disabled="!valid" > submit </v-btn> <v-btn @click="clear">clear</v-btn> </v-form> </template> <script> export default { data: () => ({ valid: true, team: '', teamRules: [ (v) => !!v || 'Team is required', ], select: null, items: [ 'Lolos', 'Tidak Lolos' ] }), methods: { submit () { if (this.$refs.form.validate()) { // Native form submission is not yet supported var data = { id: Math.round((new Date()).getTime() / 1000), tim: this.team, qualified: (this.select == 'Lolos') ? true : false } this.$store.commit('register',data) this.$router.push('/list') } }, clear () { this.$refs.form.reset() } } } </script>
Edit.vue
<template> <v-form v-model="valid" ref="form" lazy-validation> <v-text-field label="Team" v-model="team" :rules="teamRules" required ></v-text-field> <v-select label="Status" v-model="select" :items="items" :rules="[v => !!v || 'Item is required']" required ></v-select> <v-btn @click="submit" :disabled="!valid" > submit </v-btn> <v-btn @click="back">Back</v-btn> </v-form> </template> <script> export default { data: () => ({ valid: true, team: '', teamRules: [ (v) => !!v || 'Team is required', ], select: 'Lolos', items: [ 'Lolos', 'Tidak Lolos' ], checkbox: false }), created() { this.fetch(this.$route.params.id); }, methods: { fetch(value) { console.log(value) var participant = this.$store.getters.detail(value) this.team = participant.tim this.select = (participant.qualified) ? "Lolos" : "Tidak Lolos" }, submit () { if (this.$refs.form.validate()) { // Native form submission is not yet supported var data = { id: this.$route.params.id, tim: this.team, qualified: (this.select == 'Lolos') ? true : false } this.$store.commit('update',data) this.$router.push('/list') } }, back () { this.$router.push('/list') } } } </script>
Antara mengakses getter dan mutation terlihat perbedaanya, memanggil getter bisa langsung diakses seperti berikut this.$store.getters.namagetter
sedangkan untuk mutations
dengan cara this.$store.commit('namamutation',parameter)
.
Semuanya sudah maka jika apliaksi dijalankan akan seperti berikut
Jika ingin lihat seluruh source nya :
https://gitlab.com/ariesmaulana/belajarvue
Lalu pilih branch vuex.
Referensi :
https://vuex.vuejs.org/en/intro.html
https://www.youtube.com/watch?v=2CSr2vBApSI&list=PL55RiY5tL51pT0DNJraU93FhMzhXxtDAo