Модальные диалоги не такая и сложная задача в разработке. Разве что можно легко запутаться в коде, если нужно вызывать модальные окна по цепочке. Но это очень монотонная и неинтересная работа с повторяющейся логикой, которую подчас копируют из компонента в компонет с незначительными изменениями.


Хорошим решением было бы создать функцию, которая принимала бы компонент диалога и управляла бы его рендерингом в шаблоне, а этот диалог можно "промисифицировать" и работать с ним как с асинхронной функцией. Как например в этой библиотеке vue-modal-dialogs. К сожалению она давно не обновлялась и не опддерживает Vue 3.


А еще отличной идеей для организации кода было бы отдельные хуки, код в которых выполнялся бы в зависимости от действий пользователя.


Все это есть в плагине vuejs-confirm-dialog, о котором я хочу вам рассказать.


Конечный результат можно посмотреть в песочнице. Код немного отличается от того, что в посте.


Установка


Начнем с создания нового проекта на Vue 3. Введем в консоли:


vue create dialogs-guide

// Pick a second option

? Please pick a preset:
  Default ([Vue 2] babel, eslint)
> Default (Vue 3) ([Vue 3] babel, eslint)
  Manually select features

Мы получили стандартный шаблон нового проекта. Далее установим библиотеку согласно документации в README.md.


npm i vuejs-confirm-dialog

Заменим код в main.js на такой:


import { createApp } from 'vue'
import App from './App.vue'
import * as ConfirmDialog from 'vuejs-confirm-dialog'

createApp(App).use(ConfirmDialog).mount('#app')

Использование


Теперь перейдем в файл App.vue. Первым делом исправим код шаблона. Для работы библиотеки нам обязательно нужно добавить в него компонент <DialogsWrapper /> и удалим HelloWord:


<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <DialogsWrapper />
</template>

Теперь изучим как использовать функцию createConfirmDialog. Используем новый синтаксис setup для раздела script. createConfirmDialog первым аргументом принимает компонент, который будет использоваться как модальное окно, а вторым входные данные для него. Функция возвращает обьект с методами для работы с модальным окном, так метод reveal вызывает диалог, а хук onConfirm принимает код, который выполниться если пользователь нажмет на "согласен". Можно заставить появляться компонент HelloWord при нажатии на лого и передать ему значение пропса msg:


// App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png" @click="reveal">
  <DialogsWrapper />
</template>

<script setup>
import HelloWorld from './components/HelloWorld.vue'
import { createConfirmDialog } from 'vuejs-confirm-dialog'

const { reveal } = createConfirmDialog(HelloWorld, { msg: 'Hi!'})
</script>

Никакой дополнительной логики не требуется. Компонент отрисовывается после вызова функции reveal и исчезает после того, как пользователь отреагирует на диалог.


Реальный пример компонента


Теперь напишем что-то более приближенное к реальному использованию.


Создадим новый компонент SimpleDialog.vue в папке components:


<template>
    <div class="modal-container">
        <div class="modal-body">
            <span class="modal-close" @click="emit('cancel')">????</span>
            <h2>{{ question }}</h2>
            <div class="modal-action">
                <button class="modal-button" @click="emit('confirm')">Confirm</button>
                <button class="modal-button" @click="emit('cancel')">Cancel</button>
            </div>
        </div>
    </div>
</template>

<script setup>
    import { defineProps, defineEmits } from 'vue'

    const props = defineProps(['question'])
    const emit = defineEmits(['confirm', 'cancel'])
</script>

<style>
    .modal-container {
        display: flex;
        justify-content: center;
        align-items: center;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        width: 100%;
        height: 100%;
        background-color: #cececeb5;
    }
    .modal-body {
        background-color: #fff;
        border: 2px solid #74a2cf;
        border-radius: 10px;
        text-align: center;
        padding: 20px 40px;
        min-width: 250px;
        display: flex;
        flex-direction: column;
    }
    .modal-action {
        display: flex;
        flex-direction: row;
        gap: 40px;
        justify-content: center;
    }
    .modal-button {
        cursor: pointer;
        height: 30px;
        padding: 0 25px;
        border: 2px solid #74a2cf;
        border-radius: 5px;
        background-color: #80b2e4;
        color: #fff;
    }
    .modal-close {
    cursor: pointer;
        position: relative;
        align-self: end;
        right: -33px;
        top: -17px;
    }
</style>

Обратите внимание, что для полноценной работы нужно добавить два входящих события в модальный диалог: ['confirm', 'cancel'].


А теперь используем его для подтверждения какого-либо действия, например, чтобы спрятать логотип. Логику кода, который будет исполняться после согласия пользователя, поместим в коллбек хука onConfirm.


<template>
  <img v-show="showLogo" alt="Vue logo" src="./assets/logo.png">
  <button @click="reveal">Hide Logo</button>

  <DialogsWrapper />
</template>

<script setup>
import SimpleDialog from './components/SimpleDialog.vue'
import { createConfirmDialog } from 'vuejs-confirm-dialog'
import { ref } from 'vue'

const showLogo = ref(true)

const { reveal, onConfirm } = createConfirmDialog(SimpleDialog, { question: 'Are you sure you want to hide the logo?'})

onConfirm(() => {
  showLogo.value = false
})
</script>

Переиспользование


Что делать, если у нас есть много случаев, когда требуется подтверждение каких-либо действий? Не писать же каждый раз createConfirmDialog заново?


Можно написать функцию, которая автоматизирует этот процесс для нас.


// src/composables/useConfirmBeforeAction.js

import SimpleDialog from './../components/SimpleDialog'
import { createConfirmDialog } from 'vuejs-confirm-dialog'

const useConfirmBeforeAction = (action, props) => {
  const { reveal, onConfirm } = createConfirmDialog(SimpleDialog, props)

  onConfirm(action)

  reveal()
}

export default useConfirmBeforeAction

Теперь используем ее для подтверждения перехода по внешним ссылкам:


// App.vue

<template>
    <ul>
        <li v-for="(link, i) in LINKS" @click="goToLink(link)" :key="i">
            {{ link }}
        </li>
    </ul>
    <DialogsWrapper />
</template>

<script setup>
    import useConfirmBeforeAction from './composables/useConfirmBeforeAction'

    const LINKS = [
        'https://vuejs.org/',
        'https://github.com/',
        'https://vueuse.org/',
    ]

    const goToLink = (link) => {
        useConfirmBeforeAction(
            () => {
                window.location = link
            },
            { question: `Do you want to go to ${link}?` }
        )
    }

</script>

Заключение


Функция createConfirmDialog позволяет упростить работу с модальными окнами, переиспользование логики и создание цепочек последовательных диалогов. Она берет на себе заботу о рендере модального окна, передачу входящих параметров в компонент и получении данныт от него. Она очень гибкая — ее очень легко подстроить под ваши нужды.


Это не все ее возможности. Например, если концеция хуков вам не близка, можно заменить их на работу с промисом, который возвращает функция reveal. И даже использовать её в Options API.

Комментарии (4)


  1. danilovmy
    24.04.2022 20:05

    Зануда.mode = on
    Импорты SimpleDialog и createConfirmDialog в итоговом примере App.vue не нужны
    Зануда.mode = off
    А теперь по смыслу: js_n00b, а если сравнить с нативным Dialog — в чем преимущество vue-dialog-vrapper?


    1. js_n00b Автор
      24.04.2022 22:51

      Спасибо, поправил.
      На твой вопрос нет ответа, потому что это неконкурентные вещи. Можно написать компонент используя нативный диалог, а можно любое другое кастомное решение, которые захочет бизнес. Можно использовать библиотеку и для совсем других вещей, например алерты, чат боты, и многое другое. createConfirmDialog принимает и рендерит любые компоненты.
      Как я уже писал модальный диалог не сложная задача в реализации. Просто если у вас в проекте на каждой странице десяток похожих окон, может стоит абстрагировать эту логику, а не писать каждый раз похожий код?


  1. makar_crypt
    24.04.2022 20:52
    +1

    ну для вас же создали teleport в vue 3 . почему вы опять костыли самодельные городите?


    1. js_n00b Автор
      25.04.2022 03:16

      Это никак не связанно, смотри ответ выше. Если кратко этот плагин полезен для абстракции и переиспользования логики диалогов.