0

I'm working on a shopping cart functionality for a fictitious online store. The core of this functionality is a Cart class that manages the information and performs calculations. This class doesn't interact with the HTML document or the DOM, it simply performs calculations.

The Cart class haves methods to manage products. Each product has a SKU, title, price properties and I'm trying to add a quantity property too but it's not working I think. I have two buttons, addButton and subtractButton, for each product to increase or decrease the quantity.

I want to achieve the following:

  1. When the addButton is clicked, the quantity of the product should increase by 1, and the total price for that SKU should be updated in the total-SKU span.
  2. When the subtractButton is clicked, the quantity of the product should decrease by 1 (if it's greater than 0), and the total price for that SKU should be updated in the total-SKU span.
  3. The total price of all SKUs should be calculated and displayed in the total div.

Here's the code in jsFiddle: https://jsfiddle.net/mlpz2/edfjLhb2

Here's the code here:

class Cart {
    constructor(products, currency) {
        this.products = [];
        this.currency = "";
    }
    initializeQuantity = () => {
        for (let product of this.products) {
          product.quantity = 0; // Initialize quantity to 0 for each product
        }
        console.log(this.products.quantity);
      };
    updateUnits = (sku, units) => {
        // Update the number of units to be purchased for a product
        let product = this.products.find((p) => p.sku === sku);
        if (product) {
            product.quantity = units;
            console.log(this.products.quantity);
        } else {
            this.products.push({ sku: sku, quantity: units });
        }
    };

    getProductInformation = (sku) => {
        // Returns the data of a product along with the selected units
        // For example:
        // {
        // "sku": "0K3QOSOV4V",
        // "quantity": 3
        // }
        return this.products.find((p) => p.sku === sku);
    };

    getAllProducts = () => {
        return this.products;
      };

    getCart = () => {
        // Returns information about the products added to the cart
        // Along with the calculated total of all products
        // For example:
        // {
        // "total": "5820",
        // "currency: "€",
        // "products" : [
        // {
        // "sku": "0K3QOSOV4V"
        // ..
        // }
        // ]}
        let total = this.products.reduce(
            (sum, p) => sum + p.quantity * p.price,
            0
        );
        return { total: total, currency: this.currency, products: this.products };
    };
}

let cart = new Cart();
cart.initializeQuantity();
const getProductsData = async () => {
    let response = await fetch(
        "https://jsonblob.com/api/jsonBlob/1241305513466912768"
    );
    let data = await response.json();
    console.log(data);
    return data; // return the full response
};

const showProducts = (products) => {
    console.log(products);
    
    let productsContainer = document.getElementById("productsContainer");
    for (let product of products) {
        let quantity = product.quantity || 0; // Initialize quantity here

        let productElement = document.createElement("div");
        productElement.innerHTML = `
                        <h2>${product.title}</h2>
                        <p>Ref: ${product.SKU}</p>
                        <p>Price: ${product.price}€/unit</p>
                        <button class="substractButton">-</button>
                        <span id="quantity-${product.SKU}">${quantity}</span>
                        <button class="addButton">+</button>
                        <p>Total: <span id="total-${product.SKU}">0</span>€</p>
                `;
        productElement.className = "product";
        productsContainer.appendChild(productElement);
        let addButtons = productElement.querySelectorAll(".addButton");
        for (let i = 0; i < addButtons.length; i++) {
            addButtons[i].addEventListener('click', () => updateQuantity(product.SKU, 1));
        }
        console.log(addButtons);
        let subtractButtons = productElement.querySelectorAll(".substractButton");
        for (let i = 0; i < subtractButtons.length; i++) {
            subtractButtons[i].addEventListener('click', () => updateQuantity(product.SKU, -1));
            console.log(typeof subtractButtons[i], subtractButtons[i]);
        }
    }
    console.log(productsContainer);
};


const updateTotal = (sku) => {
    let products = cart.getAllProducts(); // Assuming getCart returns an array of products
    let total = 0;
    console.log(products); // Check if the products are being fetched correctly

    for (let product of products) {
        total += product.quantity * product.price;
    }
    document.getElementById('total').textContent = `TOTAL: ${total}`; // Assuming 'total' is the id of the element displaying the total price
};

const updateQuantity = (sku, change) => {
    let product = cart.getProductInformation(sku);
    if (product) {
        product.quantity += change;
        if (product.quantity < 0) {
            // Ensure the quantity doesn't go below 0
            product.quantity = 0;
        }
        document.getElementById(`quantity-${sku}`).textContent = product.quantity;
        document.getElementById(`total-${sku}`).textContent =
            product.quantity * product.price;
        updateTotal();
    }
};

getProductsData().then((data) => {
    if (data && data.products) {
        cart.products = data.products;
        cart.products = data.products.map((product) => {
            return {
                ...product,
                price: parseFloat(product.price),
            };
        }); // assign the currency to the cart object
        showProducts(cart.products);
        updateTotal();
    } else {
        console.error("Failed to fetch products");
    }
});

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <div id="productsContainer">
        <div id="total">
            <!-- The total will be shown here -->
        </div>
    </div> 
<script src="script.js"></script>
</body>

</html>
#productsContainer {
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
    margin-top: 20px;
    position: relative;
}

.product {
    width: 200px;
    margin: 10px;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
    text-align: center;

}

#total {
    position: absolute;
    font-size: 20px;
    font-weight: bold;
    bottom: -20px;
    left: 10px;
    background-color: #ececec;
}

@media (min-width: 768px) {
    #productsContainer {
        flex-direction: row;
    }
}

The products are fetched from an API. I'm using jsonblob.com(https://jsonblob.com/api/jsonBlob/1241305513466912768) to create a mock API.

{
  "currency": "€",
  "products": [
    {
      "SKU": "0K3QOSOV4V",
      "title": "iFhone 13 Pro",
      "price": "938.99"
    },
    {
      "SKU": "TGD5XORY1L",
      "title": "Cargador",
      "price": "49.99"
    },
    {
      "SKU": "IOKW9BQ9F3",
      "title": "Funda de piel",
      "price": "79.99"
    }
  ]
}

I think I'm not sure how to:

  1. Create new elements in the DOM for the product list.
  2. Listen for events to update the number of units for each product.
  3. Update the total price in the DOM whenever a change is made.

I'm not sure how to create the event listeners for the addButton and subtractButton and how to update the quantity and total price in the DOM.

I'm also open to any suggestions for improving the code. Any help would be appreciated!

Thank you for reading :)

2 Answers 2

0

After being through your code throughly, I've found this two major fixes

  1. p.sku you are accessing the invalid property of product through the class, so just change it to p.SKU
  2. p.quantity is not in initial product object that why it shows NAN it can be fixed by adding that while initialising so you won't need any if condition to check that.
cart.products = data.products.map(product => {
    return {
        ...product,
        price: parseFloat(product.price),
        quantity: 0 // THIS WILL FIX MAJOR PROBLESM
    };
}); 

Also some recommendations, you won't need querySelectorAll since there is only one .addButton, the forEach will alway run once. Instead try getElementsByClassName or querySelector

/* 
let subtractButtons = productElement.querySelectorAll(".substractButton");

for (let i = 0; i < addButtons.length; i++) {
    addButtons[i].onclick(() => console.warn(1));
} 
*/

let addButtons = productElement.getElementsByClassName("addButton")[0];
        
addButtons.onclick = () => updateQuantity(product.SKU, 1);

JSFIDDLE

0

const TotalCart = document.getElementById('finalTotal');


class Cart {
  constructor(products, currency) {
    this.products = [];
    this.currency = "";
  }
  initializeQuantity = () => {
    for (let product of this.products) {
      product.quantity = 0; // Initialize quantity to 0 for each product
    }
    console.log(this.products.quantity);
  };
  updateUnits = (sku, units) => {
    // Update the number of units to be purchased for a product
    let product = this.products.find((p) => p.sku === sku);
    if (product) {
      product.quantity = units;
      console.log(this.products.quantity);
    } else {
      this.products.push({
        sku: sku,
        quantity: units
      });
    }
  };

  getProductInformation = (sku) => {
    // Returns the data of a product along with the selected units
    // For example:
    // {
    // "sku": "0K3QOSOV4V",
    // "quantity": 3
    // }
    return this.products.find((p) => p.SKU === sku);
  };

  getAllProducts = () => {
    return this.products;
  };

  getCart = () => {
    // Returns information about the products added to the cart
    // Along with the calculated total of all products
    // For example:
    // {
    // "total": "5820",
    // "currency: "€",
    // "products" : [
    // {
    // "sku": "0K3QOSOV4V"
    // ..
    // }
    // ]}
    let total = this.products.reduce(
      (sum, p) => sum + p.quantity * p.price,
      0
    );
    console.log(total);
    return {
      total: total,
      currency: this.currency,
      products: this.products
    };
  };
}

let cart = new Cart();
cart.initializeQuantity();
const getProductsData = async() => {
  let response = await fetch(
    "https://jsonblob.com/api/jsonBlob/1241305513466912768"
  );
  let data = await response.json();
  console.log(data);
  return data; // return the full response
};

const showProducts = (products) => {
  console.log(products);

  let productsContainer = document.getElementById("productsContainer");
  for (let product of products) {
    let quantity = product.quantity || 0; // Initialize quantity here

    let productElement = document.createElement("div");
    productElement.innerHTML = `
                        <h2>${product.title}</h2>
                        <p>Ref: ${product.SKU}</p>
                        <p>Price: ${product.price}€/unit</p>
                        <button class="substractButton" id=${product.SKU}>-</button>
                        <span id="quantity-${product.SKU}">${quantity}</span>
                        <button class="addButton" id=${product.SKU}>+</button>
                        <p>Total: <span id="total-${product.SKU}">0</span>€</p>
                `;
    productElement.className = "product";
    productsContainer.appendChild(productElement);
    let addButtons = productElement.querySelectorAll(".addButton");
    addButtons.forEach(button => {
      button.addEventListener('click', event => {
        updateQuantity(event.target.id, 1);
        updateTotalCart();
      });
    });


    console.log(addButtons);
    let subtractButtons = productElement.querySelectorAll(".substractButton");

    subtractButtons.forEach(button => {
      button.addEventListener('click', event => {
        updateQuantity(event.target.id, -1);
        console.log(event.target);
        updateTotalCart();
      });
    });
  }
  console.log(productsContainer);
};


const updateTotal = () => {
  let products = cart.getAllProducts(); // Assuming getCart returns an array of products
  let total = 0;
  for (let product of products) {
    if (product.quantity) {
      total += product.quantity * product.price;
    }
  }

  let totalElement = document.getElementById('total');
  if (totalElement) {
    totalElement.innerHTML = ''; // Clear previous content if any
    let totalText = document.createElement('div');
    totalText.textContent = `TOTAL: ${total.toFixed(2)}€`;
    totalElement.appendChild(totalText);
  } else {
    console.error("Element with id 'total' not found.");
  }
};

const updateQuantity = (sku, change) => {

  let product = cart.getProductInformation(sku);
  if (product) {
    if (!product.quantity) {
      product.quantity = 0;
    }
    product.quantity += change;
    if (product.quantity < 0) {
      // Ensure the quantity doesn't go below 0
      product.quantity = 0;
    }
    document.getElementById(`quantity-${sku}`).textContent = product.quantity;
    document.getElementById(`total-${sku}`).textContent =
      product.quantity * product.price;
    updateTotal(product.quantity * product.price);
  }
};

getProductsData().then((data) => {
  if (data && data.products) {
    cart.products = data.products;
    cart.products = data.products.map((product) => {
      return {
        ...product,
        price: parseFloat(product.price),
      };
    }); // assign the currencay to the cart object
    showProducts(cart.products);
    updateTotal();
  } else {
    console.error("Failed to fetch products");
  }
});

const updateTotalCart = () => {
  let products = cart.getAllProducts();
  let finalTotal = 0;
  console.log('Called');
  let finalTotalContainer = document.getElementById('finalTotal');
  finalTotalContainer.innerHTML = ''; // Clear previous content

  for (let product of products) {
    if (product.quantity > 0) {
      let productTotal = product.quantity * product.price;
      finalTotal += productTotal;

      let productSummary = document.createElement("div");
      productSummary.innerHTML = `
                <p>${product.title}: ${product.quantity} x ${product.price}€ = ${productTotal.toFixed(2)}€</p>
            `;
      finalTotalContainer.appendChild(productSummary);
    }
  }

  let grandTotal = document.createElement("div");
  grandTotal.innerHTML = `<h3>Grand Total: ${finalTotal.toFixed(2)}€</h3>`;
  finalTotalContainer.appendChild(grandTotal);
};
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Shopping Cart</title>
</head>

<body>
  <div id="productsContainer">
  </div>
  <div id="finalTotal">
  </div>
  <script src="./product.js"></script>
</body>

</html>

Add the event listener to all the buttons seperately and check the events with the id for updating specific elements.

hope this helps.

6
  • It helps but for some reason the total div of all the products total is still showing NaN and not the sum up of all the products total. I still can't get the total of totals to work correctly :/ Why the total only shows and starts working touching the first product and not the other two?
    – mlpz
    Commented May 23 at 4:16
  • i have updated the code please check . Add the current quantity with price of each product and update it on the Total of the all the products. any other issues? @mlpz
    – Karthik P
    Commented May 23 at 4:31
  • you won't need querySelectorAll since there is only one .addButton, the forEach will alway run once. Instead try getElementsByClassName or querySelector
    – Yuvaraj M
    Commented May 23 at 4:41
  • @KarthikP try to mention the mistakes made by OP, like p.sku but actual is p.SKU in product find
    – Yuvaraj M
    Commented May 23 at 4:52
  • @KarthikP Wow, that worked like a charm! I really appreciate your help. I've applied your code and everything is running smoothly now. Thanks a ton for your assistance! Hey, just wondering, do you know how to list the product title/name along with the quantity (only if it's 1 or more) and the product price multiplied by that quantity? I'd like to display all of this before showing the grand total. Any ideas?
    – mlpz
    Commented May 23 at 5:27

Not the answer you're looking for? Browse other questions tagged or ask your own question.