確認ダイアログ(confirm)でユーザー応答を待つ【Nuxt.js】


はじめに

Webアプリを開発していて必ず欲しくなる確認ダイアログ。

window.confirm('こういうやつ!')

と書いてもいいのですが「デザインをこだわるために自作したい!」となって作成しました。

目標

作りたかったものは以下です。

  • テキストが変更できる
  • 前ページ共通で使いまわせる(各ページに配置などしない)
  • window.confirmのように処理を中断してtrue / falseを返せる

ダイアログを作る

ダイアログコンポーネントを作ります(HTML部分は適当&CSSは省略)。

DialogConfirm.vue
<template>
  <div v-if="isShown" class="dialog">
    <p>
      {{ text }}
    </p>
    <div class="dialog__buttons">
      <button @click="ok()">
        OK
      </button>
      <button @click="cancel()">
        Cancel
      </button>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';

type Data = {
  isShown: boolean,
  text: string,
  resolve: (v: boolean) => void
}

export default Vue.extend({
  data (): Data {
    return {
      isShown: false,
      text: '',
      resolve: () => {}
    };
  },

  methods: {
    confirm (text: string): Promise<boolean> {
      this.text = text;
      this.isShown = true;
      return new Promise((resolve: (v: boolean) => void) => {
        this.resolve = resolve;
      });
    },

    ok () {
      this.reset();
      this.resolve(true);
    },

    cancel () {
      this.reset();
      this.resolve(false);
    },

    reset () {
      this.isShown = false;
      this.text = '';
    }
  }
});
</script>

confirmメソッドでpromiseの解決をせず、コンポーネント内のdataにresolveを逃がしています。

confirm (text: string): Promise<boolean> {
  this.text = text;
  this.isShown = true;
  return new Promise((resolve: (v: boolean) => void) => {
      this.resolve = resolve;
  });
}

そしてダイアログ内のボタン押下でresolveしPromiseでラッピングしたbooleanを返します。

ok () {
  this.reset();
  this.resolve(true);
},

cancel () {
  this.reset();
  this.resolve(false);
}

Vueはrefsを使うことで子コンポーネントのメソッドを実行できます。
これを利用しダイアログコンポーネントのconfirmメソッドを叩くことを想定しています。

各ページから呼び出せるようにする

作成したダイアログコンポーネントを適当なlayoutsに配置し、呼び出せるようにします。

共通でメソッドを扱いたいため、メソッドをstoreに格納することにしました。
refsはVueオブジェクトから取るため、layoutscreatedstoreに格納します。
(もっといいやりかたありそう......)

default.vue
created () {
  this.$store.dispatch('setConfirmMethod', this.confirm);  // ストアに格納
},

methods: {
  confirm(text: string): Promise<boolean> {
    return (this.$refs.dialog as InstanceType<typeof DialogConfirm>).confirm(text);
  }
}

使いかた

これで適当なif文内でストアのconfirmを呼んでやると

async click() {
  if (await this.$store.state.confirm('こういうやつ!'))) {
    window.alert('confirm!')
  }
}

と出てきて確認してくれます。

おわりに

目標としていた3点を満たすコンポーネントを実装できました。

  • テキストが変更できる
  • window.confirmのように処理を中断してtrue / falseを返せる
  • 前ページ共通で使いまわせる(各ページに配置などしない)