DEV Community

Cover image for Creando una modal con Vue y Typescript
gugadev
gugadev

Posted on

Creando una modal con Vue y Typescript

Crear una modal con Vue y Typescript

GitHub logo gugadev / vue-modal

Simple modal built on Vue and Typescript.

Simple Modal

Simple modal built on Vue and Typescript.

Properties

  • title: Title of the modal
  • open: Flag that indicates if the modal is or not opened.

Events

  • close: Fired on close click.

Uno de los frameworks JavaScript que más ha crecido este último año ha sido Vue. Este framework, caracterizado por su sencillez y a la vez, su potencia, ha tomado por asalto la comunidad frontend.

No por nada Vue ha superado a React y Angular en popularidad en Github aunque no quiere decir que a nivel de uso por parte de los desarrolladores se mantenga la misma equivalencia. Lo cierto es que Vue es un framework increíble, flexible, potente y lleno de posibilidades. Aprovecho para felicitar a Evan You y a todo el equipo y contribuyentes detrás de Vue. Congratz guys, you're awesome!

Preparación del proyecto

Bien, empecemos. Lo primero que vamos a necesitar es inicializar el proyecto e instalar algunas dependencias. Vamos a dividir las dependencias en dos: dependencias de desarrollo y de funcionamiento.

Las dependencias de desarollo serán básicamente loaders para Typescript y Vue. Estas son:

  • typescript
  • tslint
  • ts-loader
  • vue-loader
  • vue-style-loader
  • vue-template-compiler
  • css-loader
  • style-loader
  • html-webpack-plugin
  • webpack-dev-server
  • webpack
  • webpack-cli

Y las depenencias principales son:

  • vue
  • vue-class-component
  • vue-property-decorator

Ahora que tenemos las dependencias instaladas, procedemos a crear un archivo llamado tsconfig.json, el cual leerá Typescript para tomar en cuenta algunas configuraciones.

{
  "include": [
    "./src/**/*"
  ],
  "compilerOptions": {
    "target": "esnext",
    "lib": ["dom", "esnext"],
    "module": "es2015",
    "moduleResolution": "node",
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  },
  "compileOnSave": false
}
Enter fullscreen mode Exit fullscreen mode

Lo que hacemos es, en teoría, decirle que tenga en cuenta a cualquier archivo dentro de src/, que queremos usar ES Modules y que active el uso de decoradores.

Una vez realizado este paso, lo siguiente es preparar el archivo de configuración de Webpack:

const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  context: __dirname,
  entry: './src/index.ts',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm.js'
    },
    extensions: ['.ts', '.js']
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: {
          loader: 'ts-loader',
          options: {
            appendTsSuffixTo: [/\.vue$/]
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.vue$/,
        exclude: /node_modules/,
        use: {
          loader: 'vue-loader'
        }
      }
    ]
  },
  devtool: 'sourcemap',
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
}
Enter fullscreen mode Exit fullscreen mode

Utilizaremos html-webpack-plugin para levantar webpack-dev-server. El archivo index.html lo ponemos en la carpeta src de nuestro proyecto. Quedará así:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Simple Modal</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Ahora, seguimos con el entry point de nuestra aplicación. Aquí levantaremos Vue junto con el componente principal.

import Vue from 'vue'
import App from './index.vue'

new Vue({
  el: '#app',
  template: '<App/>',
  render: h => h(App)
})
Enter fullscreen mode Exit fullscreen mode

Ya tenemos todo listo para empezar con la creación de la modal.

Creación de la modal

La modal será sencilla y orientada a componentes. Realizaremos la estructura, mapearemos algunas propiedades y estableceremos los eventos que debe emitir. El contenido de la modal será insertado de acuerdo a lo que necesitemos en cada ocasión.

Lo primero será crear el template:

Nota: recuerda que tanto el template, como style y script va dentro de un archivo .vue.

<template>
  <div class="modal" :class="{ open }">
    <div class="modal-content">
      <header class="modal-header">
        <h3>{{ title }}</h3>
        <span @click="close">&times;</span>
      </header>
      <article class="modal-body">
        <slot name="content"></slot>
      </article>
      <footer class="modal-footer">
        <slot name="actions"></slot>
      </footer>
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Como se puede ver a simple vista, es una estructura bastante sencilla. El título de la modal deberá ser proporcionado por
medio de una la propiedad title, además, el sabremos si está abierta o cerrada por medio de la propiedad open.

La siguiente línea:

<span @click="close">&times;</span>
Enter fullscreen mode Exit fullscreen mode

Nos dice que cuando se haga click en la "x", se ejecutará el método close de nuestro componente.

Además, para poder mostrar u ocultar la modal, nos basaremos en esta línea:

<div class="modal" :class="{ open }">
Enter fullscreen mode Exit fullscreen mode

Que nos indica que si la propiedad open es true, entonces se agregará una clase CSS llamada open, la cual mostrará la modal con un efecto transitorio, como se puede apreciar en el código CSS:

<style scoped>
  .modal {
    align-items: flex-start;
    background-color: rgba(0,0,0,.75);
    display: flex;
    height: 100vh;
    justify-content: center;
    opacity: 0;
    position: fixed;
    transition: visibility 250ms, opacity 250ms;
    width: 100%;
    z-index: -1;
  }
  .modal.open {
    opacity: 1;
    visibility: visible;
    z-index: 2;
  }
  .modal.open .modal-content {
    transform: translateY(100px);
  }
  .modal-content {
    background-color: #fff;
    border-radius: 2px;
    box-shadow: 0 8px 16px 0 rgba(0,0,0,.25);
    display: inline-block;
    min-width: 320px;
    max-width: 480px;
    transition: transform 450ms ease;
    width: 100%;
  }
  .modal-header {
    border-bottom: 1px solid #eee;
    padding: 20px;
    position: relative;
    text-align: center;
  }
  .modal-header h3 {
    color: #444;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 14px;
    font-weight: 600;
    text-transform: uppercase;
  }
  .modal-header span {
    cursor: pointer;
    font-weight: bolder;
    position: absolute;
    right: 15px;
    top: 50%;
    transform: translateY(-50%);
  }
  .modal-body {
    padding: 40px;
  }
  .modal-footer {
    background-color: #f8f8f8;
    border-top: 1px solid #eee;
    display: flex;
    justify-content: flex-end;
    padding: 20px;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

Nota: hacemos uso del atributo scoped para determinar que estos estilos serán encapsulados dentro del componente, evitando conflictos con el resto de estilos de la aplicación.

El código CSS anterior simplemente agrega una transición de opacidad a la modal, así mismo, hace que esta se deslice desde arriba hacia abajo, dando un efecto llamativo y suave.

Finalmente, escribimos nuestro componente principal, el que se comunica con el template y tiene las propiedades y métodos que el template necesitará consumir.

<script lang="ts">
  import Vue from 'vue'
  import Component from 'vue-class-component'
  import { Prop, Emit } from 'vue-property-decorator'

  @Component
  export default class Modal extends Vue {
    @Prop({ required: true, type: String }) title: string
    @Prop({ required: true, type: Boolean, default: false }) open

    @Emit('close')
    close(): void {}
  }
</script>
Enter fullscreen mode Exit fullscreen mode

Lo primero que hacemos es importar los decoradores Component, el cual sirve para indicarle a Vue que dicha clase es un componente, Prop que indica que dicha variable es una prop que recibirá el componente y Emit que indica que ese método emitirá un evento hacia el padre.

La propiedad titley open, como dijimos, son requeridas. Así mismo, open será inicializada en false.

El método close, al ser ejecutado emitirá un evento hacia el padre que contenga la modal, notificando que se quiere cerrar la modal.

Utilizando la modal

Para utilizar la modal es bastante sencillo. Basta con incluirla en la lista de componentes y colocarla en el template. Veamos un ejempo.

<template>
  <div class="container" @keypress="catchKey" tabindex="0">
    <Modal :title="modalTitle" :open="modalOpened" @close="closeModal">  
      <template slot="content">
        <blockquote>
          <p>Debido a nuevas políticas de seguridad, a partir de hoy, 22 de Enero del 2019, usted es reponsable de la seguridad de sus archivos. Para saber como reforzar y manejar la seguridad de su cuenta, lea la <a href="#">Documentación.</a></p>
          <caption>TI & Information Security</caption>
        </blockquote>
      </template>
      <template slot="actions">
        <button class="decline">Declinar</button>
        <button class="accept">Aceptar</button>
      </template>
    </Modal>
    <h1>Presiona O para abrir la modal</h1>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue'
  import Component from 'vue-class-component'
  import Modal from './modal.vue'

  @Component({
    components: {
      Modal
    }
  })
  export default class App extends Vue {
    modalTitle = 'Alerta de seguridad'
    modalOpened = false
    MODAL_TRIGGER = 111

    catchKey(e: KeyboardEvent) {
      if (e.keyCode === this.MODAL_TRIGGER) {
        this.modalOpened = true
      }
    }
    closeModal() {
      this.modalOpened = false
    }
  }
</script>
Enter fullscreen mode Exit fullscreen mode

Como vemos, la propiedad title de la modal está ligada con modalTitle y open con modalOpened, de modo que, cuando se presione la tecla O se cambie el estado de modalOpened a true, mostrando la modal.

Fíjate en el método closeModal, es este método el que se ejecutará en cuando se detecte que Modal ha emitido un evento de tipo close, sobre el cual estamos escuchando mediante la línea @close="closeModal".

Resultado


Conclusiones

Como vemos, crear un componente en Vue es realmente sencillo. No nos tomará más de un par de horas de tener un componente relativamente complejo y funcional. Personalmente, creo que todo desarrollador Frontend debería de darle la oportunidad a este genial framework. 😉

Top comments (7)

Collapse
 
juanfrank77 profile image
Juan F Gonzalez

Muy buen articulo, a pesar de que yo no uso ni Vue ni Typescript (ni lo voy a hacer por el momento :p) pero conozco que son buenas tecnologías para el Frontend. Y además el resultado se ve cool e_e

Collapse
 
gugadev profile image
gugadev • Edited

¡Gracias por tus palabras, Juan! La verdad que sí, sobre todo Typescript que está siendo adoptado por la mayoría de frameworks :)

Collapse
 
gugadev profile image
gugadev

¡Realmente sí! Lo genial es que al final sigues usando JavaScript, no como en el caso de CoffeeScript ;)

Collapse
 
taverasmisael profile image
Misael Taveras

Buen post. Me sorprendió ver en español aquí, y eso me encantó!!

Collapse
 
gugadev profile image
gugadev

¡Hola Misael! Gracias por tu feedback ;) La verdad es que hace falta material actualizado en Español y bueno, quiero poner mi grano de arena. Hay mucho talento escondido en España y Latinoamérica.

Collapse
 
sauloco profile image
Saulo Vargas • Edited

Hace rato estoy tratando de seguir con la movida de Español acá adentro.
Trabajo con Typescript y Vue.
Genial post!

Collapse
 
gugadev profile image
gugadev

¡Gracias, Saulo! La verdad que hace falta un poco de material actualizado en Español. ¡Genial que trabajes con Vue!