9 min read

Catatan Belajar Vue : Vuex

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