Back to Table of Contents

Cart and Theme Storage

What CoPilot thinks a cat looks like

By Jesse Pence

Introduction

Just a few more steps and we’ll have the finished product! We can get the product information and create individual pages with it, but we can’t add products to the cart or save the theme preference. We’ll also need to build a cart page to view the products in the cart.

After seeing the way that we cached the product data, I hope that you understand that localStorage isn’t necessary to implement a cart in vanilla JavaScript unless you want the cart to persist. We could simply create an empty object the first time we load the page and add the products to it as we add them to the cart. But, since we’re implementing a theme preference feature that the user will want to persist anyways, we’ll just use localStorage for everything.

Cart Code

I think that the code is pretty self-explanatory, so I’ll just go over the important parts. We’ll start by adding our dynamic spans in the nav bar to our collection of global variables. Next, we’ll initiate the cart while checking to see if one already exists. Then, we’ll create the cart component. And finally, we will create a series of functions to add products to the cart, remove them, and update the event listeners on whatever page we may be on. That’s a lot! But, hopefully my comments will help you understand what’s going on.

index.html

<!-- Rest of the code -->
const cartCount = document.querySelector("#cart-count") 
// Get the cart count element
const cartAmount = document.querySelector("#cart-total") 
// Get the cart total element

// Bring in the cart and initiate the DOM with the current cart state
  const cart = JSON.parse(localStorage.getItem("cart")) || {}
  // Get the cart from localStorage, or set it to an empty object if it doesn't exist
  cartCount.innerText = Object.keys(cart).length
  // Set the cart count to the number of items in the cart
  cartAmount.innerText =
    "$" +
    Object.values(cart)
      .map((item) => item.product.price * item.quantity)
      .reduce((a, b) => a + b, 0)
      .toFixed(2)
      // Update the cart amount

// Create the cart component
const Cart = async () => {
    products = await getProducts()
    // Get the products if they haven't been cached
    if (Object.keys(cart).length === 0) {
      // If the cart is empty, render a message saying so.
      render(`
      <h1>Cart</h1>
      <p>Your cart is empty. Go buy one of our two amazing items!</p>
      `)
    } else {
      // If the cart isn't empty, render the cart items.
      const cartItems = Object.keys(cart).map((id) => {
        const product = products.find(
          (product) => product.id === Number(id)
        )
        return `
        <div class="cart-item">
          <img src="${product.image}" alt="${product.name}" />
          <h2>${product.name}</h2>
          <p>$${product.price}</p>
          <p>Quantity: ${cart[id].quantity}</p>
          <button class="remove-from-cart" id="${product.id}">Remove from cart</button>
        </div>
        `
      })
      render(`
      <h1>Cart</h1>
      ${cartItems.join("")}
      `)
      buttonFinderRemove()
      // Update the event listeners on the remove buttons.
    }
  }

  // This function will run every time the user clicks the "Add to cart" button.
  function addToCart(product) {
    if (cart[product.id]) {
      // If the product is already in the cart, increment the quantity.
      cart[product.id].quantity++
    } else {
      // If the product isn't in the cart,
      cart[product.id] = {
        quantity: 1,
        product,
      }
      // add the product with the info and a quantity property to the cart.
    }
    localStorage.setItem("cart", JSON.stringify(cart))
    // Set the cart in localStorage to the updated cart.
    alert(`${product.name} added to cart!`)
    // Alert the user that the product was added to the cart.
    cartCount.innerText = Object.keys(cart).length
    // Update the cart count.
    cartAmount.innerText =
      "$" +
      Object.values(cart).reduce((acc, item) => {
        return acc + item.product.price * item.quantity
      }, 0) // Update the cart amount.
  }

  // This function will run every time the user clicks the "Remove from cart" button.
  function removeFromCart(product) {
    cart[product.id].quantity--
    // Decrement the quantity.
    if (cart[product.id].quantity === 0) {
      // If the quantity is 0, remove the product from the cart.
      delete cart[product.id]
    }
    localStorage.setItem("cart", JSON.stringify(cart))
    // Set the cart in localStorage to the updated cart.
    alert(`${product.name} removed from cart!`)
    // Alert the user that the product was removed from the cart.
    Cart()
    // Re-render the cart.
    cartCount.innerText = Object.keys(cart).length
    // Update the cart count.
    cartAmount.innerText =
      "$" +
      Object.values(cart)
        .map((item) => item.product.price * item.quantity)
        .reduce((a, b) => a + b, 0)
        .toFixed(2) // Update the cart amount.
  }

    // This function is for our product pages so users can add items to the cart.
  function buttonFinderAdd() {
    document.querySelectorAll(".add-to-cart").forEach((button) => {
        // Get all the buttons with the class "add-to-cart"
        if(button.alreadyAdded) return
        button.alreadyAdded = true
        // Check if the button already has an event listener
      button.addEventListener("click", (e) => {
        // Add an event listener to each button if not
        const id = e.target.id
        // Get the id of the product from the button
        const product = products.find(
          (product) => product.id === Number(id)
        )
        // Find the product in the products array
        addToCart(product)
        // Add the product to the cart
      })
    })
  }

  // This function is for our cart page so users can remove items from the cart.
  function buttonFinderRemove() {
    document.querySelectorAll(".remove-from-cart").forEach((button) => {
        // Get all the buttons with the class "remove-from-cart"
        if(button.alreadyAdded) return
        button.alreadyAdded = true
        // Check if the button already has an event listener
      button.addEventListener("click", (e) => {
        // Add an event listener to each button if not
        const id = e.target.id
        // Get the id of the product from the button
        const product = products.find(
          (product) => product.id === Number(id)
        )
        // Find the product in the products array
        removeFromCart(product)
        // Remove the product from the cart
      })
    })
  }

Hopefully you noticed how we fixed the buttons on the products page while we were making our new functions. If you look in the code, you’ll see that the all the product pages now run the buttonFinderAdd function. Now, we have full cart functionality. Let’s fix our theme selection with just a few lines of code.

Theme Code

We again start by checking local storage for a theme. However, since the initial state is defined in the HTML, we don’t need to create a back-up value like the cart. This will replace the old, annoying verbose code. Let’s see what this looks like:

index.html

const themeCheck = localStorage.getItem("theme")
// Get the theme from localStorage
const toggleTheme = document.querySelector("#toggleTheme")
// Get the toggleTheme button

// Set the theme
  themeCheck
  // If there is a theme in localStorage
    ? (document.body.className = themeCheck)
    // Set the theme on the body element
    : (document.body.className = "dark")
    // Otherwise, accept the default theme

// This function will run every time the user clicks the toggleTheme button and change the theme.
  toggleTheme.addEventListener("click", () => {
    // Check current theme
    const currentTheme = localStorage.getItem("theme") || "dark"
    // Get the current theme from localStorage, or set it to dark if it doesn't exist.
    const newTheme =
      currentTheme === "dark"
      // if the current theme is dark
        ? "purple"
        // set the new theme to purple
        : currentTheme === "purple"
        // but, if the current theme is purple
        ? "green"
        // set the new theme to green
        : "dark"
        // otherwise, accept the default theme
    localStorage.setItem("theme", newTheme)
    // Save the new theme to localStorage
    document.body.className = newTheme
    // Set the new theme on the body element
  })

Now, we can change the theme by clicking the toggleTheme button. Let’s add a few more features to our cart.

Conclusion and Demo

So, the app is almost done! Everything works except the search function. We’ll fix that in the next chapter, but for now feel free to play with the demo and add or remove items from the cart. You can also change the theme by clicking the toggleTheme button. Everything will be saved in localStorage, so you can refresh the page and the cart and theme will still be there. If you want to see the code for this chapter, you can find it on Github.

Table of Contents Comments View Source Code Next Page!