Vue.js と本気で戦ってみた ~ 第2章 : Laravel Mix でコンポーネント化

Vue.js で RESTful API

で、ひとまず1枚のビューファイルでページの一覧表示と更新ができるようになりました。
今回は Laravel Mix を使ってコンポーネントに分割していきます。

コンポーネントの作成

まずコンポーネントを2つ作成します。page-summary はそのままコピペ。
やってることは、「自分自身をクリックされたら親の edit メソッドを呼ぶ」だけ。

<template>
    <article>
        <div class="card" @click="edit(page.id)">
            <header class="card-header">
                <h3 class="card-title" v-html="page.title"></h3>
                <div>slug : <span v-html="page.slug"></span></div>
            </header>
            <div class="card-body">
                <div v-html="page.content"></div>
            </div>
        </div>
    </article>
</template>
<script>
    export default {
        name: "page-summary",
        props: ["page"],
        methods: {
            edit: function (id) {
                this.$emit("edit", id);
            }
        }
    }
</script>

次に edit-page-modal 。こちらは初期化 init メソッドと保存 save メソッドで API を叩く必要があるのですが、.vue 内では blade で使えてた Laravel の URL 生成関数が使えないので、init メソッドで引数として渡すようにしました。

<template>
    <div id="edit-page"
            class="modal fade"
            tabindex="-1"
            role="dialog"
            aria-labelledby="edit-page-label"
            aria-hidden="true">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <input type="hidden"
                            name="id"
                            id="page-id"
                            v-bind:value="id" />
                    <h5 class="modal-title" id="edit-page-label">Edit page</h5>
                    <button type="button"
                            class="close"
                            data-dismiss="modal"
                            aria-label="Close">
                        <span aria-hidden="true">×</span>
                    </button>
                </div>
                <form v-on:submit.prevent="save(id)">
                    <div class="modal-body">
                        <div class="form-group">
                            <label for="page-title">Title</label>
                            <input type="text"
                                    class="form-control"
                                    id="page-title"
                                    v-model="title"
                                />
                        </div>
                        <div class="form-group">
                            <label for="page-slug">Slug</label>
                            <input type="text"
                                    class="form-control"
                                    id="page-slug"
                                    v-model="slug"
                                />
                        </div>
                        <div class="form-group">
                            <label for="page-content">Content</label>
                            <textarea
                                    class="form-control"
                                    id="page-content"
                                    v-model="content"
                                    rows="10"
                                ></textarea>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="button"
                                class="btn btn-secondary"
                                data-dismiss="modal"
                            >Cancel</button>
                        <button type="submit"
                                class="btn btn-primary"
                            >Save</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</template>
<script>
    export default {
        name: "edit-page-modal",
        data: function () {
            return {
                urlShow: "",
                urlUpdate: "",
                id: 0,
                title: "",
                slug: "",
                content: ""
            }
        },
        methods: {
            init: function (id, urlShow, urlUpdate) {
                console.log("modal::init");
                // initialize
                this.urlShow = urlShow;
                this.urlUpdate = urlUpdate;
                this.id = 0;
                this.title = "";
                this.slug = "";
                this.content = "";
                // get detail
                var url = this.urlShow;
                console.log("get", url);
                axios.get(url)
                .then(response => {
                    this.id = response.data.id;
                    this.title = response.data.title;
                    this.slug = response.data.slug;
                    this.content = response.data.content;
                })
                .catch(response => {
                    alert(response);
                });
                // show modal
                var el = "#" + this.$el.id;
                $(el).modal();
            },
            save: function () {
                console.log("modal::save", this.id);
                // update
                var url = this.urlUpdate;
                console.log("put", url);
                axios.put(url, {
                    id: this.id,
                    title: this.title,
                    slug: this.slug,
                    content: this.content
                })
                .then(response => {
                    // hide modal
                    var el = "#" + this.$el.id;
                    $(el).modal('hide');
                    // redraw list
                    this.$emit("redraw");
                })
                .catch(response => {
                    alert(response);
                });
                return false;
            },
            redraw: function (id) {
                this.$emit("init");
            }
        }
    }
</script>

そして本体。API の URL をここで生成し、コンポーネントに渡しています。

@extends('admin_layout')
{{-- authorized user --}}
@set( $user, \Illuminate\Support\Facades\Auth::user() )
@section('child')
            <head>
                <h2>Page manage</h2>
            </head>
            <section id="admin-pages">
                <div class="row">
                    <page-summary
                        class="col-md-6"
                        v-for="page in pages"
                        v-bind:key="page.id"
                        v-bind:page="page"
                        ref="summary"
                        @edit="modal"
                    ></page-summary>
                </div>
                <edit-page-modal
                        ref="modal"
                        @redraw="init"
                ></edit-page-modal>
            </section>
@endsection
@section('script')
            <script>
var vuePages;
window.onload = function() {
    // list view
    vuePages = new Vue({
        el: "#admin-pages",
        data: {
            pages: []
        },
        component: [
            "page-summary",
            "edit-page-modal"
        ],
        methods: {
            init: function() {
                console.log("vuePages::init");
                // get list
                this.pages = [];
                var url = "{{ route('page.index') }}";
                console.log("get", url);
                url += "?api_token=" + "{{ $user->api_token }}";
                axios.get(url)
                .then(response => {
                    for (idx in response.data) {
                        this.pages.push(response.data[idx]);
                    }
                })
                .catch(response => {
                    alert(response);
                });
            },
            modal: function(id) {
                console.log("vuePages::modal", id);
                var showUrl = "{{ route('page.show', ['page' => ':id']) }}".replace(":id", id);
                showUrl += "?api_token=" + "{{ $user->api_token }}";
                var updateUrl = "{{ route('page.update', ['page' => ':id']) }}".replace(":id", id);
                updateUrl += "?api_token=" + "{{ $user->api_token }}";
                this.$refs.modal.init(id, showUrl, updateUrl);
            }
        }
    });
    vuePages.init();
};
            </script>
@endsection

ここで、親から子を呼び出すところに注目。
子コンポーネントで使うメソッド名を定義しています。

<page-summary
    class="col-md-6"
    v-for="page in pages"
    v-bind:key="page.id"
    v-bind:page="page"
    ref="summary"
    @edit="modal"
></page-summary>

@edit="modal" と書いていますが、子が呼び出すメソッド edit に親の modal を紐付けています。エイリアスみたいなもんですね。

<template>
<script>
    export default {
        name: "page-summary",
        props: ["page"],
        methods: {
            edit: function (id) {
                this.$emit("edit", id);
            }
        }
    }
</script>

これにより、子コンポーネント内で this.$emit("edit") と出てきたら親の modal() が動くようになります。
イメージとしてはこんな感じ。

// 子のメソッド
function modal()
{
    // モーダル表示
}
var obj = {};
// 親にメソッドを追加
obj.edit = modal;
// 親のメソッドを呼ぶ
obj.edit();

Laravel Mix へ登録

そしてコンポーネントを Laravel Mix に登録。デフォルトの app.js ではなく admin.js としているのは、Vue.js を読み込んでいるかどうかの違いだけです。

/**
 * 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');
import fontawesome from '@fortawesome/fontawesome';
import regular from '@fortawesome/fontawesome-free-solid';
import solid from '@fortawesome/fontawesome-free-regular';
import brands from '@fortawesome/fontawesome-free-brands';
fontawesome.library.add(regular);
fontawesome.library.add(solid);
fontawesome.library.add(brands);
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 -> 
 */
// 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);
Vue.component('page-summary', require('./components/PageSummaryComponent.vue').default);
Vue.component('edit-page-modal', require('./components/EditPageModalComponent.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',
});
*/

これでコンソールから npm run dev を実行。
ブラウザで /admin/page を開くと……
Vue.js で RESTful API
できた!!
API の api_token はユーザ認証情報なので隠してます。

あとはコンポーネントの template がやたらと長いので、Pug なんかも使ってみたいですねー。
次は API にバリデーションを組み込んでみましょう。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です