Vue.jsで独自の確認ダイアログ(confirm)をつくる


WEBアプリケーションでユーザに意思を確認したいときは`window.confirm`が使われるかと思います。
しかし、`window.confirm`系で出せるダイアログはブラウザ依存なので、独自のスタイルを当てることができません。
無機質な確認ダイアログだと統一性がなくて悲しい気持ちになりますよね。

そこでVue.jsで独自の確認ダイアログを作成してみたいと思います。

## 実装方針

簡単に実装方針をまとめます。

- メソッドはVueのグローバルミックスインとする
  - 引数は表示するメッセージ
  - 返り値は非同期で受け取る
      - メソッドの結果は`Promise<boolean>`
- Vuetifyの`v-dialog`を使用する
  - ~~スタイルやアニメーションを考えるのが面倒なので~~
- コンポーネントは呼び出し毎に作成・破棄する
  - 常に用意しておくことはしない
  - bodyの末尾に追加

## 実装

上記の観点から確認ダイアログを実装します。

### 確認ダイアログのコンポーネント

`created`のタイミングで`body`に追加をしています。

※ `vue-class-component`を使用しています。

```vue:Confirm.vue
<template lang="pug">
v-dialog(v-model="isActive" persistent max-width="30%")
  v-card
    v-card-text {{ message }}
    v-card-actions.pt-0
      v-spacer
      v-btn(
        color="primary"
        depressed
        @click.native="ok"
      ) OK
      v-btn(
        outline
        @click.native="cancel"
      ) キャンセル
</template>

<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
import { Prop } from 'vue-property-decorator';

@Component
export default class extends Vue {

    isActive = false;

    @Prop() message: string;
    @Prop() success: () => void;
    @Prop() failure: () => void;

    created() {
        const el = document.createElement('div');
        document.getElementsByTagName('body')[0].appendChild(el);
        this.$mount(el);

        this.isActive = true;
    }

    ok() {
        this.success();
        this.destroy();
    }

    cancel() {
        this.failure();
        this.destroy();
    }

    destroy() {
        this.isActive = false;

        // 即座にdomを削除するとtransitionする前に消えてしまうので、200ms待つ
        setTimeout(() => {
            this.$el.parentNode.removeChild(this.$el);
            this.$destroy();
        }, 200);
    }

}
</script>
```

### インストーラ

次にミックスインをするインストーラです。
TypeScriptを想定しているので、定義も書いておきます。


```ts:ConfirmMixin.ts
import { VueConstructor } from 'vue';
import Confirm from '/path/to/Confirm.vue';

declare module 'vue/types/vue' {
    interface Vue {
        $confirm(message: string): Promise<boolean>;
    }
}

export default class {
  static install(Vue: VueConstructor) {
    Vue.mixin({
      methods: {
        $confirm: (message: string) => {
          return new Promise((resolve) => {
            new Confirm({
              propsData: {
                message,
                success: () => resolve(true),
                failure: () => resolve(false),
              },
            });
          });
        },
      },
    });
  }
}
```

### 利用部分

最後に実際にインストールする部分。
通常のライブラリと同様に`Vue.use`を呼ぶだけです。

```ts:index.ts
import Vue from 'vue';
import Vuetify from 'vuetify';
import ConfirmMixin from '/path/to/ConfirmMixin';

Vue.use(Vuetify);
Vue.use(ConfirmMixin);
```

### 呼び出すとき

実際にアプリケーションの中で呼び出す時はこんな感じです。

```vue
<template lang="pug">
v-layout(column fill-height)
  v-btn(@click="hoge") hogehogeする
</template>

<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";

@Component
export default class extends Vue {
  async hoge() {
    const result = await this.$confirm("本当にhogehogeする？");
    console.log(result)
  }
}
</script>
```

## 動かしてみた

実際に動作してみるとこんな感じになります。

![confirm.gif](https://qiita-image-store.s3.amazonaws.com/0/97227/3605cb52-0b0e-8108-4e13-da25bbcea727.gif)

## まとめ

Vue.js + Vuetifyを使って独自の確認ダイアログを作成してみました。
ここまで作成してしまえば、少し手を加えるだけでアラートやエラーダイアログも作成できますし、そのほかの情報も表示できるので、いろんな使い方を試してみてください。