Using checkboxes as buttons to auto calculate a total in jquery?

Viewed 87

I have a form that's a food menu, and I am having trouble with a section with check-boxes.

I wanted to make the form nicer so I styled the check-boxes as buttons so that when clicked the box gets checked. This works fine, but the issue is when I move from one check-box to another without unchecking the first one. It doesn't subtract the amount from my total.

I used the following jQuery script:

$('.drinks').on('change', function() {

  $('.drinks').not(this).prop('checked', false);
  
});

to switch from one checkbox to the next automatically which shows up graphically but in my console it doesn't actually uncheck the other boxes.

$(document).ready(function(){
   console.log("Jquery DOM ready.");
     let totalprice="200";
     $('#pricedisplay').html("$" + totalprice);

     $('input[name=drink-one]').change(function(){
        console.log("an input just changed.");
        $('input[name=drink-one]:checked').each(function(){
           let oneAddon = this.value;
           console.log(this.value);
           totalprice = parseFloat(totalprice) + parseFloat(oneAddon);
           console.log(totalprice);
        });
        $('input[name=drink-one]:not(:checked)').each(function (){
           let oneAddon = this.value;
           console.log(this.value);
           totalprice = parseFloat(totalprice) - parseFloat(oneAddon);
           console.log(totalprice);
        });
        $('#pricedisplay').html("$" + totalprice)
     });

     $('input[name=drink-two]').change(function(){
        console.log("an input just changed.");
        $('input[name=drink-two]:checked').each(function(){
           let twoAddon = this.value;
           console.log(this.value);
           totalprice = parseFloat(totalprice) + parseFloat(twoAddon);
           console.log(totalprice);
        });
        $('input[name=drink-two]:not(:checked)').each(function (){
           let twoAddon = this.value;
           console.log(this.value);
           totalprice = parseFloat(totalprice) - parseFloat(twoAddon);
           console.log(totalprice);
        });
        $('#pricedisplay').html("$" + totalprice)
     });

     $('input[name=drink-three]').change(function(){
        console.log("an input just changed.");
        $('input[name=drink-three]:checked').each(function(){
           let threeAddon = this.value;
           console.log(this.value);
           totalprice = parseFloat(totalprice) + parseFloat(threeAddon);
           console.log(totalprice);
        });
        $('input[name=drink-three]:not(:checked)').each(function (){
           let threeAddon = this.value;
           console.log(this.value);
           totalprice = parseFloat(totalprice) - parseFloat(threeAddon);
           console.log(totalprice);
        });
        $('#pricedisplay').html("$" + totalprice)
     });
     $('.drinks').on('change', function() {

       $('.drinks').not(this).prop('checked', false);

     });
   });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<body>
    <div class="chks">
        <div id="ck-button-one">
            <label>
                <input class="drinks" type="checkbox" name="drink-one" value="5"><span>One</span></input>
            </label>
        </div>
        <div id="ck-button-two">
            <label>
                <input class="drinks" type="checkbox" name="drink-two" value="10"><span>Two</span></input>
            </label>
        </div>
        <div id="ck-button-three">
            <label>
                <input class="drinks" type="checkbox" name="drink-three" value="20"><span>Three</span></input>
            </label>
        </div>
    </div>
    <div class="price">Purchase Price:
        <span id="pricedisplay"></span>
    </div>
</body>
</html>

When I click on the button, the total price adds correctly. If I click the same button, it subtracts correctly. If I click another button before un-clicking the previous button, the whole thing breaks and just keeps adding because you can't get back to the other button you clicked previously to undo its value.

I need help to make this function do the following:

  1. if a user doesn't want any extra drinks, the price stays the same.

  2. if a user chooses 'drink one', the price goes up by 5.

  3. if a user clicks on 'drink one's' button again, the price does down by 5.

  4. if a user clicks on 'drink one's' button, then immediately clicks on 'drink two's' button and the price will only add 'drink two's' value and subtract 'drink one's' value. The same goes for 'drink-three' and so on.

  5. only one button can be checked at a time or no buttons can be checked at a time.

I tried to include console logs for the different steps to help in debugging.

1 Answers

The problem concerns programmatically unchecking inputs without raising the change event.

Explanation: The change event only fires "when an alteration to the element's value is committed by the user ... by clicking or using the keyboard" and is "not necessarily fired for each alteration to an element's value" (MDN). Also see Changing .prop using jQuery does not trigger .change event.

In your case: When an option is checked, we want to uncheck other options. We want to add the price of the checked option and subtract the price of options that were unchecked. However:

  1. In order to subtract options that were unchecked, we need to trigger the change event.

  2. We only want to uncheck other options when an option is checked.
    We do not want to uncheck other options when an option is unchecked.

  3. We only want to subtract options that change from checked to unchecked.
    We do not want to subtract options that were already unchecked.

So, instead of this:

$('.drinks').on('change', function() {
  $('.drinks').not(this).prop('checked', false);
});

We can use this:

$('.drinks').on('change', function() {
  if (this.checked) {
    $('.drinks:checked').not(this).prop('checked', false).trigger('change');
  }
}

Below, I consolidated some of the repeated code in an attempt to keep things as DRY as possible. Each option now has the same name "drink" and a single handler is bound to all of them.

Demonstration:

$(function() {

  const $priceDisplay = $('#pricedisplay');
  const $drinks = $('.drinks');
  let totalprice = 200;

  $priceDisplay.html("$" + totalprice);

  $drinks.on('change', function() {

    // if changed input is checked ...
    if (this.checked) {
      // for all checked except this, uncheck and trigger "change" event
      $drinks.filter(':checked').not(this).prop('checked', false).trigger('change');
    }

    // add or subtract price depending on checked state
    totalprice = this.checked
      ? totalprice + parseFloat(this.value)
      : totalprice - parseFloat(this.value);

    // update display
    $priceDisplay.html("$" + totalprice);

  });

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!DOCTYPE html>

<div class="chks">
  <div id="ck-button-one">
    <label>
      <input class="drinks" type="checkbox" name="drink" value="5">
      <span>One</span>
    </label>
  </div>
  <div id="ck-button-two">
    <label>
      <input class="drinks" type="checkbox" name="drink" value="10">
      <span>Two</span>
    </label>
  </div>
  <div id="ck-button-three">
    <label>
      <input class="drinks" type="checkbox" name="drink" value="20">
      <span>Three</span>
    </label>
  </div>
</div>
<div class="price">Purchase Price:
  <span id="pricedisplay"></span>
</div>


Alternative Method

Rather than adding and subtracting from a floating price, consider adding only selected options to a fixed base price. Each time a drink option changes, recalculate the total price by incrementing the base price for selected options.

When a drink option changes, prices for selected drinks are added to the base price.
If no drinks are selected, nothing is added to the base price.

$(function() {

  const basePrice = "200";
  const $priceDisplay = $('#pricedisplay');
  const $drinks = $('.drinks');

  $priceDisplay.html("$" + basePrice);

  $drinks.on('change', function() {

    let newPrice = parseFloat(basePrice);

    $drinks.not(this).prop('checked', false);

    $drinks.filter(':checked').each(function() {
      newPrice += parseFloat(this.value);
    });

    $priceDisplay.html("$" + newPrice);

  });

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="chks">
  <div id="ck-button-one">
    <label>
      <input class="drinks" type="checkbox" name="drink" value="5">
      <span>One</span>
    </label>
  </div>
  <div id="ck-button-two">
    <label>
      <input class="drinks" type="checkbox" name="drink" value="10">
      <span>Two</span>
    </label>
  </div>
  <div id="ck-button-three">
    <label>
      <input class="drinks" type="checkbox" name="drink" value="20">
      <span>Three</span>
    </label>
  </div>
</div>
<div class="price">Purchase Price:
  <span id="pricedisplay"></span>
</div>


With a slightly different structure, this concept can be expanded to handle multiple option groups:

$(function() {

  const basePrice = "200";
  const $priceDisplay = $('#pricedisplay');
  const $options = $('.option');

  $priceDisplay.html("$" + basePrice);

  $options.on('change', function() {

    // define new price from base price
    let newPrice = parseFloat(basePrice);

    // uncheck other options in this group
    let $thisGroupOpts = $options.filter('[name=' + this.name + ']');
    $thisGroupOpts.not(this).prop('checked', false);

    // add prices for all checked options
    $options.filter(':checked').each(function() {
      newPrice += parseFloat(this.value);
    });

    // display total price
    $priceDisplay.html("$" + newPrice);

  });

});
.optionsPane {
  display: flex;
  flex-direction: row;
  margin: 0 0 2em;
}

.optionGroup {
  flex: 1 1 auto;
}

.optionGroup label {
  display: block;
}

#pricedisplay {
  font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="optionsPane">
  <div class="optionGroup">
    <h2>Drinks</h2>
    <label>
      <input class="option" type="checkbox" name="drink" value="5">
      <span>One</span>
    </label>
    <label>
      <input class="option" type="checkbox" name="drink" value="10">
      <span>Two</span>
    </label>
    <label>
      <input class="option" type="checkbox" name="drink" value="20">
      <span>Three</span>
    </label>
  </div>
  <div class="optionGroup">
    <h2>Sides</h2>
    <label>
      <input class="option" type="checkbox" name="side" value="3">
      <span>One</span>
    </label>
    <label>
      <input class="option" type="checkbox" name="side" value="4">
      <span>Two</span>
    </label>
    <label>
      <input class="option" type="checkbox" name="side" value="5">
      <span>Three</span>
    </label>
  </div>
  <div class="optionGroup">
    <h2>Deserts</h2>
    <label>
      <input class="option" type="checkbox" name="desert" value="5">
      <span>One</span>
    </label>
    <label>
      <input class="option" type="checkbox" name="desert" value="7">
      <span>Two</span>
    </label>
    <label>
      <input class="option" type="checkbox" name="desert" value="10">
      <span>Three</span>
    </label>
  </div>
</div>
<div class="price">Purchase Price:
  <span id="pricedisplay"></span>
</div>

Related