Pendahuluan
Tak bisa dipungkiri, Datatable telah menarik banyak perhatian sekaligus pengguna karena ragam fitur yang ditawarkannya serta kemudahan dalam mengintegrasikan dengan aplikasi yang sedang kita develop. Library ini sudah ada & banyak digunakan jauh sebelum Vue.js berada pada puncak kejayaannya, dimana ketika era jQuery masih menjadi penguasa.
Dewasa ini telah ramai framework Javascript yang bermunculan, salah satunya adalah Vue.js. Para pengguna Datatable juga bersikeras untuk memboyong library ini untuk diterapkan pada aplikasi mereka menggunakan Vue.js. Lalu, pernahkah kita berpikir untuk membuat Datatable versi sendiri menggunakan Vue.js dan Laravel 6 sebagai backend untuk menyuplai datanya? Berikut adalah panduan cara membuat Datatable dengan Vue.js.
Baca Juga: Membuat Aplikasi Ekspedisi NuxtJS #3: CRUD Data Users Bagian 2
Instalasi & Konfigurasi
Ada beberapa bagian yang akan kita gunakan dalam menunjang proses pembuatan Datatable sendiri menggunakan Vue.js dan Laravel 6. Sedikit penjelasan mengenai perannya masing-masing, dimana Laravel akan berperan menjadi backend dalam menyediakan struktur data API yang akan ditampilkan, adapun case kali ini adalah data postingan.
Sedangkan dari sisi frontend, kita akan menggunakan beberapa library bantuan seperti Bootstrap Vue, Lodash, dan Axios. Ketiga library ini akan kita padu padankan dalam menciptakan component Datatable sendiri yang bisa digunakan berulang kali pada semua module aplikasi kita.
Langkah pertama, kita akan memulainya dari awal, sehingga install Laravel 6 terlebih dahulu dengan command
composer create-project --prefer-dist laravel/laravel dw-datatable
Buat database bernama dw-vue-datatable
, kemudian buka file .env
dan sesuaikan informasi database-nya
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=dw-vue-datatables
DB_USERNAME=root
DB_PASSWORD=
Dari sisi Laravel sudah selesai, maka saatnya kita melakukan instalasi untuk persiapan frontend-nya. Pada command line, jalankan command
npm install
Lalu install beberapa bagian berikut secara bergantian.
npm install vue
npm install axios
npm install bootstrap-vue
npm i --save lodash
Kemudian buat file app.js
di dalam folder resources/js/
dan tambahkan code
import Vue from 'vue'
import App from './App.vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
Vue.use(BootstrapVue)
Vue.use(IconsPlugin)
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
new Vue({
el: '#daengweb',
components: {
'dw-app': App
}
})
Bagian yang terakhir adalah modifikasi file welcome.blade.php
menjadi
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Daengweb.id Datatables</title>
</head>
<body>
<div id="daengweb">
<dw-app></dw-app>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>
Pertanyaannya adalah bagaimana dengan file App.vue
? Bukankah sudah di-import tapi file-nya belum ada? Pertanyaan ini akan kita jawab pada sub-section selanjutnya.
Database & Dummy Data With Seeder
Struktur data yang akan akan kita buat sangat sederhana kali ini, karena tujuan materi ini adalah belajar bagaimana cara membuat datatable sendiri dengan Vue.js. Dari command line, jalankan command
php artisan make:model Post -m
Buka file migration yang baru saja di-generate dan modifikasi menjadi
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title');
$table->string('author');
$table->string('category');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('posts');
}
}
Eksekusi migration diatas dengan command php artisan migrate
. Lalu buka file app/Post.php
untuk mengizinkan mass assignment.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $guarded = [];
}
Saatnya untuk membuat data dummy menggunakan seeder dan model factory, generate sebuah seeder dengan command
php artisan make:seeder PostTableSeeder
Buka file database/seeds/PostTableSeeder.php
dan modifikasi menjadi.
<?php
use Illuminate\Database\Seeder;
class PostTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//AKAN MEMBUAT DATA DUMMY SEBANYAK 50 DATA.
factory(App\Post::class, 50)->create();
}
}
Adapun model factory-nya akan menggunakan faker untuk membuat data dummy, dari command line, jalankan
php artisan make:factory PostFactory
Buka file database/factories/PostFactory.php
dan modifikasi menjadi
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Post;
use Faker\Generator as Faker;
$factory->define(Post::class, function (Faker $faker) {
return [
'title' => $faker->paragraph($nbSentences = 3, $variableNbSentences = true),
'author' => $faker->name,
'category' => $faker->company
];
});
Register seeder kita agar bisa dijalankan secara umum, buka file database/seeds/DatabaseSeeder.php
dan tambahkan line berikut pada method run()
$this->call(PostTableSeeder::class);
Langkah terakhir adalah menjalankan seeder di atas untuk men-generate data dummy. Dari command line, jalankan command php artisan db:seed
.
Membuat Struktur Json Data
Data yang akan ditampilkan sudah tersedia, maka saatnya kita membuat response dalam bentuk json untuk dikonsumsi oleh datatable. Generate controller baru dengan command
php artisan make:controller PostController
Buka file PostController.php
dan modifikasi menjadi
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Post;
class PostController extends Controller
{
public function index()
{
//ORDER BY NYA BERDASARKAN PARAMETER YANG DIKIRIM DARI VUEJS
//SEHINGGA PENGURUTAN DATANYA SESUAI DENGAN KOLOM YG DIINGINKAN OLEH USER
$posts = Post::orderBy(request()->sortby, request()->sortbydesc)
//JIKA Q ATAU PARAMETER PENCARIAN INI TIDAK KOSONG
->when(request()->q, function($posts) {
//MAKA FUNGSI FILTER AKAN DIJALANKAN
$posts = $posts->where('title', 'LIKE', '%' . request()->q . '%')
->orWhere('author', 'LIKE', '%' . request()->q . '%')
->orWhere('category', 'LIKE', '%' . request()->q . '%');
})->paginate(request()->per_page); //KEMUDIAN LOAD PAGINATIONNYA BERDASARKAN LOAD PER_PAGE YANG DIINGINKAN OLEH USER
return response()->json(['status' => 'success', 'data' => $posts]);
}
}
Bagian terakhir adalah membuat endpoint atau menghubungkannya dengan routing. Buka file routes/api.php
dan tambahkan code.
Route::get('/posts', 'PostController@index');
Membuat Datatable Dengan Vue.js
Beberapa fitur yang dimiliki oleh datatable, diantaranya adalah pencarian data, pagination, load per page, sorting dan table-nya itu sendiri. Untuk membuat seluruh fitur ini, kita akan menggunakan component dari Bootstrap Vue, lebih tepatnya untuk component table dan pagination.
Adapun tampilan yang akan kita buat seperti screenshot yang sudah dilampir pada sub-section pendahuluan dan untuk mewujudkannnya kita akan memisahkannya ke dalam component tersendiri agar menjadi reusable component sehingga bisa digunakan berkali-kali.
Buat file Datatable.vue
di dalam folder resources/views/components
dan tambahkan code.
<template>
<div class="row">
<!-- BLOCK INI AKAN MENGHANDLE LOAD DATA PERPAGE, DENGAN DEFAULT ADALAH 10 DATA -->
<div class="col-md-4 mb-2">
<div class="form-inline">
<label class="mr-2">Showing</label>
<!-- KETIKA SELECT BOXNYA DIGANTI, MAKA AKAN MENJALANKAN FUNGSI loadPerPage -->
<select class="form-control" v-model="meta.per_page" @change="loadPerPage">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<label class="ml-2">Entries</label>
</div>
</div>
<!-- BLOCK INI AKAN MENG-HANDLE PENCARIAN DATA -->
<div class="col-md-4 offset-md-4">
<div class="form-inline float-right">
<label class="mr-2">Search</label>
<!-- KETIKA ADA INPUTAN PADA KOLOM PENCARIAN, MAKA AKAN MENJALANKAN FUNGSI SEARCH -->
<input type="text" class="form-control" @input="search">
</div>
</div>
<!-- BLOCK INI AKAN MENGHASILKAN LIST DATA DALAM BENTUK TABLE MENGGUNAKAN COMPONENT TABLE DARI BOOTSTRAP VUE -->
<div class="col-md-12">
<!-- :ITEMS ADALAH DATA YANG AKAN DITAMPILKAN -->
<!-- :FIELDS AKAN MENJADI HEADER DARI TABLE, MAKA BERISI FIELD YANG SALING BERKORELASI DENGAN ITEMS -->
<!-- :sort-by.sync & :sort-desc.sync AKAN MENGHANDLE FITUR SORTING -->
<b-table striped hover :items="items" :fields="fields" :sort-by.sync="sortBy" :sort-desc.sync="sortDesc" show-empty></b-table>
</div>
<!-- BAGIAN INI AKAN MENAMPILKAN JUMLAH DATA YANG DI-LOAD -->
<div class="col-md-6">
<p>Showing {{ meta.from }} to {{ meta.to }} of {{ meta.total }} items</p>
</div>
<!-- BLOCK INI AKAN MENJADI PAGINATION DARI DATA YANG DITAMPILKAN -->
<div class="col-md-6">
<!-- DAN KETIKA TERJADI PERGANTIAN PAGE, MAKA AKAN MENJALANKAN FUNGSI changePage -->
<b-pagination
v-model="meta.current_page"
:total-rows="meta.total"
:per-page="meta.per_page"
align="right"
@change="changePage"
aria-controls="dw-datatable"
></b-pagination>
</div>
</div>
</template>
<script>
import _ from 'lodash' //IMPORT LODASH, DIMANA AKAN DIGUNAKAN UNTUK MEMBUAT DELAY KETIKA KOLOM PENCARIAN DIISI
export default {
//PROPS INI ADALAH DATA YANG AKAN DIMINTA DARI PENGGUNA COMPONENT DATATABLE YANG KITA BUAT
props: {
//ITEMS STRUKTURNYA ADALAH ARRAY, KARENA BAGIAN INI BERISI DATA YANG AKAN DITAMPILKAN DAN SIFATNYA WAJIB DIKIRIMKAN KETIKA COMPONENT INI DIGUNAKAN
items: {
type: Array,
required: true
},
//FIELDS JUGA SAMA DENGAN ITEMS
fields: {
type: Array,
required: true
},
//ADAPUN META, TYPENYA ADALAH OBJECT YANG BERISI INFORMASI MENGENAL CURRENT PAGE, LOAD PERPAGE, TOTAL DATA, DAN LAIN SEBAGAINYA.
meta: {
required: true
}
},
data() {
return {
//VARIABLE INI AKAN MENGHADLE SORTING DATA
sortBy: null, //FIELD YANG AKAN DISORT AKAN OTOMATIS DISIMPAN DISINI
sortDesc: false //SEDANGKAN JENISNYA ASCENDING ATAU DESC AKAN DISIMPAN DISINI
}
},
watch: {
//KETIKA VALUE DARI VARIABLE sortBy BERUBAH
sortBy(val) {
//MAKA KITA EMIT DENGAN NAMA SORT DAN DATANYA ADALAH OBJECT BERUPA VALUE DARI SORTBY DAN SORTDESC
//EMIT BERARTI MENGIRIMKAN DATA KEPADA PARENT ATAU YANG MEMANGGIL COMPONENT INI
//SEHINGGA DARI PARENT TERSEBUT, KITA BISA MENGGUNAKAN VALUE YANG DIKIRIMKAN
this.$emit('sort', {
sortBy: this.sortBy,
sortDesc: this.sortDesc
})
},
//KETIKA VALUE DARI SORTDESC BERUBAH
sortDesc(val) {
//MAKA CARA YANG SAMA AKAN DIKERJAKAN
this.$emit('sort', {
sortBy: this.sortBy,
sortDesc: this.sortDesc
})
}
},
methods: {
//JIKA SELECT BOX DIGANTI, MAKA FUNGSI INI AKAN DIJALANKAN
loadPerPage(val) {
//DAN KITA EMIT LAGI DENGAN NAMA per_page DAN VALUE SESUAI PER_PAGE YANG DIPILIH
this.$emit('per_page', this.meta.per_page)
},
//KETIKA PAGINATION BERUBAH, MAKA FUNGSI INI AKAN DIJALANKAN
changePage(val) {
//KIRIM EMIT DENGAN NAMA PAGINATION DAN VALUENYA ADALAH HALAMAN YANG DIPILIH OLEH USER
this.$emit('pagination', val)
},
//KETIKA KOTAK PENCARIAN DIISI, MAKA FUNGSI INI AKAN DIJALANKAN
//KITA GUNAKAN DEBOUNCE UNTUK MEMBUAT DELAY, DIMANA FUNGSI INI AKAN DIJALANKAN
//500 MIL SECOND SETELAH USER BERHENTI MENGETIK
search: _.debounce(function (e) {
//KIRIM EMIT DENGAN NAMA SEARCH DAN VALUE SESUAI YANG DIKETIKKAN OLEH USER
this.$emit('search', e.target.value)
}, 500),
}
}
</script>
Tiba saatnya untuk menggunakan component Datatable di atas, buat file resources/js/App.vue
dan tambahkan code berikut
<template>
<div class="container" style="padding-top: 30px">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">VueJS Datatables</h5>
</div>
<div class="card-body">
<!-- COMPONENT YANG SUDAH KITA BUAT, MENGHARAPKAN 3 BUAH PROPS, YAKNI ITEMS, FIELDS DAN META. KETIGA DATA YANG DIMINTA OLEH PROPS INI AKAN KITA KIRIMKAN -->
<!-- ADAPUN CARA MENGAMBIL VALUE DARI EMIT YANG SUDAH KITA BUAT ADALAH DENGAN MENAMBAHKAN @ DAN DIIKUTI DENGAN NAMA EMITNYA -->
<!-- DAN PARAMETER SETELAH SAMA DENGAN DARI EMIT ADALAH FUNGSI UNTUK MENERIMA VALUENYA -->
<app-datatable
:items="items"
:fields="fields"
:meta="meta"
@per_page="handlePerPage"
@pagination="handlePagination"
@search="handleSearch"
@sort="handleSort"
/>
</div>
</div>
</div>
</div>
</div>
</template>
Masih menggunakan file yang sama, saatnya bagi kita untuk meng-handle dari sisi Javascript-nya. tambahkan code berikut pada baris paling bawah
<script>
import Datatable from './components/Datatable.vue' //IMPORT COMPONENT DATATABLENYA
import axios from 'axios' //IMPORT AXIOS
export default {
//KETIKA COMPONENT INI DILOAD
created() {
//MAKA AKAN MENJALANKAN FUNGSI BERIKUT
this.loadPostsData()
},
data() {
return {
//UNTUK VARIABLE FIELDS, DEFINISIKAN KEY UNTUK MASING-MASING DATA DAN SORTABLE BERNILAI TRUE JIKA INGIN MENAKTIFKAN FITUR SORTING DAN FALSE JIKA TIDAK INGIN MENGAKTIFKAN
fields: [
{key: 'title', sortable: true},
{key: 'author', sortable: true},
{key: 'category', sortable: true},
{key: 'created_at', sortable: true}
],
items: [], //DEFAULT VALUE DARI ITEMS ADALAH KOSONG
meta: [], //JUGA BERLAKU UNTUK META
current_page: 1, //DEFAULT PAGE YANG AKTIF ADA PAGE 1
per_page: 10, //DEFAULT LOAD PERPAGE ADALAH 10
search: '',
sortBy: 'created_at', //DEFAULT SORTNYA ADALAH CREATED_AT
sortByDesc: false //ASCEDING
}
},
components: {
'app-datatable': Datatable //REGISTER COMPONENT DATATABLE
},
methods: {
//METHOD INI AKAN MENGHANDLE REQUEST DATA KE API
loadPostsData() {
let current_page = this.search == '' ? this.current_page:1
//LAKUKAN REQUEST KE API UNTUK MENGAMBIL DATA POSTINGAN
axios.get(`/api/posts`, {
//KIRIMKAN PARAMETER BERUPA PAGE YANG SEDANG DILOAD, PENCARIAN, LOAD PERPAGE DAN SORTING.
params: {
page: current_page,
per_page: this.per_page,
q: this.search,
sortby: this.sortBy,
sortbydesc: this.sortByDesc ? 'DESC':'ASC'
}
})
.then((response) => {
//JIKA RESPONSENYA DITERIMA
let getData = response.data.data
this.items = getData.data //MAKA ASSIGN DATA POSTINGAN KE DALAM VARIABLE ITEMS
//DAN ASSIGN INFORMASI LAINNYA KE DALAM VARIABLE META
this.meta = {
total: getData.total,
current_page: getData.current_page,
per_page: getData.per_page,
from: getData.from,
to: getData.to
}
})
},
//JIKA ADA EMIT TERKAIT LOAD PERPAGE, MAKA FUNGSI INI AKAN DIJALANKAN
handlePerPage(val) {
this.per_page = val //SET PER_PAGE DENGAN VALUE YANG DIKIRIM DARI EMIT
this.loadPostsData() //DAN REQUEST DATA BARU KE SERVER
},
//JIKA ADA EMIT PAGINATION YANG DIKIRIM, MAKA FUNGSI INI AKAN DIEKSEKUSI
handlePagination(val) {
this.current_page = val //SET CURRENT PAGE YANG AKTIF
this.loadPostsData()
},
//JIKA ADA DATA PENCARIAN
handleSearch(val) {
this.search = val //SET VALUE PENCARIAN KE VARIABLE SEARCH
this.loadPostsData() //REQUEST DATA BARU
},
//JIKA ADA EMIT SORT
handleSort(val) {
//MAKA SET SORT-NYA
this.sortBy = val.sortBy
this.sortByDesc = val.sortDesc
this.loadPostsData() //DAN LOAD DATA BARU BERDASARKAN SORT
}
}
}
</script>
Langkah terakhir adalah melakukan compiling dengan menjalakan command berikut dari command line
npm run dev
[UPDATE] Menambahkan Tombol Actions
Ada banyak permintaan terkait bagaimana cara menambahkan tombol actions ke dalam Datatable yang sudah kita buat. Ada beberapa bagian yang harus diperbaharui agar bisa menerapkan tombol actions tersebut, buka file /components/Datatable.vue
dan modifikasi code berikut
<b-table striped hover :items="items" :fields="fields" :sort-by.sync="sortBy" :sort-desc.sync="sortDesc" show-empty>
<!-- TAMBAHKAN CODE INI -->
<template v-slot:cell(actions)="row">
<!-- TOMBOL EDIT HANYA AKAN DITAMPILKAN, JIKA PROPS DARI editUrl ADA VALUENYA -->
<a :href="editUrl" v-if="editUrl" class="btn btn-warning btn-sm">Edit</a>
<!-- TOMBOL DELETE AKAN MEMBUKA MODAL KONFIRMASI -->
<button class="btn btn-danger btn-sm" @click="openDeleteModal(row)">Delete</button>
</template>
</b-table>
Masih di dalam file yang sama, tambahkan code untuk modal berikut ini tepat setelah tag pagination.
<!-- MODAL INI AKAN DIBUKA JIKA deleteModal = TRUE -->
<!-- TITLE MODAL AKAN MENYESUAIKAN DENGAN VALUE PROPS TITLE -->
<b-modal v-model="deleteModal" :title="title">
<p>Kamu yakin ingin menghapus data ini?</p>
<template v-slot:modal-footer>
<div class="w-100 float-right">
<b-button
variant="secondary"
size="sm"
@click="deleteModal=false"
>
Close
</b-button>
<!-- JIKA TOMBOL DELETE DITEKAN, MAKA FUNGSI deleteModalButton AKAN DIJALANKAN -->
<b-button
variant="primary"
size="sm"
@click="deleteModalButton"
>
Delete
</b-button>
</div>
</template>
</b-modal>
Pada bagian props, tambahkan code props berikut ini.
props: {
//[.. PROPS SEBELUMNYA ..]
title: {
type: String,
default: "Delete Modal"
},
editUrl: {
type: String,
default: null
}
},
Lalu pada bagian properti data, tambahkan variable.
data() {
return {
sortBy: null,
sortDesc: false,
//TAMBAHKAN DUA VARIABLE INI UNTUK MENGHANDLE MODAL DAN DATA YANG AKAN DIHAPUS
deleteModal: false,
selected: null
}
},
Bagian terakhir dari file Datatable.vue
adalah dengan menambahkan methods dibawah ini untuk membuka modal dan mengirimkan emit apabila tombol delete pada modal konfirmasi ditekan
openDeleteModal(row) {
this.deleteModal = true
this.selected = row.item
},
deleteModalButton() {
this.$emit('delete', this.selected)
this.deleteModal = false
}
Adapun cara penggunaannya juga cukup sederhana, buka file App.vue
, modifikasi tag untuk app-datatable
menjadi
<app-datatable
:items="items"
:fields="fields"
:meta="meta"
:editUrl="'/a/b'"
:title="'Delete Posts'"
@per_page="handlePerPage"
@pagination="handlePagination"
@search="handleSearch"
@sort="handleSort"
@delete="handleDelete"
/>
Note: Ada dua props yang dikirimkan, yakni: editUrl
dan title
. Adapun actions untuk meng-handle emit adalah @delete
.
Langkah terakhir adalah dengan menambahkan fields actions
pada properti fields, masih dengan file yang sama, modifikasi fields menjadi
fields: [
{key: 'title', sortable: true},
{key: 'author', sortable: true},
{key: 'category', sortable: true},
{key: 'created_at', sortable: true},
{key: 'actions', sortable: false}, //TAMBAHKAN CODE INI
],
Dari sisi client sudah tersedia, maka saatnya untuk meng-handle fungsi untuk menghapus data. Buka file PostController.php
dan tambahkan method
public function destroy($id)
{
$post = Post::find($id);
$post->delete();
return response()->json(['status' => 'success']);
}
Kemudian definisikan routing-nya, buka file routes/api.php
dan tambahkan code
Route::delete('/posts/{id}', 'PostController@destroy');
Sedikit catatan, ketika tombol edit ditekan maka dia akan mengarah ke halaman edit yang dituju, dalam hal ini adalah /a/b
. Akan tetapi, kita tidak membahas tentang CRUD pada materi ini, maka halaman edit-nya tidak akan dibuat sehingga halamannya akan menjadi not found. Tapi pada intinya, poin yang ingin disampaikan adalah bagaimana menambahkan tombol actions pada datatable yang sudah kita buat.
Baca Juga: Membuat Aplikasi Ekspedisi NuxtJS #3: CRUD Data Users Bagian 1
Kesimpulan
Kita telah belajar bagaimana cara membuat datatable sendiri dengan mengadopsi fitur yang dimilikinya menggunakan Vue.js dan Laravel 6 sebagai backend-nya. Mungkin akan pertanyaan, mengapa kita harus repot membuat nya sendiri, sedangkan sudah ada library yang bisa langsung digunakan? Jawabannya adalah untuk mengetahui cara kerjanya dan juga jika kita memiliki kebutuhan khusus, maka akan dengan mudah untuk melakukan custom karena kita telah memahami strukturnya.
Adapun dokumentasi code dari artikel ini bisa dilihat di Github
Comments