Vueで確認ダイアログ(Confirmation Dialog)を実装する

Vueで確認ダイアログ(Confirmation Dialog)を実装する

Vueで確認ダイアログを実装する簡単な方法を説明します。

axiosなどを使ってデータを送信する場合と、フォームを使ってデータを送信する場合とで実装が少し異なるため、それぞれのパターンで説明します。

目次

プロジェクトの作成

まずは動作確認を行うプロジェクトを作成します。

$ npm create vue@latest
Vue.js - The Progressive JavaScript Framework

✔ Project name: … vue-confirm-dialog
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/t0k0sh1/Workspace/vue-confirm-dialog...

Done. Now run:

  cd vue-confirm-dialog
  npm install
  npm run dev

設定に制約は特にありませんが、以降ではJavaScriptでコードを示します。

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

確認ダイアログコンポーネントを作成します。

<template>
  <div v-if="isVisible" class="dialog">
    <div class="dialog-content">
      <!-- ダイアログの内容 -->
      <button @click="submit" class="button ok">OK</button>
      <button @click="cancel" class="button cancel">キャンセル</button>
    </div>
  </div>
</template>

<style>
.dialog {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  border: 1px solid #ccc;
  padding: 20px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  z-index: 1000;
}

.dialog-content {
  text-align: center;
}

.button {
  margin: 10px;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.ok {
  background-color: #4caf50;
  color: white;
}

.ok:hover {
  background-color: #45a049;
}

.cancel {
  background-color: #f44336;
  color: white;
}

.cancel:hover {
  background-color: #d73829;
}
</style>

<script>
export default {
  props: ["isVisible"],
  methods: {
    submit() {
      this.$emit("confirm");
    },
    cancel() {
      this.$emit("cancel");
    },
  },
};
</script>

この確認ダイアログは表示/非表示の制御をv-ifで行い、その制御は親コンポーネントに委ねています。また、OKボタンを押したか、キャンセルボタンを押したかを$emitで返すようにしています。

ダイアログの表示/非表示

ダイアログの表示/非表示の制御はv-if="isVisible"で行っています。

<template>
  <div v-if="isVisible" class="dialog">
    ・・・
  </div>
</template>

isVisiblepropsに定義されており、親コンポーネントから子コンポーネントへの単方向データフローによって値が渡されます。

そのため、親コンポーネントで以下のように定義した場合、

<confirm-dialog-component :is-visible="isDialogVisible"></confirm-dialog-component>

親コンポーネントでisDialogVisible = trueとすればダイアログが表示され、isDialogVisible = falseとすればダイアログが非表示になります。propsではキャメル形式になっていますが、コンポーネント使用時に指定する名前はケバブ形式になっている点に注意してください。

OKボタンを押下したときの処理

OKボタンを押下したときの処理について見ていきます。

<template>
  ・・・
      <button @click="submit" class="button ok">OK</button>
  ・・・
</template>

OKボタンには@click="submit"が付与されていて、このボタンをクリックするとmethodsに定義されているsubmit関数を呼び出します。

submit関数では、$emit関数を使って"confirm"というイベントを発生させ、子コンポーネントから親コンポーネントへの単方向データフローによってイベントと必要であればデータを渡します。親コンポーネントがバインドしたコールバック関数を呼び出している感覚に近いイメージになります。

<script>
export default {
  ・・・
  methods: {
    submit() {
      this.$emit("confirm");
    },
    ・・・
  },
}
</script>

confirmイベントが発生すると、親コンポーネントがこのコンポーネントに設定している関数を呼び出します。

例えば、親コンポーネントがこのコンポーネントを以下のように定義している場合、confirmイベントが発生すると、親コンポーネントのhandleConfirm関数が呼び出されます。

<template>
    ・・・
    <confirm-dialog-component @confirm="handleConfirm"></confirm-dialog-component>
    ・・・
</template>

親コンポーネントではOKボタンが押下されたことを検知できるので、サーバーへのデータ送信やダイアログを非表示にする処理を実装します。

<script>
import axios from 'axios';  

export default {
  ・・・
  methods: {
    handleConfirm() {
      axios.post(・・・)
        .then(response => {
          // サーバーへのデータ送信が成功したときの処理
        })
        .catch(error => {
          // サーバーへのデータの送信が失敗したときの処理
        });
      // 確認ダイアログを非表示にする
      this.isDialogVisible = false;
    },
    ・・・
</script>

上記の例では、axiosを使ってサーバーにデータを送信しつつ、確認ダイアログを非表示にしています。

キャンセルボタンを押下したときの処理

同様にキャンセルボタンを押下したときの処理を見てみましょう。

<template>
  ・・・
      <button @click="cancel" class="button cancel">キャンセル</button>
  ・・・
</template>

クリック時に呼び出される関数が異なるだけで基本的にはOKボタン押下時と同じで、methodsに設定しているcancel関数が呼び出されます。

<script>
export default {
  ・・・ 
   cancel() {
      this.$emit("cancel");
    },
    ・・・
  },
}
</script>

キャンセルボタンを押下したときは$emit関数を使って"cancel"というイベントを発生させます。

OKボタンのときと同様に、親コンポーネントでcancelイベントを処理する関数を定義します。

<template>
  ・・・
    <confirm-dialog-component @cancel="handleCancel"></confirm-dialog-component>
  ・・・
</template>

handleCancel関数では単に確認ダイアログを閉じるだけにします。

export default {
  ・・・
  methods: {
    ・・・
    handleCancel() {
      // 確認ダイアログを非表示にする
      this.isDialogVisible = false;
    },
     ・・・
  },
}
</script>

ボタンクリックによる確認ダイアログの表示

axiosなど使ってサーバーにデータを送信するような使い方の場合はこちらが適しています。

前述のとおり、作成した確認ダイアログコンポーネントは以下のように

  • ダイアログ表示・非表示制御用のフラグ(isDialogVisible
  • ダイアログを表示する処理(handleClick
  • OKボタン押下時の処理(handleConfirm
  • キャンセルボタン押下時の処理(handleCancel

を用意することで確認ダイアログを使用することができます。。

<template>
  <input type="button" value="クリック" @click.prevent="handleClick" />
    <confirm-dialog-component
      :is-visible="isDialogVisible"
      @confirm="handleConfirm"
      @cancel="handleCancel"
    ></confirm-dialog-component>
  </form>
</template>

<script>
import ConfirmDialogComponent from "./ConfirmDialogComponent.vue";

export default {
  components: {
    ConfirmDialogComponent,
  },
  data() {
    return {
      isDialogVisible: false,
    };
  },
  methods: {
    handleConfirm() {
      axios.post(・・・)
        .then(response => {
          // サーバーへのデータ送信が成功したときの処理
        })
        .catch(error => {
          // サーバーへのデータの送信が失敗したときの処理
        });
      this.isDialogVisible = false;
    },
    handleCancel() {
      this.isDialogVisible = false;
    },
    handleClick(e) {
      this.isDialogVisible = true;
    },
  },
};
</script>

ボタンクリック時に呼び出されるhandleClick関数はダイアログ表示後にすぐに処理が終了するため、@click.preventをつけることで、preventDefault関数を呼び出したのと同様にクリック処理が発火しないようにしています。

ほとんどのコードはすでに説明済みですが、ダイアログを表示する関数だけ説明していなかったので、コードを示しておきます。といっても確認ダイアログコンポーネントに設定しているisDialogVisibleをtrueに変更してダイアログを表示するだけの処理になります。

export default {
  ・・・
  methods: {
    ・・・
    handleClick(e) {
      this.isDialogVisible = true;
    },
  },
}
</script>

サブミットによる確認ダイアログの表示

サーバーへのデータ送信をフォームで行う場合はこちらが適しています。

前述のとおり、確認ダイアログの表示中は呼び出し元関数の処理をブロックしません。また、サブミットはENTERキー押下でも行えるため、これらに対する対策が必要となります。

<template>
  <form @submit.prevent="handleSubmit">
    <input type="submit" value="サブミット" />
    <confirm-dialog-component
      :is-visible="isDialogVisible"
      @confirm="handleConfirm"
      @cancel="handleCancel"
    ></confirm-dialog-component>
  </form>
</template>

<script>
import ConfirmDialogComponent from "./ConfirmDialogComponent.vue";

export default {
  components: {
    ConfirmDialogComponent,
  },
  data() {
    return {
      isDialogVisible: false,
      resolveDialog: null,
    };
  },
  methods: {
    showDialog() {
      this.isDialogVisible = true;
      return new Promise((resolve, reject) => {
        this.resolveDialog = resolve;
      });
    },
    handleConfirm() {
      if (this.resolveDialog) {
        this.resolveDialog(true);
      }
      this.isDialogVisible = false;
    },
    handleCancel() {
      if (this.resolveDialog) {
        this.resolveDialog(false);
      }
      this.isDialogVisible = false;
    },
    async handleSubmit(e) {
      // show confirm dialog
      const confirmed = await this.showDialog();
      if (confirmed) {
        e.target.submit();
      }
    },
  },
};
</script>

ここではプロミスを使っていますが、プロミスを使わなくても実装は可能です。ただ、少しだけスマートに実装できるようになるため、プロミスを使って実装ています。

ダイアログ表示時にプロミスを作成する

まず、ダイアログを表示するときにプロミスを作成し、それを返します。このとき、resolvedataに保持しておきます。

<script>
import ConfirmDialogComponent from "./ConfirmDialogComponent.vue";

export default {
  components: {
    ConfirmDialogComponent,
  },
  data() {
    return {
      isDialogVisible: false,
      resolveDialog: null,
    };
  },
  methods: {
    showDialog() {
      this.isDialogVisible = true;
      return new Promise((resolve, reject) => {
        this.resolveDialog = resolve;
      });
    },
    ・・・
  },
};
</script>

プロミス内でdataに保存しているresolveはOKボタン、キャンセルボタンが処理結果を返すために使用します。

OKボタンを押下したときの処理

OKボタンを押下するときに呼び出されるhandleConfirm関数では、ダイアログを非表示にする処理以外にプロミス作成時にdataに保存したresolveresolveDialog)にtrueを設定しています。

export default {
  ・・・
  methods: {
    ・・・
    handleConfirm() {
      if (this.resolveDialog) {
        this.resolveDialog(true);
      }
      this.isDialogVisible = false;
    },
     ・・・
  },
}
</script>

また、フォームでデータを送信するため、handleConfirm関数ではサーバーでデータを送信する処理は行いません。

キャンセルボタンを押下したときの処理

同様にキャンセルボタンを押下したときに呼び出されるhandleCancel関数を見ていきます。

export default {
  ・・・
  methods: {
    ・・・
    handleCancel() {
      if (this.resolveDialog) {
        this.resolveDialog(false);
      }
      this.isDialogVisible = false;
    },
     ・・・
  },
}
</script>

違いはresolveresolveDialog)にfalseを設定している点でそれ以外に違いはありません。

ダイアログを開く処理

ダイアログを開く処理を見ていきます。

まず、ダイアログを開く関数はフォームに設定し、このとき使用するのは@submit.preventになります。

<form @submit.prevent="handleSubmit">

サブミットボタンではなく、フォームのsubmitイベントに関数を設定し、.preventをつけてフォームの送信処理を行わないようにする点がポイントとなります。これによりサブミットボタンをクリックしてもENTERキーを押下してもフォームの送信は行われなくなります。そのため、別の手段でフォームの送信を行う必要が出てきます。

次に@submit.preventに設定したhandleSubmit関数を見ていきます。

export default {
  ・・・
  methods: {
    ・・・
    async handleSubmit(e) {
      // show confirm dialog
      const confirmed = await this.showDialog();
      if (confirmed) {
        e.target.submit();
      }
    },
  },
}
</script>

この関数はshowDialog関数が返すプロミスの結果を受け取るためにasync関数として実装します。そして、showDialog関数をawaitをつけて同期化し、ダイアログが閉じるまで(OKボタンまたはキャンセルボタンでresolveを呼び出されるまで)待つようにします。これがダイアログが閉じるまでの間ブロックしない問題に対する対策です。

showDialog関数が返すプロミスの結果はtrue(OKボタン押下時)、false(キャンセルボタン押下時)のいずれかであるため、trueのときにe.target(これはフォームそのもの)のsubmit関数を呼び出すことでフォームの送信を行うことができます。falseが返ってきたときは何も処理をしなければpreventpreventDefault関数と同じ効果)によってフォームの送信は行われません。

前述のとおり、プロミスを使わず、async-awaitを使わずにhandleConfirm関数でフォームのsubmit関数を呼び出すことも可能ですが、フォームを探す一手間があるため、見やすくするためにプロミスとasync-awaitを使っています。

まとめ

Vueで作成した確認ダイアログを表示し、OKボタンをクリックしたときだけ処理を続行する方法をボタンクリックの場合とフォームを使ったサブミットの2パターンの実装方法を見ていきました。

この考え方は他のフレームワークでも適用可能です。今回はVue 3.3.4で動作確認していますが、Vue 3で実装された機能を使っていないため、Vue 2でも動作すると思います。

動作可能なコードははhttps://github.com/t0k0sh1/vue-confirm-dialog.gitにあります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次