前回の第1章で、ひとまず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
を開くと……
できた!!
API の api_token
はユーザ認証情報なので隠してます。
あとはコンポーネントの template がやたらと長いので、Pug なんかも使ってみたいですねー。
次は API にバリデーションを組み込んでみましょう。