1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

vue3でvue-confirm-dialogが使えなかったので確認画面を使えるようにしてみた

Last updated at Posted at 2023-01-20

vue3ではTeleportを使ったモーダルを作成できます。
vue2ではvue-confirm-dialogAPI機能を使ってモーダルを作成していました(関数内からモーダルを呼べて便利)。
ですが、vue3に対応していなかったのでAPI機能を使いたかったので一部機能を自作してみました。

ディレクトリ構成

ディレクトリ内
※ 使用していないものは省略しています

├─ public/
│   └── index.html
└─ src/
    ├── components/
    │        └ ConfirmDialog.vue
    ├── mixins/
    │        └ index.js
    │
    ├── App.vue
    └── main.js

確認画面の動作の流れ

  1. $confirm関数を実行
  2. index.htmlの<div id="mount-point"></div>ConfirmDialog.vueをマウント
  3. モーダルのokまたはcancelのコマンドを押すと渡されたコマンドを実行
  4. 表示中のモーダルをアンマウント

public/index.html
<!DOCTYPE html>
<html lang="">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
  <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
  <div id="app"></div>
+  <div id="mount-point"></div>
  <!-- built files will be auto injected -->
</body>
</html>

main.jsにmixinを追加

src/main.js
import { createApp } from 'vue'
import App from './App.vue'

import Mixin from '@/mixins'

- createApp(App).mount('#app')
+ createApp(App).mixin(Mixin).mount('#app')

Mixinsに$confirm関数を作成

デフォルトでokActioncancelActionにはBooleanを返すように設定してます。
createApp()の第一引数にコンポーネント、第二引数にpropsを渡して#mount-pointにマウントしています。

src/mixins/index.js
import * as Vue from 'vue'
import ConfirmDialog from '@/components/ConfirmDialog'

export default {
  methods: {
    async $confirm(payload) {
      return new Promise((resolve) => {
        const defaultOption = {
          okAction: () => resolve(true),
          cancelAction: () => resolve(false),
        }
        const Option = Object.assign(defaultOption, payload)
        Vue.createApp(ConfirmDialog, Option).mount('#mount-point')

      }).catch((err) => {
        throw err;
      })
    },
  }
}

プロパティ

Attribute Type Default Description
message String メッセージ メッセージ表示内容
title String タイトル タイトルの表示内容
button Object { yes: 'Yes', no: 'No' } OKの時とNGの時の表示内容
okAction Function ()=>{} OK時の処理
cancelAction Function ()=>{} キャンセル時の処理

ボタンを押して、処理が終わった後にthis.$.appContext.app.unmount()を実行するとアンマウントすることができます。
参考サイト:How to destroy/unmount vue.js 3 components?

vue-confirm-dialogからhtmlとcssを拝借してます。

src/omponents/ConfirmDialog.vue
<template>
  <div id="vueConfirm" class="vc-overlay">
    <div class="vc-container">
      <span class="vc-text-grid">
        <h4 class="vc-title">{{ message }}</h4>
        <p class="vc-text">{{ title }}</p>
      </span>
      <div class="vc-btn-grid">
        <button class="vc-btn left" @click="ok" v-if="button.yes">{{ button.yes }}</button>
        <button class="vc-btn" @click="cancel" v-if="button.no">{{ button.no }}</button>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'ConfirmDialog',
  props: {
    message: {
      type: String,
      required: true,
      default: 'メッセージ',
    },
    title: {
      type: String,
      required: true,
      default: 'タイトル',
    },
    button: {
      type: Object,
      required: true,
      default: function () {
        return { yes: 'Yes', no: 'No' }
      },
    },
    okAction: {
      type: Function,
      required: true,
      default: () => {},
    },
    cancelAction: {
      type: Function,
      required: true,
      default: () => {},
    },
  },
  methods: {
    ok() {
      this.okAction()
      this.$.appContext.app.unmount()
    },
    cancel() {
      this.cancelAction()
      this.$.appContext.app.unmount()
    },
  },
}
</script>
<style>
:root {
  --title-color: #000;
  --message-color: #000;
  --overlay-background-color: #0000004a;
  --container-box-shadow: #0000004a 0px 3px 8px 0px;
  --base-background-color: #fff;
  --button-color: #4083ff;
  --button-background-color: #fff;
  --button-border-color: #e0e0e0;
  --button-background-color-disabled: #f5f5f5;
  --button-background-color-hover: #f5f5f5;
  --button-box-shadow-active: inset 0 2px 0px 0px #00000014;
  --input-background-color: #ebebeb;
  --input-background-color-hover: #dfdfdf;
  --font-size-m: 16px;
  --font-size-s: 14px;
  --font-weight-black: 900;
  --font-weight-bold: 700;
  --font-weight-medium: 500;
  --font-weight-normal: 400;
  --font-weight-light: 300;
}

.vc-overlay *,
.vc-overlay *:before,
.vc-overlay *:after {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  text-decoration: none;
  -webkit-touch-callout: none;
  -moz-osx-font-smoothing: grayscale;
  margin: 0;
  padding: 0;
}

.vc-title {
  color: var(--title-color);
  padding: 0 1rem;
  width: 100%;
  font-weight: var(--font-weight-black);
  text-align: center;
  font-size: var(--font-size-m);
  line-height: initial;
  margin-bottom: 5px;
}
.vc-text {
  color: var(--message-color);
  padding: 0 1rem;
  width: 100%;
  font-weight: var(--font-weight-medium);
  text-align: center;
  font-size: var(--font-size-s);
  line-height: initial;
}
.vc-overlay {
  background-color: var(--overlay-background-color);
  width: 100%;
  height: 100%;
  transition: all 0.1s ease-in;
  left: 0;
  top: 0;
  z-index: 999999999999;
  position: fixed;
  display: flex;
  justify-content: center;
  align-items: center;
  align-content: baseline;
}
.vc-container {
  background-color: var(--base-background-color);
  border-radius: 1rem;
  width: 286px;
  height: auto;
  display: grid;
  grid-template-rows: 1fr max-content;
  box-shadow: var(--container-box-shadow);
}
.vc-text-grid {
  padding: 1rem;
}
.vc-btn-grid {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-radius: 0 0 1rem 1rem;
  overflow: hidden;
}
.vc-btn-grid.isMono {
  grid-template-columns: 1fr;
}
.vc-btn {
  border-radius: 0 0 1rem 0;
  color: var(--button-color);
  background-color: var(--button-background-color);
  border: 0;
  font-size: 1rem;
  border-top: 1px solid var(--button-border-color);
  cursor: pointer;
  font-weight: var(--font-weight-bold);
  outline: none;
  min-height: 50px;
}
.vc-btn:hover {
  background-color: var(--button-background-color-hover);
}
.vc-btn:disabled {
  background-color: var(--button-background-color-disabled);
}
.vc-btn:active {
  box-shadow: var(--button-box-shadow-active);
}
.vc-btn.left {
  border-radius: 0;
  border-right: 1px solid var(--button-border-color);
}
.vc-input[type='password'] {
  width: 100%;
  outline: none;
  border-radius: 8px;
  height: 35px;
  border: 0;
  margin: 5px 0;
  background-color: var(--input-background-color);
  padding: 0 0.5rem;
  font-size: var(--font-size-m);
  transition: 0.21s ease;
}
.vc-input[type='password']:hover,
.vc-input[type='password']:focus {
  background-color: var(--input-background-color-hover);
}

/**
* Transition
*/
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.21s;
}

.fade-enter,
.fade-leave-to {
  opacity: 0;
}

.zoom-enter-active,
.zoom-leave-active {
  animation-duration: 0.21s;
  animation-fill-mode: both;
  animation-name: zoom;
}

.zoom-leave-active {
  animation-direction: reverse;
}

@keyframes zoom {
  from {
    opacity: 0;
    transform: scale3d(1.1, 1.1, 1.1);
  }

  100% {
    opacity: 1;
    transform: scale3d(1, 1, 1);
  }
}
</style>

確認画面から削除する処理をApp.vueに記述

App.vue
<template>
  <table>
    <tr>
      <th>タイトル</th>
      <th>操作</th>
    </tr>
    <tr v-for="(item, index) in this.data" :key="index">
      <td>{{ item.name }}</td>
      <td><button @click="DelData(index)">削除</button></td>
    </tr>
  </table>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      data: [],
    }
  },
  mounted() {
    for (let i = 0; 30 > this.data.length; i++) {
      this.data.push({
        name: 'test' + i,
      })
    }
  },
  methods: {
    async DelData(index) {
      this.$confirm({
        message: 'メッセージが入ります:' + index,
        title: 'タイトルが入ります:' + index,
        button: {
          yes: '削除する',
          no: 'キャンセル',
        },
        okAction: () => {
          // データ削除
          this.data.splice(index, 1)
          console.log('ok')
        },
        cancelAction: () => {
          console.log('ng')
        },
      })
    },
  },
}
</script>

<style>
table {
  border-collapse: collapse;
  margin: 0 auto;
}
td,
th {
  padding: 10px;
}
th {
  color: #fff;
  background: #005ab3;
}
table tr:nth-child(odd) {
  background: #e6f2ff;
}
</style>

実際の動き

CPT2301201913-1018x1035.gif

trueの時しか処理しない場合はthen()関数でブール値を判別できます。

async hoge() {
  await this.$confirm().then((value) => {
    if (value) console.log('YESが押されました!')
  })
}

おわり

vue3系で便利になったこともあれば、プラグインが対応しきっていないことがあり思わぬところで時間を使ってしまいます。
vue-confirm-dialogがvue3に対応されたら、すぐ使うんですけどね・・・

vue3-confirm-dialogができたみたいです

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?