2

I've read as much as I can on event delegation, and I cannot figure out why my code is acting the way it is. When the page loads, all of the check buttons work properly (toggles between 2 classes). Whenever I add a new item to the shopping list, the check button works for ONLY that new item. If I add a 2nd new item, the check button works for that new item, the 4 original items, but NOT the first new item. If I add any more items, it will not work for the previous item, but will work for all items before that one.

How can I get the check button to work for all items?

HTML...

<body>

  <div class="container">
    <h1>Shopping List</h1>

    <form id="js-shopping-list-form">
      <label for="shopping-list-entry">Add an item</label>
      <input type="text" name="shopping-list-entry" id="shopping-list-entry" placeholder="e.g., broccoli">
      <button type="submit">Add item</button>
    </form>

    <ul class="shopping-list">
      <li>
        <span class="shopping-item">apples</span>
        <div class="shopping-item-controls">
          <button class="shopping-item-toggle">
            <span class="button-label">check</span>
          </button>
          <button class="shopping-item-delete">
            <span class="button-label">delete</span>
          </button>
        </div>
      </li>
      <li>
        <span class="shopping-item">oranges</span>
        <div class="shopping-item-controls">
          <button class="shopping-item-toggle">
            <span class="button-label">check</span>
          </button>
          <button class="shopping-item-delete">
            <span class="button-label">delete</span>
          </button>
        </div>
      </li>
      <li>
        <span class="shopping-item shopping-item__checked">milk</span>
        <div class="shopping-item-controls">
          <button class="shopping-item-toggle">
            <span class="button-label">check</span>
          </button>
          <button class="shopping-item-delete">
            <span class="button-label">delete</span>
          </button>
        </div>
      </li>
      <li>
        <span class="shopping-item">bread</span>
        <div class="shopping-item-controls">
          <button class="shopping-item-toggle">
            <span class="button-label">check</span>
          </button>
          <button class="shopping-item-delete">
            <span class="button-label">delete</span>
          </button>
        </div>
      </li>
    </ul>
  </div>
<script
  src="https://code.jquery.com/jquery-3.4.1.js"
  integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
  crossorigin="anonymous"></script>
<script src="script.js"></script>
</body>
</html>

CSS...

* {
    box-sizing: border-box;
  }

  body {
    font-family: 'Roboto', sans-serif;
  }

  button, input[type="text"] {
    padding: 5px;
  }

  button:hover {
    cursor: pointer;
  }

  #shopping-list-item {
    width: 250px;
  }

  .container {
    max-width: 600px;
    margin: 0 auto;
  }

  .shopping-list {
    list-style: none;
    padding-left: 0;
  }

  .shopping-list > li {
    margin-bottom: 20px;
    border: 1px solid grey;
    padding: 20px;
  }

  .shopping-item {
    display: block;
    color: grey;
    font-style: italic;
    font-size: 20px;
    margin-bottom: 15px;
  }

  .shopping-item__checked {
    text-decoration: line-through;
  }

JS...

//Make the Check Button functional
function checkButton() {
    $(".shopping-item-toggle").on("click", "span", function() {
    $(this).closest("li").find(".shopping-item").toggleClass("shopping-item__checked");
  })};
checkButton();

//Make the Delete Button functional
function delButton() {
    $(".shopping-item-delete").on("click", function() {
    $(this).parent().parent().remove();
  })};
delButton();

  //Create a variable called buttons to add the 2 buttons in function below
  let buttons = "<div class='shopping-item-controls'><button class='shopping-item-toggle'>\
                 <span class='button-label'>check</span></button>\
                 <button class='shopping-item-delete'><span class='button-label'>delete</span></button></div>";

  //Add new Item to List
  $("#js-shopping-list-form").submit(function() {
    let item = $("#shopping-list-entry").val();
    if (item != "") { //As long as the input has text in it, run the function below
    $(".shopping-list").append("<li>" + "<span class='shopping-item'>" + item + "</span>" + buttons + "</li>");
    event.preventDefault();
    checkButton();//Make the check button functional
    delButton();//Make the delete button functional
    } else { //if NO text is in the input, show this alert
      alert("Must enter an item name!");
    }
  });
  • 1
    Please include all the relevant code *here* as text. The question should be self-contained and not rely on external links in order to be understandable or answerable - links can change or stop working, which means that any visitor in the future cannot benefit from any answers nor can anybody post a new answer. Also, make sure you have a [mcve]. – VLAZ Apr 13 '20 at 22:57
  • @VLAZ Thank you for that. I'm new to all of this. I've added the code. – Dustin Venable Apr 13 '20 at 23:03

1 Answers1

2

Whenever you add a new item, new click handlers are bound to all items. These means that items that existed on the page might get two or more duplicate handlers. If two handlers are bound to an item, the first one will toggleClass to turn on the "checked" class and the second one will immediately toggle the class back off.

To use event delegation, I recommend binding the handlers only once, and binding them to an ancestor that always exists on the page.

For example:

// Bind "check" and "delete" handlers to the ".shopping-list" ancestor.

$(".shopping-list").on("click", ".shopping-item-toggle span", function() {
  $(this).closest("li").find(".shopping-item").toggleClass("shopping-item__checked");
});

$(".shopping-list").on("click", '.shopping-item-delete', function() {
  $(this).parent().parent().remove();
});


// Create a variable called buttons to add the 2 buttons in function below
let buttons = "<div class='shopping-item-controls'><button class='shopping-item-toggle'>\
                   <span class='button-label'>check</span></button>\
                   <button class='shopping-item-delete'><span class='button-label'>delete</span></button>\
               </div>";

//Add new Item to List
$("#js-shopping-list-form").submit(function(event) {
  event.preventDefault();
  let item = $("#shopping-list-entry").val();
  if (item != "") { // If the input has text in it, add the new item
    $(".shopping-list").append("<li>" + "<span class='shopping-item'>" + item + "</span>" + buttons + "</li>");
  } else { //if NO text is in the input, show this alert
    alert("Must enter an item name!");
  }
});
* {
  box-sizing: border-box;
}

body {
  font-family: 'Roboto', sans-serif;
}

button,
input[type="text"] {
  padding: 5px;
}

button:hover {
  cursor: pointer;
}

#shopping-list-item {
  width: 250px;
}

.container {
  max-width: 600px;
  margin: 0 auto;
}

.shopping-list {
  list-style: none;
  padding-left: 0;
}

.shopping-list>li {
  margin-bottom: 20px;
  border: 1px solid grey;
  padding: 20px;
}

.shopping-item {
  display: block;
  color: grey;
  font-style: italic;
  font-size: 20px;
  margin-bottom: 15px;
}

.shopping-item__checked {
  text-decoration: line-through;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="container">
  <h1>Shopping List</h1>

  <form id="js-shopping-list-form">
    <label for="shopping-list-entry">Add an item</label>
    <input type="text" name="shopping-list-entry" id="shopping-list-entry" placeholder="e.g., broccoli">
    <button type="submit">Add item</button>
  </form>

  <ul class="shopping-list">
    <li>
      <span class="shopping-item">apples</span>
      <div class="shopping-item-controls">
        <button class="shopping-item-toggle"><span class="button-label">check</span></button>
        <button class="shopping-item-delete"><span class="button-label">delete</span></button>
      </div>
    </li>
    <li>
      <span class="shopping-item">oranges</span>
      <div class="shopping-item-controls">
        <button class="shopping-item-toggle"><span class="button-label">check</span></button>
        <button class="shopping-item-delete"><span class="button-label">delete</span></button>
      </div>
    </li>
    <li>
      <span class="shopping-item shopping-item__checked">milk</span>
      <div class="shopping-item-controls">
        <button class="shopping-item-toggle"><span class="button-label">check</span></button>
        <button class="shopping-item-delete"><span class="button-label">delete</span></button>
      </div>
    </li>
    <li>
      <span class="shopping-item">bread</span>
      <div class="shopping-item-controls">
        <button class="shopping-item-toggle"><span class="button-label">check</span></button>
        <button class="shopping-item-delete"><span class="button-label">delete</span></button>
      </div>
    </li>
  </ul>
</div>

Here's a demonstration of how multiple handlers are fired after adding a new item:

//Make the Check Button functional
function checkButton() {
  $(".shopping-item-toggle").on("click", "span", function() {
    let $item = $(this).closest("li").find(".shopping-item");
    console.log('Check Handler ' + ($item.hasClass('shopping-item__checked') ? 'OFF' : 'ON'));
    $item.toggleClass("shopping-item__checked");
  })
};
checkButton();

//Make the Delete Button functional
function delButton() {
  $(".shopping-item-delete").on("click", function() {
    console.log('Delete Handler');
    $(this).parent().parent().remove();
  })
};
delButton();

//Create a variable called buttons to add the 2 buttons in function below
let buttons = "<div class='shopping-item-controls'><button class='shopping-item-toggle'>\
                 <span class='button-label'>check</span></button>\
                 <button class='shopping-item-delete'><span class='button-label'>delete</span></button></div>";

//Add new Item to List
$("#js-shopping-list-form").submit(function(e) {
  event.preventDefault();
  let item = $("#shopping-list-entry").val();
  if (item != "") { //As long as the input has text in it, run the function below
    $(".shopping-list").append("<li>" + "<span class='shopping-item'>" + item + "</span>" + buttons + "</li>");
    checkButton(); //Make the check button functional
    delButton(); //Make the delete button functional
  } else { //if NO text is in the input, show this alert
    alert("Must enter an item name!");
  }
});
* {
  box-sizing: border-box;
}

body {
  font-family: 'Roboto', sans-serif;
}

button,
input[type="text"] {
  padding: 5px;
}

button:hover {
  cursor: pointer;
}

#shopping-list-item {
  width: 250px;
}

.container {
  max-width: 600px;
  margin: 0 auto;
}

.shopping-list {
  list-style: none;
  padding-left: 0;
}

.shopping-list>li {
  margin-bottom: 20px;
  border: 1px solid grey;
  padding: 20px;
}

.shopping-item {
  display: block;
  color: grey;
  font-style: italic;
  font-size: 20px;
  margin-bottom: 15px;
}

.shopping-item__checked {
  text-decoration: line-through;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="container">
  <h1>Shopping List</h1>

  <form id="js-shopping-list-form">
    <label for="shopping-list-entry">Add an item</label>
    <input type="text" name="shopping-list-entry" id="shopping-list-entry" placeholder="e.g., broccoli">
    <button type="submit">Add item</button>
  </form>

  <ul class="shopping-list">
    <li>
      <span class="shopping-item">apples</span>
      <div class="shopping-item-controls">
        <button class="shopping-item-toggle"><span class="button-label">check</span></button>
        <button class="shopping-item-delete"><span class="button-label">delete</span></button>
      </div>
    </li>
    <li>
      <span class="shopping-item">oranges</span>
      <div class="shopping-item-controls">
        <button class="shopping-item-toggle"><span class="button-label">check</span></button>
        <button class="shopping-item-delete"><span class="button-label">delete</span></button>
      </div>
    </li>
    <li>
      <span class="shopping-item shopping-item__checked">milk</span>
      <div class="shopping-item-controls">
        <button class="shopping-item-toggle"><span class="button-label">check</span></button>
        <button class="shopping-item-delete"><span class="button-label">delete</span></button>
      </div>
    </li>
    <li>
      <span class="shopping-item">bread</span>
      <div class="shopping-item-controls">
        <button class="shopping-item-toggle"><span class="button-label">check</span></button>
        <button class="shopping-item-delete"><span class="button-label">delete</span></button>
      </div>
    </li>
  </ul>
</div>
showdev
  • 28,454
  • 37
  • 55
  • 73