Luks
Luks

Reputation: 25

How to add/remove class from a parent List-item(li) when their child checkbox is changed in javascript?

I am building a simple Todo app in Javascript but I got stuck trying to add/remove a class to a List-item(li) that is the parent of a checkbox.

By default a list-item (Todo) checkbox is unchecked (with no class added). Whenever a user check a todo checkbox, a class is added, and the todo text gets a line through.

I managed to make it work but nothing happens.

// ADD ITEM, REMOVE ITEM - FUNCIONALITY 
const btn = document.getElementById('btn');
const ulList = document.getElementById('list');

// Button event listener with adding li elemnts with input value
btn.addEventListener('click', function() {
  var input = document.getElementById('input').value; // Capture input value
  var newItem = document.createElement("LI"); // Create a <li> node
  newItem.innerHTML = input + '<input type="checkbox" class="checkboxes" ><p class="delet">x</p>'; // Add content to li element for todo.                   
  ulList.insertBefore(newItem, ulList.childNodes[0]); // Insert <li> before the first child of <ul>
  // input = ' ';  // Reset input value to empty field

  // Remove item funcionality 
  newItem.childNodes[2].onclick = function() {
    this.parentNode.remove(this);
  }
})

// ********** IMPORTANT CODE BELOW ***********************
// MARK DONE TODO  - FUNCIONALITY 

var checkBox = document.getElementsByClassName('checkboxes');

for (var i = 0; i < checkBox; i++) {
  checkBox[i].addEventListener('change', function() {
    if (this.checked) {
      // Checkbox is checked..
      this.parentNode.classList.add("line-through");
    } else {
      // Checkbox is not checked..
      this.parentNode.classList.remove("line-through");
    }
  });
}
.line-through {
  text-decoration: line-through;
}
<p class="lead text-center">Welcome to my todoList applications</p>
<div class="row">
  <form id="form" class="col-lg-6 col-8 mx-auto">
    <div class="input-group">
      <input type="text" id="input" class="form-control"><span>
        <button id="btn" type="button" class="btn btn-primary">Submit</button></span>
    </div>
  </form>
</div>
<div class="row">
  <ul id="list" class="list col-lg-6 col-8 mx-auto">
    <!-- <li>this is a todo item <input type="checkbox" class="checkbox"></li>
        <li>this is a todo item <input type="checkbox" class="checkbox"></li> -->
  </ul>
</div>
<div class="row">
  <button id="btnClr" type="button" class="btn btn-primary mx-auto btnHide">Clear All Todos</button>
</div>

I would appreciate any help. Thanks in advance everyone! :)

Upvotes: 0

Views: 808

Answers (3)

Isisco
Isisco

Reputation: 141

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <style>
        .line-through li {
            text-decoration: line-through;
        }
    </style>
</head>
<body>
<p class="lead text-center">Welcome to my todoList applications</p>
<div class="row">
    <form id="form" class="col-lg-6 col-8 mx-auto">
        <div class="input-group">
            <input type="text" id="input" class="form-control" ><span>
        <button id="btn" type="button" class="btn btn-primary">Submit</button></span>
        </div>
    </form>
</div>
<div class="row">
    <ul id="list" class="list col-lg-6 col-8 mx-auto">
        <!-- <li>this is a todo item <input type="checkbox" class="checkbox"></li>
              <li>this is a todo item <input type="checkbox" class="checkbox"></li> -->
    </ul>
</div>
<div class="row">
    <button id="btnClr" type="button" class="btn btn-primary mx-auto btnHide">Clear All Todos</button>
</div>
</body>
<script>
    // ADD ITEM, REMOVE ITEM - FUNCIONALITY
    const btn = document.getElementById('btn');
    const ulList = document.getElementById('list');
    let checkBox = document.querySelectorAll('.checkboxes li');


    // Button event listener with adding li elemnts with input value
    btn.addEventListener('click', function(){
        var input = document.getElementById('input').value; // Capture input value
        var newItem = document.createElement("LI");     // Create a <li> node
        newItem.innerHTML = input + '<input type="checkbox" class="checkboxes" ><p class="delet">x</p>';  // Add content to li element for todo.
        ulList.insertBefore(newItem, ulList.childNodes[0]);  // Insert <li> before the first child of <ul>
        // input = ' ';  // Reset input value to empty field

        // Remove item funcionality
        newItem.childNodes[2].onclick = function() {this.parentNode.remove(this);}
    });

    // ********** IMPORTANTO CODE BELOW ***********************
    // MARK DONE TODO  - FUNCIONALITY


    document.body.addEventListener( 'click', function ( event ) {
        if (event.srcElement.className == 'checkboxes') {

                console.log(this);
                this.classList.toggle('line-through');

        }
    });

    checkBox.forEach(el => {
        el.addEventListener('change', myFunction);
    }, false);

    function myFunction(){
        if(this.checked) {
            console.log('here')
            this.classList.toggle('line-through');
        }
    }
</script>
</html>

Upvotes: 0

Davi
Davi

Reputation: 4157

A complete and working example below. Generally speaking, it is easier (for me, but your personal experiences may vary) to utilize document.createElement instead of .innerHTML for tasks like yours, because attaching event listeners to elements created by document.createElement is (again, in my opinion) much easier.

The example creates a new <li>, <input type="checkbox">, <span> (for the todo's title) and a <button> (for deleting the todo) whenever the "Submit" button is clicked. After all inner elements are created, they are easy to append to the <li> with .appendChild.

I tried to use descriptive names, so following along shouldn't be complicated.

const todoAddBtn = document.getElementById('btn');
const todoDeleteBtn = document.getElementById('btnClr');
const todosList = document.getElementById('list');
const todoInput = document.getElementById('input');

todoAddBtn.addEventListener('click', function(){
  const todoTopic = readAndClearValue(todoInput);
  const todoLi = createListItem();
  const todoCheckbox = createCheckbox();
  const todoTitle = createTitle(todoTopic);
  const todoDelete = createDeleteButton();
  
  todoLi.appendChild(todoCheckbox);
  todoLi.appendChild(todoTitle);
  todoLi.appendChild(todoDelete);
  
  todosList.insertBefore(todoLi, todosList.firstElementChild);
});

todoDeleteBtn.addEventListener('click', function () {
  todosList.innerHTML = '';
});

// readAndClearValue :: HTMLElement -> String
function readAndClearValue (element) {
  const value = element.value;
  element.value = '';
  return value;
}

// createListItem :: () -> HTMLElement
function createListItem () {
  return document.createElement('li');
}

// createTitle :: String -> HTMLElement
function createTitle (text) {
  const title = document.createElement('span');
  title.textContent = text;
  return title;
}

// createDeleteButton :: () -> HTMLElement
function createDeleteButton () {
  const button = document.createElement('button');
  button.textContent = 'X';
  button.className = 'delet';
  button.addEventListener('click', function () {
    button.parentNode.removeChild(button);

    // to remove the <li>, use something like
    // button.parentNode.parentNode.removeChild(button.parentNode)
    // or button.closest('li').remove() if supported
    
  });

  return button;
}

// createCheckbox :: () -> HTMLElement
function createCheckbox () {
  const checkbox = document.createElement('input');
  checkbox.type = 'checkbox';
  checkbox.className = 'checkboxes';
  checkbox.addEventListener('change', function () {
    if (checkbox.checked) {
      checkbox.parentNode.classList.add('line-through');
    } else {
      checkbox.parentNode.classList.remove('line-through');
    }
  });
  
  return checkbox;
}
.line-through {
  text-decoration: line-through;
}
<p class="lead text-center">Welcome to my todoList applications</p>
<div class="row">
  <form id="form" class="col-lg-6 col-8 mx-auto">
    <div class="input-group">
      <input type="text" id="input" class="form-control" >
      <button id="btn" type="button" class="btn btn-primary">Submit</button>
    </div>               
  </form>
</div>            
<div class="row">             
  <ul id="list" class="list col-lg-6 col-8 mx-auto">
  </ul>
</div>
<div class="row">
  <button id="btnClr" type="button" class="btn btn-primary mx-auto btnHide">
    Clear All Todos
  </button>
</div>

Upvotes: 2

Seems like you need to add listeners after checkbox creation. Shat's happening now You load the page and at the start, you don't have any checkbox, so when you run for loop no handlers attached

Here is a sniped of how to make it work. There are a lot of changes but I've tried to leave detailed comments.

Feel free to ask if you have any questions :)

https://codesandbox.io/embed/bootstrap-r3ud0

Also here is JS part.

const btn = document.getElementById("btn");
const ulList = document.getElementById("list");

// Button event listener with adding li elemnts with input value
btn.addEventListener("click", function() {
  var input = document.getElementById("input").value; // Capture input value
  var newItem = document.createElement("LI"); // Create a <li> node

  // manually create input element
  var inputEl = document.createElement("input");
  // set attributes
  inputEl.type = "checkbox";
  inputEl.class = "checkboxes";

  // also create p element
  var xmark = document.createElement("p");
  xmark.innerHTML = "x";
  xmark.class = "delet";

  // set click handler
  xmark.onclick = function() {
    this.parentNode.remove(this);
  };

  // most important part!
  // we add change listener on input create step
  inputEl.addEventListener("change", changeHandler);
  newItem.innerHTML = input;

  // and append our new elements to the li
  newItem.appendChild(inputEl);
  newItem.appendChild(xmark);

  ulList.insertBefore(newItem, ulList.childNodes[0]); // Insert <li> before the first child of <ul>
});

// create separate handler for change event (first param is event)
const changeHandler = event => {
  // we can access checked property of an element
  const checked = event.target.checked;
  // also we need the target (input in this case) for further manipulations
  const element = event.target;

  if (checked) {
    // Checkbox is checked..
    element.parentNode.classList.add("line-through");
  } else {
    // Checkbox is not checked..
    element.parentNode.classList.remove("line-through");
  }
};

Upvotes: -1

Related Questions