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>
isVisible
はprops
に定義されており、親コンポーネントから子コンポーネントへの単方向データフローによって値が渡されます。
そのため、親コンポーネントで以下のように定義した場合、
<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>
ここではプロミスを使っていますが、プロミスを使わなくても実装は可能です。ただ、少しだけスマートに実装できるようになるため、プロミスを使って実装ています。
ダイアログ表示時にプロミスを作成する
まず、ダイアログを表示するときにプロミスを作成し、それを返します。このとき、resolve
をdata
に保持しておきます。
<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
に保存したresolve
(resolveDialog
)に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>
違いはresolve
(resolveDialog
)に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
が返ってきたときは何も処理をしなければprevent
(preventDefault
関数と同じ効果)によってフォームの送信は行われません。
前述のとおり、プロミスを使わず、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にあります。