0

im building a to-do list but cant figure out how to keep my array values that have line-through decoration. the moment render method is called, the array is built from the start. means that if i delete an li, all other li that have been marked by the checkbox with a line-through, losing the decoration. what can i do to keep the line-through ? i tried so far in the markTask method to replace the original value with the value that have line-through on it but it didn't work. basically what im trying to accomplish is by inserting the value with line-through, to be able to check if this value have the line-through style and after the render to be able to keep the checked checkboxes as checked.

my code so far:

class Todo {
  constructor() {
    this.input = document.getElementById("input");
    this.ul = document.getElementById("ul");
    this.form = document.getElementById("form");
    this.tasks = [];
    this.registerEvent();
  }

  registerEvent() {
    this.form.addEventListener("submit", (event) => {
      event.preventDefault();
      this.createTask(this.input.value);
      this.form.reset();
    });
  }

  createTask(task) {
    if (task.trim().length === 0) {
      return;
    }
    this.tasks.push(task);
    this.render();
  }

  deleteTask(task) {
    const myTask = task.target;
    const parent = myTask.parentNode;
    const taskToRemove = parent.childNodes[1].textContent;
    const index = this.tasks.indexOf(taskToRemove);
    this.tasks.splice(index, 1);
    this.render();
  }

  markTask(task) {
    const myTask = task.target;
    const parent = myTask.parentNode;
    
    if (myTask.checked) {
      parent.style.textDecoration = "line-through";
    } else {
      parent.style.textDecoration = "none";
    }
  }

  render() {
    this.ul.innerHTML = "";
    this.tasks.forEach((task) => {
      const li = document.createElement("li");
      const cb = document.createElement("input");
      cb.type = "checkbox";
      cb.addEventListener("click", (e) => {
        this.markTask(e);
      });
      li.appendChild(cb);

      li.append(document.createTextNode(task));

      const btn = document.createElement("button");
      li.appendChild(btn);
      btn.textContent = "Delete";
      btn.classList.add("remove");
      btn.addEventListener("click", (e) => {
        this.deleteTask(e);
      });
      this.ul.appendChild(li);
    });
  }
}

new Todo();
<form id="form">
  <input id="input" />
  <button id="add">Add</button>
</form>
<ul id="ul">
  
</ul>

1
  • I'd probably turn the task into an object that has text & active attributes that drive the display Commented Dec 1, 2020 at 22:04

3 Answers 3

1

it's because you're not tracking which tasks are done and you're just pushing strings. for your createTask method you need to push an object with a done property to indicate which tasks have been done like so

 createTask(task) {
 if (task.trim().length === 0) {
  return;
 }
 this.tasks.push({title: task, done: false});
 this.render();
 }

update your render to account for tasks already done

render() {
this.ul.innerHTML = "";
this.tasks.forEach((task) => {
  const li = document.createElement("li");
  const cb = document.createElement("input");
  cb.type = "checkbox";
  cb.addEventListener("click", (e) => {
    this.markTask(e);
  });
  li.appendChild(cb);

  li.append(document.createTextNode(task.title));

  const btn = document.createElement("button");
  li.appendChild(btn);
  btn.textContent = "Delete";
  btn.classList.add("remove");
  btn.addEventListener("click", (e) => {
    this.deleteTask(e);
  });
  this.ul.appendChild(li);

   if (task.done) {
    cb.checked = true;
    li.style.textDecoration = "line-through";
   } else {
    cb.checked = false;
    li.style.textDecoration = "none";
   }
 });
}

in your constructor update your tasks variable to see this in effect

constructor() {
this.input = document.getElementById("input");
this.ul = document.getElementById("ul");
this.form = document.getElementById("form");
this.tasks = [{title: 'mill', done: true}, {title: 'jus', done: false}];
this.registerEvent();
}

hope you get the general idea. I won't do the entire implementation on markTask as this should be enough to give you a view of what the solution should be. good luck.

Sign up to request clarification or add additional context in comments.

4 Comments

thank you so much! i got the idea! im just new to js and programming logic and sometimes scared to actually try out things. you didnt only provide me with a solution but also motivated me to try out different approaches! for now i console.log a lot to see what i actually get, but one day i'll be able to write and understand without it! codepen.io/angardia/pen/VwKvYNL?editors=0111
@Anna You're welcome. I understand how it feels to start out as well. Let me know if you need additional resources to study. would be glad to provide them.
thank you! i would love to have any resources on how to improve my programming logic!
@Anna github.com/kamranahmedse/developer-roadmap hope this helps in some way
1

If I may, I have revised your code a bit.

The technique you need is event delegation: any click on a child element is also a click on its parent elements. we plas the event listener on the parent and we see on which child element it occurred. In your case, this only makes one event listerner for all your 'remove' buttons.

the other idea is not to ignore the DOM, it also keeps the list of tasks, you don't need to keep them in a table in memory, this is redundant.

here is the code: css is also helfull

class Todo 
  {
  constructor()
    {
    this.form  = document.getElementById('todo-form')
    this.liste = document.getElementById('todo-list')
    this.form.onsubmit = e => this.addTask(e)
    this.liste.onclick = e => this.delTask(e)
    }
  addTask(e)
    {
    e.preventDefault()
    if (this.form.task.value.trim() === '') return

    let li = document.createElement('li')
      , cb = document.createElement('input')
      , sp = document.createElement('span')
      , bt = document.createElement('button')
      ;
    cb.type        = 'checkbox'
    sp.textContent = this.form.task.value
    bt.textContent = 'Delete'
    bt.className   = 'remove'

    li.appendChild(cb)
    li.appendChild(sp)
    li.appendChild(bt)
    this.liste.appendChild(li)
    this.form.reset()
    }
  delTask(e)
    {
    if (!e.target.matches('button.remove')) return // reject others clicks
    e.target.closest('li').remove()
    }
  }

new Todo();
#todo-list li > span {
  display          : inline-block;
  background-color : whitesmoke;
  width            : 20em;
  }
#todo-list li input[type=checkbox]:checked + span {
  text-decoration : line-through;
  }
#todo-list li button.remove {
  font-size: .6em;
  }
<form id="todo-form">
  <input name="task">
  <button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>

As you can see this code is shorter. You can also use a IIFE unstead of a class, like that :

(function() // IIFE
  {
  let form  = document.getElementById('todo-form')
    , liste = document.getElementById('todo-list')
    ;
  form.onsubmit = e =>  // addTask
    {
    e.preventDefault()
    if (form.task.value.trim() === '') return

    let li = document.createElement('li')
      , cb = document.createElement('input')
      , sp = document.createElement('span')
      , bt = document.createElement('button')
      ;
    cb.type        = 'checkbox'
    sp.textContent = form.task.value
    bt.textContent = 'Delete'
    bt.className   = 'remove'

    li.appendChild(cb)
    li.appendChild(sp)
    li.appendChild(bt)
    liste.appendChild(li)
    form.reset()
    }
  liste.onclick = e =>  // delTask
    {
    if (!e.target.matches('button.remove')) return // reject others clicks
    e.target.closest('li').remove()
    }
  }
)()


btTaskList.onclick = e =>
  {
  let tasks = [...document.querySelectorAll('#todo-list li')].map(li=> 
    {
    let val = li.querySelector('span').textContent
      , chk = li.querySelector('input[type=checkbox]').checked
      ;
    return {val,chk}
    })
  console.clear()
  console.log( tasks )
  }
#todo-list li > span {
  display          : inline-block;
  background-color : whitesmoke;
  width            : 20em;
  }
#todo-list li input[type=checkbox]:checked + span {
  text-decoration : line-through;
  }
#todo-list li button.remove {
  font-size: .6em;
  }
<form id="todo-form">
  <input name="task">
  <button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>

  
<button id="btTaskList">get task list</button>

I also added a get task list button...

Comments

0

After marking an element you are changing only the stayle and atrribute of element. But after delete you recreate with render whole list and in render you are not rendereing checked parameter.

Your render should be:

  render() {
    this.ul.innerHTML = "";
    this.tasks.forEach((task) => {
      const li = document.createElement("li");
      const cb = document.createElement("input");
      cb.type = "checkbox";
      cb.addEventListener("click", (e) => {
        this.markTask(e);
      });
      li.appendChild(cb);

      // missed rendering checked
      if (task.checked) {
        li.style.textDecoration = "line-through";
        cb.checked = 'checked';
      }

      li.append(document.createTextNode(task));

      const btn = document.createElement("button");
      li.appendChild(btn);
      btn.textContent = "Delete";
      btn.classList.add("remove");
      btn.addEventListener("click", (e) => {
        this.deleteTask(e);
      });

      this.ul.appendChild(li);
    });
  }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.