Creando una modal con Vue y Typescript
Tags: vue, typescript, css
Published: 2019-01-22 21:27:36

# Crear una modal con Vue y Typescript

{% github gugadev/vue-modal %}


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](https://zendev.com/2018/06/19/react-usage-beating-vue-angular.html). 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.

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

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](https://github.com/tc39/proposal-decorators).

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

```javascript
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'
    })
  ]
}
```

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

```html
<!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>
```

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

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

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

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`.

```html
<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>
```

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:

```html
<span @click="close">&times;</span>
```

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:

```html
<div class="modal" :class="{ open }">
```

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:

```html
<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>
```

> **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.

```html
<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>
```

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 `title`y `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.

```html
<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>
```

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**

<p align="center">
  <img src="http://g.recordit.co/QXsNyolLfy.gif" />
</p>

---

## 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. 😉