Модальные диалоги не такая и сложная задача в разработке. Обычно это очень монотонная и неинтересная работа с повторяющейся логикой, которую подчас копируют из компонента в компонент с незначительными изменениями. Часто используется для подтверждения пользователем каких-либо действий. Например удалить какие-либо данные или выполнить авторизацию.
Но что делать, если у вас десятки или даже сотни похожих диалогов на одной странице или даже во всем проекте? Или нужно вызывать диалоги по цепочке в зависимости от выбора пользователя? Как абстрагировать такой функционал и не получить в результате запутанный и плохо поддерживаемый код?
Неплохо было бы создать такую функцию, которая принимала бы компонент диалога и управляла бы его рендерингом в шаблоне, а возвращаемый ею объект содержал бы состояние диалога и методы вызова, чтобы с ним можно было работать как с промисами. Как например в этой библиотеке vue-modal-dialogs. К сожалению она давно не обновлялась и не поддерживает Vue 3.
В этом гайде я представлю вам плагин vuejs-confirm-dialog и покажу как им пользоваться. Начну с простых примеров и закончу созданием функциии полностью абстрагирующей вызов диалога для подтверждения действия, которую можно применять в любом компоненте вашего проекте. Примеры написаны на JavaScript
для облегчения восприятия, но сам плагин на TypeScript
. Плагин полность типизирован и задокументирован, что значительно облегчает работу с ним. В отличие от vue-modal-dialogs
этот плагин имеет дополнительный функционал. Это особые хуки: onConfirm
и onCancel
. Они принимают колбек и срабатывают в зависимости от решения пользователя: onConfirm
если пользователь согласится и onCancel
если откажется.
Конечный результат можно посмотреть в песочнице. Код незначительно отличается от того, что в посте.
Установка
Начнем с создания нового проекта на 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, если вас не отпускает ностальгия по Vue 2.