Laravel-Vue.js Datatable Workflow

A quick look at the vue-good-table plugin

David Abiola
7 min readFeb 5, 2020
Photo by Mika Baumeister on Unsplash

Disclaimer:

This content is developed to give junior-intermediate Laravel/Vue.js developers (or enthusiasts) a general sense of what’s made possible via a handful of batteries shipped out-of-the-box with the plugin in view. My featured implementation isn’t necessarily ‘perfect/superior/acclaimed’. My intention is to help you find a path to your own ‘perfect’ implementation — one that’ll hopefully suit your use case better.

Before we begin, I’d like to assume a few things — not covered herein:

  • You have a working installation of Laravel based application
  • Your database is setup
  • and you have a terminal in place to run some commands

Some technical details about my local setup — just for reference

Versions:

  • Nginx ~ 1.16
  • MariaDB ~ 10.2.3
  • PHP ~ 7.4
  • Laravel ~ 6.13

‘What for?!’, you say?

Datatables can be a bit tricky to deal with using the conventional jQuery-based approach when utilizing JS powered component-based views, especially while trying to preserve reactivity while rendering dom changes on-the-fly.

In these scenarios, it might make more sense to use a ‘vue-centric’ approach to our datatable implementation.

Feature Implementation Todo List

Frontend todos

  • require laravel/ui — (optional ) only applicable in Laravel ≥v6
  • install Vue.js, Bootstrap, and Auth scaffolding — (optional ) only applicable in Laravel ≥v6; otherwise, just install Auth scaffolding to use the default blade templates
  • install NPM dependencies and start auto compilation

// trigger corresponding package.json script(s)

npm install && npm run watch

  • Build datatable module
  • build-up UsersList component
  • create blade view to render table

Backend todos

  • run migrations
  • define a route to handle requests to our users_list view
  • make the UserListController
  • Generate test data (manual registration via the inbuilt auth feature or seeding data automatically)

A quick aside: I deploy features using the Gitflow Workflow, so the first steps I naturally gravitate towards is to create a new release/feature branch based off of my updated stable/production branch. And when my feature is ready for release, I just simply merge it into my development branch for testing and so fort…before updating the production code. Pretty nifty flow, ehh?!

Frontend chores

Task 1 — require laravel/ui package by running:

  • composer require laravel/ui --dev

Task 2 — install Vue.js, Bootstrap, and Auth scaffolding

  • php artisan ui bootstrap --auth && php artisan ui vue

Task 3 — install NPM dependencies and start auto compilation

// trigger corresponding package.json script(s) with:

npm install && npm run watch

The actual frontend features

Task 4 — Build datatable module

So let’s start by creating a users_datatable.js file at /resources/js/plugins directory, then:

  • new-up a vue instance in there
  • pull in the vue-good-table plugin
npm install --save vue-good-table
  • import the necessary JS and CSS modules

When that’s done, your users_datatable.js should now look something close to this:

// @ /resources/js/plugins/users_datatable.jsimport Vue from 'vue';
import UsersList from '../components/UsersList';

// import the styles
import 'vue-good-table/dist/vue-good-table.css'

new Vue({
el: 'users-list',
components:{
UsersList
}
});

Remember to tell Laravel Mix about this module — in webpack.mix.js

//before
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
//after
mix.js('resources/js/app.js', 'public/js')
.js('resources/js/plugins/users_datatable.js', 'public/js/plugins')
.sass('resources/sass/app.scss', 'public/css');

Notice how we informed mix about the destination of the compiled version of our module? That’s what’s passed in as the second argument tothe js method called on mix‘public/js/plugins’

Let’s also remove the Vue instance boilerplate from our app.js file, since we’re building our own instance scoped to our component.

I’ll comment out irrelevant codes and leave require(‘./bootstrap’)

/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/

require('./bootstrap');

// window.Vue = require('vue');

/**
* The following block of code may be used to automatically register your
* Vue components. It will recursively scan this directory for the Vue
* components and automatically register them with their "basename".
*
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/

// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))

// Vue.component('example-component', require('./components/ExampleComponent.vue').default);

/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/

// const app = new Vue({
// el: '#app',
// });

Task 5 — build-up UsersList referenced earlier in our Vue instance

Let’s head into /resources/js/components, and whip up a .vue file UsersList.vue

A few things worth mentioning here:

  • we set a few optional params with the vue-good-table props
  • we conditionally define the values of our table’s rows in the ‘table-row’ slot
  • we define our table columns labels and value pairs in the data property —’ columns’
<template>
<vue-good-table
:columns="columns"
:rows="users"
:search-options="{ enabled: true }"
:pagination-options="{enabled: true}"
:line-numbers="true">
<template slot="table-row" slot-scope="props">
<span v-if="props.column.field === 'name'">
{{props.row.name}}
</span>
<span v-else-if="props.column.field === 'email'">
{{props.row.email}}
</span>
<span v-else-if="props.column.field === 'created_at'">
{{props.row.created_at}}
</span>
<span v-else-if="props.column.field === 'courses_registered'">
<!--<span class="badge badge-pill badge-default-outline" v-for="course in props.row.courses">{{course.code}}</span>-->
</span>
<span v-else-if="props.column.field === 'delete'">
<a :href="`/user/${props.row.id}/delete`"><span class="badge badge-danger">Delete</span></a>
</span>
<span v-else>
{{ props.formattedRow[props.column.field] }}
</span>
</template>
</vue-good-table>
</template>

<script>
import { VueGoodTable } from 'vue-good-table';

export default {
props: ['users'],
components: {
VueGoodTable,
},
data(){
return {
columns: [
{
label: 'Name',
field: 'name',

},
{
label: 'Email',
field: 'email',
},
{
label: 'Time Registered',
field: 'created_at',
},
{
label: 'Courses Registered',
field: 'courses_registered',
},
{
label: 'Delete',
field: 'delete',
},
],
}
},
};
</script>

Task 6— create blade view to render our table

Thanks to Laravel’s blade templating engine, we don’t necessarily need to build up our page from scratch, in fact, I’ll just create a blade template; users_list.blade.php and extend a base template in the /layouts directory of our views. Also, let’s make room for any potential scripts we may need later in any of the extending blade templates by adding the following code just before the HTML closing body tag </body>

@yield('scripts')

Then we’ll add the UsersList component with the <users-list></users-list> element tag, and include the compiled module in a script tag at the bottom of the page— like so:

@extends('layouts.app')

@section(
'content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">All Users</div>

<div class="card-body">
<users-list :users="{{ $users }}"></users-list>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
<script src="/js/plugins/users_datatable.js"></script>
@endsection

A value is assigned to the data prop ‘users’ — defined inside the component. When the component is mounted, the $users data (expected from the server) is passed in as value of the ‘users’ prop.

Backend bits

Task 7 — let’s run some migrations

At this point, we have some migration files generated for us when we installed the auth scaffolding earlier; at /database/migrations. Let’s run them now, so we can at least use the prebuilt user registration to create a user —

php artisan migrate

Now we should have the user associated database tables, with their respective columns added to our database. You can take the user registration system for a spin now, at /register. Go ahead and whip up a new User.

Task 8 — define a route to handle requests to our users_list view

Let’s quickly define a new Route in our web.php file, to handle GET requests to /users/list/all. The lifecycle of this request will be completed after executing the block of codes within the controller method reference passed in as a second argument.

Route::get('/users/list/all', 'UserListController@index');

When a GET request is made to ‘/users/list/all’ URL, the request should be resolved by calling the ‘index’ method within the ‘UserListController’ (within that namespace) — essentially like saying:

Route::get('/users/list/all', function () {
(new App\Http\Controllers\UserListController)->index();
});

Task 9 — make the UserListController

php artisan make:controller UserListController

Next, we’ll modify the index method to meed the needs of our operation —

<?php

namespace
App\Http\Controllers;

use App\User;

class UserListController extends Controller
{
/**
* Create a new controller instance.
*
*
@return void
*/
public function __construct()
{
$this->middleware('auth');
}

/**
* Show the application dashboard.
*
*
@return \Illuminate\Contracts\Support\Renderable
*/
public function index()
{
return view('users_list', [
'users' => User::all()
]);
}
}

Note:

The constructor method adds an auth middleware to our controller on instantiation. This means only authenticated users can access the methods within. Also, you’d normally not want to query your entire model at a long like this — User::all().

It’s bad practice, and can really impact performance in a bad way, especially at scale. You’d instead want to use laravel pagination API, and others like Lazy Collections availale in Laravel 6.

Our blade view ‘users_list’ is returned with the data $users passed in and ready to be used on the client-side of our app.

Task 10 — Generate data (manual registration via the inbuilt auth feature or seeding data automatically)

I’ll sit this one out and let you take care of this one.

Final Project Preview

In the end, you should have your datatable looking something close to this:

Mazel tov, comrades! Nicely done.

A few things to note here from inside our component—

  1. We’d have been able to loop over a nested array of, for example; courses associated with each user in this commented code. It was just intended to show you how that’d have been implemented if we had our backend wired up for that.
<!--<span class="badge badge-pill badge-default-outline" v-for="course in props.row.courses">{{course.code}}</span>-->

2. Also, the delete button we mocked isn’t hooked into any actual backend code — the goal was just to show you another use case you might be wondering how to implement:

<span v-else-if="props.column.field === 'delete'">
<a :href="`/user/${props.row.id}/delete`"><span class="badge badge-danger">Delete</span></a>
</span>

Associated Github branch:

I’ll push the codes in this lesson to a branch on my public Github repo where you can go over my codes for reference:

https://github.com/davealex/experiments/tree/ft-datatable

I’m David — A Nigerian software engineer, coffee drinker, and innovator. Thanks for tuning in!

--

--

David Abiola

Human & software engineer. Interested in ()=> {[Arts, Education, Music, Science, Tech, AI, co-Humans]};