Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/diegoromemora27-creator/HTMLCSSEXPLAIN/llms.txt

Use this file to discover all available pages before exploring further.

Shopping Cart Overview

The shopping cart uses JavaScript’s Map data structure to store product IDs and quantities. It features a visual badge that updates in real-time using CSS attribute selectors.

What You’ll Learn

  • Map data structure (key-value pairs)
  • Event delegation for dynamic elements
  • CSS attribute selectors
  • Array reduce for totals
  • Real-time UI updates

Cart Badge HTML

The cart badge is part of the header navigation:
<a href="#" class="header__nav-link header__nav-link--cart" data-cart-count="0">
  <svg><!-- Cart icon --></svg>
  <span class="header__cart-label">Carrito</span>
</a>
Key attribute: data-cart-count="0" stores the current count.

Cart Badge CSS

The badge is created entirely with CSS:
.header__nav-link--cart {
  position: relative; /* For absolute positioning of badge */
}

/* Show badge only when count > 0 */
.header__nav-link--cart[data-cart-count]:not([data-cart-count="0"])::after {
  content: attr(data-cart-count); /* Read from HTML attribute */
  
  /* Position in top-right corner */
  position: absolute;
  top: -4px;
  right: -8px;
  
  /* Badge styling */
  min-width: 18px;
  height: 18px;
  padding: 0 var(--spacing-xs);
  background-color: var(--color-error); /* Red badge */
  color: var(--color-white);
  font-size: var(--font-size-xs);
  font-weight: 600;
  border-radius: var(--border-radius-full); /* Circle */
  
  /* Center the number */
  display: flex;
  align-items: center;
  justify-content: center;
}

CSS Selector Breakdown

.header__nav-link--cart[data-cart-count]:not([data-cart-count="0"])::after
Let’s break this down:
  1. .header__nav-link--cart - The cart link element
  2. [data-cart-count] - Must have the data attribute
  3. :not([data-cart-count="0"]) - Exclude when count is “0”
  4. ::after - Pseudo-element for the badge
content: attr(data-cart-count) - This reads the value from the HTML attribute. When JavaScript updates the attribute, the badge updates automatically!

Map Data Structure

We use a Map to store cart data:
const cart: Map<number, number> = new Map();
Map<number, number> means:
  • Key (first number): Product ID
  • Value (second number): Quantity

Why Map Instead of Object?

FeatureMapObject
Key typesAny typeStrings only
Size property.sizeManual count
Iteration.forEach(), for...ofObject.keys()
PerformanceOptimized for frequent add/deleteSlower
OrderInsertion order guaranteedNot guaranteed

Map Methods

// Add or update
cart.set(productId, quantity);

// Get value (returns undefined if not found)
const qty = cart.get(productId);

// Check if exists
if (cart.has(productId)) { }

// Delete
cart.delete(productId);

// Get size
const total = cart.size;

// Get all values
const quantities = Array.from(cart.values());

// Iterate
for (const [id, qty] of cart) {
  console.log(`Product ${id}: ${qty}`);
}

Add to Cart Function

1

Get Current Quantity

Check if product already exists in cart:
function addToCart(productId: number): void {
  const currentQuantity = cart.get(productId) || 0;
  // If undefined, default to 0
}
2

Update Map

Increment quantity and save:
cart.set(productId, currentQuantity + 1);
This works for both new products and existing ones.
3

Update UI

Refresh the cart badge:
updateCartCount();
4

Show Notification

Give user feedback:
const product = appState.products.find(p => p.id === productId);
if (product) {
  showNotification(`"${product.title}" agregado al carrito`);
}

Complete Function

function addToCart(productId: number): void {
  // Get current quantity (0 if not in cart)
  const currentQuantity = cart.get(productId) || 0;
  
  // Update quantity
  cart.set(productId, currentQuantity + 1);
  
  // Update badge
  updateCartCount();
  
  // Find product for notification
  const product = appState.products.find(p => p.id === productId);
  if (product) {
    showNotification(`"${product.title}" agregado al carrito`);
  }
}

Update Cart Count

Calculate total items and update the badge:
function updateCartCount(): void {
  // Sum all quantities in the cart
  const totalItems = Array.from(cart.values())
    .reduce((sum, qty) => sum + qty, 0);
  
  // Update the data attribute
  const cartLink = document.querySelector('[data-cart-count]');
  if (cartLink) {
    cartLink.setAttribute('data-cart-count', totalItems.toString());
  }
}

Understanding reduce()

Array.from(cart.values()).reduce((sum, qty) => sum + qty, 0)
Example walkthrough: If cart contains: { 1: 2, 5: 1, 9: 3 }
  1. cart.values() → Iterator of [2, 1, 3]
  2. Array.from() → Convert to array [2, 1, 3]
  3. reduce() process:
    • Initial: sum = 0
    • Iteration 1: sum = 0 + 2 = 2
    • Iteration 2: sum = 2 + 1 = 3
    • Iteration 3: sum = 3 + 3 = 6
    • Result: 6
reduce() is perfect for calculating totals. The pattern is: reduce((accumulator, currentValue) => newAccumulator, initialValue)

Event Delegation

We use event delegation to handle clicks on dynamically created buttons:
function setupAddToCartButtons(): void {
  const grid = getElement<HTMLDivElement>("#products-grid");
  
  // One listener on parent, handles all product buttons
  grid.addEventListener('click', (event: MouseEvent) => {
    const target = event.target as HTMLElement;
    
    // Find the button (even if icon inside was clicked)
    const button = target.closest('[data-action="add-to-cart"]');
    if (!button) return; // Not a cart button
    
    // Find the product card to get ID
    const card = button.closest('.product-card') as HTMLElement;
    if (!card) return;
    
    // Get product ID from data attribute
    const productId = card.dataset.productId;
    if (productId) {
      addToCart(parseInt(productId, 10));
    }
  });
}

Why Event Delegation?

Without delegation (bad):
// Need to add listener to EVERY button
const buttons = document.querySelectorAll('.product-card__btn');
buttons.forEach(btn => {
  btn.addEventListener('click', handleClick); // 100 listeners!
});
With delegation (good):
// ONE listener on parent
grid.addEventListener('click', handleClick); // 1 listener!
Benefits:
  • Only one event listener (better performance)
  • Works with dynamically added products
  • Less memory usage

Event Bubbling

When you click a button, the event “bubbles” up:
Button (click here)

Product Card

Products Grid (listener here)

Body

HTML
The grid’s listener catches all clicks inside it.

Toast Notification

Show a temporary message when adding to cart:
function showNotification(message: string, duration: number = 3000): void {
  // Create notification element
  const notification = document.createElement('div');
  
  // Apply inline styles
  notification.style.cssText = `
    position: fixed;
    bottom: 20px;
    right: 20px;
    padding: 16px 24px;
    background-color: #333;
    color: white;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    z-index: 1000;
    animation: slideIn 0.3s ease;
    max-width: 300px;
  `;
  
  notification.textContent = message;
  document.body.appendChild(notification);
  
  // Remove after duration
  setTimeout(() => {
    notification.style.animation = 'slideOut 0.3s ease';
    setTimeout(() => notification.remove(), 300);
  }, duration);
}

Notification Animations

@keyframes slideIn {
  from { 
    transform: translateX(100%); 
    opacity: 0; 
  }
  to { 
    transform: translateX(0); 
    opacity: 1; 
  }
}

@keyframes slideOut {
  from { 
    transform: translateX(0); 
    opacity: 1; 
  }
  to { 
    transform: translateX(100%); 
    opacity: 0; 
  }
}

Cart Summary Example

Here’s how you could display cart contents:
function displayCartSummary(): void {
  console.log('=== CART SUMMARY ===');
  
  let total = 0;
  
  for (const [productId, quantity] of cart) {
    const product = appState.products.find(p => p.id === productId);
    if (product) {
      const subtotal = product.price * quantity;
      total += subtotal;
      
      console.log(`${product.title}`);
      console.log(`  Quantity: ${quantity}`);
      console.log(`  Price: $${product.price}`);
      console.log(`  Subtotal: $${subtotal}`);
    }
  }
  
  console.log(`TOTAL: $${total}`);
}

Data Flow Diagram

User clicks "Add to Cart"

Event bubbles to grid listener

Extract product ID from data attribute

addToCart(productId)

cart.set(id, quantity + 1)

updateCartCount()

Calculate total with reduce()

Update data-cart-count attribute

CSS ::after automatically shows new badge

showNotification()

Complete Code Reference

Map Declaration: /workspace/source/mi-tutorial/src/main.ts:283 Add to Cart: /workspace/source/mi-tutorial/src/main.ts:760-779 Update Count: /workspace/source/mi-tutorial/src/main.ts:812-827 Event Delegation: /workspace/source/mi-tutorial/src/main.ts:703-737 Notifications: /workspace/source/mi-tutorial/src/main.ts:847-882

Enhancement Ideas

function removeFromCart(productId: number): void {
  cart.delete(productId);
  updateCartCount();
  showNotification('Producto eliminado del carrito');
}
function updateQuantity(productId: number, newQuantity: number): void {
  if (newQuantity <= 0) {
    cart.delete(productId);
  } else {
    cart.set(productId, newQuantity);
  }
  updateCartCount();
}
function clearCart(): void {
  cart.clear();
  updateCartCount();
  showNotification('Carrito vaciado');
}
function saveCart(): void {
  const cartArray = Array.from(cart.entries());
  localStorage.setItem('cart', JSON.stringify(cartArray));
}

function loadCart(): void {
  const saved = localStorage.getItem('cart');
  if (saved) {
    const cartArray = JSON.parse(saved);
    cartArray.forEach(([id, qty]) => cart.set(id, qty));
    updateCartCount();
  }
}

Next Steps

API Integration

Learn how product data is fetched from the server

State Management

Understand how application state is managed

Build docs developers (and LLMs) love