简体   繁体   中英

How can I delete a JS created LI with a JS created button?

I am creating a TODO app in JavaScript. How can I understand how to link two created JavaScript items and remove them from the DOM?

TL;DR: The place where I keep getting stuck is navigating the DOM after the list item has been created. I have tried to use aa setAttribute to the created elements with an incriminating "id" tag, but I still can figure out how to iterate over the LI's to find which button was pressed.

Code:

        <div class="entry-field">
          <label class="task-input" for="task-input">Task:</label>
          <input type="text" class="task-field" placeholder="Enter Item"/>
          <button class="add-btn">ADD</button>
        </div>
        <section class="task-list">
          <h2>Task List</h2>
          <div class="list-wrap"></div>
            <ul class="item-log">
              <li class="list-item">
                <span class="item-text">this is the item</span>
                <button class="delete-btn">delete</button>
              </li>
            </ul>
          </div>
        </section>
      </main>

JavaScript:
let index = 0;

//Obtaining user text, creating a Span element, append span with user text:
function addItem() {
  let itemLog = document.querySelector(".item-log");
  let listItem = document.createElement("LI");
  let deleteBtn = document.createElement("BUTTON");
  let userEntry = document.querySelector(".task-field").value;
  let spanItem = document.createElement("SPAN");
  index++;
  //Creating LI "list-item" and append UL "itemLog"
  itemLog.appendChild(listItem);
  listItem.classList.add("list-item");

  listItem.setAttribute("id", index);
  //Create Span element, assign text from user input, append LI
  spanItem.innerHTML = userEntry;
  listItem.appendChild(spanItem);
  //Create deleteBtn and append to LI
  deleteBtn.innerHTML = "Delete";
  deleteBtn.classList.add("delete-btn");
  deleteBtn.setAttribute("onclick", "deleteItem()");
  deleteBtn.setAttribute("id", index);
  listItem.appendChild(deleteBtn);

  userEntry.value = "";
}

/*
2) Deleting item from list:
  -use event listener to trigger parent node
  -delete child
  */

function deleteItem() {
  let listItem = this.parentElement;
  listItem.parentElement.removeChild(listItem);
}

// Page event listeners
let addBtn = document.querySelector(".add-btn");

addBtn.addEventListener("click", addItem);

The issue in your example is what this refers to in your deleteItem function. If you console.log it in the deleteItem function you'll see that this refers to the window not the button pressed.

If instead of using setAttribute to set the onClick , you can add an event listener: deleteBtn.addEventListener("click", deleteItem);

Inside the deleteItem function you'll have access to the event object, as well as the target of that event, and your code will work as expected.

function deleteItem(event) {
  let listItem = event.target.parentElement;
  listItem.parentElement.removeChild(listItem);
}

On any mouse event listener, there will always be a MouseEvent parameter passed to the event handler. That MouseEvent object has the propertie target , which is the HTMLElement that was the target of the event. For example:

 let div = document.querySelector('div.button'); let toggleState = 0; let messages = ['Clicked!', 'Click me!']; let handler = function(event) { event.target.innerText = messages[toggleState]; toggleState ^= 1; } div.addEventListener("click", handler, false);
 <div class="button">Click me!</div>

In addition, every HTMLElement has a remove() method, which removes it from the DOM. For example:

document.querySelector('div').remove()

or:

let handler = function(event) {
  event.target.remove();
}
document.querySelector('div').addEventListener('click', handler, false);
  • Assign a click handler on Delete element creation.
  • Create an array of tasks to prepopulate

 const myTasks = [ "This is the item", "Well, this is Another item" ]; const EL_text = document.querySelector("#text"); const EL_add = document.querySelector("#add"); const EL_tasks = document.querySelector("#tasks"); function addTask(text) { // Determine if we have a text string argument (prepopulate) // otherwise grab the input value: text = typeof text === 'string' ? text : EL_text.value.trim(); // Trim your values! if (!text) return; // do nothing if no value! const EL_task = document.createElement('li'); const EL_delete = document.createElement('button'); EL_delete.textContent = "Delete"; EL_delete.addEventListener('click', () => EL_task.remove()); EL_task.insertAdjacentHTML('afterbegin', `<div>${text}</div>`); EL_task.appendChild(EL_delete); EL_tasks.appendChild(EL_task); EL_text.value = ''; // Clear current input text value } EL_add.addEventListener("click", addTask); // Prepopulate myTasks.forEach(addTask);
 #tasks { padding: 0; list-style: none; } #tasks li { display: flex; background: #eee; padding: 0.6em; margin-bottom: 0.5em; border-radius: 0.4em; } #tasks li div { flex: 1; }
 <label>Task:<input id="text" type="text" placeholder="Enter text"></label> <button id="add">ADD</button> <ul id="tasks"></ul>

JS Fiddle - Todo App

When creating applications where you must dynamically insert similar elements and track them by an ID system, I recommend a more organized approach to eliminate any confusion in your code.

For basic applications you can create custom classes that each have an element , template , ui , uiEventCallbacks , toggleUIEvents , and render methods. On each class, the element is going to be a main HTML element that contains the template markup. The render method parses the template text and assigns it to element.innerHTML . To instantiate the application, just create a new instance of the base class, render it, then append the element to body.

I created an Todo Application with your markup. There are TodoApp , Todos , and Todo classes. The TodoApp contains Todos , and Todos contain instances of Todo .

Todo

const Todo = class {
  constructor(settings) {
    this.settings = settings
  }
  get settings() { return this._settings }
  set settings(settings) { this._settings = settings }
  get element() {
    if(!this._element) {
      this._element = document.createElement('li')
      this._element.setAttribute('class', 'list-item')
      this._element.setAttribute('id', this.settings.id )
    }
    return this._element
  }
  get ui() { return {
    deleteButton: this.element.querySelectorAll(':scope > .delete-btn'),
  } }
  get uiEventCallbacks() { return {
    deleteButtonClick: (event) => {
      let customEvent = new CustomEvent('removeTodo', {
        bubbles: true,
        details: {
          id: this.settings.id
        }
      })
      this.element.dispatchEvent(
        customEvent
      )
    },
  } }
  remove() { 
    this.element.parentElement.removeChild(this.element)
    }
  template() { return `
    <span>${this.settings.value}</span>
    <button class="delete-btn">Delete</button>
  ` }
  toggleUIEvents() {
    [
      'removeEventListener',
      'addEventListener'

    ].forEach((eventMethod) => {
      this.ui.deleteButton.item(0)[eventMethod]('click', this.uiEventCallbacks.deleteButtonClick)
    })
  }
  render() {
    const template = this.template()
    this.element.innerHTML = template
    this.toggleUIEvents()
    return this
  }
}

Todos

const Todos = class {
    constructor() {}
  get element() {
    if(!this._element) {
      this._element = document.createElement('section')
      this._element.setAttribute('class', 'task-list')
    }
    return this._element
  }
  get ui() { return {
    itemLog: this.element.querySelectorAll('.item-log')
  } }
  get uiEventCallbacks() { return {
    removeTodo: (event) => {
      console.log(event)
      this.removeTodo()
    },
  } }
  get todos() {
    this._todos = this._todos || []
    return this._todos
  }
  template() { return `
    <h2>Task List</h2>
    <div class="list-wrap">
      <ul class="item-log"></ul>
    </div>
  ` }
  toggleUIEvents() {
    [
      'removeEventListener',
      'addEventListener'
    ].forEach((eventMethod) => {
      this.ui.itemLog.item(0)[eventMethod]('removeTodo', this.uiEventCallbacks.removeTodo)
    })
  }
  addTodo(data) {
    const todo = new Todo(data)
    this.todos.push(todo)
    this.ui.itemLog.item(0).appendChild(
      todo.render().element
    )
    return this
  }
  removeTodo(id) {
    let todoIndex = this.todos.reduce((_todoIndex, todo, todoIndex) => {
      if(todo.id === id) _todoIndex = todoIndex
      return _todoIndex
    }, -1)
    let todo = this.todos.splice(todoIndex, 1)[0]
    todo.remove()
    return this
  }
  render(data) {
    const template = this.template(data)
    this.element.innerHTML = template
    this.toggleUIEvents()
    return this
  }
}

Todo Application

const TodoApp = class {
  constructor() {}
  get element() {
    if(!this._element) {
      this._element = document.createElement('main')
    }
    return this._element
  }
  get ui() { return {
    addButton: this.element.querySelectorAll('.add-btn'),
    input: this.element.querySelectorAll('.task-field'),
  } }
  get uiEventCallbacks() { return {
    addTodo: (event) => {
      let taskFieldData = this.ui.input.item(0).value
      this.todos.addTodo({
        id: this.uuid(),
        value: taskFieldData,
      })
    },
  } }
  get todos() {
    this._todos = this._todos || new Todos()
    return this._todos
  }
  uuid() {
    var uuid = '', i, random
    for (i = 0; i < 32; i++) {
      random = Math.random() * 16 | 0
      if (i === 8 || i === 12 || i === 16 || i === 20) {
        uuid += "-"
      }
      uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16)
    }
    return uuid
  }
  template() { return `
    <div class="entry-field">
      <label class="task-input" for="task-input">Task:</label>
      <input type="text" class="task-field" placeholder="Enter Item"/>
      <button class="add-btn">ADD</button>
    </div>
  ` }
  toggleUIEvents() {
    [
      'removeEventListener',
      'addEventListener'
    ].forEach((eventMethod) => {
      this.ui.addButton.item(0)[eventMethod]('click', this.uiEventCallbacks.addTodo)
    })
  }
  render(data) {
    const template = this.template(data)
    this.element.innerHTML = template
    this.element.appendChild(this.todos.render().element)
    this.toggleUIEvents()
    return this
  }
}

Instantiate Todo Application

const todoApp = new TodoApp()
const body = document.querySelector('body')
body.insertAdjacentElement('afterbegin', todoApp.render().element)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM