juc
juc

Reputation: 27

Javascript sum of items

So using Vanilla Javascript i have managed to create a dropdown list of items, once a button is clicked, the item is added to the basket.

I have a total cost section that should add up the items cost as they're added to the basket. From my knowledge i need to setup an array and assign items a price. But then id need to link it with the corresponding item in the dropdown list (HTML). And i presume ill need to sum these up using a formulae. Whats the best way to go about this? Or if someone could show me an example?

Here is what I have:

var button = document.getElementById("button");
var select = document.getElementById("select");
var basket = document.getElementById("basket");
var totalCost = document.getElementById("total-cost");

function addToBasket() {
 var li = document.createElement("li");
 li.innerHTML = select.options[select.selectedIndex].text;
 basket.appendChild(li);

}

document.getElementById("remove-button").onclick = function() {
var list = document.getElementById("basket");
list.removeChild(list.childNodes[0])

}


button.addEventListener("click", addToBasket);
<div  id="select-items">
 <form id="myForm">

        <p>Item: <select id="select">

            <option value="1" id="a">A</option>
            <option value="2" id="b">B</option>
            <option value="3" id="c">C</option>
            <option value="4" id="d">D</option>

        </select>

    <button id="button" type="button">Add</button></p>
    <button id="remove-button" type="button">Remove</button>

</form>
</div>

<div id="basket-total">    
<p>Basket</p>
<div id="basket"></div>
</div>

<div id="total-of-basket">
<p>Total Cost</p>
<p id="total-cost"></p>
</div>

Im pretty new to this so any help is much appreciated! Thanks!

Upvotes: 0

Views: 1155

Answers (3)

Scott Sauyet
Scott Sauyet

Reputation: 50787

One technique would be to store the current state in the DOM, using data- attributes. That would mean putting the prices directly on the select elements, and querying them when one is chosen:

var button = document.getElementById("button");
var select = document.getElementById("select");
var basket = document.getElementById("basket");
var totalCost = document.getElementById("total-cost");

function addToBasket() {
  var li = document.createElement("li");
  li.innerHTML = select.options[select.selectedIndex].text;
  var price = Number(select.options[select.selectedIndex].dataset.price);
  li.dataset.price = price;
  basket.appendChild(li);
  totalCost.innerHTML = ((Number(totalCost.innerHTML) || 0) + price).toFixed(2);
}

document.getElementById("remove-button").onclick = function() {
  var list = document.getElementById("basket");
  var price = Number(list.childNodes[0].dataset.price);
  list.removeChild(list.childNodes[0]);
  totalCost.innerHTML = ((Number(totalCost.innerHTML) || 0) - price).toFixed(2);
}


button.addEventListener("click", addToBasket);
div {float: left; margin-left: 3em;}
<div  id="select-items">
 <form id="myForm">

        <p>Item: <select id="select">
            <option value="1" data-price="1.23" id="a">A</option>
            <option value="2" data-price="2.35" id="b">B</option>
            <option value="3" data-price="7.11" id="c">C</option>
            <option value="4" data-price="9.87" id="d">D</option>

        </select>

    <button id="button" type="button">Add</button></p>
    <button id="remove-button" type="button">Remove</button>

</form>
</div>

<div id="basket-total">    
<p>Basket</p>
<div id="basket"></div>
</div>

<div id="total-of-basket">
<p>Total Cost</p>
<p id="total-cost"></p>
</div>

This shares the problem with the original that "remove" on an empty basket throws errors. That should be easy to fix, but is outside the scope of the problem.

I don't actually think that's your best bet. I think it would be better to keep the data in JS variables. But you'd need a way to get them there from whatever generated your dropdown. If this is manual, that's not an issue, but if this is generated server-side, you might need a dynamic generation of prices:

var button = document.getElementById("button");
var select = document.getElementById("select");
var basket = document.getElementById("basket");
var totalCost = document.getElementById("total-cost");

var prices = {a: 1.23, b: 2.35, c: 7.11, d: 9.87} // dynamically generated?
var total = 0;

function addToBasket() {
  var li = document.createElement("li");
  total += prices[select.options[select.selectedIndex].id];
  totalCost.innerHTML = total.toFixed(2);
  li.innerHTML = select.options[select.selectedIndex].text;
  li.itemId = select.options[select.selectedIndex].id;
  basket.appendChild(li);
}

document.getElementById("remove-button").onclick = function() {
  var list = document.getElementById("basket");
  total -= prices[list.childNodes[0].itemId];
  totalCost.innerHTML = total.toFixed(2);
  list.removeChild(list.childNodes[0]);
}


button.addEventListener("click", addToBasket);
div {float: left; margin-left: 3em;}
<div  id="select-items">
 <form id="myForm">

        <p>Item: <select id="select">
            <option value="1" data-price="1.23" id="a">A</option>
            <option value="2" data-price="2.35" id="b">B</option>
            <option value="3" data-price="7.11" id="c">C</option>
            <option value="4" data-price="9.87" id="d">D</option>

        </select>

    <button id="button" type="button">Add</button></p>
    <button id="remove-button" type="button">Remove</button>

</form>
</div>

<div id="basket-total">    
<p>Basket</p>
<div id="basket"></div>
</div>

<div id="total-of-basket">
<p>Total Cost</p>
<p id="total-cost"></p>
</div>

This solution still stores some state in the DOM -- the id property of the select items and the itemId of the newly created basket items. But that's it. The calculations use those ids to find the correct price in the prices object, and they use the javascript total variable to store the current price.

Again, I don't try to solve the remove problem here.


Honestly, this is still not code I really like. I'm not a fan of the global variables, and would probably find a way to embed them in some other scope. And there is other cleanup I would do as well, but this might get you on your way.

Upvotes: 0

Thijs
Thijs

Reputation: 2351

Here is an example of what you could do. What makes this solution easier to work with than your setup is that there is a single array in which selected products are placed and removed from. After adding or removing item the refreshBasket method is called which will rerender the basket content and total price.

There is an object with the product information. The ID on the options is used to get the matching product info. This information is placed in the array of selected items.

I noticed that your method to remove an item generates errors when the basket is empty. You should check first if there are items to remove before removing something.

const
  products = {
    a: { name: 'product A', price: 12.50 },
    b: { name: 'product B', price: 7.25 },
    c: { name: 'product C', price: 10 },
    d: { name: 'product D', price: 17.90 }
  },
  selectedItems = [];
var button = document.getElementById("button");
var select = document.getElementById("select");

/**
 * Gets product information for the provided ID. Passing an ID that doesn't
 * have a matching product will return undefined.
 */
function getSelectedProduct(productId) {
  return products[productId];
}

/**
 * Handles the event which is dispatched when the user adds an item to
 * the basket.
 */
function onAddItemToBasket(event) {
  // Get the option for the selected item.
  const
    selectedOption = select.options[select.selectedIndex],
    product = getSelectedProduct(selectedOption.id);
  // When there is no product with the ID, exit the method.
  if (product === undefined) {
    return;
  }
  // Add the selected item to the basket array with selected items.
  selectedItems.push(product);
  
  // Refresh the basket.
  refreshBasket();
}

/**
 * Handles the event which is dispatched when the user removes an item from
 * the basket.
 */
function onRemoveItemFromBasket(event) {
  // When there are no items there is nothing to do.
  if (selectedItems.lenght === 0) {
    return;
  }
  // Remove the item at index 0 from the array.
  selectedItems.shift();
  refreshBasket();
}

function refreshBasket() {
  const
    basket = document.getElementById("basket"),
    totalCost = document.getElementById("total-cost");

  // Create a string with item wrapped in an li element
  itemHTML = selectedItems.reduce((html, item) => html + `<li>${item.name} ($ ${item.price})</li>`,''),
  // Iterate over all the items and calculate the sum of all the items.
  totalPrice = selectedItems.reduce((total, item) => total + item.price, 0);
  
  // Update the inner HTML of the basket element with the items currently selected.
  basket.innerHTML = itemHTML;
  // Update the price of all the items combined.
  totalCost.innerHTML = `$ ${totalPrice}`;
}

const
  addTrigger = document.getElementById("button"),
  removeTrigger = document.getElementById("remove-button");
addTrigger.addEventListener('click', onAddItemToBasket);
removeTrigger.addEventListener('click', onRemoveItemFromBasket)
<div  id="select-items">
 <form id="myForm">

        <p>Item: <select id="select">

            <option value="1" id="a">A</option>
            <option value="2" id="b">B</option>
            <option value="3" id="c">C</option>
            <option value="4" id="d">D</option>

        </select>

    <button id="button" type="button">Add</button></p>
    <button id="remove-button" type="button">Remove</button>

</form>
</div>

<div id="basket-total">    
<p>Basket</p>
<div id="basket"></div>
</div>

<div id="total-of-basket">
<p>Total Cost</p>
<p id="total-cost"></p>
</div>

Upvotes: 0

danielgormly
danielgormly

Reputation: 1070

Here's what you want written in vanillaJS. This isn't crazy complex but it isn't the easiest either. It uses arrays and some array methods (foreach, reduce) that may be unfamiliar to you.

The basic concept is that you encapsulate an item into a Javascript Object with a name and value property, add it to an array called myBasket. Every time you add an item or remove one of these items, you recalculate the total cost and also reprint the entire list. For simplicity's sake, I remove the entire list, then reprint from scratch.

var button = document.getElementById("button");
var select = document.getElementById("select");
var basket = document.getElementById("basket");
var totalCost = document.getElementById("total-cost");

var myBasket = [];

function addToBasket() {
  var item = {
    value: Number(select.options[select.selectedIndex].value),
    name: select.options[select.selectedIndex].text
  };
  myBasket.push(item)
  recalculate();
}
 
function recalculate() {
  printBasket();
  printCost();
}

function printCost() {
  var cost = myBasket.reduce(function (acc, item) {
    return acc + item.value;
  }, 0);
  totalCost.innerText = cost;
}

function printBasket() {
  basket.innerHTML = '';
  myBasket.forEach(function(item) {
     var li = document.createElement("li");
 li.innerHTML = item.name;
 basket.appendChild(li);
  })
}

document.getElementById("remove-button").onclick = function() {
myBasket.pop();
recalculate();
}


button.addEventListener("click", addToBasket);
<div  id="select-items">
 <form id="myForm">

        <p>Item: <select id="select">

            <option value="1" id="a">A</option>
            <option value="2" id="b">B</option>
            <option value="3" id="c">C</option>
            <option value="4" id="d">D</option>

        </select>

    <button id="button" type="button">Add</button></p>
    <button id="remove-button" type="button">Remove</button>

</form>
</div>

<div id="basket-total">    
<p>Basket</p>
<div id="basket"></div>
</div>

<div id="total-of-basket">
<p>Total Cost</p>
<p id="total-cost"></p>
</div>

Upvotes: 1

Related Questions