Membuat Aplikasi Ekspedisi NuxtJS #3: CRUD Data Users Bagian 1

Membuat Aplikasi Ekspedisi NuxtJS #3: CRUD Data Users Bagian 1

Pendahuluan

Mengelola data users akan menjadi topik perbincangan kita kali ini dalam seri belajar membuat aplikasi ekspedisi dengna episode NuxtJS. Pada seri ini kita akan belajar beberapa hal mengenai CRUD dengan berinteraksi melalui API yang sudah disediakan pada episode Lumen. Materi ini sedikit banyaknya kita akan belajar bagaimana menampilkan data, menambahkan data, mengubah dan menghapus data. Tidak lupa pula, kita akan belajar bagaimana membuat validasi di Nuxtjs, membuat pagination dan fitur pencarian data users.

Sebelum membaca artikel ini, pastikan kamu sudah menyelesaikan episode Lumen dari seri pertama hingga ke empat, karena seluruh API yang akan digunakan sudah dibahas pada materi tersebut. Berikut adalah seri CRUD Data Users menggunakan NuxtJS.

Baca Juga: Membuat Aplikasi Ekspedisi #2: Authentication From Scratch

Konfigurasi & Install Bootstrap Vue di NuxtJS

Component atau tag yang akan meng-handle segala hal yang berkaitan dengan bootstrap akan menggunakan Bootstrap Vue, dimana library ini sudah sangat lengkap dan dokumentasinya akan menunjang selama proses development.

Dari command line, jalankan command berikut untuk proses instalasi.

npm install bootstrap-vue

Buka file nuxt.config.js dan modifikasi attribute modules menjadi

modules: [
  '@nuxtjs/axios',
  '@nuxtjs/auth',
  'bootstrap-vue/nuxt'
],

Sampai pada tahap ini konfigurasi dari Bootstrap Vue sudah selesai, tapi dalam section ini kita akan sekaligus melakukan konfigurasi untuk axios. Buat file plugins/axios.js dan tambahkan code

export default function ({ $axios, store }) {
    $axios.setHeader('Content-Type', 'application/json');
    $axios.setToken(store.state.api_token, 'Bearer')
}

Kemudian buka lagi file nuxt.config.js dan modifikasi attribute plugins menjadi

plugins: [
  '@/plugins/axios.js'
],

Bagian terakhir adalah membuat state untuk meng-handle api_token dari user yang sedang login. Buka file store/index.js dan modifikasi menjadi

export const state = () => ({
    isAuth: false,
    api_token: null //STATE UNTUK DATA API TOKEN
})

export const mutations = {
    SET_IS_AUTH(state, payload) {
        state.isAuth = payload
    },
    SET_API_TOKEN(state, payload) {
        state.api_token = payload
    }
}

export const actions = {
    nuxtServerInit({ commit }, context) {
        commit('SET_IS_AUTH', context.app.$auth.$state.loggedIn)
        commit('SET_API_TOKEN', context.app.$auth.$state.user.api_token) //SET API TOKEN KE STATE DIMANA DATA TOKEN DIAMBIL DARI STATE USER YANG SEDANG LOGIN
    }
}

Get Users Data With Axios

Fitur yang akan kita kerjakan kali ini adalah sebuah fitur untuk menampilkan data dari database melalui hit ke API menggunakan axios. Skenarionya sederhana, ketika page diakses maka secara otomatis akan melakukan request dan ketika data didapatkan, maka data tersebut akan ditampilkan pada sebuah table.

Langkah pertama, buat folder users di dalam folder pages dan tambahkan sebuah file bernama index.vue di dalam folder users yang baru saja dibuat.

<!-- /pages/users/index.vue -->

<template>
    <div class="container-fluid">
        <div class="card">
            <div class="card-header">
                <h4 class="card-title">
                  	<!-- TOMBOL YANG KETIKA DIKLIK AKAN DIARAHKAN KE HALAMAN ADD USER -->
                    <nuxt-link class="btn btn-primary float-right btn-sm" :to="{name: 'users-add'}">Add New</nuxt-link>
                </h4>
            </div>
            <div class="card-body">
              	<!-- TAG BOOTSTRAP VUE UNTUK TABLE, DIMANA ITEMS ADALAH DATA YANG AKAN DITAMPILKAN, DAN FIELDS AKAN MENJADI HEADER DARI TABLE -->
                <b-table striped hover :items="users.data" :fields="fields" show-empty>
                    <!-- MEMBUAT CUSTOM DATA YANG AKAN DI-RENDER DI DALAM KOLOM NAME -->
                    <template v-slot:cell(name)="row">
                        <p><strong>{{ row.item.name }}</strong></p>
                        <p>
                            ID: <span class="badge badge-success">{{ row.item.identity_id }}</span> <br>
                            Gender: <span class="badge badge-success">{{ row.item.gender ? 'Male':'Female' }}</span>
                        </p>
                    </template>
                </b-table>
            </div>
        </div>
    </div>
</template>

<script>
import { mapActions, mapState } from 'vuex'
export default {
    //KETIKA PAGE DILOAD, MAKA FUNGSI INI AKAN DIJALANKAN SECARA OTOMATIS
    //DIMANA KITA MEMANGGIL FUNGSI GETUSERSDATA DARI STORE USER
    async asyncData({store}) {
        await Promise.all([
            store.dispatch('user/getUsersData')
        ])
        return
    },
    data() {
        return {
            //FIELD UNTUK MENJADI HEADER TABLE
            fields: ['name', 'address', 'email', 'phone_number'], 
            items: []
        }
    },
    computed: {
        //SETIAP REQUEST KE API, DATANYA AKAN DISIMPAN KE DALAM STATE YANG ADA DIMASING-MASING MODULE STORE VUEX
        //DALAM HAL INI, DATA USER AKAN DISIMPAN KE DALAM STATE USERS, DI DALAM MODULE USER ATAU USER.JS YANG ADA DI DALAM FOLDER STORE
        ...mapState('user', {
            users: state => state.users
        })
    },
    methods: {
        ...mapActions('user', ['getUsersData'])
    }
}
</script>

Kemudian buat file store/user.js untuk meng-handle state dan fungsi hit ke API, tambahkan code berikut di dalam file tersebut

export const state = () => ({
    users: [], //DATA USER AKAN DISIMPAN KE DALAM STATE INI
    errors: []
})

export const mutations = {
    //MUTATION INI DIGUNAKAN UNTUK MENGUBAH VALUE DARI STATE USERS
    SET_USER_DATA(state, payload) {
        state.users = payload
    },
    SET_ERRORS(state, payload) {
        state.errors = payload
    }
}

export const actions = {
    //FUNGSI UNTUK MENGAMBIL DATA USER
    getUsersData({ commit }) {
        return new Promise((resolve, reject) => {
            //DIMANA KITA MELAKUKAN REQUEST DENGAN METHOD GET KE URL /USERS
            this.$axios.get('/users').then((response) => {
                //DAN MENYIMPAN DATANYA KE STATE USERS MELALUI MUTATIONS
                commit('SET_USER_DATA', response.data.data)
                resolve()
            })
        })
    },
}

Kabar baiknya, jika kita menggunakan NuxtJS, maka module dari store Vuex tidak harus diregister karena sudah secara otomatis dikenali oleh Vuex. Maka tugas selanjutnya yang akan kita kerjakan adalah membuat menu navigasi agar bisa diakses dari sidebar, buka file Sidebar.vue dan tambahkan code berikut setelah menu dashboard

<div class="sidebar-heading">
    Master Data
</div>
<li class="nav-item">
    <nuxt-link class="nav-link" :to="{name: 'users'}">
        <i class="fas fa-fw fa-users"></i>
        <span>Manage Users</span>
    </nuxt-link>
</li>

Add New Data & Validation

Tugas selanjutnya adalah membuat halaman yang berisi form input-an agar user bisa memasukkan data baru yang akan ditambahkan. Buat file pages/users/add/index.vue, kemudian tambahkan code berikut

<template>
    <div class="container-fluid">
        <div class="card mb-3">
            <div class="card-header">
                <h4 class="card-title"></h4>
            </div>
            <div class="card-body">
                <app-user-form />
            </div>
        </div>
    </div>
</template>
<script>
import UserForm from '@/components/form/User.vue' 
export default {
    components: {
        'app-user-form': UserForm //CUSTOM COMPONENT UNTUK FORM INPUT
    }
}
</script>

Karena form input-nya kita pisahkan ke dalam file atau component tersendiri dengan tujuan agar lebih maintainable, maka buat file components/form/User.vue dan tambahkan code

<template>
    <div>
        <div class="form-group">
            <label for="">Identity ID</label>
            <input type="text" class="form-control" :class="{'is-invalid': errors.identity_id}" v-model="users.identity_id">
          	
          	<!-- UNTUK MENAMPILKAN VALIDASI ERROR -->
            <p class="text-danger" v-if="errors.identity_id">{{ errors.identity_id[0] }}</p>
        </div>
        <div class="form-group">
            <label for="">Name</label>
            <input type="text" class="form-control" :class="{'is-invalid': errors.name}" v-model="users.name">
            <p class="text-danger" v-if="errors.name">{{ errors.name[0] }}</p>
        </div>
        <div class="form-group">
            <label for="">Gender</label>
            <select class="form-control" :class="{'is-invalid': errors.gender}" v-model="users.gender">
                <option value="">Pilih</option>
                <option value="1">Male</option>
                <option value="0">Female</option>
            </select>
            <p class="text-danger" v-if="errors.gender">{{ errors.gender[0] }}</p>
        </div>
        <div class="form-group">
            <label for="">Address</label>
            <input type="text" class="form-control" :class="{'is-invalid': errors.address}" v-model="users.address">
            <p class="text-danger" v-if="errors.address">{{ errors.address[0] }}</p>
        </div>
        <div class="form-group">
            <label for="">Email</label>
            <input type="text" class="form-control" :class="{'is-invalid': errors.email}" v-model="users.email">
            <p class="text-danger" v-if="errors.email">{{ errors.email[0] }}</p>
        </div>
        <div class="form-group">
            <label for="">Password</label>
            <input type="password" class="form-control" :class="{'is-invalid': errors.password}" v-model="users.password">
            <p class="text-danger" v-if="errors.password">{{ errors.password[0] }}</p>
        </div>
        <div class="form-group">
            <label for="">Phone Number</label>
            <input type="text" class="form-control" :class="{'is-invalid': errors.phone_number}" v-model="users.phone_number">
            <p class="text-danger" v-if="errors.phone_number">{{ errors.phone_number[0] }}</p>
        </div>
        <div class="form-group">
            <label for="">Role</label>
            <select class="form-control" :class="{'is-invalid': errors.role}" v-model="users.role">
                <option value="">Pilih</option>
                <option value="0">Admin</option>
                <option value="1">Driver</option>
                <option value="2">Users</option>
            </select>
            <p class="text-danger" v-if="errors.role">{{ errors.role[0] }}</p>
        </div>
        <div class="form-group">
            <label for="">Status</label>
            <select class="form-control" :class="{'is-invalid': errors.status}" v-model="users.status">
                <option value="">Pilih</option>
                <option value="0">Tidak Aktif</option>
                <option value="1">Aktif</option>
            </select>
            <p class="text-danger" v-if="errors.status">{{ errors.status[0] }}</p>
        </div>
        <button class="btn btn-primary btn-sm" @click="submit">Save</button>
    </div>
</template>
<script>
import {mapActions, mapState} from 'vuex' 
export default {
    data() {
        return {
            //VARIABLE UNTUK MENAMPUNG DATA INPUTAN DARI FORM
            users: {
                name: '',
                identity_id: '',
                gender: '',
                address: '',
                email: '',
                password: '',
                phone_number: '',
                role: '',
                status: ''
            }
        }
    },
    computed: {
        //PANGGIL STATE ERRORS DARI MODULE USERS
        //DIMANA STATE INI AKAN BERISI INFORMASI ERROR VALIDASI
        ...mapState('user', {
            errors: state => state.errors
        })
    },
    methods: {
        ...mapActions('user', ['storeUsersData']), //LOAD FUNGSI DARI MODULE USER
        //KETIKA TOMBOL SAVE DITEKAN, MAKA FUNGSI INI AKAN DIJALANKAN
        submit() {
            //DIMANA KITA MEMANGGIL FUNGSI UNTUK MENYIMPAN DATA YANG AKAN DIDEFINISIKAN DI ACTIONS DARI MODULE USER
            //DAN KETIKA BERHASIL, MAKA AKAN DIREDIRECT KE HALAMAN LIST USER
            this.storeUsersData(this.users).then(() => this.$router.push({name: 'users'}))
        }
    }
}
</script>

Bagian terakhir adalah mendefinisikan fungsi storeUsersData ke dalam module user, buka file store/user.js dan tambahkan code berikut didalam block actions.

storeUsersData({ dispatch, commit }, payload) {
    return new Promise((resolve, reject) => {
        //MENGIRIM REQUEST KE SERVER DENGAN METHOD POST DAN DATA DARI PAYLOAD
        this.$axios.post('/users', payload)
        .then(() => {
            //JIKA BERHASIL, MAKA LOAD DATA TERBARU
            dispatch('getUsersData')
            resolve()
        })
        .catch((error) => {
          //JIKA TERJADI ERROR VALIDASI, SET STATE UNTUK MENAMPUNG DATA ERROR VALIDASINYA
            commit('SET_ERRORS', error.response.data)
        })
    })
},

[ISSUE] Set Token User Login

Terdapat sedikit kendala ketika users melakukan logout, dimana state data user yang sedang login adalah null atau tidak ditemukan. Hal ini akan menampilkan error page terkait ketika user me-reload halaman. Untuk mengatasinya, buka file store/index.js dan modifikasi nuxtServerInit menjadi

nuxtServerInit({ commit }, context) {
    commit('SET_IS_AUTH', context.app.$auth.$state.loggedIn)
    //LAKUKAN PENGECEKAN, DIMANA JIKA USER SUDAH LOGIN
    if (context.app.$auth.$state.loggedIn) {
        //MAKA SET API TOKENNYA
        commit('SET_API_TOKEN', context.app.$auth.$state.user.api_token)
    }
}

Baca Juga: Membuat Aplikasi Ekspedisi #1: Install & Templating

Kesimpulan

Melalui artikel serial membuat aplikasi ekspedisi NuxtJS kali ini, kita belajar beberapa hal, diantaranya HTTP Request menggunakan axios, mengambil token user yang sedang login, menggunakan Bootstrap Vue component dan menampilkan validasi. Adapun pembahasan mengenai update dan delete data akan dibahas pada seri selanjutnya dengan maksud dan tujuan agar menghilangkan kejenuhan dalam belajar.

Dokumentasi code dari artikel ini bisa dilihat di Github.

 

Category:
Share:

Comments