0

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! :)

msg
  • 7,863
  • 3
  • 14
  • 33
Luks
  • 25
  • 5
  • Your event listener setup only runs on page load, so it isn't bound for new elements. Have a look [here](https://stackoverflow.com/questions/34896106/attach-event-to-dynamic-elements-in-javascript). But you basically have to set up the `eventListener` on each new item (just like you are already doing with remove). – msg Jul 01 '19 at 15:02

3 Answers3

2

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>
David
  • 3,552
  • 1
  • 13
  • 24
  • Thanks for your response. Its working fine, Im blown away you went so far to redo the whole thing. It very interesting to see other better ways to do things. As Im only a beginner and Im testing stuff, I also wondering if there is also a work around on what I was trying? – Luks Jul 01 '19 at 20:42
0
<!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>
Isisco
  • 141
  • 9
  • Hey, many thanks for that. I tried your code but is not applying it to the Li element, its applying it to the body tag instead. – Luks Jul 01 '19 at 20:44
-1

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");
  }
};
  • Your example immediately throws an error. Besides that, you cannot add a element created with `document.createElement` to another element using `.innerHTML`. If you want to use `.innerHTML`, you have to use a `String`. Also, `inputEl.class` isn't a valid property. You should use `inputEl.className`. – David Jul 01 '19 at 15:49
  • Where does it throw? Sandbox example works fine. No need to use String. I didn't rewrite all the code and `input` here is a string (and cannot be anything else) about class - agree. Should use classList.add method Also, where do you see I'm adding an element by innerHtml? – Богдан Денисюк Jul 01 '19 at 15:59
  • Thanks for trying though. – Luks Jul 01 '19 at 20:43