Emil Avara
Emil Avara

Reputation: 35

How to edit span content with a onclick button function

I'm making a simple to-do application in Javascript. The user can enter a new todo list item. I also want the user to be able to edit an existing to-do item.

So I'm trying to make a button with a onClick function to edit a span. But it keeps returning a "span is null" error.

How can I fix this?

Here is my code so far:

function addToDo() {
    let input = document.getElementById("input").value + " ";
    var list = document.getElementById("list1");
    var li = document.createElement("li");
    var doneButton = document.createElement("button");
    doneButton.innerHTML = "Done";
    doneButton.onclick = moveToDo;
    doneButton.className = "move";
    var editButton = document.createElement("button");
    editButton.innerHTML = "Edit";
    editButton.onclick = editToDo;
    editButton.className = "edit";
    var deleteButton = document.createElement("button");
    deleteButton.innerHTML = "Delete";
    deleteButton.onclick = deleteToDo;
    deleteButton.className = "delete";
    var span = document.createElement("span");
    span.textContent = input;
    span.className = "node";
    li.appendChild(span);
    li.appendChild(doneButton);
    li.appendChild(editButton);
    li.appendChild(deleteButton);
    list.appendChild(li);
}

function moveToDo(x) {
    x.preventDefault();
    var button = x.target;
    var li = button.closest("li");
    button.remove();
    document.getElementById("list2").append(li);
}

function editToDo(b) {
    b.preventDefault();
    var button = b.target;
    var span = button.closest('span');
    var edit = prompt("Edit to-do item:");
    span.appendChild(edit);
}

function deleteToDo(x) {
    x.preventDefault();
    var button = x.target;
    var li = button.closest("li");
    li.remove();
}
<title>emil avara &nbsp;&mdash;&nbsp; inlämningsuppgift 2</title>

<h1 class="center">BIG BOI TO DO LIST APPLICATION</h1>
<!-- <a href=""><span id="reset" onclick="window.location.reload();">🔄</span></a> -->
<div id="input-wrapper">
  <input type="text" id="input" placeholder="Add a to-do"></input>
  <button id="add-button" onclick="addToDo()">Add</button>
</div>

<div id="wrapper">
  <div id="list1-div">
    <h1>TO-DO</h1>
    <ul id="list1">
    </ul>
  </div>
  <div id="list2-div">
    <h1>DONE</h1>
    <ul id="list2">
    </ul>
  </div>
</div>

Upvotes: 0

Views: 227

Answers (4)

mplungjan
mplungjan

Reputation: 177692

  • closest does not work on siblings
  • append needs a node. You have a string

This will work better

    var span = button.closest('li').querySelector('span');
    var edit = prompt("Edit to-do node:");
    span.textContent = edit;

function moveToDo() {}
function deleteToDo() {}
function addToDo() {
  let input = document.getElementById("input").value + " ";
  var list = document.getElementById("list1");
  var li = document.createElement("li");
  var doneButton = document.createElement("button");
  doneButton.innerHTML = "Done";
  doneButton.onclick = moveToDo;
  doneButton.className = "move";
  var editButton = document.createElement("button");
  editButton.innerHTML = "Edit";
  editButton.onclick = editToDo;
  editButton.className = "edit";
  var deleteButton = document.createElement("button");
  deleteButton.innerHTML = "Delete";
  deleteButton.onclick = deleteToDo;
  deleteButton.className = "delete";
  var span = document.createElement("span");
  span.textContent = input;
  span.className = "node";
  li.appendChild(span);
  li.appendChild(doneButton);
  li.appendChild(editButton);
  li.appendChild(deleteButton);
  list.appendChild(li);
}

function editToDo(b) {
  b.preventDefault();
  var button = b.target;
  var span = button.closest('li').querySelector('span');
  var edit = prompt("Edit to-do node:");
  span.textContent = edit;
}
<title>emil avara &nbsp;&mdash;&nbsp; inlämningsuppgift 2</title>

<h1 class="center">BIG BOI TO DO LIST APPLICATION</h1>
<!-- <a href=""><span id="reset" onclick="window.location.reload();">🔄</span></a> -->
<div id="input-wrapper">
  <input type="text" id="input" placeholder="Add a to-do"></input>
  <button id="add-button" onclick="addToDo()">Add</button>
</div>

<div id="wrapper">
  <div id="list1-div">
    <h1>TO-DO</h1>
    <ul id="list1">
    </ul>
  </div>
  <div id="list2-div">
    <h1>DONE</h1>
    <ul id="list2">
    </ul>
  </div>
</div>

Here is a more elegant version inspired by Mister Jojo's refactoring but with my own preferences

  • made the buttons type="button" and added class btn
  • delegated using the btn class
  • used a template instead of createElement

// buttons of type="button" do not need e.preventDefault();

window.addEventListener("load", function() {
  const list1 = document.getElementById("list1")
  const list2 = document.getElementById("list2")
  document.getElementById("wrapper").addEventListener("click", function(e) {
    const tgt = e.target;
    if (!tgt.classList.contains("btn")) return; // not a button
    const li = tgt.closest("li");
    if (tgt.classList.contains("move")) {
      tgt.remove();
      list2.append(li);
    } else if (tgt.classList.contains("edit")) {
      const span = li.querySelector('span');
      const edit = prompt("Edit to-do node:", span.textContent); // allow modification of existing text
      span.textContent = edit;
    } else if (tgt.classList.contains("delete")) {
      li.remove();
    }
  });

  document.getElementById("add-button").addEventListener("click", function() {
    let input = document.getElementById("input").value;
    let li = document.getElementById("liTemplate").content.cloneNode(true)
    if (input) {
      li.querySelector("span").textContent = input;
      list1.appendChild(li);
    }  
  })

})
<title>emil avara &nbsp;&mdash;&nbsp; inlämningsuppgift 2</title>

<h1 class="center">BIG BOI TO DO LIST APPLICATION</h1>
<div id="input-wrapper">
  <input type="text" id="input" placeholder="Add a to-do" />
  <button type="button" id="add-button">Add</button>
</div>

<div id="wrapper">
  <div id="list1-div">
    <h1>TO-DO</h1>
    <ul id="list1">
    </ul>
  </div>
  <div id="list2-div">
    <h1>DONE</h1>
    <ul id="list2">
    </ul>
  </div>
</div>

<template id="liTemplate">
<li>
  <span class="node"></span>
  <button type="button" class="move btn">Done</button>
  <button type="button" class="edit btn">Edit</button>
  <button type="button" class="delete btn">Delete</button>
</li>
</template>

Upvotes: 3

Mister Jojo
Mister Jojo

Reputation: 22265

the time to redo this code mplungjan had responded.
But since I made many improvements ...

const DomParser = new DOMParser()
  ,   eInput    = document.getElementById('input')
  ,   list_s    = document.querySelector('div#wrapper')
  ,   todoList  = document.querySelector('ul#list1')
  ,   doneList  = document.querySelector('ul#list2')
  ;
function addToDo() 
  {
  let newLI = `
    <li>
      <span class="node">${eInput.value} </span>
      <button class="move"   data-op="done"   > Done </button>
      <button class="edit"   data-op="edit"   > Edit </button>
      <button class="delete" data-op="delete" > Delete </button>
    </li>`;
  todoList.appendChild(  (DomParser.parseFromString( newLI, 'text/html')).body.firstChild );
  }
list_s.onclick = e =>  // use event delegation for All buttons
  {
  if (!e.target.matches('button[data-op]')) return  // reject others click on area
  let li = e.target.closest('li')
  switch (e.target.dataset.op)
    {
    case 'done':
      e.target.remove()
      doneList.append(li);
      break;
    case 'edit':
      let span  = li.querySelector('span')
        , txt   = prompt('Edit to-do item:', span.textContent)
        ;
      if (txt != null)  // cancel
        span.textContent =  txt
      break;
    case 'delete':
      li.remove()
      break;
    }
  }
div#wrapper span 
  { 
  display       : inline-block;
  width         : 12em;
  border-bottom : 1px solid lightgray;
  }
<h1 class="center">BIG BOI TO DO LIST APPLICATION</h1>
 
<div id="input-wrapper">
  <input type="text" id="input" placeholder="Add a to-do"></input>
  <button id="add-button" onclick="addToDo()">Add</button>
</div>

<div id="wrapper">
  <div id="list1-div">
    <h1>TO-DO</h1>
    <ul id="list1">
    </ul>
  </div>
  <div id="list2-div">
    <h1>DONE</h1>
    <ul id="list2">
    </ul>
  </div>
</div>

recognize that it is still more readable! ;)

Upvotes: 0

jacobkim
jacobkim

Reputation: 1090

Because closest couldn't find span selector. The reason is .closest() find only parents selectors. If button.closest('ul') or button.closest('li') will get the element. You should use other method to select a span.

Upvotes: 0

Thunderbolt Engineer
Thunderbolt Engineer

Reputation: 1563

You shouldn't use button.closest('span') to select the span element. closest() traverses the element's ancestors, not its siblings. In your current code, edit button and span elements are within same parent node, so result of closest() should be always null.

Upvotes: 0

Related Questions