Vue.jsとBootstrapを使った仕事でモーダルを使うことになったのですが、ベタに書いていると手に負えなくなってしまいそうだったのでいい感じに扱う方法を考えました。
ADs
・モーダルのコンテンツ部分を個別のコンポーネントにし、簡潔に管理できるようにする
・モーダルの中からもう1枚モーダルを開く場面もある
上記要件を達成するには、まずモーダルの外側を一つのコンポーネントとし、コンテンツ部分を個別のコンポーネントとして動的に読み込むようにします。
つまり以下のような構成にします。
DEMOでは2つのモーダルコンテンツを順繰りに読み込んでいますので、すべてが個別のモーダルとして開かれ無限ループとなります。
よく使う処理なので関数部分をmain.jsに書き、どのコンポーネントからでも呼べるようにします。
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 62 63 64 |
import Vue from 'vue' import App from './App.vue' import $ from "jquery"; //BootstrapのモーダルはjQueryを使っているので… //Bootstrap用のCSSとJSを読み込む import 'bootstrap/dist/css/bootstrap.min.css' require('bootstrap/dist/js/bootstrap'); Vue.config.productionTip = false //コンポーネントのレジスト //components配下の.vueファイルを全部コンポーネントとして登録します。 //厳密にやる場合は使うコンポーネントを1個ずつimportしましょう。 const files = require.context('./components/', true, /\.vue$/); const components = {}; files.keys().forEach(key => { components[key.replace(/(\.\/|\.vue)/g, '')] = files(key).default; }); // 読み込んだvueファイルをグローバルコンポーネントとして登録 Object.keys(components).forEach(key => { Vue.component(key, components[key]); }); //モーダル用function //上記でコンポーネント登録しているはずなのになぜかエラーが出るので… import modalOuter from './components/modalOuter.vue' //どこからでも使えるようにglobal mixinとして登録 var modalFunc = { methods: { openModal(contents, size) { var currentModal = document.querySelectorAll(".modal"); var modalLength = currentModal.length; var modalId = 'modal' + (modalLength + 1); var ComponentClass = Vue.extend(modalOuter); var instance = new ComponentClass({ propsData: { contents: contents,//openModal関数の引数としてコンテンツ用のコンポーネントを指定する size: size, //モーダルのサイズ(modal-sm.modal-lgなどを指定) id: modalId //モーダルID(modal-数字 で自動的に設定されます) } }); instance.$mount(); document.body.appendChild(instance.$el); //z-indexを調整して2枚めのモーダルのbackdropが1枚めモーダルの全面に来るようにする var zIndexbase = 1050; //モーダルのデフォルトz-index this.$nextTick(function () { var $currentModal = $('#' + modalId); //show.bs.modalへのバインドだとbackdropが生成されていないのでtransitionstartにバインドする $currentModal.css('z-index', zIndexbase + modalLength).on('transitionstart', function () { $(this).next('.modal-backdrop').css('z-index', zIndexbase + modalLength - 1) }) $currentModal.modal('show'); }) } }, } Vue.mixin(modalFunc); new Vue({ render: h => h(App) }).$mount('#app') |
openModal関数の引数としてモーダルのコンテンツ用コンポーネントとサイズを指定し、それをpropsとしてモーダル外側コンポーネントに渡します。
モーダルの外側コンポーネント(ここではmodalOuter.vueとしている)はpropsでコンテンツ用コンポーネントとサイズを受け取るだけで、コンテンツ部分をcomponentタグで動的に読み込むようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<template> <div class="modal fade" :id="id" tabindex="-1" role="dialog" aria-labelledby="id" aria-hidden="true" > <div class="modal-dialog" :class="size" role="document"> <component v-bind:is="contents"></component> </div> </div> </template> <script> export default { name: "modalOuter", props: { contents: null, size: null, id: null } //methodsやcomputedなどは必要に応じて追加 }; </script> |
あとはopenModal関数をaやbuttonタグにv-on:clickで設定します。
1 2 3 4 |
<a href="#" v-on:click="openModal('開きたいコンポーネント名','モーダルサイズ')">モーダルを開く</a> たとえば、modalContentsというコンポーネントをlgサイズで開く場合は以下になります。 <a href="#" v-on:click="openModal('modalContents','modal-lg')">モーダルを開く</a> |
modalContents.vueは以下のようにコンテンツ部分を抜き出したものです。
必要に応じてmethodsやdataなどの処理を追加します。
コンポーネントとして独立しているため、個別の管理が可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<template> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalLabel">これはmodalContentsです。</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> これはmodalContentsです。 <br /> <br /> <a href="#" v-on:click="openModal('modalsubContents','modal-sm')">さらにモーダルを開く!</a> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button> <button type="button" class="btn btn-primary">OK</button> </div> </div> </template> <script> export default {}; </script> |
コード全文を公開していますので、参考になれば幸いです。
ADs
コメントはまだありません。