window.confirmと似た使用感の確認ダイアログを自作しました。
## 確認バージョン * vue: 2.6.11 * vuetify: 2.3.2
## 使用例 以下のように、コンポーネントごとにmixinを読み込んで使います。 文字列の配列を渡すと、複数行のメッセージとして表示される仕組みになっています。
@/components/MyComponent.vue
<template>
<div class="container">
<h2>氏名変更</h2>
<v-btn small class="button primary" @click="changeName">実行</v-btn>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import ConfirmDialogMixin from '@/mixins/ConfirmDialogMixin.ts';
export default Vue.extend({
mixins: [ConfirmDialogMixin],
data() {
return {
userAccountBefore: {jpName: '田中 一郎', enName: 'Tnaka Ichiro'},
userAccountAfter: {jpName: '田中 二郎', enName: 'Tnaka Jiro'},
};
},
methods: {
async changeName(): Promise<void> {
// こんな感じで呼び出す
const isUserClickOK = await this.mixinConfirm([
'以下の通り変更します。よろしいでしょうか?',
'■氏名',
`変更前: ${this.userAccountBefore.jpName}`,
`変更後: ${this.userAccountAfter.jpName}`,
'■English Name',
`変更前: ${this.userAccountBefore.enName}`,
`変更後: ${this.userAccountAfter.enName}`,
]);
if (isUserClickOK) {
// OKを押した時の処理
}
},
},
});
</script>
<style scoped lang="scss">
.container {
padding: 20px;
}
</style>
## confirm部分のコード ConfirmDialogMixin.tsにて非グローバルのmixinを定義。 mixin関数を呼ぶとConfirmDialog.vueのインスタンスを作成し、OK/キャンセルを押すと破棄する作りです。
@/mixins/ConfirmDialogMixin.ts
import Vue from 'vue';
import vuetify from '@/plugins/vuetify';
import ConfirmDialog from '@/components/generalParts/ConfirmDialog.vue';
declare module 'vue/types/vue' {
interface Vue {
mixinConfirm(messages: string[]): Promise<boolean>;
}
}
export default {
methods: {
// window.confirm()と似た感じで使うconfirm用の共通関数
mixinConfirm: (messages: string[]): Promise<boolean> => {
return new Promise((resolve) => {
const VM = Vue.extend(ConfirmDialog);
new VM({
vuetify, // <- これをつけなくても動くが、consoleエラーが出まくる
propsData: {
messages,
onClickOK: () => {
return resolve(true);
},
onClickClose: () => {
return resolve(false);
},
},
});
});
},
},
};
@/components/generalParts/ConfirmDialog.vue
<template>
<v-dialog :value="isDialogActive" max-width="600" persistent>
<div class="container">
<div class="messages">
<div v-for="(message, index) in messages" :key="index">
{{ message }}
</div>
</div>
<v-btn small class="button primary" @click="ok">OK</v-btn>
<v-btn small class="button secondary" @click="cancel">キャンセル</v-btn>
</div>
</v-dialog>
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
export default Vue.extend({
props: {
messages: {
type: Array as PropType<string[]>,
required: true,
},
onClickOK: {
type: Function,
required: true,
},
onClickClose: {
type: Function,
required: true,
},
},
data() {
return {
isDialogActive: false,
};
},
created() {
this.$mount();
document.body.appendChild(this.$el);
this.isDialogActive = true;
},
methods: {
ok() {
this.onClickOK();
this.close();
},
cancel() {
this.onClickClose();
this.close();
},
close() {
this.isDialogActive = false;
// アニメーションが見えるよう若干待つ
setTimeout(() => {
if (document.body.contains(this.$el)) {
document.body.removeChild(this.$el);
}
this.$destroy();
}, 200);
},
},
});
</script>
<style lang="scss" scoped>
$spacingNormal: 8px;
.container {
border: solid 2px;
border-radius: 4px;
background-color: #F2F7F2;
padding: $spacingNormal;
}
.messages {
max-height: 300px; // メッセージが長くてもこの高さが最大
overflow: scroll; // はみ出た分はスクロールさせる
border-radius: 4px;
background-color: #FFFFFF;
padding: $spacingNormal;
line-height: 1.25;
}
.button {
width: 120px;
margin-top: $spacingNormal;
margin-right: $spacingNormal;
}
</style>
## vuetifyを使えるようにするコード 一応、vuetifyを使えるようにするコードも載せておきます。 main.tsと別ファイルに書くことで、main/test/mixinで同じ定義を使える利点があります。
@plugin/vuetify.ts
import Vue from 'vue';
import Vuetify from 'vuetify';
import 'vuetify/dist/vuetify.min.css';
Vue.use(Vuetify);
export default new Vuetify({
theme: {
dark: false,
themes: {
light: {
primary: '#EA9034',
secondary: '#564C46',
},
},
},
});
main.ts
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import vuetify from './plugins/vuetify';
new Vue({
router,
vuetify,
render: (h) => {
return h(App);
},
}).$mount('#app');
## 実装意図 * なぜ自作のconfirmを作ったの? * ページ全体のデザインを統一したかったから * なぜvuetifyを使ったの? * デザイン周りの実装を最低限にしたかったから * なぜ文字列の配列を渡すことにしたの? テンプレートリテラルではだめなの? * コードの見た目が、文章の見た目と近くなるから * 他コードとインデントを揃えられるから (テンプレートリテラルで複数行メッセージを書く場合、インデントしにくいのが嫌だった) * 繰り返し要素に強いから (`mixinConfirm(dataArray.map((data) => {return toMessage(data)})`みたいにできる) * なぜグローバルミックスインにしないの? * 依存関係を明示的にしたかったから * mixinの乱用を避けたかったから (mixinはアンチパターンという意見が多い)
## 参考 https://qiita.com/totto357/items/6e5df072fdb0ccbe8c51 https://zukucode.com/2020/04/vue-alert-confirm.html 技術的には上記2記事の完全なパクリです。 2例を組み合わせ、好みの感じにチューニングしてみた、という記事です。 先人に感謝。
## 最後に はやくvuetifyがvue3に対応しますように。