Как стать автором
Обновить

Простой способ создания и переиспользования модальных диалогов во Vue 3

Время на прочтение5 мин
Количество просмотров7.8K

image


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


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


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

Теги:
Хабы:
Всего голосов 1: ↑1 и ↓0+1
Комментарии4

Публикации

Истории

Работа

Ближайшие события