Anna
Anna

Reputation: 53

change array element value's style

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>

Upvotes: 0

Views: 87

Answers (3)

Mister Jojo
Mister Jojo

Reputation: 22265

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...

Upvotes: 1

Rogelio
Rogelio

Reputation: 1094

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.

Upvotes: 1

Darius
Darius

Reputation: 1180

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);
    });
  }

Upvotes: 0

Related Questions