Модальные диалоги не такая и сложная задача в разработке. Разве что можно легко запутаться в коде, если нужно вызывать модальные окна по цепочке. Но это очень монотонная и неинтересная работа с повторяющейся логикой, которую подчас копируют из компонента в компонет с незначительными изменениями.
Хорошим решением было бы создать функцию, которая принимала бы компонент диалога и управляла бы его рендерингом в шаблоне, а этот диалог можно "промисифицировать" и работать с ним как с асинхронной функцией. Как например в этой библиотеке 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)
makar_crypt
24.04.2022 20:52+1ну для вас же создали teleport в vue 3 . почему вы опять костыли самодельные городите?
js_n00b Автор
25.04.2022 03:16Это никак не связанно, смотри ответ выше. Если кратко этот плагин полезен для абстракции и переиспользования логики диалогов.
danilovmy
Зануда.mode = on
Импорты SimpleDialog и createConfirmDialog в итоговом примере App.vue не нужны
Зануда.mode = off
А теперь по смыслу: js_n00b, а если сравнить с нативным Dialog — в чем преимущество vue-dialog-vrapper?
js_n00b Автор
Спасибо, поправил.
На твой вопрос нет ответа, потому что это неконкурентные вещи. Можно написать компонент используя нативный диалог, а можно любое другое кастомное решение, которые захочет бизнес. Можно использовать библиотеку и для совсем других вещей, например алерты, чат боты, и многое другое.
createConfirmDialog
принимает и рендерит любые компоненты.Как я уже писал модальный диалог не сложная задача в реализации. Просто если у вас в проекте на каждой странице десяток похожих окон, может стоит абстрагировать эту логику, а не писать каждый раз похожий код?