What is the best way to automatically adjust the quantity and total cost for every SKU using JavaScript?

Currently, I am developing the functionality for a virtual store's shopping cart. At the core of this feature lies a Cart class that manages data and executes calculations. This specific class does not interact with the HTML document or the DOM; its primary purpose is to perform computations.

The Cart class contains methods to handle products. Each product possesses SKU, title, and price properties. I am endeavoring to introduce a quantity property as well, but it seems to be malfunctioning. Two buttons, namely addButton and subtractButton, are available for each product to increase or decrease their respective quantities.

My objectives include:

  1. Upon clicking the addButton, the product's quantity should increment by 1, thereby updating the total price for that SKU in the total-SKU span.
  2. Once the subtractButton is clicked, the product's quantity should decrement by 1 (if greater than 0), followed by an update to the total price for that SKU within the total-SKU span.
  3. The cumulative value of all SKUs ought to be computed and displayed within the total div.

For those interested, here is the code on jsFiddle: https://jsfiddle.net/mlpz2/edfjLhb2

The code snippet is provided below:

insert your unique rewrite of the script.js file here... 
insert your unique rewrite of the styles.css file here... 

The product information is retrieved from an API. The mock API utilized is jsonblob.com().

insert your unique rewrite of the JSON data here...

I seem to be grappling with the following challenges:

  1. Incorporating new elements into the DOM for displaying the product list.
  2. Detecting events to adjust the unit quantity for each product.
  3. Refreshing the total price on the DOM whenever modifications occur.

I am unsure about how to create event listeners for the addButton and subtractButton or how to manage updating the quantity and total price on the DOM. Any guidance on enhancing the code would be sincerely appreciated!

Thank you kindly for your attention :)

Answer №1

Upon careful inspection of your code, I have identified two key fixes:

  1. p.sku is accessing an invalid property of the product through the class. To resolve this issue, simply change it to p.SKU.
  2. p.quantity is not part of the initial product object, resulting in it displaying NAN. This can be rectified by including it during initialization so that there is no need for any if condition to check it.
cart.products = data.products.map(product => {
    return {
        ...product,
        price: parseFloat(product.price),
        quantity: 0 // THIS WILL ADDRESS MAJOR ISSUES
    };
}); 

Additionally, I recommend using getElementsByClassName or querySelector instead of querySelectorAll since there is only one .addButton, and the forEach will always run once.

/* 
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

Answer №2

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 specific event listeners to all buttons individually and utilize their IDs to update corresponding elements effectively.

I hope you find this guidance useful.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Difficulty with MultiHandleSliderExtender and postback in JavaScript

Attempting to implement the multihandlesliderextender (from the Ajax Toolkit) for creating a price filter option on a webshop. Upon selecting a new price, it should trigger a reload of everything including extensive behind-the-scenes code. Referenced an a ...

Utilize Google Drive and scripts to incorporate map images into a React application

I'm currently working on setting up an album feature on my react website for a friend, and I would like the images in the album to be linked to a Google Drive so that he can easily upload new images whenever he wants. After successfully inserting the ...

The Facebook API's JavaScript SDK displays the status as 'connected' even after logging out

As I navigate my AngularJS website, I am utilizing the Facebook SDK for JavaScript to facilitate registration forms. After successfully logging in and retrieving the necessary data from my first attempt, I proceeded to register and eventually logged out of ...

Image does not appear on server side when using FormData for AJAX image upload

I am currently working on an image upload system where the image is uploaded as soon as it is selected in the input[type='file'] element. Despite my efforts, I am facing challenges in transferring the image to the server side. Scenario HTML: & ...

The efficiency of the react native Animated.spring function leaves much to be desired, as it

I am currently using the React Native animated API to create a seamless transition from left to right positions. This is how I set up my initial state: constructor(props) { super(props); this.state = { isDrawerOpened: false, ...

What is the process of installing an npm module from a local directory?

I recently downloaded a package from Github at the following link: list.fuzzysearch.js. After unzipping it to a folder, I proceeded to install it in my project directory using the command: npm install Path/to/LocalFolder/list.fuzzysearch.js-master -S Howe ...

Enhancing transparency with a touch of background color

After successfully exporting my chart created in canvas as an image file, I noticed that the image turned out to be transparent without any background. Is there a way through code to add a background color to this existing image obtained from canvas? For ...

Filter and select JavaScript objects based on the content of an array

I have a challenge filtering specific fields from a set of JavaScript objects: A= [{ asset_bubble: 17, biodiversity_loss: 15, code: "CH", critical_information: 14, cyber_attacks: 19, data_fraud: 13, de ...

Modifying array elements in JavaScript without explicit instructions

I am relatively new to Javascript and seem to be encountering a rather puzzling issue that I'm unable to resolve. Currently, I have two classes: Country and PPM_Data. The Country class contains parameters such as name, area, ppm, etc., along with a f ...

Creating a layered effect by overlaying one image on top of another in an HTML5

Currently, I am facing an issue with drawing a level field inside my canvas. The images of the tank and enemies are being drawn underneath the field image, which is causing some problems as they should actually be moving above the field. Here is a link t ...

Guide on accessing a method from a div element in VueJS

Is there a way to only render a div based on a specific condition and call a method when the condition is true? Here's an example of what I'm trying to achieve: <div v-if="timetable === null" @click="mymethodhere"> ...

The functionality of TypeScript's instanceof operator may fail when the method argument is not a simple object

There seems to be an issue with a method that is being called from two different places but returns false for both argument types. Despite checking for the correct types, the problem persists and I am unsure why. Although I have read a similar question on ...

Storing the information received from an API as an HTML element

My HTML file contains JavaScript, along with a URL that displays data retrieved from an AWS lambda API call via AWS API Gateway. The page initially appears blank and the data is structured like this: [ {"user": "bob", "groups": ["bobsGroup"], "policies": ...

Having trouble with Node integration in Electron?

In my inventory application, I have the backend written in Python 3.7 and I am using Electron to create a GUI for it. To communicate with the Python code, I am utilizing the Node.js Module "python-shell" and would like to keep all of its code in a separate ...

An issue occurred while attempting to retrieve information from the database table

'// Encounter: Unable to retrieve data from the table. // My Code const sql = require('mssql/msnodesqlv8'); const poolPromise = new sql.ConnectionPool({ driver: 'msnodesqlv8', server: "test.database.windows.net", ...

Is there a way to create a nested object literal that will return the length of

I am working with JSON data that includes different locations (sucursales) and cars for each location. How can I write a function to calculate the total number of cars across all locations? [{"sucursal": "Quilmes", "direccion&q ...

Adjusting canvas dimensions for angular chart.js: A quick guide

I am currently creating my first sample code using angular chart.js, but I am facing an issue with changing the custom height and width of my canvas. How can I adjust the height and width accordingly? CODE: CSS #myChart{ width:500px; he ...

Troubleshooting issue with ng-hide in AngularJS when using multiple filters

I'm struggling to implement two filters using ng-hide. Check out this fiddle where I've showcased the issue: http://jsfiddle.net/czPf6/2/ I have a piece of code that dynamically selects which filter to use based on input values from an input box ...

What is the most effective way to access content from a webpage that is rendered

Is there a reliable way to download from links on a JavaScript rendered webpage using Python as the preferred language? I have attempted to use the Selenium Python bindings on a headless server, but it has proven to be slow, error-prone, and unable to acc ...

Tips for prolonging the visibility of the Bootstrap 5 Toast alert

Within my HTML code, I have defined a toast element: <div class="position-fixed top-0 start-50 translate-middle-x" style="z-index: 9999"> <div id="errNeutralAlreadyExists" class="toast fade bg-solid-white&quo ...