📝
Vueでダイアログボックスを表示する | Vue3
概要
Vueでアプリケーションを作成していて、ダイアログボックスを表示したかったので作った。
こだわったのは、APIの呼び出し方を、従来のブラウザのAPIのように、const result = await this.$dialog.alert('Hello!')
と、一行で書けるようにしたこと。
ファイル構成
root/
├ plugins/
│ └ dialog.js
├ App.vue # 今回は関係ない
└ main.js
ソースコード
dialog.js
import { createApp, h, ref } from 'vue';
// --- ダイアログコンポーネントの定義 ---
const DialogComponent = {
name: 'DialogComponent',
props: {
// 表示モード ('alert', 'confirm', 'prompt')
mode: {
type: String,
default: 'confirm',
},
// 表示メッセージ
message: {
type: String,
required: true,
},
// ダイアログを閉じる際のコールバック
onClose: {
type: Function,
required: true,
},
},
emits: ['close'],
// promptモードでの入力値を保持
setup() {
const inputValue = ref('');
return { inputValue };
},
render() {
// --- 子要素の動的な構築 ---
const children = [
// メッセージ
h('p', { class: 'dialog-message' }, this.message),
];
// promptモードの場合、input要素を追加
if (this.mode === 'prompt') {
children.push(
h('input', {
class: 'dialog-input',
type: 'text',
value: this.inputValue,
onInput: (event) => (this.inputValue = event.target.value),
// ref属性はrender関数内ではこのように扱う
ref: 'promptInput'
})
);
}
// --- ボタンの動的な構築 ---
const buttons = [];
// confirmまたはpromptモードの場合、キャンセルボタンを追加
if (this.mode === 'confirm' || this.mode === 'prompt') {
buttons.push(
h('button', { class: 'dialog-button cancel', onClick: this.handleCancel }, 'キャンセル')
);
}
// OKボタンは常に追加
buttons.push(
h('button', { class: 'dialog-button confirm', onClick: this.handleConfirm }, 'OK')
);
children.push(h('form', { method: 'dialog', class: 'dialog-buttons' }, buttons));
// --- コンポーネント全体の構築 ---
return h('dialog', { class: 'dialog-overlay', onClick: this.handleOverlayClick }, [
h('div', { class: 'dialog-box' }, children),
]);
},
methods: {
// OKボタンの処理
handleConfirm() {
const result = this.mode === 'prompt' ? this.inputValue : true;
this.onClose(result);
},
// キャンセルボタンの処理
handleCancel() {
// promptではnull、confirmではfalseを返す
const result = this.mode === 'prompt' ? null : false;
this.onClose(result);
},
// オーバーレイのクリック処理
handleOverlayClick(event) {
if (event.target === event.currentTarget) {
this.handleCancel();
}
},
// キーボードイベントの処理
handleKeydown(e) {
if (e.key === 'Escape') {
this.handleCancel();
}
if (e.key === 'Enter' && this.mode === 'prompt') {
this.handleConfirm();
}
},
},
mounted() {
document.addEventListener('keydown', this.handleKeydown);
// promptモードの場合、inputに自動でフォーカスを当てる
if (this.mode === 'prompt') {
this.$refs.promptInput.focus();
}
},
beforeUnmount() {
document.removeEventListener('keydown', this.handleKeydown);
},
};
// --- ダイアログを生成するヘルパー関数 ---
function createDialog(mode, message) {
return new Promise(resolve => {
const container = document.createElement('div');
document.body.appendChild(container);
const cleanup = (result) => {
dialogApp.unmount();
document.body.removeChild(container);
resolve(result);
};
const dialogApp = createApp({
render() {
return h(DialogComponent, {
mode: mode,
message: message,
onClose: cleanup,
});
},
});
dialogApp.mount(container);
});
}
// --- プラグイン本体 ---
const DialogPlugin = {
install(app) {
app.config.globalProperties.$dialog = {
/**
* アラートダイアログを表示します。
* @param {string} message - 表示するメッセージ
* @returns {Promise<boolean>} 常にtrueを返します
*/
alert(message) {
return createDialog('alert', message);
},
/**
* 確認ダイアログを表示します。
* @param {string} message - 表示するメッセージ
* @returns {Promise<boolean>} OKでtrue, キャンセルでfalseを返します
*/
confirm(message) {
return createDialog('confirm', message);
},
/**
* 入力ダイアログを表示します。
* @param {string} message - 表示するメッセージ
* @returns {Promise<string|null>} OKで入力文字列, キャンセルでnullを返します
*/
prompt(message) {
return createDialog('prompt', message);
},
};
// --- スタイルの注入 (prompt用のスタイルを追加) ---
const style = document.createElement('style');
style.textContent = `
.dialog-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex; justify-content: center; align-items: center; z-index: 1000;
}
.dialog-box {
background-color: white; padding: 24px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
min-width: 320px; max-width: 90%; text-align: center;
}
.dialog-message {
margin: 0 0 20px; font-size: 16px; color: #333;
}
.dialog-input { /* prompt用inputのスタイル */
display: block; width: 100%; padding: 8px; margin-bottom: 20px;
font-size: 16px; border: 1px solid #ccc; border-radius: 4px;
box-sizing: border-box;
}
.dialog-buttons {
display: flex; justify-content: space-between; gap: 12px;
}
.dialog-button {
border: none; border-radius: 4px; padding: 8px 16px;
font-size: 14px; cursor: pointer; transition: background-color 0.2s;
}
.dialog-button.cancel {
background-color: #f0f0f0; color: #333;
}
.dialog-button.cancel:hover { background-color: #e0e0e0; }
.dialog-button.confirm {
background-color: #007bff; color: white;
}
.dialog-button.confirm:hover { background-color: #0056b3; }
`;
document.head.appendChild(style);
},
};
export default DialogPlugin;
使い方
設定
main.js
import { createApp } from 'vue';
import App from './App.vue';
import dialog from './plugins/dialog'; // dialog.js 読み込み
createApp(App).use(dialog).mount('#app');
// ^^^ dialog 読み込み
呼び出し方
Component.vue
<template>
<p>Hello</p>
</template>
<script>
export default {
...
async mounted() {
const res = await this.$dialog.alert('This is message');
const res = await this.$dialog.prompt('This is message');
const res = await this.$dialog.confirm('This is message');
},
</script>
Discussion