Home > database >  how to make multiple counter buttons not interfere with each other
how to make multiple counter buttons not interfere with each other

Time:03-12

So basically I am making an online restaurant website where you can order food. I am going to make cards for each food item listed. Right now, I am making buttons that add and subtract the number of each item the customer wants to purchase.

var counter = document.querySelector(".count");
var currentNumber = 0;
var classicBurger = 5.18;
var bargeBurger = 6.13;

function addOne(price) {
  currentNumber  = 1;
  counter.innerHTML = currentNumber;
  document.querySelector(".total-price").innerHTML =
    "$"   (price * currentNumber).toFixed(2);
}

function subtractOne(price) {
  if (currentNumber > 0) {
    currentNumber  = -1;
    counter.innerHTML = currentNumber;
    document.querySelector(".total-price").innerHTML =
      "$"   (price * currentNumber).toFixed(2);
  } else {
    counter.innerHTML = 0;
  }
}
   <html>
     <div >
        <h3 >Classic Burger</h3>
        <p >
          Classic beef patty with lettuce, tomato, and cheese.
        </p>

        <div >
          <button  onclick="subtractOne(classicBurger)">
            -
          </button>
          <span >0</span>
          <button  onclick="addOne(classicBurger)"> </button>
        </div>

        <p >$0.00</p>
      </div>

      <div >
        <h3 >Barge Burger</h3>
        <p >
          Classic beef patty with lettuce, tomato, and cheese.
        </p>

        <div >
          <button  onclick="subtractOne(bargeBurger)">
            -
          </button>
          <span >0</span>
          <button  onclick="addOne(bargeBurger)"> </button>
        </div>

        <p >$0.00</p>
      </div>
      <html/>

How do I get the second button to effect the correct HTML rather than the total of the first item?

CodePudding user response:

The main issue is that currentNumber is shared between both counters, making it impossible to differentiate one from the other.

Additionally, keeping state in the HTML (the current price per burger) and using onclick makes it difficult to manage scoping.

I suggest removing all of the state from the HTML, then writing a loop over the .food-item elements to add click handlers to their buttons and be able to manipulate each of their outputs. You can use a data structure like burgers to keep track of prices for each burger instead of separate variables, which aren't amenable to looping over.

Here's one approach:

const burgers = [
  {name: "Classic Burger", price: 5.18},
  {name: "Barge Burger", price: 6.13},
];
const foodItemEls = [...document.querySelectorAll(".food-item")];
foodItemEls.forEach((e, i) => {
  let quantity = 0;
  e.querySelector(".add-item").addEventListener("click", () => {
      quantity;
    showTotal();
  });
  e.querySelector(".subtract-item").addEventListener("click", () => {
    quantity = Math.max(quantity - 1, 0);
    showTotal();
  });
  const showTotal = () => {
    e.querySelector(".total-price").textContent = 
      "$"   (burgers[i].price * quantity).toFixed(2);
  };
});
<div >
  <h3 >Classic Burger</h3>
  <p >
    Classic beef patty with lettuce, tomato, and cheese.
  </p>

  <div >
    <button >-</button>
    <span >0</span>
    <button > </button>
  </div>

  <p >$0.00</p>
</div>

<div >
  <h3 >Barge Burger</h3>
  <p >
    Classic beef patty with lettuce, tomato, and cheese.
  </p>

  <div >
    <button >-</button>
    <span >0</span>
    <button > </button>
  </div>

  <p >$0.00</p>
</div>

If you have more burgers, you might want to keep them all in an array like burgers shown above, and generate and inject the HTML using a loop over that data structure. To do this, you'd add the description to the array as well. That said, I'm not sure if it's the right fit for your app without more context.

CodePudding user response:

The problem with your code is the point at which you declare your variables, as well as the means by which you select the elements:

// here we get the first element in the document:
var counter = document.querySelector(".count");
// we set the currentNumber (the 'count') variable to 0:
var currentNumber = 0;

/* ... */

function addOne(price) {
  // regardless of which button, for which menu-item,
  // is pressed we increase the count (but this variable
  // is applied as the counter for *all* menu-items):
  currentNumber  = 1;

  // here we're using the first '.count' element in the
  // document (regardless of which menu-item we're
  // trying to order, or remove from the order):
  counter.innerHTML = currentNumber;

  // and again, we're using the first element within the
  // document (regardless of which menu-item the buttons
  // relate to):
  document.querySelector(".total-price").innerHTML =
    "$"   (price * currentNumber).toFixed(2);
}

// the same is true, below, for the subtraction function
// which I've removed for brevity

Instead, we need to look at which <button> was pressed and from there find the correct menu-item to increment or decrement; so in the following code I've taken advantage of EventTarget.addEventListener() to pass a reference to the triggered Event to the function that is bound as the event-listener:

// declaring named functions using Arrow functions (since we're not using the
// 'this' variable).
// a named function to format the cost to two decimal places:
const formatCost = (cost) => {
    return (Math.ceil(cost * 100) / 100).toFixed(2);
  },
  // the following functions take advantage of EventTarget.addEventListener()
  // passing the event-object to the function that's bound as the event-listener:
  addOne = (evt) => {
    // from the event-object we first retrieve the 'evt.currentTarget', this
    // is the element to which the event-listener was bound (in this case the
    // <button > elements), from there we use Element.closest()
    // to find the ancestor <div > element (effectively to find
    // the first ancestor that wraps the <button> elements as well as the other
    // elements we wish to use):
    let cardParent = evt.currentTarget.closest('div.food-item'),
      // from the cardParent we then use Element.querySelector() to select
      // the first element within that Element (the cardParent) that matches
      // the supplied CSS selector:
      costElement = cardParent.querySelector('span.cost'),
      countElement = cardParent.querySelector('span.count'),

      // from those elements we determine the cost, using parseFloat()
      // to convert the text-content of the element into a number we
      // can work with:
      cost = parseFloat(costElement.textContent),
      // we update the current count by adding 1 to the number
      // retrieved with parseInt():
      count = parseInt(countElement.textContent)   1;

    // we then update the countElement to reflect the new - increased - count:
    countElement.textContent = count;

    // and we then find the '.total-price' element, and update its text-content
    // to reflect the formatted cost:
    cardParent.querySelector('.total-price').textContent = formatCost(cost * count);
  },
  subtractOne = (evt) => {
    // here we do almost exactly as we did above:
    let cardParent = evt.currentTarget.closest('div.food-item'),
      costElement = cardParent.querySelector('span.cost'),
      countElement = cardParent.querySelector('span.count'),
      cost = parseFloat(costElement.textContent),
      // obviously we don't (yet) adjust the count:
      count = parseInt(countElement.textContent);

    // because we first need to check that the count is greater than zero:
    if (count > 0) {
      // if so, we then subtract one from the count:
      count = count - 1;
      // we then update the countElement to reflec the new count:
      countElement.textContent = count;
    }

    // and finally we update the '.total-price' element to reflect the new cost:
    cardParent.querySelector('.total-price').textContent = formatCost(cost * count);
  };

// here we select all elements in the document that match the supplied CSS selector, and
// use NodeList.forEach() to iterate over those elements, and:
document.querySelectorAll('button.subtract-item').forEach(
  // we then bind the subtractOne() function (note the lack of parentheses)
  // as the 'click' handler for the current element of the NodeList:
  (subtraction) => subtraction.addEventListener('click', subtractOne)
);
// as above, but obviously we're binding the addOne() function
// to the 'button.add-item' elements:
document.querySelectorAll('button.add-item').forEach(
  (addition) => addition.addEventListener('click', addOne)
);
*,
 ::before,
 ::after {
  box-sizing: border-box;
  font: normal 400 1rem / 1.5 sans-serif;
  margin: 0;
  padding: 0;
}

body {
  display: flex;
  flex-wrap: wrap;
  gap: 1em;
  padding: 1em;
}

.food-item {
  border: 1px solid #000;
  border-radius: 1em;
  padding: 0.5em;
}

h3 {
  font-weight: 600;
}

.add-subtract-items {
  align-content: center;
  display: flex;
  gap: 0.5em;
  justify-content: center;
  width: minmax(6em, 40%);
}

button,
span.count {
  text-align: center;
}

button {
  cursor: pointer;
  width: 2em;
}

:is(.cost, .total-price)::before {
  content: "$";
}
<div >
  <h3 >Classic Burger</h3>
  <span >5.18</span>
  <p >
    Classic beef patty with lettuce, tomato, and cheese.
  </p>

  <div >
    <button >
      -
    </button>
    <span >0</span>
    <button > </button>
  </div>

  <p >0.00</p>
</div>

<div >
  <h3 >Barge Burger</h3>
  <span >6.13</span>
  <p >
    Classic beef patty with lettuce, tomato, and cheese.
  </p>

  <div >
    <button >
      -
    </button>
    <span >0</span>
    <button > </button>
  </div>

  <p >0.00</p>
</div>

<div >
  <h3 >Milkshake</h3>
  <span >4.35</span>
  <p >
    Tastes like a five-dollar shake, for a little bit less.
  </p>

  <div >
    <button >
      -
    </button>
    <span >0</span>
    <button > </button>
  </div>

  <p >0.00</p>
</div>

JS Fiddle demo.

References:

  • Related