いつもと同じようにまずは、出来上がりのイメージです。
ボタンクリックでモーダルダイアログを表示、OKで入力テキストを返しダイアログを閉じてテキストを表示、Cancelで何もせずダイアログを閉じる。といった振る舞いのサンプルに挑戦です。

f:id:Shikataramuno:20181130093123p:plain
モーダルダイアログの出来上がりイメージ
コードはここです。 GitHub - Shikataramuno/modal-dialog-sample at sample

モーダルダイアログの出し方(考え方)

公式サイトを見て、私なりに理解した内容です。
モーダルダイアログを子 コンポーネント として定義しておき、親 コンポーネント 側で v-if ディレクティブを使って子 コンポーネント の表示を制御するというアプローチです。

コンポーネント (モーダルダイアログ コンポーネント 側)のスタイル指定で z-index を大きな値にして画面全体にマスクをかけ、その上にダイアログの要素を配置するようにします。こうすることで、子 コンポーネント を表示させたときに親 コンポーネント がマスクされ、子 コンポーネント がモーダルダイアログとして表示されるようになります。ダイアログを閉じるときには子 コンポーネント から親 コンポーネント へイベントを emit し、 v-if ディレクティブの条件を非表示に切り替える様にします。

では、簡単なサンプルを作りながら確認していきしょう。

コンポーネント の構成

作成したプロジェクトの src/components フォルダにダイアログを呼び出す側の コンポーネント とダイアログ本体の コンポーネント をそれぞれ定義します。

│ App.vue │ main.ts │ shims-tsx.d.ts │ shims-vue.d.ts ├─assets │ logo.png ├─components │ ModalDialog.vue │ ModalDialogSample.vue └─style modal.css

ModalDialogSample.vueがダイアログを呼び出す側(i.e. 親) コンポーネント 、ModaiDialog.vueがダイアログ本体(i.e. 子) コンポーネント となります。

< div class = "ModalDialogSampleBase" > < h1 > {{ msg }} </ h1 > < button @click= "show"'>ダイアログの表示</button> <div class="message-field">ダイアログ入力メッセージ<br>{{message}}</div> <ModalDialog v-if="showDialog" @ok=' ok ' @cancel=' cancel '/> </template>

6行目で子 コンポーネント を配置しています。ここでは v-if ディレクティブを使い showDialog の真偽で表示/非表示を切り替えています。

  • ダイアログの表示
    この showDialog は4行目のボタンのClick イベントハンドラ 内でfalse→trueにセットしダイアログを表示させます。

  • ダイアログの非表示
    コンポーネント からEmitされるイベントを補足し、 showDialog をtrue→falseにリセットしダイアログを非表示にします。 6行目の v-if の後にある @ok @cancel が子 コンポーネント からのイベントを補足して イベントハンドラ を呼び出す定義になります。

  • 次にScriptです。

    Script

    <script lang="ts">
    import { Component, Vue } from 'vue-property-decorator';
    import ModalDialog from './ModalDialog.vue';
    @Component({
      components: {
        ModalDialog,
    export default class ModalDialogSample extends Vue {
      name: string = 'ModalDialogSample';
      msg: string = 'モーダルダイアログのサンプル';
      message: string = '';
      showDialog: boolean = false;
      show(): void {
        this.showDialog = true;
      ok(message: string): void {
        this.showDialog = false;
        this.message = message;
      cancel(): void {
        this.showDialog = false;
    </script>
    

    三つのイベントハンドラを定義しています。

    ダイアログ表示ボタンのイベントハンドラshow()、子コンポーネントからのイベントハンドラok()cancel() になります。 それぞれメンバ変数のshowDialogの真偽をセット、リセットして表示/非表示を切り替えています。

    ここで ok(message: string) は引数に string 型の message が定義されていますが、これは ok イベントのパラメタでダイアログの input フィールドに入力された文字列を子コンポーネントから受けとる為です。 では、次に子コンポーネント側を見てみましょう。

    <div class="modal-wrapper"> <div class="modal-container"> <div class="modal-header">モーダルダイアログ </div> <div class="modal-body"> <input type='text' v-model='message' placeholder="メッセージ入力欄です"> </div> <div class="modal-footer"> <button class="modal-default-button" @click='ok(message)'>OK</button> <button class="modal-default-button" @click='cancel()'>Cancel</button> </div> </div> </div> </div> </transition> </template>

    modal-mask が親コンポーネントを覆う為の要素になります。Styleで背景色を透過させてダイアログを表示した際でも親コンポーネントが薄く見えるように、それっぽくしています。

    ダイアログ本体は modal-container 要素の中身がそれにあたります。ヘッダ、ボディ、フッタを定義していてボディにinput要素を、フッタにok、cancelのボタンを配置しています。 この辺りはbootstrap*1CSSが参考になります。

    12,13行目でok、cancelのイベントハンドラを登録しています。ok ハンドラでは引数にmessageを指定していますが、この message は 9行目で input にバインドされているので、入力テキストが入ります。ここがイベントハンドラ経由で子→親へ入力テキストを伝える動作の起点になります。

    Script

    最後にダイアログ本体のScriptです。

    <script lang="ts">
    import { Component, Emit, Vue } from 'vue-property-decorator';
    export default class ModalDialog extends Vue {
      message: string = '';
      @Emit('ok')
      ok(str: string): void {
      @Emit('cancel')
      cancel(): void {
    </script>
    

    これも至ってシンプルです。 ok、cancelのイベントハンドラを定義しているだけです。TypeScript なので従来の this.$emit にあたる実装がデコーレータを使った記述の仕方になっています。 例えば6~9行目の ok イベントハンドラは 従来の JavaScript で書くと

    ok(str) {
      this.$emit('ok', str)
    

    という感じだと思うのですが、デコーレータを使った記述では、@Emitでemitするイベントを定義し、その後にハンドラを実装するという書き方になります。 また、親コンポーネントに渡すパラメタの指定が暗黙的になってしまっている気がして個人的には違和感を感じて馴染めていません。この辺りご意見あればぜひ、お願いしたいと思います。

    出来上がり!

    npm run serve

    で動かします。
    TypeScript 故の戸惑いもありましたが、ひとまずモーダルダイアログの実装に加えコンポーネント間のデータ受け渡しも完成しました。必ずといっていいほど使う局面が多いモーダルダイアログの実装、自分が見るレシピとしても活用したいと思います。

    最後まで読んでいただきありがとうございます。
    今回はモーダルダイアログの実装を具体的に解説してみました。少しでもお役に立てれば幸いです。