20/01/2019, 18:02

Xây dựng ựng dụng thêm sửa xóa với Vue, Vuex, Vue Router và Laravel

Ai đã từng theo dõi các bài viết cũng mình có lẽ đã từng đọc qua bài viết Laravel 5.5 và React JS, hôm nay mình sẽ viết về một framework khác cũng thường được sử dụng với Laravel, đó là VueJs. Trước khi đọc bài viết này bạn cần có kiến thức cơ bản về Laravel và VueJS hoặc có thể tìm hiểu qua ...

Ai đã từng theo dõi các bài viết cũng mình có lẽ đã từng đọc qua bài viết Laravel 5.5 và React JS, hôm nay mình sẽ viết về một framework khác cũng thường được sử dụng với Laravel, đó là VueJs. Trước khi đọc bài viết này bạn cần có kiến thức cơ bản về Laravel và VueJS hoặc có thể tìm hiểu qua tại đây: Laravel - Frontend, VueJS - Get Started.

Tạo seeder cho bảng users:

php artisan make:seeder UsersTableSeeder

Thêm đoạn code này vào run() function trong file database/seeds/UsersTableSeeder:

factory(AppUser::class, 50)->create();

Chạy migrate command

php artisan migrate

Chạy seed command chỉ định file seeder chúng ta vừa tạo:

php artisan db:seed --class=UsersTableSeeder

Cài đặt các js packages. Để chạy được command npm bạn cần cài đặt Npm và Nodejs

npm install

Cài đặt package vue-router để routing. Chạy command sau:

npm install --save vue-router

Cài đặt package vuex. Vuex là một thư viện quản lý state cho ứng dụng Vuejs. Nó hoạt động như một centralized store cho tất cả các thành phần trong một ứng dụng, với các quy tắc đảm bảo rằng state chỉ có thể được thay đổi khi có một action cụ thể đoán trước được. Nếu bạn đã từng tìm hiểu về ReactJs thì nó cũng giống như flux hay redux bên ReactJs vậy.

npm install --save vuex

Để recomplile tự động mỗi khi chúng ta thay đổi script ở thư mục assets chạy command sau:

npm run watch

Restful API

Thêm file appHttpControllersUserController.php với nội dung:

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use AppUser;

class UserController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return IlluminateHttpResponse
     */
    public function index()
    {
        $users = User::all();
        return response()
            ->json($users);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return IlluminateHttpResponse
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  IlluminateHttpRequest  $request
     * @return IlluminateHttpResponse
     */
    public function store(Request $request)
    {
        User::create($request->all());
        return response()
            ->json(['message' => 'Success: You have added an user']);
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function edit($id)
    {
        $user = User::find($id);
        if (! $user) {
            return response()
            ->json(['error' => 'The user is not exists']);
        }
        return response()
            ->json($user);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function update(Request $request, $id)
    {
        $user = User::find($id);
        if (! $user) {
            return response()
            ->json(['error' => 'Error: User not found']);
        }
        $user->update($request->all());
        return response()
            ->json(['message' => 'Success: You have updated the user']);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function destroy($id)
    {
        $user = User::find($id);
        if (! $user) {
            return response()
            ->json(['error' => 'Error: User not found']);
        }
        $user->delete();
        return response()
            ->json(['message' => 'Success: You have deleted the user']);
    }
}

Đoạn code trên implement các function với mục đích như sau:

  • index: Lấy tất cả các users để hiển thị ngoại list
  • edit: Lấy user info ra màn edit
  • update: Cập nhật thông tin user
  • destroy: Xoá user

Routes

Chúng ta sửa file routes/web.php với nội dung như sau:

<?php
Route::group(['prefix' => 'api'], function () {
    Route::resource('users', 'UserController');    
});
Route::view('/{any}', 'welcome')
    ->where('any', '.*');

Điều chúng ta cần chú ý nhất ở đây là đoạn route này cần đặt cuối file để bắt tất cả những uri khác các các request trên vào view chứa reactjs.

Route::view('/{any}', 'welcome')
    ->where('any', '.*');

Đoạn routes này thì chỉ đơn giản là nhóm các api vào 1 group có prefix là api mà thôi:

Route::group(['prefix' => 'api'], function () {
    Route::resource('users', 'UserController');    
});

Chỉnh sửa file view

Thay đổi file resources/views/welcome.blade.php với nội dung:

<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="awidth=device-awidth, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <title>Laravel 5.5 - ReactJS Example</title>
        <link rel="stylesheet" type="text/css" href="{{ asset('css/app.css') }}">
        <script type="text/javascript">
            window.Laravel = {!! json_encode([
                'baseUrl' => url('/'),
                'csrfToken' => csrf_token(),
            ]) !!};
        </script>
    </head>
    <body>
        <div id="app"></div>
        <script type="text/javascript" src="{{ asset('js/app.js') }}"></script>
    </body>
</html>

File view trên có thêm biến javascript window.Laravel sẽ chứa những giá trị động lấy từ Laravel sử dụng trong javascript. Chúng ta sẽ fill nội dung Vuejs vào thẻ div có id là app.

Tạo sẵn các file component và sửa file app.js

Đây là cấu trúc thư mục phần Vuejs sau khi hoàn thành:

Đầu tiên tạo sẵn các file App.vue, UserList.vue, UserRow.vue, CreateUser.vue, EditUser.vue trong resourcesjscomponents.

File resourcesjsapi.js chứa URI của các APIs, sau này nếu muốn sửa chỉ cần sửa một nơi:

const RESOURCE_USER = 'api/users';

export {
    RESOURCE_USER
};

File resourcesjsstoreusersStore.js với nội dung:

import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
import {RESOURCE_USER} from '../api';

Vue.use(Vuex);

const usersStore = new Vuex.Store({
    state: {
        users: [],
        user: {},
    },
    mutations: {
        FETCH(state, users) {
            state.users = users;
        },
        FETCH_ONE(state, user) {
            state.user = user;
        },
    },
    actions: {
        fetch({ commit }) {
            return axios.get(RESOURCE_USER)
                .then(response => commit('FETCH', response.data))
                .catch();
        },
        fetchOne({ commit }, id) {
            axios.get(`${RESOURCE_USER}/${id}/edit`)
                .then(response => commit('FETCH_ONE', response.data))
                .catch();
        },
        deleteUser({}, id) {
            axios.delete(`${RESOURCE_USER}/${id}`)
                .then(() => this.dispatch('fetch'))
                .catch();
        },
        editUser({}, user) {
            axios.put(`${RESOURCE_USER}/${user.id}`, {
                name: user.name,
                email: user.email,
                password: user.password,
            })
                .then(() => this.dispatch('fetch'));
        },
        addUser({}, user) {
            axios.post(`${RESOURCE_USER}`, {
                name: user.name,
                email: user.email,
                password: user.password,
            });
        }
    }
});

export default usersStore;

Trung tâm của các ứng dụng Vuex chính là store, chứa toàn bộ các state của ứng dụng. Khi các components nhận được state từ nó, mỗi khi nó thay đổi sẽ được cập nhật một cách hiệu quả, và nó không thể bị thay đổi trực tiếp. Store bao gồm:

  • State: Chứa tất cả trạng thái của ứng dụng.
  • Mutations: Bạn chỉ có thể thay đổi store khi commit một mutation. Nó gần giống như event.
  • Actions: Giống với mutations, nhưng khác là:
  • Thay vì mutate trực tiếp state, nó commit qua các mutations .
  • Nó có thể chứa các các hành động bất đồng bộ. Ngoài ra còn có Getters, và Modules, các bạn có thể thao khảo ở trang tài liệu của Vuex: https://vuex.vuejs.org/guide/

Sửa nội dung file resourcesjsapp.js:

// resourcesjsapp.js

require('./bootstrap');

window.Vue = require('vue');

import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './components/App.vue';
import routes from './routes';
import usersStore from './store/usersStore';

Vue.use(VueRouter);

const router = new VueRouter({
    routes,
    // mode: 'history',
});
window.events = new Vue();


new Vue({
    el: '#app',
    render: h => h(App),
    router,
    store: usersStore,
});

Đầu tiên để có thể sử dụng được các thuộc tính của VueRouter chúng ta cần use nó

Vue.use(VueRouter);

Sau đó khởi tạo router

const router = new VueRouter({
    routes,
    // mode: 'history',
});

Khởi tạo vuejs với router, và store, chúng ta sẽ sử dụng component App làm layout:

new Vue({
    el: '#app',
    render: h => h(App),
    router,
    store: usersStore,
});

File resources/routes.js định nghĩa các routes trong ứng dụng Vuejs:

import CreateUser from './components/CreateUser'
import EditUser from './components/EditUser'
import UserList from './components/UserList'

const routes = [
    {
        path: '/',
        component: UserList,
        name: 'users.index',
    },
    {
        path: '/users/create',
        component: CreateUser,
        name: 'users.create',
    },
    {
        path: '/users/edit/:id',
        component: EditUser,
        name: 'users.edit',
    },
];
export default routes;

Path là đường dẫn hiển thị trên thanh địa chỉ, component là component sẽ được render vào layout, và name dùng để sử dụng để định danh route, giúp cho chúng ta có thể sử dụng một cách thuận tiện hơn. Ngoài ra còn có rất nhiều thuộc tính khác, các bạn có thể tham khảo ở link tài liệu https://router.vuejs.org/guide/essentials/named-routes.html

Tạo layout

Tạo file resources/js/components/App.vue với nội dung:

// resources/js/components/App.vue

<template>
    <div class="container">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <a class="navbar-brand" href="#">Laravel 5.5 - ReactJS Example</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav mr-auto">
                    <li :class="['nav-item', {active: $route.name === 'users.index'}]">
                        <router-link class="nav-link" :to="{name: 'users.index'}">Users</router-link>
                    </li>
                    <li :class="['nav-item', {active: $route.name === 'users.create'}]">
                        <router-link class="nav-link" :to="{name: 'users.create'}">Add User</router-link>
                    </li>
                </ul>

            </div>
        </nav>
        <router-view></router-view>
    </div>
</template>

<script>
    export default {
    }
</script>

Ở đoạn code trên có component mới là <router-link> có sẵn trong VueRouter, tác dụng của nó là tạo ra thẻ <a/>, nhưng khác là nó có thể sử dụng được object $route, và nhiều hỗ trợ khác nữa bạn có thể tìm hiểu rõ hơn tại đây: https://router.vuejs.org/api/#router-link-props .

Ngoài ra còn có component khác là <router-view> chính là nơi các component UserList, CreateUser, EditUser được định nghĩa ở file routes.js được render.

Tạo component hiển thị danh sách users

User List

Tạo file resources/js/components/UserList.vue với nội dung:

// resources/js/components/UserList.vue

<template>
    <div>
        <h1>Users</h1>
        <div class="clearfix">
            <router-link class="btn btn-success pull-righ" :to="{name: 'users.create'}">Add User</router-link>
        </div><br />
        <table class='table table-hover'>
            <thead>
            <tr>
                <td>ID</td>
                <td>Name</td>
                <td>Email</td>
                <td>Actions</td>
            </tr>
            </thead>
            <tbody>
            <user-row v-for="user in users" :user="user" :key="user.id"></user-row>
            </tbody>
        </table>
    </div>
</template>

<script>
    import { mapState } from 'vuex';
    import UserRow from "./UserRow";
    export default {
        components: {UserRow},
        computed: {
            ...mapState(['users']),
        },
        created: function () {
            this.$store.dispatch('fetch');
        }
    }
<
                                          
0