Backend DevelopmentFrontend DevelopmentVueJs Tutorials

Learn How To Make CRUD Pages With Laravel And Vuejs Part 1

Learn How To Make CRUD Pages With Laravel And Vuejs

In this tutorial we will implement a crud (Create – Read – Update- Delete) pages using Laravel and Vuejs2 and axios library.

 

 

Series Topics:

 

Requirements:

  • Laravel framework 5.5
  • Vuejs 2 and vuex
  • npm

 

CRUD mainly refers to (Create, Read, Update, Delete) operations and most of us already using crud in every web application in admin panels or to manipulate dashboard modules so in this tutorial we will be using Vuejs 2 and laravel but the advantage of using Vuejs as a frontend library beside laravel makes it more speed amazing as you be processing items without need to page reload.

 

Let’s create a new laravel project i will use version 5.5 in this tutorial so run this command in terminal:

composer create-project laravel/laravel vue-crud  "5.5.*" --prefer-dist

After that we need to install npm dependencies so before start open package.json and replace it with those contents:

{
    "private": true,
    "scripts": {
        "dev": "npm run development",
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
    "devDependencies": {
        "axios": "^0.16.2",
        "babel-preset-stage-2": "^6.24.1",
        "bootstrap-sass": "^3.3.7",
        "cross-env": "^5.0.1",
        "jquery": "^3.1.1",
        "laravel-mix": "^1.0",
        "lodash": "^4.17.4",
        "vue": "^2.1.10"
    },
    "dependencies": {
        "babel-preset-es2015": "^6.24.1",
        "vue-events": "^3.1.0",
        "vue-router": "^3.0.2",
        "vue-template-compiler": "^2.6.8",
        "vuetable-2": "^1.7.5",
        "vuex": "^3.1.0"
    }
}

Then in terminal run

npm install

This will install all dependencies in package.json as you see we installed (vue, vuex) which are mandatory, vue-router to enable routing between pages, vuetable-2 this component renders a datatabe to be used in the listing page you can read more about it in this article, vue-events this component enable to use vue events in more easy way instead of using the classic vue event system.

 

Preparing Database:

To clarify things i suppose you already connected to database we will create one table so let’s create a laravel migration that represent a posts table:

php artisan make:migration create_posts_table

Then open database/migrations/XXXXX_create_posts_table.php and modify it like this:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePosts extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
           $table->increments('id');
           $table->string('title');
           $table->text('body');
           $table->string('photo')->nullable();
           $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('posts');
    }
}

As shown we created a posts table that have 6 fields (id, title, body, photo, timestamps (created_at, updated_at)).

Then we will create a model for table so run this command in terminal:

php artisan make:model Post

Now we have a post table and Post model we will need to create some dummy data to populate the table so to do this we use laravel seeder so create a new seeder:

php artisan make:seeder PostsTableSeeder

Open database/seeds/PostsTableSeeder.php and modify it as shown:

<?php

use Illuminate\Database\Seeder;
use Faker\Factory as Faker;

class PostsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $faker = Faker::create();

        foreach (range(1,20) as $index) {

            $post = new \App\Post();

            $post->title = $faker->word;

            $post->body = $faker->paragraph;

            $post->photo = 'Laptop.jpg';

            $post->save();
        }
    }
}

As shown in the code above we make use of the faker package to generate fake data it’s already installed by default when you install laravel.

Open database/seeds/DatabaseSeeder.php and inside run() method add this code:

public function run()
{
         $this->call(PostsTableSeeder::class);
}

Finally run this command to execute the seeder:

php artisan db:seed

Now the posts table is populated with dummy data.

Be sure you have an “uploads” folder inside the public directory with writable permissions and this photo inside it.

 

Server Side

Before digging into creating Vuejs code we will create the server side code so we will create a controller for the posts and the routes required.

Create app/Http/Controllers/PostsController.php and add the following contents:

<?php

namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;

class PostsController extends Controller
{
    public function __construct()
    {

    }

    public function getAll(Request $request)
    {
        $cur_page = $request->page;

        $per_page = $request->per_page;

        $offset = ($cur_page-1) * $per_page;

        $sortCol = "id";
        $sortDir = "asc";

        if($request->sort != "" && $sort = explode("|", $request->sort)) {
            $sortCol = $sort[0];
            $sortDir = $sort[1];
        }

        $query = Post::limit($per_page)->offset($offset)->orderBy($sortCol, $sortDir);

        if($request->filter) {
            $query->where('title', 'like', "%$request->filter%");
        }

        $rows = $query->get();

        if($request->filter) {
            $total = Post::where('title', 'like', "%$request->filter%")->count();
        } else {
            $total = Post::count();
        }

        $last_page = ceil($total / $per_page);

        if($cur_page == 1) {
            $prev_page_url = null;

            if($total > $per_page) {
                $next_page_url = url('posts/all') . '?page=2';
            } else {
                $next_page_url = null;
            }
        } else if($cur_page > 1 && $cur_page < $last_page) {
            $next_page_url = url('posts/all') . '?page=' . ($cur_page + 1);
            $prev_page_url = url('posts/all') . '?page=' . ($cur_page - 1);
        } else {
            $next_page_url = null;
            $prev_page_url = url('posts/all') . '?page=' . ($cur_page - 1);
        }

        if(count($rows)) {
            return response()->json(['links' => ['pagination' =>
                [
                    'total' => $total,
                    'per_page' => $per_page,
                    'current_page' => $cur_page,
                    'last_page' => $last_page,
                    'next_page_url' => $next_page_url,
                    'prev_page_url' => $prev_page_url,
                    'from' => $rows[0]->id,
                    'to' => $rows[$rows->count() - 1]->id
                ]
            ],
                'data' => $rows]);
        } else {
            return response()->json(['links' => ['pagination' =>
                [
                    'total' => 0,
                    'per_page' => $per_page,
                    'current_page' => 0,
                    'last_page' => null,
                    'next_page_url' => null,
                    'prev_page_url' => null,
                    'from' => null,
                    'to' => null
                ]
            ],
                'data' => []]);
        }
    }

    public function store(Request $request)
    {
        $post = new Post();

        $post->title = $request->title;

        $post->body = $request->body;

        $post->photo = $this->uploadFile($request);

        $post->save();

        return response()->json(['msg' => "Post created successfully!"]);
    }

    public function update(Request $request)
    {
        $post = Post::find($request->id);

        $post->title = $request->title;

        $post->body = $request->body;

        if($request->file('photo') != null) {
            $post->photo = $this->uploadFile($request);
        }

        $post->save();

        return response()->json(['msg' => "Post updated successfully!"]);
    }

    function view(Request $request)
    {
        if(!$request->id) {
            return;
        }

        $post = Post::find($request->id);

        $post->photo = !empty($post->photo) && file_exists(public_path('uploads/' . $post->photo))? url('uploads/' . $post->photo) : "";

        return response()->json(['data' => $post]);
    }

    function delete(Request $request)
    {
        if(!$request->id) {
            return;
        }

        $post = Post::find($request->id);

        if($post->photo != "" && file_exists(public_path('uploads/' . $post->photo))) {
            @unlink(public_path('uploads/' . $post->photo));
        }

        $post->delete();

        return response()->json(['msg' => "Post deleted successfully!"]);
    }

    function uploadFile($request) {

        try {
            $image = $request->file('photo');
            $name = time() . '.' . $image->getClientOriginalExtension();
            $destinationPath = public_path('/uploads');
            $image->move($destinationPath, $name);
            return $name;
        } catch (\Exception $ex) {
            return '';
        }
    }
}

The PostsController contains server side code for the required methods which represent the CRUD operations that will be called by vuejs.

As you see the first method getAll() which represent the R(Read) this method list the posts and can accept various parameters used for sorting, filtering, and pagination. All these parameters coming from the ajax request fired by the datatable component as we will see in the next section when we create the vuejs pages.  

The second method store() represents the C(Create) which is simply creates a new post and this method fired when when we create a new post from the form using ajax.

The third method update() represents the U(Update) which is simply updates existing post through ajax.

The fourth method view() actually not related to CRUD but some admin panels implemented it and it views the details of specific post.

The third method delete() represents the D(Delete) which is simply deletes existing post through ajax.

 

Adding routes

Let’s add the required server side routes that will be used with vuejs so open routes/web.php and modify it like this:

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('crud');
});

Route::get('{crud?}/{id?}', function () {
    return view('crud');
})->where(['crud' => '(list|create|edit|view)', 'id' => '[0-9]+']);

Route::get('posts/all', 'PostsController@getAll');

Route::post('posts/create', 'PostsController@store');

Route::get('posts/view/{id}', 'PostsController@view');

Route::post('posts/update', 'PostsController@update');

Route::delete('posts/delete', 'PostsController@delete');

Here we added the required routes for read, create, update, delete in addition we added a special route that will be triggered from inside vuejs routes which is:

Route::get('{page?}/{id?}', function () {
    return view('post');
})->where(['page' => '(list|create|edit|view)', 'id' => '[0-9]+']);

This special route uses regular expressions used to express urls having this structure:

http://localhost/vue-crud/public/list
http://localhost/vue-crud/public/create
http://localhost/vue-crud/public/edit/2
http://localhost/vue-crud/public/view/2

These urls will be used inside Vuejs routes and it tells laravel not to trigger a server side page instead to remain in the same view which is “post.blade.php” which in turn causes the vuejs routes to work properly.

 

Main view

create a new view in resources/views/post.blade.php and add the below contents:

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}" />

    <title>Crud</title>

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.7/semantic.min.css" media="screen" title="no title" charset="utf-8">

    <script>
        window.BaseUrl = '{{ url("/") }}'
    </script>
</head>
<body>
<div class="ui container">

    <base href="{{ url('/') }}" />

    <div class="content">
        <div id="app">
            <main-app></main-app>
        </div>
    </div>
</div>
<script src="{{ asset('js/app.js') }}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.7/semantic.min.js" charset="utf-8"></script>
</body>
</html>

As you see in the above code we used the semantic UI library so we included semantic css and semantic js and app.js. Also we add the main tag that represent the main Vuejs component like this:

<div id="app">
    <main-app></main-app>
</div>

<main-app> is the main Vuejs component which we will add in the next sections when we work with vuejs code. Note also i have added a special element <base /> and this element tells Vuejs to render the routes relative to the project full url.

 

Client Side

After we prepared the server side code let’s move to create the client side code, our client side in laravel reside in resources/assets/js directory, if you open “js” directory you will find bootstrap.js, app.js, components, we will add more files and components so create those files similar to below photo:

Learn how to create CRUD pages with vuejs and laravel

 

 

 

 

 

 

 

 

Create those files:

  • assets/js/components/Crud/CreatePost.vue
  • assets/js/components/Crud/EditPost.vue
  • assets/js/components/Crud/ListPosts.vue
  • assets/js/components/Crud/ViewPost.vue
  • assets/js/components/Partials/Actions.vue
  • assets/js/components/Partials/Details.vue
  • assets/js/components/Partials/Search.vue
  • assets/js/components/MainApp.vue
  • assets/js/store/index.js
  • assets/js/routes.js

Let’s populate our files with basic structure code and will return to them shortly:

assets/js/components/MainApp.vue

<template>
    <div>
        <router-view :key="$route.fullPath"></router-view>
    </div>
</template>

<script>
    export default {

    }
</script>

 

assets/js/components/Crud/CreatePost.vue

<template>
     <div>
         create post
     </div>
</template>

<script>
    export default {
         
    }
</script>

assets/js/components/Crud/EditPost.vue

<template>
     <div>
         edit post
     </div>
</template>

<script>
    export default {
         
    }
</script>

assets/js/components/Crud/ListPosts.vue

<template>
     <div>
         list posts
     </div>
</template>

<script>
    export default {
         
    }
</script>

assets/js/components/Crud/ViewPost.vue

<template>
     <div>
         view post
     </div>
</template>

<script>
    export default {
         
    }
</script>

assets/js/store/index.js

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        title: "",
        body: "",
        photoObj: "",
        photo: ""
    },
    mutations: {
        setTitle(state, value) {
            state.title = value;
        },
        setBody(state, value) {
            state.body = value
        },
        setPhotoObj(state, value) {
            state.photoObj = value
        },
        setPhoto(state, value) {
            state.photo = value
        }
    }
});

As you see above this is the vuex store we will need that when we create and edit posts to store post field into vuex store if you don’t know how to use vuex you can read more about it in this article.

assets/js/routes.js

import CreatePost from './components/Crud/CreatePost.vue';
import EditPost from './components/Crud/EditPost.vue';
import ViewPost from './components/Crud/ViewPost';
import ListPosts from './components/Crud/ListPosts.vue';


export const routes = [
    {path: '/list/', name: 'listPosts', component: ListPosts},
    {path: '/create/', name: 'createPost', component: CreatePost},
    {path: '/edit/:id', name: 'editPost', component: EditPost},
    {path: '/view/:id', name: 'viewPost', component: ViewPost}
];

assets/js/app.js

require('./bootstrap');
import VueRouter from 'vue-router';
import VueEvents from 'vue-events';
import MainApp from './components/MainApp';
import store from './store/index';


window.Vue = require('vue');

Vue.use(VueRouter);
Vue.use(VueEvents);

import { routes } from "./routes";

let router = new VueRouter({
   mode: 'history',
   routes,
   store
});

const app = new Vue({
    el: '#app',
    components: { MainApp },
    router,
    store
});

Here the main entry point for the Vue app, first we imported the required dependencies like vue-router, vue-events, store, routes, and the main component after we registerd VueRouter and VueEvents with Vue like this:

Vue.use(VueRouter);
Vue.use(VueEvents);

This code tells Vue that we can access router and events from inside Vue components like this “this.$router” and “this.$events”.

We create a new router instance passing in the routes and store:

let router = new VueRouter({
   mode: 'history',
   routes,
   store
});

Finally we output the Vue app specifying the element that our app will be binded to which is “#app” and the main component.

const app = new Vue({
    el: '#app',
    components: { MainApp },
    router,
    store
});

Now run

npm run dev

navigate to http://localhost/vue-crud/public/list you will see the listing page let’s move to put the actual code for the listing page.

 

List Posts

Open assets/js/components/Crud/ListPosts.vue and modify it like this:

<template>
    <div>
        <h2>Post listing</h2>

        <vuetable ref="vuetable"
                  :per-page="10"
                  :api-url="api_url"
                  :fields="fields"
                  :sort-order="sortOrder"
        ></vuetable>

    </div>
</template>

<script>

    import Vue from 'vue'
    import Vuetable from 'vuetable-2'
    import axios from 'axios'

    export default {
        components: {
            Vuetable
        },
        data () {
            return {
                api_url: window.BaseUrl + '/posts/all',
                sortOrder: [
                    {
                        field: 'id',
                        sortField: 'id',
                        direction: 'asc'
                    }
                ],
                fields: [
                    {
                        name: 'id',
                        sortField: 'id',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'title',
                        sortField: 'title',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'photo',
                        titleClass: 'center aligned',
                        callback: 'displayImageTag'
                    }
                ]
            }
        },
        mounted() {
            
        },
        methods: {
            displayImageTag(value) {
                if(value != null) {
                    return '<img src="' + window.BaseUrl + '/uploads/' + value + '" width="100" height="70" />'
                }
            }
        }
    }
</script>

Here we imported vuetable component and i included it in our template. The component has a lot of useful properties one these “api-url” and “fields” so we specify them in our data() method like this:

data () {
            return {
                api_url: window.BaseUrl + '/posts/all',
                sortOrder: [
                    {
                        field: 'id',
                        sortField: 'id',
                        direction: 'asc'
                    }
                ],
                fields: [
                    {
                        name: 'id',
                        sortField: 'id',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'title',
                        sortField: 'title',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'photo',
                        titleClass: 'center aligned',
                        callback: 'displayImageTag'
                    }
                ]
            }
        }

Note that i will not cover the full details of vuetable component but there is an article here that describes how to use it.

Also i defined the default sort order when we first load the page using the “sort-order” property. Now run:

npm run dev

If you reload the page you will see the post data and it displays 10 result by default because we specify the “per-page=10” property.

 

Pagination

Let’s add pagination to our list so we will include two other components related to vuetable which are VuetablePagination which show pagination links, VutablePaginationInfo which show pagination info so let’s include the pagination components like this:

<template>
    <div>
        <h2>Post listing</h2>

        <vuetable ref="vuetable"
                  :per-page="10"
                  :api-url="api_url"
                  :fields="fields"
                  :sort-order="sortOrder"
                  @vuetable:pagination-data="onPaginationData"
        ></vuetable>

        <div class="vuetable-pagination ui basic segment grid">
            <vuetable-pagination-info ref="paginationInfo"></vuetable-pagination-info>

            <vuetable-pagination ref="pagination" @vuetable-pagination:change-page="onChangePage"></vuetable-pagination>
        </div>

    </div>
</template>

<script>

    import Vue from 'vue'
    import Vuetable from 'vuetable-2'
    import axios from 'axios'
    import VuetablePagination from 'vuetable-2/src/components/VuetablePagination'
    import VuetablePaginationInfo from 'vuetable-2/src/components/VuetablePaginationInfo'

    export default {
        components: {
            Vuetable,
            VuetablePagination,
            VuetablePaginationInfo
        },
        data () {
            return {
                api_url: window.BaseUrl + '/posts/all',
                sortOrder: [
                    {
                        field: 'id',
                        sortField: 'id',
                        direction: 'asc'
                    }
                ],
                fields: [
                    {
                        name: 'id',
                        sortField: 'id',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'title',
                        sortField: 'title',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'photo',
                        titleClass: 'center aligned',
                        callback: 'displayImageTag'
                    }
                ]
            }
        },
        mounted() {
            
        },
        methods: {
            displayImageTag(value) {
                if(value != null) {
                    return '<img src="' + window.BaseUrl + '/uploads/' + value + '" width="100" height="70" />'
                }
            },
            onPaginationData (paginationData) {
                this.$refs.pagination.setPaginationData(paginationData)
                this.$refs.paginationInfo.setPaginationData(paginationData)
            },
            onChangePage (page) {
                this.$refs.vuetable.changePage(page)
            }
        }
    }
</script>

There are a lot of stuff and the above code first we imported the pagination related components, then we added them to the template like this:

<div class="vuetable-pagination ui basic segment grid">
      <vuetable-pagination-info ref="paginationInfo"></vuetable-pagination-info>

      <vuetable-pagination ref="pagination" @vuetable-pagination:change-page="onChangePage"></vuetable-pagination>
</div>

VuetablePagination component fires two events the first event is @vuetable-pagination:change-page this event is fired when you click on any page number and the second event @vuetable:pagination-data this event is also fired when you click on any page and it tells vuetable that page number changed and to render the new page.

 

Adding Search

Vuetable also support filtering and it uses a special property for that which is “append-params

Open the partial component assets/js/components/Partials/Search.vue and add the below code:

<template>
    <div class="filter-bar ui basic segment grid">
        <div class="ui form">
            <div class="inline field">
                <label>Search for:</label>
                <input type="text" v-model="searchQuery" class="three wide column" @keyup.enter="filter" placeholder="title">
                <button class="ui primary button" @click="filter">Go</button>
                <button class="ui button" @click="reset">Reset</button>
            </div>
        </div>

        <router-link to="/create/" class="ui primary button four wide column right floated">
            <i class="plus icon"></i> Create new
        </router-link>
    </div>
</template>

<script>
    export default {
        data () {
            return {
                searchQuery: ''
            }
        },
        methods: {
            filter () {
                this.$events.fire('do-filter', this.searchQuery);
            },
            reset () {
                this.searchQuery = ''
                this.$events.fire('reset-filter');
            }
        }
    }
</script>

Here we add a simple form which has one text input and two buttons (Search, reset). We bind the text input with param “searchQuery” and then defined two methods which will be used for filtering and reset input. Note that we didn’t define the actual logic for filtering and reset instead we fire events to the parent component using the vue-events we installed in the beginning of the tutorial.

this.$events.fire('do-filter', this.searchQuery);

the fire() function take event name and optional parameters.

 

Now let’s modify assets/js/components/Crud/ListPosts.vue as shown:

<template>
    <div>
        <h2>Post listing</h2>

        <search></search>

        <vuetable ref="vuetable"
                  :per-page="10"
                  :api-url="api_url"
                  :fields="fields"
                  :sort-order="sortOrder"
                  :append-params="searchParams"
                  @vuetable:pagination-data="onPaginationData"
        ></vuetable>

        <div class="vuetable-pagination ui basic segment grid">
            <vuetable-pagination-info ref="paginationInfo"></vuetable-pagination-info>

            <vuetable-pagination ref="pagination" @vuetable-pagination:change-page="onChangePage"></vuetable-pagination>
        </div>

    </div>
</template>

<script>

    import Vue from 'vue'
    import Vuetable from 'vuetable-2'
    import axios from 'axios'
    import VuetablePagination from 'vuetable-2/src/components/VuetablePagination'
    import VuetablePaginationInfo from 'vuetable-2/src/components/VuetablePaginationInfo'
    import Search from '../Partials/Search.vue'

    Vue.component('search', Search)

    export default {
        components: {
            Vuetable,
            VuetablePagination,
            VuetablePaginationInfo
        },
        data () {
            return {
                api_url: window.BaseUrl + '/posts/all',
                sortOrder: [
                    {
                        field: 'id',
                        sortField: 'id',
                        direction: 'asc'
                    }
                ],
                fields: [
                    {
                        name: 'id',
                        sortField: 'id',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'title',
                        sortField: 'title',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'photo',
                        titleClass: 'center aligned',
                        callback: 'displayImageTag'
                    }
                ],
                searchParams: {}
            }
        },
        mounted() {
            this.$events.on('do-filter', eventData => { this.doFilter(eventData) });
            this.$events.on('reset-filter', e => { this.resetFilter() });
        },
        methods: {
            doFilter(data) {

                this.searchParams = {
                    'filter': data
                }

                Vue.nextTick( () => this.$refs.vuetable.refresh());
            },
            resetFilter() {

                this.searchParams = {}

                Vue.nextTick( () => this.$refs.vuetable.refresh())
            },
            displayImageTag(value) {
                if(value != null) {
                    return '<img src="' + window.BaseUrl + '/uploads/' + value + '" width="100" height="70" />'
                }
            },
            onPaginationData (paginationData) {
                this.$refs.pagination.setPaginationData(paginationData)
                this.$refs.paginationInfo.setPaginationData(paginationData)
            },
            onChangePage (page) {
                this.$refs.vuetable.changePage(page)
            }
        }
    }
</script>

We included the search component in listPosts.vue and set another variable “searchParams” this variable holds the data coming from the input which we get here:

mounted() {
    this.$events.on('do-filter', eventData => { this.doFilter(eventData) });
    this.$events.on('reset-filter', e => { this.resetFilter() });
}
methods: {
    doFilter(data) {

                this.searchParams = {
                    'filter': data
                }

                Vue.nextTick( () => this.$refs.vuetable.refresh());
            },
            resetFilter() {

                this.searchParams = {}

                Vue.nextTick( () => this.$refs.vuetable.refresh())

                console.log('reset-filter')
            }
}

this.$refs.vuetable.refresh() tells vuetable to resend the request to server in this case with the search paramters.

 

Adding Action Buttons

Let’s add another column in the list that enable users to delete, view and update posts.

Open assets/js/components/Partials/Actions.vue and add the this code:

<template>
    <div class="ui buttons">
        <router-link :to="'/view/'+rowData.id" class="ui green button"><i class="zoom icon"></i></router-link>
        <router-link :to="'/edit/'+rowData.id" class="ui orange button"><i class="edit icon"></i></router-link>
        <button class="ui red button" @click="deletePost(rowData)"><i class="delete icon"></i></button>
    </div>
</template>

<script>
    export default {
        props: {
            rowData: {
                type: Object,
                required: true
            },
            rowIndex: {
                type: Number
            }
        },
        methods: {
            deletePost (rowData) {
                this.$events.fire('delete-post', rowData);
            }
        }
    }
</script>

Modify assets/js/components/Crud/ListPosts.vue to include the Actions component:

<template>
    <div>
        <h2>Post listing</h2>

        <search></search>

        <vuetable ref="vuetable"
                  :per-page="10"
                  :api-url="api_url"
                  :fields="fields"
                  :sort-order="sortOrder"
                  :append-params="searchParams"
                  @vuetable:pagination-data="onPaginationData"
        ></vuetable>

        <div class="vuetable-pagination ui basic segment grid">
            <vuetable-pagination-info ref="paginationInfo"></vuetable-pagination-info>

            <vuetable-pagination ref="pagination" @vuetable-pagination:change-page="onChangePage"></vuetable-pagination>
        </div>

    </div>
</template>

<script>

    import Vue from 'vue'
    import Vuetable from 'vuetable-2'
    import axios from 'axios'
    import VuetablePagination from 'vuetable-2/src/components/VuetablePagination'
    import VuetablePaginationInfo from 'vuetable-2/src/components/VuetablePaginationInfo'
    import Search from '../Partials/Search.vue'
    import Actions from '../Partials/Actions.vue'

    Vue.component('search', Search)
    Vue.component('actions', Actions)

    export default {
        components: {
            Vuetable,
            VuetablePagination,
            VuetablePaginationInfo
        },
        data () {
            return {
                api_url: window.BaseUrl + '/posts/all',
                sortOrder: [
                    {
                        field: 'id',
                        sortField: 'id',
                        direction: 'asc'
                    }
                ],
                fields: [
                    {
                        name: 'id',
                        sortField: 'id',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'title',
                        sortField: 'title',
                        titleClass: 'center aligned'
                    },
                    {
                        name: 'photo',
                        titleClass: 'center aligned',
                        callback: 'displayImageTag'
                    },
                    {
                        name: '__component:actions',
                        title: 'Actions',
                        titleClass: 'center aligned',
                        dataClass: 'center aligned'
                    }
                ],
                searchParams: {}
            }
        },
        mounted() {
            this.$events.on('do-filter', eventData => { this.doFilter(eventData) });
            this.$events.on('reset-filter', e => { this.resetFilter() });
            this.$events.on('delete-post', eventData => { this.deletePost(eventData) });
        },
        methods: {
            doFilter(data) {

                this.searchParams = {
                    'filter': data
                }

                Vue.nextTick( () => this.$refs.vuetable.refresh());
            },
            resetFilter() {

                this.searchParams = {}

                Vue.nextTick( () => this.$refs.vuetable.refresh())
            },
            deletePost(data) {
                let self = this;

                let token = document.head.querySelector('meta[name="csrf-token"]');

                if(confirm('Are you sure?')) {
                    axios.delete(BaseUrl + '/posts/delete?id=' + data.id + "&_token=" + token.content, { withCredentials: true })
                        .then((result) => {
                             self.afterDelete(result);
                        });
                }
            },
            afterDelete(result) {
                Vue.nextTick( () => this.$refs.vuetable.refresh());
            },
            displayImageTag(value) {
                if(value != null) {
                    return '<img src="' + window.BaseUrl + '/uploads/' + value + '" width="100" height="70" />'
                }
            },
            onPaginationData (paginationData) {
                this.$refs.pagination.setPaginationData(paginationData)
                this.$refs.paginationInfo.setPaginationData(paginationData)
            },
            onChangePage (page) {
                this.$refs.vuetable.changePage(page)
            }
        }
    }
</script>

The most important part above is this line:

{
        name: '__component:actions',
        title: 'Actions',
        titleClass: 'center aligned',
        dataClass: 'center aligned'
}

Vuetable enables to use a component as a field using name: __component:component_name in this case we add the actions component which includes three buttons to delete, edit and view.

Now run

npm run dev

Reload the page and experiment with search, filtering, sort, and delete

 

Continue to part 2: Creating and Updating Posts>>>

 

4 1 vote
Article Rating

What's your reaction?

Excited
2
Happy
1
Not Sure
1
Confused
1

You may also like

Subscribe
Notify of
guest

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
nami
nami
5 years ago

Hello. Good article! But where is MainApp source ?

Pranjali Thorat
Pranjali Thorat
5 years ago

where is MainApp vue file