Bienvenidos sean a este post, hoy crearemos una aplicacion de Vue para aplicar lo visto anteriormente.
En este post vimos como crear un catalogo de libros en React. Donde veiamos no solo el listado de libros sino tambien los detalles sobre estos. Luego agregamos un form por cada libro para poder seleccionar una cantidad y «agregarlo a un carrito». Hoy crearemos una aplicacion en vue que simula al carrito para procesar el pedido. Para esto, nuestro primer paso sera crear una nueva aplicacion mediante el siguiente comando:
$ vue create carrito
Al comienzo les pedira que seleccionemos la configuracion de la aplicacion, si no vienen de los posts anteriores o no almacenaron el perfil, deben seleccionar la ultima opcion de manual y seleccionen las siguientes opciones:
- typescript (solamente)
- 3.x
- Use class-style component syntax? yes
- Use babel alongside? no
- dedicated config files
- Save this as a preset for future projects? yes
Con nuestra aplicacion creada, lo primero que haremos sera ingresar al nuevo directorio, y en este ejecutaremos estos tres comandos:
$ npm install bootstrap
$ npm install mdb-ui-kit
$ npm install @fortawesome/fontawesome-free
Estos comandos nos instalaran las librerias necesarias que utilizaremos en nuestra aplicacion, y el ultimo sera para acceder a fuentes e iconos para la estetica de la aplicacion. Nuestro siguiente paso sera ir al archivo index.html en el directorio public y modificaremos al tag head de la siguiente manera:

Nota:
Debo pasarlo como imagen porque si paso el codigo me rompe todo el post 🙄
En este caso solo agregamos dos lineas, la primera es para poder importar y utilizar las utilidades de mdb-ui en nuestros scripts mediant CDN y en el segundo caso es para poder utilizar los estilos de esta misma libreria mediante CDN. Si bien podemos descargarlos y agregarlos a nuestro proyecto localmente, por un tema de practicidad vamos a tomarlo directamente desde su pagina mediante esta posibilidad. Con esto realizado debemos ir al archivo shims-vue.d.ts en el directorio src y lo modificaremos de la siguiente manera:
src/shims-vue.d.ts
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
declare module 'mdb-ui-kit/js/mdb.es.min.js';
Nota:
En mi caso al instalar el modulo le asigno ese nombre, si en tu equipo instala uno generico reemplazalo por mdb.min.js
En este caso agregamos una linea al final para declarar al nuevo modulo que habilitamos en el archivo html, esto debemos hacerlo asi porque este modulo no existe como tal para typescript y esto nos permite utilizar este modulo de node.js, sobre esto hablamos en este post, y con esto realizado podemos continuar con nuestra aplicacion. Antes de comenzar con la aplicacion en si, deben descargar el siguiente archivo:
Una vez descargado, crean un nuevo directorio dentro del directorio public con el nombre de images y en este extraigan todos los archivos. Estos seran las caratulas de nuestros libros, con esto realizado podemos continuar. Lo siguiente sera crear un nuevo archivo en el directorio src con el nombre de Elementos.ts y le agregaremos el siguiente codigo:
src/Elementos.ts
export interface ILibro {
id: number;
nombre: string;
imagen: string;
descripcion?: string;
cantidad?: number;
datos?: IDatos;
}
export interface IDatos {
autor?: string;
anyo?: string;
precio: number;
}
export class Agregados {
libros: ILibro[];
constructor() {
this.libros = [
{
id: 1,
nombre: "The Call of Cthulhu",
imagen: "cthulhu.png",
cantidad: 2,
datos: {
autor: "H.P. Lovecraft",
precio: 15.75,
}
},
{
id: 2,
nombre: "Conan, the barbarian",
imagen: "barbaro.png",
cantidad: 1,
datos: {
autor: "Robert E. Howard",
precio: 4.99,
}
},
{
id: 3,
nombre: "Hyperborea",
imagen: "hyperborea.png",
cantidad: 3,
datos: {
autor: "Clark A. Smith",
precio: 11.99,
}
}
];
};
}
Este es el archivo donde tendremos el contenido de nuestro «carrito» y para ello usaremos el mismo concepto que vimos en este post. Donde establecemos una interfaz para contener cada uno de las propiedades de los libros y otra interfaz para datos mas especificos. Ambas interfaces podemos exportarlas pero la que usaremos en otros archivos sera la primera. Luego definimos una clase donde estaran todos los elementos del carrito con sus respectivos datos, como son el id, el nombre del libro, la imagen, el precio, la cantidad y el autor. Con los elementos del carrito creados pasemos a crear el componente que los alojara. Para ello vayan al directorio components en src y creen un archivo con el nombre de Carrito.vue y agreguen el siguiente codigo:
components/Carrito.vue
<template>
<div class="container">
<div v-if="!isCheckout">
<h2>Carrito</h2>
<hr/>
<div v-bind:key="libro.id" v-for="libro in carrito.libros">
<VerItem :libro="libro" @on-remove="elemRemovido"/>
</div>
<button class="btn btn-primary" v-on:click="verificar">
Verificar
</button>
</div>
<div v-if="isCheckout">
<VerCheckout :basket="carrito" />
<button class="btn btn-secondary" v-on:click="volver()">
Volver
</button>
<button class="btn btn-primary">
Realizar Pedido
</button>
</div>
</div>
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { Agregados } from "../Elementos";
import VerItem from "./VerItem.vue";
import VerCheckout from "./VerCheckout.vue";
@Options({
props: {
carrito: Agregados,
},
components: {
VerItem,
VerCheckout,
}
})
export default class Carrito extends Vue {
carrito!: Agregados;
isCheckout!: boolean;
data() {
return {
carrito: this.carrito,
isCheckout: false
};
}
elemRemovido(id: number) {
const indice = this.carrito.libros.findIndex(
(libro) => libro.id === id);
this.carrito.libros.splice(indice, 1);
}
verificar() {
this.isCheckout = true;
}
volver() {
this.isCheckout = false;
}
}
</script>
<style scoped></style>
Hablemos primero del template. Este tendra un solo div para contener todo y luego tendremos dos div donde mediante un v-if mostraremos uno o el otro. Para ello usaremos una propiedad llamada isCheckout donde si es false, en el primer div mediante el signo de negacion, mostraremos todo el contenido de nuestro carrito. En este tendremos una propiedad llamada key donde almacenaremos el id de cada libro y luego tenemos un bucle for, v-for, donde almacenaremos cada libro de la coleccion en Agregados del archivo anterior. Para luego usar al objeto VerItem donde pasaremos cada libro para ver los datos del mismo, este elemento lo desarrollaremos un poco mas adelante. Y lo relacionaremos a un evento llamado on-remove donde llamara al metodo elemRemovido pero sobre esto hablaremos en un momento. Para finalmente tener el boton de verificar donde llamaremos a otro metodo para verificar el pedido y realizar la compra final. El siguiente div tambien posee un v-if pero aqui verifica si isCheckout es true y en caso de ser asi procede a utilizar el elemento VerCheckout y le pasa el valor de carrito a una propiedad llamada basket. Este elemento se encarga de mostrar todos los valores de la compra pero sobre esto hablaremos un poco mas adelante y finalmente tenemos dos botones para volver a la lista del carrito y otro para finalizar la compra. En el primer caso llama a un metodo que veremos en la seccion de script y el otro no realiza ninguna accion.
Luego tenemos la seccion de script donde primero importaremos del modulo vue-class-component los modulos para la decoracion, Options, y el que convertira a la clase en componente del lenguaje, Vue, y el contenido de nuestro carrito desde el archivo Elementos.ts. Las siguientes dos importaciones son de los elementos que usamos en el template pero de dos modulos que aun no creamos. Seguido a esto tenemos el decorador donde primero iniciaremos a la propiedad carrito del tipo Agregados y luego le diremos los componentes o elementos que usaremos, con nuestra decoracion estabecida nuestro siguiente paso sera definir a la clase de este componente.
La iniciaremos con el export para poder acceder desde otro archivo y el default para indicarle que es la predeterminada de aqui. Lo primero sera declarar las propiedades que usaremos como son carrito para los datos e isCheckout para saber si ya la verificamos o no. Establecemos los tipos de ambas y luego mediante data devolveremos cuales son sus valores iniciales. En el caso del carrito sera todo el contenido de Agregados, ,esto es asi gracias al constructor que tenemos en esta clase, y le establecemos un valor de false a isCheckout para que nos muestre al primer div. La siguiente accion sera definir al metodo elemRemovido y en este almacenaremos en una constante la devolucion de la funcion findIndex aplicado sobre el carrito y para ello compara cada valor de id de los libros con el id recibido y despues de esto procede a removerlo de la lista mediante splice. Los siguientes dos metodos son para establecer el valor de isCheckout. En el caso de verificar lo usamos para mostrar el segundo div que sera el ultimo paso antes de realizar la compra y volver lo establece como false para volver al primer div. Ya tenemos a nuestro carrito listo pero nos faltan algunos componentes por definir. Para ello primero crearemos un archivo en el directorio components con el nombre de VerItem.vue y le agregaremos el siguiente codigo:
components/VerItem.vue
<template>
<div>
<div class="container-sm">
<div class="row">
<h4>Elem #{{libro.id}} - {{libro.nombre}} ({{libro.datos.autor}})</h4>
</div>
<br/>
<div class="row">
<div class="col-sm-2">
<img :src="origenImagen" class="img-thumbnail image-small"/>
</div>
<div class="col-sm-10">
<div class="row">
<div class="col-sm-5">
<div class="form-outline" ref="cantIngresados">
<input type="number" class="form-control"
v-model="libro.cantidad"/>
<label class="form-label">
No. de Libros
</label>
</div>
</div>
</div>
<div class="row"> </div>
<div class="row">
<div class="col-sm-6">
<button class="btn btn-primary" v-on:click="elemRemovido">
Borrar
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<hr/>
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { ILibro } from "../Elementos";
import * as mdb from "mdb-ui-kit/js/mdb.es.min.js";
@Options({
props: {
libro: Object
},
emits: ["onRemove"],
computed: {
origenImagen() {
return `images/${this.libro.imagen}`;
}
},
mounted() {
let botones = this.$refs.cantIngresados;
new mdb.Input(botones).init();
}
})
export default class VerItem extends Vue {
libro!: ILibro;
data() {
return {
libro: this.libro,
};
}
elemRemovido() {
this.$emit("onRemove", this.libro.id);
}
}
</script>
<style>
.image-small {
width:130px;
}
</style>
Este sera el componente encargado de mostrar cada dato de los elementos de nuestro carrito. Por eso primero en el template tendremos un div general, luego un div contenedor y varios con los distintos datos. El primero sera con los datos del libro, siendo el numero de elemento o id, seguido del titulo del libro y el autor del mismo. El siguiente div sera para la imagen o caratula del libro. En este caso lo mas curioso es como pasamos el source o src del archivo de la imagen donde usaremos un metodo, del cual hablaremos en un momento, y las clases para armonizarlo con la pagina. El siguiente div a su vez contendra dos div. El primero es para indicar la cantidad que fueron comprados y en este estableceremos una ref llamada cantIngresados y un input de tipo number, le aplicamos una clase de control de form y el tipo sera establecido mediante la propiedad cantidad de libro. Recuerden que este recibe cada uno de los libros con todos sus datos. Y finalmente una etiqueta para indicar que es el numero de libros. El siguiente div posee un boton que se encarga de eliminar el elemento del carrito. En este relacionaremos el evento click con el metodo elemRemovido que posee el mismo nombre que el archivo anterior pero lo definiremos en la siguiente seccion. Con esto tenemos el layout de cada elemento del carrito pasemos a la seccion de script.
La primera linea es para la importacion del decorador y la clase que nos permite establecer a la clase predeterminada como componente de vue. La siguiente sera para importar a la interfaz ILibro del archivo Elementos.ts. Esta la necesitaremos porque debemos manejar al tipo de los libros y la ultima importacion es para utilizar las herramientas de mdb-ui-kit. Este es basicamente para que podemos usar correctamente al elemento de input y label de la seccion template pero ya veremos como. La primera accion sera establecer el decorador y en este estableceremos a la propiedad libro como un objeto, usaremos a emits para establecer el evento que utilizaremos para remover elementos del carrito, computed sera para definir al metodo que sera encargado de establecer la imagen y para ello devuelve el directorio y el valor de la propiedad imagen de cada libro y por ultimo tenemos una funcion llamada mounted. La cual usaremos para establecer correctamente cada uno de los input con los valores de la cantidad de libros. Observen que primero definimos una variable con el valor de la referencia cantIngresados y luego mediante mdb iniciamos cada uno de los mismos. Esto hara que tengamos el valor y podamos modificarlo sin problemas.
Seguido a esto tenemos la definicion de la clase que usaremos como componente. Lo primero sera declarar una propiedad llamada libro que sera del tipo ILibro y tendra el signo para indicar que no sera vacio ni null. Lo siguiente es data donde devolveremos a esta propiedad y la estableceremos con el valor del libro actual. Para finalmente definir el metodo elemRemovido donde emitiremos el evento onRemove y le pasamos como argumento el id del libro. Esto hara que se llame al metodo del archivo anterior y proceda removerlo de la lista. Por ultimo, tenemos un style donde definimos la clase que le aplicamos a la imagen para que se mantenga de ese tamaño. Con esto tenemos cada elemento de nuestro carrito pero antes veamos como quedo hasta ahora.

Si lo ejecutan les devolvera un error y probablemente no funcione pero hasta aqui es mas o menos lo que hicimos. Ahora pasaremos al siguiente componente faltante de Carrito y para ello deben crear un archivo en el directorio components con el nombre de VerCheckout.vue y agregar el siguiente codigo:
components/VerCheckout.vue
<template>
<div class="hello">
<table class="table table-sm">
<thead class="thead-dark">
<tr>
<th scope="col">ID Libro</th>
<th scope="col">Titulo</th>
<th scope="col">Autor</th>
<th scope="col" class="texto-a-la-derecha">Cantidad</th>
<th scope="col" class="texto-a-la-derecha">Precio</th>
<th scope="col" class="texto-a-la-derecha">Total</th>
</tr>
</thead>
<tbody>
<tr v-for="libro in basket.libros" v-bind:key="libro.id">
<VerTotalItem :libro="libro"></VerTotalItem>
</tr>
<tr>
<th scope="col" colspan="5">Total Carrito</th>
<th scopt="col" class="right-aligned-text">{{totalLibros}}</th>
</tr>
<tr>
<th scope="col" colspan="5">Impuesto</th>
<th scopt="col" class="right-aligned-text">{{impuesto}}</th>
</tr>
<tr>
<th scope="col" colspan="5">Envio</th>
<th scopt="col" class="right-aligned-text">{{envio}}</th>
</tr>
<tr>
<th scope="col" colspan="5">Costo Total</th>
<th scopt="col" class="right-aligned-text">{{total}}</th>
</tr>
</tbody>
</table>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import { Agregados } from '../Elementos';
import VerTotalItem from './VerTotalItem.vue';
@Options({
props: {
basket: Agregados,
},
components: {
VerTotalItem
},
computed: {
totalLibros() {
console.log(`total agregados: ${this.vTotal()}`);
return this.mostrarValor(this.vTotal());
},
impuesto() {
return this.mostrarValor(this.vImpuesto());
},
envio() {
return this.mostrarValor(this.vEnvio());
},
total() {
return this.mostrarValor(
this.vTotal() + this.vImpuesto() + this.vEnvio());
}
}
})
export default class VerCheckout extends Vue {
basket!: Agregados;
data() {
return {
basket: this.basket
};
}
vTotal() {
let total = 0;
for (let libro of this.basket.libros) {
total += <number>libro.cantidad * libro.datos.precio;
}
return total * 100;
}
vImpuesto() {
return Math.round(this.vTotal() * 0.1);
}
vEnvio() {
return 700;
}
mostrarValor(cantidad: number): string {
return (cantidad / 100).toFixed(2);
}
}
</script>
Este sera el componente encargado de mostrarnos la facturacion final antes de realizar la compra, con los valores finales. En el template tendremos una tabla donde estaran los titulos de cada valor de la factura en un thead. En el tbody primero tendremos varias filas, en la primera mostraremos al elemento VerTotalItem que se encargara de mostrar cada uno de los valores de cada elemento del carrito y en concordancia con los titulos del thead. Despues de esto, tenemos varias filas para mostrar primero el valor total del carrito, luego los impuestos, el valor de envio y el valor total con todos los valores anteriores. En cada uno de ellos mostraremos el valor mediante distintos metodos, de los cuales hablaremos en un momento.
En el script importaremos a la clase Agregados para tener todos los elementos del carrito y luego el componente VerTotalItem para mostrarlo en el template. En el decorador, primero iniciamos a la propiedad basket para recibir al carrito, despues establecemos al componente que usaremos y en computed definiremos los metodos que usamos en el template para mostrar los distintos valores. El primer metodo nos devuelve el total de libros y para ello usaremos al metodo mostrarValor y el resultado devuelto por vTotal, de esta hablaremos en un momento. Los siguientes metodos trabajan de la misma manera donde usan mostrarValor y se usa para mostrar el resultado devuelto por cada funcion relacionada a la misma. Cada una de estas las definiremos en la clase. La ultima funcion nos devuelve el total de la suma de cada una de los metodos anteriores.
Lo siguiente es definir al componente que usaremos para exportar. Primero declaramos a la propiedad basket y le establecemos el tipo de dato, Agregados, y luego mediante data le estableceremos el valor recibido a esta propiedad. Despues de este comenzaremos a definir cada uno de los metodos que utilizamos en computed para devolver el valor correspondiente a cada campo. El metodo vTotal define una variable con un valor inicial, luego mediante un bucle for pasaremos por todo el carrito y cada elemento recuperado en cada vuelta, tomaremos el valor de cantidad y lo multiplicaremos por el precio. En el primer caso, hacemos un «casteo» de tipo number para indicar que es un numero, una vez finalizado el bucle devolveremos el resultado final. El segundo metodo, vImpuesto, devolvera el redondeo del resultado del valor de vTotal multiplicado por 0.1. El metodo vEnvio solo devuelve el valor de 7 para indicar cuanto es el recargo por el envio. Y el ultimo metodo, es el encargado de mostrar/formatear el valor recibido. Para ello devuelve el valor recibido pero aplicandole la funcion toFixed para que solo muestre dos digitos despues del signo decimal. En este caso es un punto porque usa el formato americano. Ya con esto tenemos la verificacion final antes de pagar pero nos falta un componente mas como es VerTotalItem. Para ello, dentro de components creamos un archivo con el nombre de VerTotalItem.vue y le agregaremos el siguiente codigo:
components/VerTotalItem.vue
<template>
<td scope="col">{{libro.id}}</td>
<td scope="col">{{libro.nombre}}</td>
<td scope="col">{{libro.datos.autor}}</td>
<td scope="col" class="texto-a-la-derecha">{{libro.cantidad}}</td>
<td scope="col" class="texto-a-la-derecha">{{valorFormateado}}</td>
<td scope="col" class="texto-a-la-derecha">{{valorFinal}}</td>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import { ILibro } from '../Elementos';
@Options({
props: {
libro: Object
},
computed: {
valorFinal() {
return (this.libro.cantidad * this.libro.datos.precio).toFixed(2);
},
valorFormateado() {
return (this.libro.datos.precio * 1).toFixed(2);
}
}
})
export default class VerTotalItem extends Vue {
libro!: ILibro;
data() {
return {
libro: this.libro
};
}
}
</script>
<style>
.texto-a-la-derecha {
text-align: right;
}
</style>
En el template solo tendremos columnas porque es lo que debemos poner en la fila de tbody en el archivo anterior. Las primeras cuatro columnas muestran valores del carrito pero los ultimos dos son metodos. En la seccion del script importaremos a la interfaz ILibro porque la usaremos como tipo de dato.. En el decorador establecemos la propiedad libro como objeto, en computed definimos los metodos que usamos en el template. El primero devuelve la multiplicacion de la cantidad por el precio y redondea los decimales a dos.. El segundo metodo devuelve el valor del precio pero formateado, en este caso de nuevo con el redondeo a dos decimales. Por ultimo, en el componente que exportamos establecemos la propiedad libro y mediante data devolvemos esta propiedad con el valor recibido. Y finalmente, antes de terminar con el archivo en style definimos la clase que aplicamos en las columnas. Con esto ya tenemos nuestro carrito creado y funcionando completamente pero nos falta una ultima modificacion. En este caso debemos ir a App.vue en el directorio src y modifcamos el codigo de la siguiente manera:
src/App.vue
<template>
<Carrito :carrito="libros" />
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import { Agregados } from './Elementos';
import Carrito from './components/Carrito.vue';
const elementosCarrito = new Agregados();
@Options({
components: {
Carrito,
},
})
export default class App extends Vue {
data() {
return {
libros: elementosCarrito
}
}
handleBroadcastEvent(ev: string) {
console.log(`VUE: app.handleBroadcastEvent: ${ev}`);
}
}
</script>
<style scoped></style>
Lo primero sera modificar al template para que contenga unicamente al componente Carrito y le pasaremos el contenido mediante la propiedad carrito. En script importamos el contenido del carrito, Agregados, y luego el componente Carrito. Despues definimos una constante que contendra todos los elementos de la clase Agregados. Recuerden que esta clase tiene un constructor que genera automaticamente los valores en el objeto instanciado. En el decorador declaramos al componente y en el componente de este archivo medainte data devolveremos a libros que le pasaremos la constante creada y este sera el valor que usaremos en el template. Por ultimo, tenemos un metodo mas para mostrar en consola un mensaje. Con todo esto comentado podemos pasar a probarlo y ver como funciona mediante el siguiente video
En el video pueden ver como se ven los resultados finales y las distintas acciones que podemos aplicar al carrrito antes de finalizar la compra. Antes de finalizar les dejo un link con todos los archivos del proyecto para que puedan verlo:
En resumen, hoy hemos visto una aplicacion de vue en la vida real, si bien no es activa en un sitio, si nos muestra como trabaja vue ante una accion muy actual en internet como es un carrito de compras. Espero les haya resultado de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donatión
It’s for site maintenance, thanks!
$1.50
