Setting Up Laravel 9.x with Vue.js and DataTables (the Vue approach)

Integrating DataTables the Vue Way

Last time we looked at setting up DataTables using the DataTables CDN. If you read the DataTables Vue documentation, that’s not what it states. To be as comprehensive as I possibly can be on this topic, I’ve decided to show this approach as well. To read the CDN approach, check out the article below.

https://medium.com/geekculture/laravel-9-x-with-vue-js-and-datatables-b1299d0e6f09

For this article, I figured it would be good to start from scratch. No assumptions made. Let’s install it with a fresh installation of Laravel 10.x. We’ll go through setting up Vue.js and then setting up DataTables with Server Side support.

Assumptions

You have Docker Desktop and PHP installed on your device. The PHP version that I’m running is:

PHP 8.2.0 (cli) (built: Dec  9 2022 16:55:44) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.0, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.0, Copyright (c), by Zend Technologies

Laravel Installation

Setup a new Laravel projects. I’m using Docker on a Mac, so my installation is pretty straightforward.

curl -s "https://laravel.build/laravel-vue-datatables" | bash

Once installed, cd into your directory and run ./vendor/bin/sail up to start your project.

Open your Docker Desktop to see your project running.

Before we move on, click on laravel.test-1 to open the container and then click on the terminal tab. From now on, this is where we’ll enter all of our commands from.

vitejs/plugin-vue

In order to use Vue, we need to install the vite vue plugin. Open your docker container called laravel.test-1 and go into the terminal if you haven’t done so in the previous section.

Type the following command:

npm i vue@next

After that’s installed, run one more command to install the vue plugin:

npm i @vitejs/plugin-vue

vite.config.js

The vite.config.js file in your main directory needs to be modified. We’ll need to import Vue from our plugin and register it under plugins. Your file should now look like this:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import Vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
        Vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                }
            }
        }),
    ],
});

resources/js/App.vue

Time to create the App.vue file. You can name it whatever you’d like as long as you know this is the entry point. I’ll start with a simple file that contains an h1 tag.

<template>
    <h1>DataTables Example</h1>
</template>

<script>
export default {
    name: 'App',
    components: {},
}
</script>

<style>

</style>

resources/js/app.js

Next, we need to modify our app.js file. It’s currently importing bootstrap.js from the same directory, but we need for it to actually import our App.vue. We’ll also need createApp from vue so that we can mount the content onto #app id within resources/views/index.blade.php. We haven’t done this yet, but we will do it next.

Your resources/js/app.js file should look like this.

import { createApp } from "vue";
import App from "./App.vue";

createApp(App).mount("#app");

resources/index.blade.php

Create a new index.blade.php view and add the following code. Make sure to include the app.js file in index.blade.php. We can do this with vite. While you’re at it, you might as well include the app.css resource.

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel Vue.js DataTables</title>

        <!-- Fonts -->
        <link href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">

        @vite(['resources/js/app.js', 'resources/css/app.css'])
    </head>
    <body class="antialiased">
        <div id="app"></div>
    </body>
</html>

We haven’t modified the routes/web.php file yet, but we will after we install vue-router.

Installing Vue Router

We’ll need to install the dependency first. Go to the Docker container and install vue-router dependency with the following command:

npm install vue-router

routes/web.php

It’s now time to modify the web.php route file. We can add a new route that will process all of our vue-router routes. If we don’t do it this way, it will cause errors with the vue-router.

<?php

use Illuminate\Support\Facades\Route;

Route::get('/{all}', function () {
    return view('index');
})->where("all", ".*");

Creating a Home View: resources/views/home/Index.vue

Let’s create a new Vue file for our home page. This page will contain the DataTable.

<template>
    <h1>Home Page with DataTable</h1>
</template>

<script>
export default {
    name: 'Home',
    components: {},
}
</script>

<style>

</style>

resources/js/router/index.js

Time to create our routes file. This will process all of our routes instead of web.php. For this file, we need to:

  • import createRouter and createWebHistory from vue-router
  • import any page component that we need to define a route to, like Home
  • create our routes constant and bind the component to it
  • create our router and bind history and routes to it
  • export router
import {createRouter, createWebHistory } from "vue-router";
import Home from "../../views/home/Index.vue";

const routes = [
    {
        path: "/",
        name: "home",
        component: Home,
    },
];

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes,
});

export default router;

resources/js/app.js

The exported router now needs to be imported. It needs to be imported into our app.js file. Once imported, we need to use it. Add the use(router) function before we mount our app.

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index";

createApp(App)
    .use(router)
    .mount("#app");

Modify resources/js/App.vue

Finally, we need to modify the App.vue file so that it contains the contents that our router file returns. We are going to add <router-view /> into our template.

<template>
    <router-view />
</template>

<script>

export default {
    name: 'App',
    components: {},
}
</script>

<style>

</style>

Make sure it works before we start with DataTables. Go back to the container and run npm run dev. If you haven’t configured your hosts file, you’ll probably visit http://0.0.0.0 to see it running.

DataTables Prep

The data that we’ll display is going to come from the users table. Why the user’s table? Because it already exists and we can quickly run the Seeder to seed it.

Open your database/seeders/DatabaseSeeder.php file and uncomment the User factory code. Change the 10 to a 100 so that we have more to play with.

<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
         \App\Models\User::factory(100)->create();

        // \App\Models\User::factory()->create([
        //     'name' => 'Test User',
        //     'email' => 'test@example.com',
        // ]);
    }
}

Next, go back to your Docker Container and stop the application from running, if you haven’t already with CTRL+C. Run your migrations and seed the table:

php artisan migrate --seed
# php artisan migrate --seed

   INFO  Preparing database.  

  Creating migration table ............................................................. 26ms DONE

   INFO  Running migrations.  

  2014_10_12_000000_create_users_table ................................................. 43ms DONE
  2014_10_12_100000_create_password_resets_table ....................................... 24ms DONE
  2019_08_19_000000_create_failed_jobs_table ........................................... 26ms DONE
  2019_12_14_000001_create_personal_access_tokens_table ................................ 36ms DONE

   INFO  Seeding database.  

Once complete, it’s time to move on to DataTables.

DataTables for Vue3

Visit the official documentation to get the full instructions for additional items. We’re going to extract the most important information out of here.

First, install the package. Open your Docker Container and run:

npm install --save datatables.net-vue3

That’s it for the installation. We’ll need to now start using it. To make this as realistic as possible, I’m going to create a DataTable component for our Home page. We could do all of this inside of the Home Page vue file, but we’ll create a DataTable component and import it into Home Page.

DataTableComponent

I’m going to create a new directory called components under resources/js and place all of my components there. Since this is specifically for the home page, I’ll create a subdirectory home under components and place the DataTableComponent.vue file there.

The file will start off with the same skeleton as all of our other Vue files:

<template>
    <!-- DataTable -->
</template>

<script>
export default {
    name: 'DataTableComponent',
    components: {},
}
</script>

<style>

</style>

We’ll just need to import it now into our Home vue, located in resources/views/home/Index.vue.

<template>
    <h1>Home Page with DataTable</h1>
    <DataTable />
</template>

<script>
import DataTableComponent from "../../js/components/home/DataTableComponent.vue";

export default {
    name: 'Home',
    components: {DataTableComponent},
}
</script>

<style>

</style>

resources/js/components/home/DataTableComponent.vue

Time to start build out our DataTable component. The Vue instructions state that we should add the following into our script tag. We’ll need to register DataTable as one of our components so that we can start using it.

import DataTable from 'datatables.net-vue3'
import DataTablesLib from 'datatables.net';
 
DataTable.use(DataTablesLib);
<template>
    <!-- DataTable -->
</template>

<script>
import DataTable from 'datatables.net-vue3'
import DataTablesLib from 'datatables.net';

DataTable.use(DataTablesLib);

export default {
    name: 'DataTableComponent',
    components: {DataTable},
}
</script>

<style>

</style>

Let’s make sure it works. Add the following table into your template tag.

<template>
    <DataTable class="display">
        <thead>
            <tr>
                <th>First</th>
                <th>Second</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Test 1</td>
                <td>Test 2</td>
            </tr>
            <tr>
                <td>Test 3</td>
                <td>Test 4</td>
            </tr>
            <tr>
                <td>Test 5</td>
                <td>Test 6</td>
            </tr>
        </tbody>
    </DataTable>
</template>

<script>
import DataTable from 'datatables.net-vue3'
import DataTablesLib from 'datatables.net';

DataTable.use(DataTablesLib);

export default {
    name: 'DataTableComponent',
    components: {DataTable},
}
</script>

<style>

</style>

Type in the npm run dev command into your Docker container and make sure that it works. You should see the un-styled DataTable.

Adding DataTable Style

According to the official documentation, styling the DataTable should require 2 lines of code. First, run the following command in your Docker container:

npm install --save datatables.net-dt

Next, add the import into your DataTablesComponent style tag.

<style>
@import 'datatables.net-dt';
</style>

Make sure that it works by running npm run dev from your Docker container again and visiting the home page.

Server Side

This is great, but we want to be able to retrieve the data from the server side. There is a great dependency for this that makes it extremely simple to return data for DataTables from our API.

https://github.com/yajra/laravel-datatables

To install it, run the following command from your Docker container:

composer require yajra/laravel-datatables-oracle:"^10.0"

This is located in the documentation. Make sure to run your dev environment again: npm run dev.

routes/api.php

Time to add our route to the api.php file. We’ll use the datatables global function now that we have it installed.

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

// ...

Route::get('/users', function() {
    $users = \App\Models\User::select('id', 'name', 'email');

    return datatables($users)->make(true);
});

After we generate the query that we want to pass to datatables, we’ll pass it as an argument. Make sure that you’re not actually retrieving the content yourself; in other words, don’t add ->get() at the end of your statement.

Retrieving the data in resources/js/components/home/DataTableComponent.vue

We’ve finally made it. Time to make the modifications and see our data coming from the users table.

<template>
    <div class="p-6">
        <DataTable
            class="display"
            :columns="columns"
            ajax="api/users"
            ref="table"
            :options="{
              select: true,
              serverSide: true,
        }"
        >
            <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
            </tr>
            </thead>
            <tbody>
            </tbody>
        </DataTable>
    </div>
</template>

Let’s walk through the important aspects of the DataTable component.

  • We are binding :columns to our data(), which we haven’t defined yet, but we will.
  • We are using the ajax property to hit our api/users route. This will retrieve the data from our api.
  • In :options, we add the serverSide property and set it to true. This will allow us to process the requests server-side.

We need to add the data() method to our script.

export default {
    name: 'DataTableComponent',
    components: {DataTable},
    data(){
        return {
            columns: [
                {"data": "id"},
                {"data": "name"},
                {"data": "email"},
            ],
        }
    },
}

The reason we add the columns there is so that we can add additional features to our DataTable. For example, if we didn’t want the name to be sortable, we can add a sortable property and set it to false.

export default {
    name: 'DataTableComponent',
    components: {DataTable},
    data(){
        return {
            columns: [
                {"data": "id"},
                {"data": "name", "sortable": false},
                {"data": "email"},
            ],
        }
    },
}

Your DataTableComponent file should look like this.

<template>
    <div class="p-6">
        <DataTable
            class="display"
            :columns="columns"
            ajax="api/users"
            ref="table"
            :options="{
                select: true,
                serverSide: true,
            }"
        >
            <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
            </tr>
            </thead>
            <tbody>
            </tbody>
        </DataTable>
    </div>
</template>

<script>
import DataTable from 'datatables.net-vue3'
import DataTablesLib from 'datatables.net';

DataTable.use(DataTablesLib);

export default {
    name: 'DataTableComponent',
    components: {DataTable},
    data(){
        return {
            columns: [
                {"data": "id"},
                {"data": "name", "sortable": false},
                {"data": "email"},
            ],
        }
    },
}
</script>

<style>
@import 'datatables.net-dt';
</style>

Time for the big test. Type in npm run dev if it’s not running already in your Docker container and visit the home page.

Not gonna lie, I was getting a little nervous whether it was going to work or not :).

Adding a Button to the Table

Open your resources/js/components/home/DataTableComponent.vue file. Let’s add a new column to our table and also to our script. I’m going to name it Action and will also add the { “data”: “action” } object to our columns property.

<template>
    <div class="p-6">
        <DataTable
            class="display"
            :columns="columns"
            ajax="api/users"
            ref="table"
            :options="{
                select: true,
                serverSide: true,
            }"
        >
            <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
                <th>Action</th>
            </tr>
            </thead>
            <tbody>
            </tbody>
        </DataTable>
    </div>
</template>

<script>
import DataTable from 'datatables.net-vue3'
import DataTablesLib from 'datatables.net';

DataTable.use(DataTablesLib);

export default {
    name: 'DataTableComponent',
    components: {DataTable},
    data(){
        return {
            columns: [
                {"data": "id"},
                {"data": "name"},
                {"data": "email"},
                {"data": "action", "sortable": false},
            ],
        }
    },
}
</script>

<style>
@import 'datatables.net-dt';
</style>

We next need to modify the API route to return a column.

Route::get('/users', function() {
    $users = \App\Models\User::select('id', 'name', 'email');

    return datatables($users)
        ->addColumn('action', function($row) {
            return '<a href="/users/' . $row['id'] . '/edit">Edit</a>';
        })
        ->rawColumns(['action'])
        ->make(true);
});

Refresh your page and you should see something like this:

What about relationships?

Let’s say that your user model has a belongsTo relationship to some other model, like a Profile. We can imagine that the Profile model has an image associated with it. The User model will have the profile method with the relationship.

<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    // ...

    public function profile() {
        return $this->belongsTo(Profile::class);
    }
}

If we pass the profile, we’ll receive an object on the other side. In other words, we can’t simply use with.

Route::get('/users', function() {
    $users = \App\Models\User::with(['profile'])->select('id', 'name', 'email');

    return datatables($users)
        ->addColumn('action', function($row) {
            return '<a href="/users/' . $row['id'] . '/edit">Edit</a>';
        })
        ->rawColumns(['action'])
        ->make(true);
});

We have to create a new column. I’m going to call it profile-image to get the Profile image. It’ll return the image from the profile relationship.

Route::get('/users', function() {
    $users = \App\Models\User::select('id', 'name', 'email');

    return datatables($users)
        ->addColumn('action', function($row) {
            return '<a href="/users/' . $row['id'] . '/edit">Edit</a>';
        })
        ->addColumn('profile-image', function($row) {
            return $row->profile->image;
        })
        ->rawColumns(['action', 'profile-image'])
        ->make(true);
});

We need to tweak our DataTableComponent.vue to see it by adding it to our table and to our columns property.

<template>
    <div class="p-6">
        <DataTable
            class="display"
            :columns="columns"
            ajax="api/users"
            ref="table"
            :options="{
                select: true,
                serverSide: true,
            }"
        >
            <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
                <th>Profile Image</th>
                <th>Action</th>
            </tr>
            </thead>
            <tbody>
            </tbody>
        </DataTable>
    </div>
</template>

<script>
import DataTable from 'datatables.net-vue3'
import DataTablesLib from 'datatables.net';

DataTable.use(DataTablesLib);

export default {
    name: 'DataTableComponent',
    components: {DataTable},
    data(){
        return {
            columns: [
                {"data": "id"},
                {"data": "name"},
                {"data": "email"},
                {"data": "profile-image", "sortable": false},
                {"data": "action", "sortable": false},
            ],
        }
    },
}
</script>

<style>
@import 'datatables.net-dt';
</style>

You will now be able to see a link to your Profile image…if you have that type of relationship setup.

That’s it. See you next time. I have to continue writing my Laravel series: we are just getting into the Blade templating engine.

If you want the code for what was covered above, well here you go.

https://github.com/dinocajic/laravel-vue-datatables

 

Leave a Reply