skip to Main Content

I am trying to create an achievement tracker that when you click a checkbox it will update a number on how many you have completed in that section. But how I have it implemented now the number only updates on one of the div blocks.

HTML

<div class="card-body">
        <div class="heading">
          <h2>Endings</h2>
          <p><span id="achievement-complete">0</span> / 2</p>
        </div>
        <ul>
          <li id="achievement_7_0">
            <div class="card-content">
              <input type="checkbox" id="item_7_0" class="sum" value="1" />
              <label class='strikethrough' for="item_7_0">
                Ending 1
              </label>
            </div>
          </li>
          <li id="achievement_7_1">
            <div class="card-content">
              <input type="checkbox" id="item_7_1" class="sum" value="1" />
              <label class='strikethrough' for="item_7_1">
                Ending 2 
              </label>
            </div>
          </li>
        </ul>

      </div>

      <div class="card-body">
        <div class="heading">
          <h2>All Achievements</h2>
          <p><span id="achievement-complete">0</span> / 1</p>
        </div>
        <ul>
          <li id="achievement_8_0">
            <div class="card-content">
              <input type="checkbox" id="item_8_0" class="sum" value="1" />
              <label class='strikethrough' for="item_8_0">
                All Achievements
              </label>
            </div>
          </li>
        </ul>

      </div>

Javascript

var input = document.getElementsByClassName('sum'),
  total = document.getElementById('achievement-complete');

for (var i = 0; i < input.length; i++) {
  input[i].onchange = function () {
    var add = this.value * (this.checked ? 1 : -1);
    total.innerHTML = parseFloat(total.innerHTML) + add
    // console.log(add);
  }
}

Here I would like it to call a js function in each div block with class="card-body" and update the span with class="achievement-complete" with the new total of achievements completed.

2

Answers


  1. If you wrap your markup in a container element you can use event delegation to capture event from its child elements as they "bubble up" the DOM. Just attach one listener to that container rather than many listeners to many elements.

    Then you can get the checked status from the element that fired the event, find the closest element with a card-body class, and use that to find the its child element with the achievement-complete class (note: this is a change to your markup where you’ve used id – ids must be unique in a page).

    Then grab that element’s text, coerce it to an integer, and update it – adding one if the checked status is true, and subtracting one if it’s false.

    // Cache container element and add a listener to it
    const container = document.querySelector('.container');
    container.addEventListener('click', handleClick);
    
    function handleClick(e) {
    
      // Check that the element that fired the event
      // is a checkbox
      if (e.target.matches('[type="checkbox"]')) {
        
        // Destructure the checked attribute from the checkbox
        const { checked } = e.target;
        
        // Find the closest element with a `card-body` class
        const card = e.target.closest('.card-body');
        
        // Use that to find the local "achievement" element
        const achievement = card.querySelector('.achievement-complete');
        
        // Get and coerce its total to an integer
        let total = Number(achievement.textContent);
        
        // Update the text content with an new total
        achievement.textContent = checked ? total += 1 : total -= 1;
      }
    }
    <section class="container">
      <section class="card-body">
        <h2>Endings</h2>
        <p><span class="achievement-complete">0</span> / 2</p>
        <ul>
          <li id="achievement_7_0">
            <input type="checkbox" id="item_7_0" class="sum" value="1" />
            <label class='strikethrough' for="item_7_0">
              Ending 1
            </label>
          </li>
          <li id="achievement_7_1">
            <input type="checkbox" id="item_7_1" class="sum" value="1" />
            <label class='strikethrough' for="item_7_1">
              Ending 2 
            </label>
          </li>
        </ul>
      </section>
      <section class="card-body">
        <h2>All Achievements</h2>
        <p><span class="achievement-complete">0</span> / 1</p>
        <ul>
          <li id="achievement_8_0">
            <input type="checkbox" id="item_8_0" class="sum" value="1" />
            <label class='strikethrough' for="item_8_0">
              All Achievements
            </label>
          </li>
        </ul>
      </section>
    </section>

    Additional documentation

    Login or Signup to reply.
  2. It works for me.

    var inputs = document.getElementsByClassName('sum');
    
    for (var i = 0; i < inputs.length; i++) {
      inputs[i].onchange = function () {
        var cardBody = this.closest('.card-body');
        var total = cardBody.querySelector('.achievement-complete');
        var checkboxes = cardBody.querySelectorAll('.sum');
        var count = 0;
        for (var j = 0; j < checkboxes.length; j++) {
          count += checkboxes[j].checked ? 1 : 0;
        }
        total.innerHTML = count;
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search