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