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