フロントエンドのフレームワークを新たに使い始めるとき、ちょっとつまづくのがこの「モーダルの実装」じゃないでしょうか。
特に、コンポーネントに切り出してどこからでも関数一つで呼び出せるようにするところまでやるのって、意外とサンプルもなくて手間だったりします。
それをVue.jsで実装する方法をご紹介します。
モーダルのコンテナdivを用意
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<template> <div> <div class="wrap-all"> <div class="wrap-all-inner"> <div class="container-contents"> <div class="content wrap"> <router-view></router-view> </div> </div> </div> </div> <div id="modals"> <component v-for="(modal, i) in modals" :is="modal.component" v-bind:params="modal.params"></component> </div> </div> </template> <script> const store = require('../store/').default; export default { computed: { modals: () => { return store.getters.modals; } }, } </script> |
ここではこのapp.vueのrouter-viewにすべてのコンポーネントが入ってくると仮定します。
要は、どのページにいても表示されるテンプレートですね。
そこにモーダルのコンテナを用意して、modalsという配列をバインドしてコンポーネントが自動的に生成されるようにしています。
こうしておけば、modals配列にpush/popするだけでモーダルが出たり消えたりするわけです。
modals配列はVuexのストアで管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
const Vuex = require('vuex'); const {MUTATION} = require('./mutation-types'); const {ACTION} = require('./action-types'); const state = { modals: [] }; const getters = { modals: (state) => { return state.modals; } }; const actions = { [ACTION.OPEN_MODAL]({commit}, params) { commit(MUTATION.OPEN_MODAL, params); }, [ACTION.CLOSE_MODAL]({commit}) { commit(MUTATION.CLOSE_MODAL); }, }; const mutations = { [MUTATION.OPEN_MODAL](state, modal) { state.modals.push(modal); }, [MUTATION.CLOSE_MODAL](state) { state.modals.pop(); }, }; export default new Vuex.Store({ state, getters, actions, mutations }); |
1 2 3 4 5 6 7 |
export const OPEN_MODAL = 'OPEN_MODAL'; export const CLOSE_MODAL = 'CLOSE_MODAL'; export const ACTION = { OPEN_MODAL, CLOSE_MODAL, }; |
1 2 3 4 5 6 7 |
export const OPEN_MODAL = 'OPEN_MODAL'; export const CLOSE_MODAL = 'CLOSE_MODAL'; export const MUTATION = { OPEN_MODAL, CLOSE_MODAL, }; |
このようにVuexのストアでmodals配列を管理することで、どのページ/コンポーネントからでも
1 |
store.dispatch(OPEN_MODAL, modal) |
この1行でモーダルをオープンできるようになります。
モーダルのコントロールをserviceで一元化
各コンポーネントから直接store.dispatchをコールしてもいいんですが、たとえばalertモーダルとかconfirmモーダルとか、タイプ別で汎用化していくことを視野に入れて、modalサービスを作成しそこにモーダル関連の関数をまとめることにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
const store = require('../store/').default; const {OPEN_MODAL} = require('../store/action-types'); const {CLOSE_MODAL} = require('../store/action-types'); export default { alert (message, onOk) { store.dispatch(OPEN_MODAL, { component: 'modal-alert', params: { message: message, onOk: onOk }, }); }, confirm (message, onOk, onNg) { }, openComponent (component) { }, close () { store.dispatch(CLOSE_MODAL); }, closeAll() { } } |
こういう感じです。
今回は実装しませんが、confirmモーダルや任意のコンポーネントをモーダルで開く関数をこのサービスに作っていくイメージです。
alertモーダルコンポーネント
メッセージを表示して、OKボタンで閉じるだけのalertモーダルを作ります。
vueファイルはこういう感じになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<template> <transition name="modal"> <div class="modal-mask"> <div class="modal-wrapper" v-on:click="closeModal()"> <div class="modal-container" v-on:click.stop=""> <div class="modal-body"> <slot name="body"> {{params.message}} </slot> </div> <div class="modal-footer"> <slot name="footer"> <button type="button" class="btn btn04 w-auto" @click="closeModal()">閉じる</button> </slot> </div> </div> </div> </div> </transition> </template> <script> import modal from '../../../services/modal'; export default { props: ['params'], methods: { closeModal() { modal.close(); if (this.params.onOk) { this.params.onOk(); } } } } </script> |
paramsというpropsを用意して、その中に
・表示したいメッセージ
・OKボタンをクリックしたときにコールバック
が入ってくる想定です。
関連CSSは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
.modal-mask { position: fixed; z-index: 9998; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, .5); display: table; transition: opacity .3s ease; } .modal-wrapper { display: table-cell; vertical-align: middle; text-align: center; } .modal-container { display: inline-block; width: auto; margin: 0px auto; padding: 20px 30px; background-color: #fff; border-radius: 2px; box-shadow: 0 2px 8px rgba(0, 0, 0, .33); transition: all .3s ease; font-family: Helvetica, Arial, sans-serif; } .modal-header h3 { margin-top: 0; color: #42b983; } .modal-body { margin: 20px 0; } .modal-footer { display: block; text-align: center; } .modal-default-button { float: right; } .modal-enter { opacity: 0; } .modal-leave-active { opacity: 0; } .modal-enter .modal-container, .modal-leave-active .modal-container { -webkit-transform: scale(1.1); transform: scale(1.1); } |
そしてこのコンポーネントをグローバルコンポーネントに登録して、どこからでも呼び出せるようにしておきます。
1 2 3 4 5 6 7 |
・・・ // モーダル用コンポーネント import ModalAlert from './components/Modal/Alert'; Vue.component('modal-alert', ModalAlert); ・・・ |
これで仕込みは完了です。
あとは実際にテストページを作ってモーダルを開いてみましょう。
モーダルを開くテストページ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<template> <div> <div> <button @click="openAlertModal('あらーともんごんてすとてすと')">アラートモーダルおーぷん</button> </div> </div> </template> <script> import modal from '../services/modal'; export default { methods: { openAlertModal(message) { modal.alert(message); } } } </script> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
・・・ const router = new Vuerouter({ mode: 'history', routes: [ { path: '/app', component: require('./components/App.vue'), children: [ { path: '/app/test', component: require('./components/Test.vue') }, ] }, ] }); ・・・ |
テスト用のコンポーネントを作成し、ベースになるApp.vueのrouter-viewの中に表示されるようにしました。
これで、以下のような形でアラートモーダルが出せるかと思います。
モーダル呼び出し機能は最初に作っておくべし
いかがだったでしょうか。
こういう風に、汎用的なパーツは先にモジュールとして切り出しておくと、開発全体がぐっと楽になります。
特にこのモーダルのモジュール化は個人的には必須作業ですね。
ご参考になれば幸いです。