JavaScript es una tecnología básica en la construcción de sitios web. Puede ser usanda simplemente para añadir algunos efectos visuales, funcionalidades, o dibujar enteramente la interfaz (lo que conocemos como SPA: single-page application) liberando al Back-End del procesamiento. Con esta flexibilidad es interesante para algunos apartados como generar una sección dinámica de carrito.
En este tutorial se ha generado todos los productos a partir de una supuesta base de datos, dibujando todo desde el Front-End. Para simplificar únicamente se ha declarado un minimalista JSON con toda la información. A continuación se ha construido un carrito de compra capaz de mostrar y calcular el precio total.
Entre sus funcionalidades podemos encontrar:
- Generación de productos a partir de un JSON.
- Añadir producto al carrito.
- Enumerar los productos del carrito, evitando repeticiones.
- Eliminar productos del carrito.
- Vacía el carrito
- Calcular precio total.
- Recuerda los productos al volver gracias a LocalStorage (ver Extra 1).
- Usar JavaScript nativo (o JavaScript Vainilla), nada de JQuery o Frameworks complejos (React, Angular, VueJS…).
- Sólido ante errores al usar un paradigma de programación funcional.
DEMO
Interactua con el resultado del código.
HTML
He utilizado Bootstrap para facilitar la tarea de maquetación, pero siéntete libre de usar tus propios estilos.
<div class="container">
<div class="row">
<!-- Elementos generados a partir del JSON -->
<main id="items" class="col-sm-8 row"></main>
<!-- Carrito -->
<aside class="col-sm-4">
<h2>Carrito</h2>
<!-- Elementos del carrito -->
<ul id="carrito" class="list-group"></ul>
<hr>
<!-- Precio total -->
<p class="text-right">Total: <span id="total"></span>€</p>
<button id="boton-vaciar" class="btn btn-danger">Vaciar</button>
</aside>
</div>
</div>
JavaScript
Todo esta realizado para usar el máximo partido de JavaScript sin la necesidad de añadir otras herramientas. Esta comentado y con las variables en castellano. Puedes editarlo a tu gusto.
// Variables
const baseDeDatos = [
{
id: 1,
nombre: 'Patata',
precio: 1,
imagen: 'patata.jpg'
},
{
id: 2,
nombre: 'Cebolla',
precio: 1.2,
imagen: 'cebolla.jpg'
},
{
id: 3,
nombre: 'Calabacin',
precio: 2.1,
imagen: 'calabacin.jpg'
},
{
id: 4,
nombre: 'Fresas',
precio: 0.6,
imagen: 'fresas.jpg'
}
];
let carrito = [];
const divisa = '€';
const DOMitems = document.querySelector('#items');
const DOMcarrito = document.querySelector('#carrito');
const DOMtotal = document.querySelector('#total');
const DOMbotonVaciar = document.querySelector('#boton-vaciar');
// Funciones
/**
* Dibuja todos los productos a partir de la base de datos. No confundir con el carrito
*/
function renderizarProductos() {
baseDeDatos.forEach((info) => {
// Estructura
const miNodo = document.createElement('div');
miNodo.classList.add('card', 'col-sm-4');
// Body
const miNodoCardBody = document.createElement('div');
miNodoCardBody.classList.add('card-body');
// Titulo
const miNodoTitle = document.createElement('h5');
miNodoTitle.classList.add('card-title');
miNodoTitle.textContent = info.nombre;
// Imagen
const miNodoImagen = document.createElement('img');
miNodoImagen.classList.add('img-fluid');
miNodoImagen.setAttribute('src', info.imagen);
// Precio
const miNodoPrecio = document.createElement('p');
miNodoPrecio.classList.add('card-text');
miNodoPrecio.textContent = `${info.precio}${divisa}`;
// Boton
const miNodoBoton = document.createElement('button');
miNodoBoton.classList.add('btn', 'btn-primary');
miNodoBoton.textContent = '+';
miNodoBoton.setAttribute('marcador', info.id);
miNodoBoton.addEventListener('click', anyadirProductoAlCarrito);
// Insertamos
miNodoCardBody.appendChild(miNodoImagen);
miNodoCardBody.appendChild(miNodoTitle);
miNodoCardBody.appendChild(miNodoPrecio);
miNodoCardBody.appendChild(miNodoBoton);
miNodo.appendChild(miNodoCardBody);
DOMitems.appendChild(miNodo);
});
}
/**
* Evento para añadir un producto al carrito de la compra
*/
function anyadirProductoAlCarrito(evento) {
// Anyadimos el Nodo a nuestro carrito
carrito.push(evento.target.getAttribute('marcador'))
// Actualizamos el carrito
renderizarCarrito();
}
/**
* Dibuja todos los productos guardados en el carrito
*/
function renderizarCarrito() {
// Vaciamos todo el html
DOMcarrito.textContent = '';
// Quitamos los duplicados
const carritoSinDuplicados = [...new Set(carrito)];
// Generamos los Nodos a partir de carrito
carritoSinDuplicados.forEach((item) => {
// Obtenemos el item que necesitamos de la variable base de datos
const miItem = baseDeDatos.filter((itemBaseDatos) => {
// ¿Coincide las id? Solo puede existir un caso
return itemBaseDatos.id === parseInt(item);
});
// Cuenta el número de veces que se repite el producto
const numeroUnidadesItem = carrito.reduce((total, itemId) => {
// ¿Coincide las id? Incremento el contador, en caso contrario no mantengo
return itemId === item ? total += 1 : total;
}, 0);
// Creamos el nodo del item del carrito
const miNodo = document.createElement('li');
miNodo.classList.add('list-group-item', 'text-right', 'mx-2');
miNodo.textContent = `${numeroUnidadesItem} x ${miItem[0].nombre} - ${miItem[0].precio}${divisa}`;
// Boton de borrar
const miBoton = document.createElement('button');
miBoton.classList.add('btn', 'btn-danger', 'mx-5');
miBoton.textContent = 'X';
miBoton.style.marginLeft = '1rem';
miBoton.dataset.item = item;
miBoton.addEventListener('click', borrarItemCarrito);
// Mezclamos nodos
miNodo.appendChild(miBoton);
DOMcarrito.appendChild(miNodo);
});
// Renderizamos el precio total en el HTML
DOMtotal.textContent = calcularTotal();
}
/**
* Evento para borrar un elemento del carrito
*/
function borrarItemCarrito(evento) {
// Obtenemos el producto ID que hay en el boton pulsado
const id = evento.target.dataset.item;
// Borramos todos los productos
carrito = carrito.filter((carritoId) => {
return carritoId !== id;
});
// volvemos a renderizar
renderizarCarrito();
}
/**
* Calcula el precio total teniendo en cuenta los productos repetidos
*/
function calcularTotal() {
// Recorremos el array del carrito
return carrito.reduce((total, item) => {
// De cada elemento obtenemos su precio
const miItem = baseDeDatos.filter((itemBaseDatos) => {
return itemBaseDatos.id === parseInt(item);
});
// Los sumamos al total
return total + miItem[0].precio;
}, 0).toFixed(2);
}
/**
* Varia el carrito y vuelve a dibujarlo
*/
function vaciarCarrito() {
// Limpiamos los productos guardados
carrito = [];
// Renderizamos los cambios
renderizarCarrito();
}
// Eventos
DOMbotonVaciar.addEventListener('click', vaciarCarrito);
// Inicio
renderizarProductos();
renderizarCarrito();
Completo
Aquí esta todo el código listo para usarse.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
<script>
document.addEventListener('DOMContentLoaded', () => {
// Variables
const baseDeDatos = [
{
id: 1,
nombre: 'Patata',
precio: 1,
imagen: 'patata.jpg'
},
{
id: 2,
nombre: 'Cebolla',
precio: 1.2,
imagen: 'cebolla.jpg'
},
{
id: 3,
nombre: 'Calabacin',
precio: 2.1,
imagen: 'calabacin.jpg'
},
{
id: 4,
nombre: 'Fresas',
precio: 0.6,
imagen: 'fresas.jpg'
}
];
let carrito = [];
const divisa = '€';
const DOMitems = document.querySelector('#items');
const DOMcarrito = document.querySelector('#carrito');
const DOMtotal = document.querySelector('#total');
const DOMbotonVaciar = document.querySelector('#boton-vaciar');
// Funciones
/**
* Dibuja todos los productos a partir de la base de datos. No confundir con el carrito
*/
function renderizarProductos() {
baseDeDatos.forEach((info) => {
// Estructura
const miNodo = document.createElement('div');
miNodo.classList.add('card', 'col-sm-4');
// Body
const miNodoCardBody = document.createElement('div');
miNodoCardBody.classList.add('card-body');
// Titulo
const miNodoTitle = document.createElement('h5');
miNodoTitle.classList.add('card-title');
miNodoTitle.textContent = info.nombre;
// Imagen
const miNodoImagen = document.createElement('img');
miNodoImagen.classList.add('img-fluid');
miNodoImagen.setAttribute('src', info.imagen);
// Precio
const miNodoPrecio = document.createElement('p');
miNodoPrecio.classList.add('card-text');
miNodoPrecio.textContent = `${info.precio}${divisa}`;
// Boton
const miNodoBoton = document.createElement('button');
miNodoBoton.classList.add('btn', 'btn-primary');
miNodoBoton.textContent = '+';
miNodoBoton.setAttribute('marcador', info.id);
miNodoBoton.addEventListener('click', anyadirProductoAlCarrito);
// Insertamos
miNodoCardBody.appendChild(miNodoImagen);
miNodoCardBody.appendChild(miNodoTitle);
miNodoCardBody.appendChild(miNodoPrecio);
miNodoCardBody.appendChild(miNodoBoton);
miNodo.appendChild(miNodoCardBody);
DOMitems.appendChild(miNodo);
});
}
/**
* Evento para añadir un producto al carrito de la compra
*/
function anyadirProductoAlCarrito(evento) {
// Anyadimos el Nodo a nuestro carrito
carrito.push(evento.target.getAttribute('marcador'))
// Actualizamos el carrito
renderizarCarrito();
}
/**
* Dibuja todos los productos guardados en el carrito
*/
function renderizarCarrito() {
// Vaciamos todo el html
DOMcarrito.textContent = '';
// Quitamos los duplicados
const carritoSinDuplicados = [...new Set(carrito)];
// Generamos los Nodos a partir de carrito
carritoSinDuplicados.forEach((item) => {
// Obtenemos el item que necesitamos de la variable base de datos
const miItem = baseDeDatos.filter((itemBaseDatos) => {
// ¿Coincide las id? Solo puede existir un caso
return itemBaseDatos.id === parseInt(item);
});
// Cuenta el número de veces que se repite el producto
const numeroUnidadesItem = carrito.reduce((total, itemId) => {
// ¿Coincide las id? Incremento el contador, en caso contrario no mantengo
return itemId === item ? total += 1 : total;
}, 0);
// Creamos el nodo del item del carrito
const miNodo = document.createElement('li');
miNodo.classList.add('list-group-item', 'text-right', 'mx-2');
miNodo.textContent = `${numeroUnidadesItem} x ${miItem[0].nombre} - ${miItem[0].precio}${divisa}`;
// Boton de borrar
const miBoton = document.createElement('button');
miBoton.classList.add('btn', 'btn-danger', 'mx-5');
miBoton.textContent = 'X';
miBoton.style.marginLeft = '1rem';
miBoton.dataset.item = item;
miBoton.addEventListener('click', borrarItemCarrito);
// Mezclamos nodos
miNodo.appendChild(miBoton);
DOMcarrito.appendChild(miNodo);
});
// Renderizamos el precio total en el HTML
DOMtotal.textContent = calcularTotal();
}
/**
* Evento para borrar un elemento del carrito
*/
function borrarItemCarrito(evento) {
// Obtenemos el producto ID que hay en el boton pulsado
const id = evento.target.dataset.item;
// Borramos todos los productos
carrito = carrito.filter((carritoId) => {
return carritoId !== id;
});
// volvemos a renderizar
renderizarCarrito();
}
/**
* Calcula el precio total teniendo en cuenta los productos repetidos
*/
function calcularTotal() {
// Recorremos el array del carrito
return carrito.reduce((total, item) => {
// De cada elemento obtenemos su precio
const miItem = baseDeDatos.filter((itemBaseDatos) => {
return itemBaseDatos.id === parseInt(item);
});
// Los sumamos al total
return total + miItem[0].precio;
}, 0).toFixed(2);
}
/**
* Varia el carrito y vuelve a dibujarlo
*/
function vaciarCarrito() {
// Limpiamos los productos guardados
carrito = [];
// Renderizamos los cambios
renderizarCarrito();
}
// Eventos
DOMbotonVaciar.addEventListener('click', vaciarCarrito);
// Inicio
renderizarProductos();
renderizarCarrito();
});
</script>
</head>
<body>
<div class="container">
<div class="row">
<!-- Elementos generados a partir del JSON -->
<main id="items" class="col-sm-8 row"></main>
<!-- Carrito -->
<aside class="col-sm-4">
<h2>Carrito</h2>
<!-- Elementos del carrito -->
<ul id="carrito" class="list-group"></ul>
<hr>
<!-- Precio total -->
<p class="text-right">Total: <span id="total"></span>€</p>
<button id="boton-vaciar" class="btn btn-danger">Vaciar</button>
</aside>
</div>
</div>
</body>
</html>
Cómo anécdota te puedo contar que todo el ejemplo esta construido sobre los principios de la programación funcional, si quieres introducirte puedes empezar por leer Breve introducción a la programación funcional en JavaScript o responder a la pregunta ¿Qué es la programación funcional y por qué es tan especial?.
Extra 1: LocalStorage
Si necesitas que no se borren los productos cuando vuelva el usuario, o navegue por otras páginas de tu sitio, deberás almacenar la información en el navegado del usuario: LocalStorage. Aumenta un poco la complejidad, aunque los resultados beneficia al visitante. Dejo a continuación el ejemplo anterior con el añadido.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
<script>
document.addEventListener('DOMContentLoaded', () => {
// Variables
const baseDeDatos = [
{
id: 1,
nombre: 'Patata',
precio: 1,
imagen: 'patata.jpg'
},
{
id: 2,
nombre: 'Cebolla',
precio: 1.2,
imagen: 'cebolla.jpg'
},
{
id: 3,
nombre: 'Calabacin',
precio: 2.1,
imagen: 'calabacin.jpg'
},
{
id: 4,
nombre: 'Fresas',
precio: 0.6,
imagen: 'fresas.jpg'
}
];
let carrito = [];
const divisa = '€';
const DOMitems = document.querySelector('#items');
const DOMcarrito = document.querySelector('#carrito');
const DOMtotal = document.querySelector('#total');
const DOMbotonVaciar = document.querySelector('#boton-vaciar');
const miLocalStorage = window.localStorage;
// Funciones
/**
* Dibuja todos los productos a partir de la base de datos. No confundir con el carrito
*/
function renderizarProductos() {
baseDeDatos.forEach((info) => {
// Estructura
const miNodo = document.createElement('div');
miNodo.classList.add('card', 'col-sm-4');
// Body
const miNodoCardBody = document.createElement('div');
miNodoCardBody.classList.add('card-body');
// Titulo
const miNodoTitle = document.createElement('h5');
miNodoTitle.classList.add('card-title');
miNodoTitle.textContent = info.nombre;
// Imagen
const miNodoImagen = document.createElement('img');
miNodoImagen.classList.add('img-fluid');
miNodoImagen.setAttribute('src', info.imagen);
// Precio
const miNodoPrecio = document.createElement('p');
miNodoPrecio.classList.add('card-text');
miNodoPrecio.textContent = `${info.precio}${divisa}`;
// Boton
const miNodoBoton = document.createElement('button');
miNodoBoton.classList.add('btn', 'btn-primary');
miNodoBoton.textContent = '+';
miNodoBoton.setAttribute('marcador', info.id);
miNodoBoton.addEventListener('click', anyadirProductoAlCarrito);
// Insertamos
miNodoCardBody.appendChild(miNodoImagen);
miNodoCardBody.appendChild(miNodoTitle);
miNodoCardBody.appendChild(miNodoPrecio);
miNodoCardBody.appendChild(miNodoBoton);
miNodo.appendChild(miNodoCardBody);
DOMitems.appendChild(miNodo);
});
}
/**
* Evento para añadir un producto al carrito de la compra
*/
function anyadirProductoAlCarrito(evento) {
// Anyadimos el Nodo a nuestro carrito
carrito.push(evento.target.getAttribute('marcador'))
// Actualizamos el carrito
renderizarCarrito();
// Actualizamos el LocalStorage
guardarCarritoEnLocalStorage();
}
/**
* Dibuja todos los productos guardados en el carrito
*/
function renderizarCarrito() {
// Vaciamos todo el html
DOMcarrito.textContent = '';
// Quitamos los duplicados
const carritoSinDuplicados = [...new Set(carrito)];
// Generamos los Nodos a partir de carrito
carritoSinDuplicados.forEach((item) => {
// Obtenemos el item que necesitamos de la variable base de datos
const miItem = baseDeDatos.filter((itemBaseDatos) => {
// ¿Coincide las id? Solo puede existir un caso
return itemBaseDatos.id === parseInt(item);
});
// Cuenta el número de veces que se repite el producto
const numeroUnidadesItem = carrito.reduce((total, itemId) => {
// ¿Coincide las id? Incremento el contador, en caso contrario no mantengo
return itemId === item ? total += 1 : total;
}, 0);
// Creamos el nodo del item del carrito
const miNodo = document.createElement('li');
miNodo.classList.add('list-group-item', 'text-right', 'mx-2');
miNodo.textContent = `${numeroUnidadesItem} x ${miItem[0].nombre} - ${miItem[0].precio}${divisa}`;
// Boton de borrar
const miBoton = document.createElement('button');
miBoton.classList.add('btn', 'btn-danger', 'mx-5');
miBoton.textContent = 'X';
miBoton.style.marginLeft = '1rem';
miBoton.dataset.item = item;
miBoton.addEventListener('click', borrarItemCarrito);
// Mezclamos nodos
miNodo.appendChild(miBoton);
DOMcarrito.appendChild(miNodo);
});
// Renderizamos el precio total en el HTML
DOMtotal.textContent = calcularTotal();
}
/**
* Evento para borrar un elemento del carrito
*/
function borrarItemCarrito(evento) {
// Obtenemos el producto ID que hay en el boton pulsado
const id = evento.target.dataset.item;
// Borramos todos los productos
carrito = carrito.filter((carritoId) => {
return carritoId !== id;
});
// volvemos a renderizar
renderizarCarrito();
// Actualizamos el LocalStorage
guardarCarritoEnLocalStorage();
}
/**
* Calcula el precio total teniendo en cuenta los productos repetidos
*/
function calcularTotal() {
// Recorremos el array del carrito
return carrito.reduce((total, item) => {
// De cada elemento obtenemos su precio
const miItem = baseDeDatos.filter((itemBaseDatos) => {
return itemBaseDatos.id === parseInt(item);
});
// Los sumamos al total
return total + miItem[0].precio;
}, 0).toFixed(2);
}
/**
* Varia el carrito y vuelve a dibujarlo
*/
function vaciarCarrito() {
// Limpiamos los productos guardados
carrito = [];
// Renderizamos los cambios
renderizarCarrito();
// Borra LocalStorage
localStorage.clear();
}
function guardarCarritoEnLocalStorage () {
miLocalStorage.setItem('carrito', JSON.stringify(carrito));
}
function cargarCarritoDeLocalStorage () {
// ¿Existe un carrito previo guardado en LocalStorage?
if (miLocalStorage.getItem('carrito') !== null) {
// Carga la información
carrito = JSON.parse(miLocalStorage.getItem('carrito'));
}
}
// Eventos
DOMbotonVaciar.addEventListener('click', vaciarCarrito);
// Inicio
cargarCarritoDeLocalStorage();
renderizarProductos();
renderizarCarrito();
});
</script>
</head>
<body>
<div class="container">
<div class="row">
<!-- Elementos generados a partir del JSON -->
<main id="items" class="col-sm-8 row"></main>
<!-- Carrito -->
<aside class="col-sm-4">
<h2>Carrito</h2>
<!-- Elementos del carrito -->
<ul id="carrito" class="list-group"></ul>
<hr>
<!-- Precio total -->
<p class="text-right">Total: <span id="total"></span>€</p>
<button id="boton-vaciar" class="btn btn-danger">Vaciar</button>
</aside>
</div>
</div>
</body>
</html>
Extra 2: carrito desde Back-End
Si prefieres un carrito que funcione desde el Back-End puedes visitar mi artículo carrito en PHP con cookies donde lo implemento en PHP.
Extra 3: botón para pagar
Pagar implica mover un dinero, en este caso de la cuenta bancaria del cliente a la tuya. Existen varias formas, como enviarte un cheque, hacer una transferencia a un número de cuenta o… automatizar el pago con una pasarela de pagos. ¿Has oído hablar de Paypal o Stripe? Se encargan de esta tarea, trasladan de forma segura un dinero acordado entre 2 cuentas. Añadir un botón para pagar implica que uses alguno de estos servicios. Para integrar cualquiera de estas plataformas habrá que seguir sus instrucciones en las páginas oficiales. No hay ningún atajo.
Extra 4: enviar email o correo
Necesitarás un Back-End (código servidor como PHP o Python…) para esta tarea. JavaScript posee sus limitaciones.
Extra 5: cambiar divisa como de euros a dólares
Buscar la siguiente línea.
const divisa = '€';
Modifica el texto por el que necesites, por ejemplo:
const divisa = '$';
Extra 6: más personalización
Si necesitas ayuda puedes enviarme un mensaje privado y encantado te doy un presupuesto.
¿Te ha resultado útil? Déjame un comentario.
{{ comments.length }} comentarios