Модальные диалоги — не такая уж сложная задача для разработки. Они часто используются для подтверждения действий пользователя. Например, удалить какие-либо данные или выполнить авторизацию. Это очень монотонная и неинтересная работа с повторяющейся логикой, которая иногда копируется из компонента в компонент с незначительными изменениями.
Но что если у вас десятки или даже сотни подобных диалогов на одной странице или даже во всем проекте? Или если вам нужно вызывать диалоги по цепочке в зависимости от выбора пользователя? Как сделать многократно используемую функцию, которая также имеет читаемый и сопровождаемый код?
Было бы полезно создать функцию, которая принимала бы компонент диалога и управляла его отрисовкой в шаблоне. Функция возвращала бы объект, содержащий состояние диалога и методы, чтобы с ним можно было работать, как с обещаниями. Например, этот плагин 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
.
И, конечно, для лучшего опыта разработчиков используйте его с TypeScript!