Crear una modal con Vue y Typescript
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
}
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'
})
]
}
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>
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)
})
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">×</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:
<span @click="close">×</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:
<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:
<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.
<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.
<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
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)
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
¡Gracias por tus palabras, Juan! La verdad que sí, sobre todo Typescript que está siendo adoptado por la mayoría de frameworks :)
¡Realmente sí! Lo genial es que al final sigues usando JavaScript, no como en el caso de CoffeeScript ;)
Buen post. Me sorprendió ver en español aquí, y eso me encantó!!
¡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.
Hace rato estoy tratando de seguir con la movida de Español acá adentro.
Trabajo con Typescript y Vue.
Genial post!
¡Gracias, Saulo! La verdad que hace falta un poco de material actualizado en Español. ¡Genial que trabajes con Vue!