Adding editing functionality to a todo list

Viewed 56

Looking to add an editing functionality to my to do list. I have set the button up but Im not very familiar with javascript yet and what i have tried doesn't seem to work. Is there a way to add it in the same way i have my check and delete button or am I missing something completely? Any help would be greatly appreciated

const todoInput = document.querySelector('.todo-input');
const todoBtn = document.querySelector('.todo-btn');
const todoList = document.querySelector('.todo-list');

todoBtn.addEventListener('click', createPost);
todoList.addEventListener('click', deletePost);
todoList.addEventListener('click', completePost);
todoList.addEventListener('click', editPost);

function createPost(e){
    event.preventDefault();
    //todo div
    const todoDiv = document.createElement("div");
    todoDiv.classList.add('todo');
    //create li
    const newTodo = document.createElement("li");
    newTodo.innerText = todoInput.value;
    newTodo.classList.add('todo-item');
    todoDiv.appendChild(newTodo);

    //completed button
    const completedBtn = document.createElement('button');
    completedBtn.innerHTML = '<i class="fas fa-check"></i>';
    completedBtn.classList.add("completed-btn");
    todoDiv.appendChild(completedBtn);

    const deleteBtn = document.createElement('button');
    deleteBtn.innerHTML = '<i class="fas fa-trash"></i>';
    deleteBtn.classList.add("trash-btn");
    todoDiv.appendChild(deleteBtn);

    const editBtn = document.createElement('button');
    editBtn.innerHTML = '<i class="fa-solid fa-pen-to-square"></i>';
    editBtn.classList.add("edit-btn");
    todoDiv.appendChild(editBtn);

    //Append to list
    todoList.appendChild(todoDiv);


    //Clear value
    todoInput.value = "";
}

function deletePost(e){
    const item = e.target;
    if(item.classList[0]=== 'trash-btn'){
        const todo = item.parentElement;
        todo.remove();
    }

    // if(item.classList[0]==='completed-btn'){
    //     const todo = item.parentElement;
    //     todo.classList.toggle('completed');
    // }
}

function completePost(e){
    const item = e.target;
    if(item.classList[0]==='completed-btn'){
        const todo = item.parentElement;
        todo.classList.toggle('completed');
    }
}
*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body{
    background-color: #A0CFD3;
    color:white;
    min-height:100vh;
}

header {
    font-size: 1.5rem;
}

header,form{
    min-height:20vh;
    display:flex;
    justify-content: center;
    align-items: center;
}

form input, form button{
    padding: 0.5rem;
    font-size: 2rem;
    border: none;
    background: white; 
}

form button{
    color:black;
    background: grey;
    cursor: pointer;
    transition-delay: all 0.3s ease;
}

form button:hover{
    background: black;
    color:white;
}

.todo-container {
    display:flex;
    position: relative;
    left:350px;
    justify-content: center;
    align-items: center;
    border: 1px solid black;
    height: 50vh;
    width: 80vh;
    background-color: grey;
}

.todo-list{
    min-width: 50%;
    list-style:none;
    padding:10px;
}

.todo {
    margin:0.5rem;
    background:white;
    color:black;
    display: flex;
    justify-content: space-between;
    align-items: center;
    transition: all 0.5s ease;
}

.todo li{
    flex: 1;
}

.trash-btn{
    background-color: red;
    color:white;
    padding:10px;
    border:none;
    cursor: pointer;
    font-size: 1rem;
}

.completed-btn{
    background-color: green;
    color: white;
    padding:10px;
    border:none;
    cursor: pointer;
    font-size: 1rem;
}

.edit-btn{
    background-color: blue;
    color: white;
    padding: 10px;
    border:none;
    cursor: pointer;
    font-size: 1rem;
}

todo-item{
    padding: 0rem 0.5rem;
}

.fa-trash, .fa-check{
    pointer-events: none;
}

.completed{
    text-decoration: line-through;
    opacity: 0.5;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Nezam's To Do List</title>
    <link rel="stylesheet" href="./master.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
    
    <header>
        <h1>To-Do List</h1>
    </header>
        <form action="">
            <input type="text" class="todo-input">
            <button class="todo-btn" type="submit">
                <i class="fa-solid fa-plus"></i>
            </button>
        </form>
        <div class="todo-container">
            <ul class="todo-list">
                <div class="todo">
                </div>
            </ul>
        </div>


    <script src="./app.js"></script>
</body>
</html>

3 Answers
  • Don't use just event.target unless you really know what you're doing. Always use Event.currentTarget - by finger memory. Or in your case, use target the proper way, and that's in combination with .closest()
  • Never use item.classList[0]=== 'trash-btn' classes can be swapped around.
  • <ul> accepts only <li>; don't use a <div> as immediate child
  • Use an <input> element to store the entered value

See the suggested solution using most of your code:

const todoInput = document.querySelector('.todo-input');
const todoBtn = document.querySelector('.todo-btn');
const todoList = document.querySelector('.todo-list');

todoBtn.addEventListener('click', createPost);
todoList.addEventListener('click', deletePost);
todoList.addEventListener('click', completePost);
todoList.addEventListener('click', editPost);

function createPost(e) {
  event.preventDefault();
  //todo div
  const todoDiv = document.createElement("div");
  todoDiv.classList.add('todo-item');

  //todo text input
  const todoText = document.createElement("input");
  todoText.value = todoInput.value;
  todoText.classList.add('todo-text');
  todoText.readOnly = true;
  todoDiv.appendChild(todoText);

  //completed button
  const completedBtn = document.createElement('button');
  completedBtn.innerHTML = '<i class="fas fa-check"></i>';
  completedBtn.classList.add("completed-btn");
  todoDiv.appendChild(completedBtn);

  const deleteBtn = document.createElement('button');
  deleteBtn.innerHTML = '<i class="fas fa-trash"></i>';
  deleteBtn.classList.add("trash-btn");
  todoDiv.appendChild(deleteBtn);

  const editBtn = document.createElement('button');
  editBtn.innerHTML = '<i class="fa-solid fa-pen-to-square"></i>';
  editBtn.classList.add("edit-btn");
  todoDiv.appendChild(editBtn);

  //Append to list
  todoList.appendChild(todoDiv);

  //Clear value
  todoInput.value = "";
}

function editPost(evt) {
  const item = evt.target.closest(".edit-btn");
  if (!item) return; // Do nothing
  const todo = item.closest(".todo-item");
  const input = todo.querySelector(".todo-text");
  input.readOnly = false;
  input.focus();
  todo.classList.toggle('editing');
}

function deletePost(evt) {
  const item = evt.target.closest(".trash-btn");
  if (!item) return; // Do nothing
  const todo = item.closest(".todo-item");
  todo.remove();
}

function completePost(evt) {
  const item = evt.target.closest(".completed-btn");
  if (!item) return; // Do nothing
  const todo = item.closest(".todo-item");
  todo.classList.toggle('completed');
}
* {
  margin: 0;
  box-sizing: border-box;
}

body {
  background-color: #A0CFD3;
  color: white;
}

header {
  font-size: 1.5rem;
}

header,
form {
  min-height: 20vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

form input,
form button {
  padding: 0.5rem;
  font-size: 2rem;
  border: none;
  background: white;
}

form button {
  color: black;
  background: grey;
  cursor: pointer;
  transition-delay: all 0.3s ease;
}

form button:hover {
  background: black;
  color: white;
}

.todo-container {
  display: flex;
  position: relative;
  justify-content: center;
  align-items: center;
  border: 1px solid black;
  background-color: grey;
}

.todo-list {
  min-width: 50%;
  padding: 10px;
  margin: 0.5rem;
  background: white;
  color: black;
  display: flex;
  flex-direction: column;
  transition: all 0.5s ease;
  flex: 1;
  gap: 0.6em;
}

.todo-item {
  display: flex;
}

.todo-text {
  flex: 1;
}

.trash-btn {
  background-color: red;
  color: white;
  padding: 10px;
  border: none;
  cursor: pointer;
  font-size: 1rem;
}

.completed-btn {
  background-color: green;
  color: white;
  padding: 10px;
  border: none;
  cursor: pointer;
  font-size: 1rem;
}

.edit-btn {
  background-color: blue;
  color: white;
  padding: 10px;
  border: none;
  cursor: pointer;
  font-size: 1rem;
}

.fa-trash,
.fa-check {
  pointer-events: none;
}

.completed {
  text-decoration: line-through;
  opacity: 0.5;
}

.completed .todo-item {
  text-decoration: line-through;
  color: red;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Nezam's To Do List</title>
  <link rel="stylesheet" href="./master.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" referrerpolicy="no-referrer"
  />
</head>

<body>

  <header>
    <h1>To-Do List</h1>
  </header>
  <form action="">
    <input type="text" class="todo-input">
    <button class="todo-btn" type="submit"><i class="fa-solid fa-plus"></i></button>
  </form>
  <div class="todo-container">
    <div class="todo-list"></div>
  </div>


  <script src="./app.js"></script>
</body>

</html>

As you can see from the example above, it makes not much UX sense to have a button to make the input text editable. I would just get rid of that button and remove the .readOnly = bool parts from the above code.
Also, you're not checking for if the main text input value is an empty string. And also you're missing some server-side database, or at least JS's localStorage to save the to-do list...
but I'll leave those to you.

You can add editing functionality in multiple ways the bellow is a simpler way of doing it, but you can also use modals.

function editPost(e){
    const target = e.target;
    if(!target.classList.includes('edit-btn')) return;
    
    const newTodo = prompt('Edit Todo');
    target.closest('.todo-item').innerText = newTodo;
}

I also think its better to use the .includes method to check if an element contains a certain class name.

Changes

<div> generated was placed as a direct child of <ul> which is invalid. The only element allowed to be a direct child of an <ul> is a <li>. The following HTML layout for a new list item is illustrated in Figure I.

Figure I - Changes to the HTML

<ul class="todo-list">
  <li class="todo-item"> <!--⬅️ li.todo-item is now the direct child of ul -->
    <input readonly="true" class="todo"> <!--⬅️ input.todo has the text -->
    <button class="completed-btn" type="button">
      <i class="fas fa-check"></i>
    </button>
    <button class="trash-btn" type="button"><!--↖️-->
      <i class="fas fa-trash"></i><!-- ↖️↙️ All 3 buttons are type="button"--> 
    </button>
    <button class="edit-btn" type="button">
      <i class="fa-solid fa-pen-to-square"></i>
    </button>
  </li>
</ul>

Next, the editPost() event handler was pretty much a copy of delete and completed. The function just toggles it's own .editMode class to show it's active and it toggles the readonly attribute of the <input class='todo'> so it can be edited.

Figure II

function editPost(e) {
  const item = e.target;
  if (item.classList[0] === 'edit-btn') {
    const todo = item.parentElement;
    item.classList.toggle('editMode');
    todo.firstElementChild.toggleAttribute('readonly');
  }
}

Also, there's a lot of CSS changes because it was impossible to edit and test the way it was styled.

const todoInput = document.querySelector('.todo-input');
const todoBtn = document.querySelector('.todo-btn');
const todoList = document.querySelector('.todo-list');

todoBtn.addEventListener('click', createPost);
todoList.addEventListener('click', deletePost);
todoList.addEventListener('click', completePost);
todoList.addEventListener('click', editPost);

function createPost(e) {
  event.preventDefault();

  const item = document.createElement("li");
  item.classList.add('todo-item');

  const task = document.createElement("input");
  task.setAttribute('readonly', true);
  task.value = todoInput.value;
  task.classList.add('todo');
  item.append(task);

  const completedBtn = document.createElement('button');
  completedBtn.innerHTML = '<i class="fas fa-check"></i>';
  completedBtn.classList.add("completed-btn");
  completedBtn.type = 'button';
  item.appendChild(completedBtn);

  const deleteBtn = document.createElement('button');
  deleteBtn.innerHTML = '<i class="fas fa-trash"></i>';
  deleteBtn.classList.add("trash-btn");
  deleteBtn.type = 'button';
  item.appendChild(deleteBtn);

  const editBtn = document.createElement('button');
  editBtn.innerHTML = '<i class="fa-solid fa-pen-to-square"></i>';
  editBtn.classList.add("edit-btn");
  editBtn.type = 'button';
  item.appendChild(editBtn);

  todoList.appendChild(item);
  todoInput.value = "";
}

function deletePost(e) {
  const item = e.target;
  if (item.classList[0] === 'trash-btn') {
    const todo = item.parentElement;
    todo.remove();
  }
}

function completePost(e) {
  const item = e.target;
  if (item.classList[0] === 'completed-btn') {
    const todo = item.parentElement;
    todo.classList.toggle('completed');
  }
}

function editPost(e) {
  const item = e.target;
  if (item.classList[0] === 'edit-btn') {
    const todo = item.parentElement;
    item.classList.toggle('editMode');
    todo.firstElementChild.toggleAttribute('readonly');
  }
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background-color: #A0CFD3;
  color: white;
  min-height: 100vh;
}

header {
  font-size: 1.5rem;
  text-align: center;
}

form {
  min-height: 100vh;
  display: flex;
  flex-flow: column nowrap;
  align-items: center;
  padding: 20px;
}

form input,
form button {
  padding: 0.5rem;
  font-size: 2rem;
  border: none;
  background: white;
}

form button {
  color: black;
  background: grey;
  cursor: pointer;
  transition-delay: all 0.3s ease;
}

form button:hover {
  background: black;
  color: white;
}

.todo-container {
  display: flex;
  flex-flow: column nowrap;
  justify-content: center;
  border: 1px solid black;
  width: min-content;
  max-width: calc(100% - 30px);
  min-height: 50vh;
  background-color: grey;
}

.todo-list {
  min-width: 400px;
  list-style: none;
  padding: 10px;
  margin: 10px auto;
}

.todo-item {
  margin: 0.5rem;
  background: white;
  color: black;
  display: flex;
  align-items: center;
  transition: all 0.5s ease;
  width: 90%
}

.todo {
  height: 2.25rem;
  width: 20rem;
}

.trash-btn {
  background-color: red;
  color: white;
  padding: 10px;
  border: none;
  cursor: pointer;
  font-size: 1rem;
}

.completed-btn {
  background-color: green;
  color: white;
  padding: 10px;
  border: none;
  cursor: pointer;
  font-size: 1rem;
}

.edit-btn {
  background-color: blue;
  color: white;
  padding: 10px;
  border: none;
  cursor: pointer;
  font-size: 1rem;
}

.fa-trash,
.fa-check {
  pointer-events: none;
}

.completed {
  text-decoration: line-through;
  opacity: 0.5;
}

.box {
  margin-bottom: 12px
}

.editMode {
  background: cyan;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Nezam's To Do List</title>
  <link rel="stylesheet" href="./master.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" referrerpolicy="no-referrer"
  />
</head>

<body>

  <header>
    <h1>To-Do List</h1>
  </header>
  <form action="">

    <label class='box'>
  <input type="text" class="todo-input">
    <button class="todo-btn" type="submit">
                <i class="fa-solid fa-plus"></i>
            </button>
            </label>

    <div class="todo-container">
      <ul class="todo-list">

      </ul>
    </div>

  </form>
  <script src="./app.js"></script>
</body>

</html>

Related