ToDo List. Delete task button - javascript

I am trying to build todo list and I like to put on button(.cancel-task) action which remove exactly item which connected with that button, but when I try to put addEventListener I meet error like "its not a function". Please explain me how to make it with using attribute id which I add before for tasks and also how to remove this item from local storage. Thank you, for your attention.
const taskList = document.querySelector(".todo_tasks-wrapper");
const formTodo = document.querySelector(".control");
const inputTask = document.querySelector(".todo_input");
const btnDeleteTask = document.querySelectorAll(".cancel-task");
const taskKeeper = [];
let taskIdCounter = 0;
const data = JSON.parse(localStorage.getItem("tasks"));
const updateHtml = (taskObj) => {
const newLi = document.createElement("li");
newLi.innerHTML = `<li id="${taskObj.id}" class="item-task">
<span>${taskObj.task}</span>
<button class="cancel-task">
<img src="assets/todo-cancel.png" alt="Cancel">
</button>
</li>`;
taskList.append(newLi);
}
const newTask = (info) => {
taskIdCounter += 1;
const taskObj = {
task: info,
id: taskIdCounter,
};
taskKeeper.push(taskObj);
localStorage.setItem("tasks", JSON.stringify(taskKeeper));
updateHtml(taskObj);
};
formTodo.addEventListener("submit", event => {
event.preventDefault();
const info = inputTask.value.trim();
if(info.length !== 0) {
newTask(info);
inputTask.value = "";
inputTask.focus();
}
});
if(data !== null) {
for(let item of data) {
updateHtml(item);
}
}
<div class="todo_wrapper">
<ul class="todo_tasks-wrapper">
</ul>
<form class="control" action="">
<label class="todo_label-form" for="task">
<input class="todo_input" id="task" type="text" placeholder="Enter new task" maxlength="30">
<input class="todo_submit" type="submit" value="+">
</label>
</form>
</div>

You can use a onclick listener on the todo_tasks-wrapper element and check every element inside the event path to match the criterias.
Example:
todoTaskWrapper.addEventListener("click", (event) => {
for (let el of event.composedPath()) {
// window and document have no matches function, but they are included in the path
if (el.matches && el.matches("button.cancel-task")) {
console.log(el, "is the button clicked")
console.log(el.parentNode, "is the li element");
// remove it
el.parentNode.remove();
}
}
});
MDN: Event.composedPath()
MDN: Element.matches()

Related

How to setAtribute "disabled" for button

I am trying to have the complete button disabled when the edit button is pressed.
Here is the render list function
function renderList() {
// This resets the list innerHTML to the new list
el.list.innerHTML = taskList.map(function (data, i) {
return `<div class="task">
<div class="task-content">
<div class="task-set" data-id="${data.id}">
<input class="new-task-created" value="${
data.taskNew
}" readonly style="${data.textDecoration ? "text-decoration: line-through" : ""}"></input>
<input class="due-date" type="date" value="${
data.taskDate
}" readonly></input>
<input class="due-time" type="time" value="${
data.taskTime
}" readonly></input>
</div>
<div class="action-buttons">
<button onclick="editItem(event, ${i})" class="edit" data-id="${data.id}">Edit</button>
<button onclick="deleteItem(event, ${i})" class="delete" data-id="${data.id}">Delete</button>
<button onclick="completeItem(event, ${i})" class="complete" data-id="${data.id}">Complete</button>
</div>`
});
disable()
el.input.value = "";
}
This is my attempt trying to set the attribute to disable to the complete button when the edit button is being pressed.
function disable(){
let selectEdit = document.querySelector(".edit")
let selectComplete = document.querySelector(".complete")
if(selectEdit){
selectComplete.setAttribute("disabled", "true")
}
else{
selectComplete.removeAttribute("disabled")
}
// render list again because you've added a new entry
renderList();
}
Am I doing the set Attribute wrong?
Just an update to the code: The edit button is not working. Below is the complete function. I am not sure if this is causing a conflict with the disable function
//function that that edits tasks with date and time.
function editItem(event, i) {
const editEl = event.target.closest(".task");
let taskUpdate = editEl.querySelector(".new-task-created");
let dateUpdate = editEl.querySelector(".due-date");
let timeUpdate = editEl.querySelector(".due-time");
let editbtn = editEl.querySelector(".edit");
if (editbtn.innerHTML.toLowerCase() == "edit") {
taskUpdate.removeAttribute("readonly");
dateUpdate.removeAttribute("readonly");
timeUpdate.removeAttribute("readonly");
taskUpdate.focus();
editbtn.innerHTML = "Save";
} else {
taskUpdate.setAttribute("readonly", "readonly");
dateUpdate.setAttribute("readonly", "readonly");
timeUpdate.setAttribute("readonly", "readonly");
editbtn.innerHTML = "Edit";
taskList[i] = {
id: taskList[i].id,
taskNew: taskUpdate.value,
taskDate: dateUpdate.value,
taskTime: timeUpdate.value,
};
// store the list on localstorage because data changed
storeList();
// render list again because you've added a new entry
renderList();
}
}
I managed to find a solution.
I got the HTML element let selectComplete = document.querySelector(".complete") and added it to the editItem function and set the attribute of selectComplete to selectComplete.setAttribute("disabled", "")
The editItem function now looks like this:
//function that that edits tasks with date and time.
function editItem(event, i) {
const editEl = event.target.closest(".task");
let taskUpdate = editEl.querySelector(".new-task-created");
let dateUpdate = editEl.querySelector(".due-date");
let timeUpdate = editEl.querySelector(".due-time");
let editbtn = editEl.querySelector(".edit");
let selectComplete = document.querySelector(".complete")
if (editbtn.innerHTML.toLowerCase() == "edit") {
taskUpdate.removeAttribute("readonly");
dateUpdate.removeAttribute("readonly");
timeUpdate.removeAttribute("readonly");
taskUpdate.focus();
//Set the disable for the complete button.
selectComplete.setAttribute("disabled", "");
editbtn.innerHTML = "Save";
} else {
taskUpdate.setAttribute("readonly", "readonly");
dateUpdate.setAttribute("readonly", "readonly");
timeUpdate.setAttribute("readonly", "readonly");
selectComplete.removeAttribute('disabled');
editbtn.innerHTML = "Edit";
taskList[i] = {
id: taskList[i].id,
taskNew: taskUpdate.value,
taskDate: dateUpdate.value,
taskTime: timeUpdate.value,
};
// store the list on localstorage because data changed
storeList();
// render list again because you've added a new entry
renderList();
}
}

Javascript adding li element value to the empty list if it is not existing

I have a problem in Javascript.I am adding new list items to the 'ul' elements and this list is empty at first and I do not want to add same values twice. When I write the if statement I get the exception because my list is empty so the result return null.
How can I fix this this problem?
Thank you in advance...
Html Codes
<input type="text" id="the-filter" placeholder="Search For..." />
<div class="list-container">
<ul id="myList"></ul>
<button id="button">Click</button>
Javascript Codes
let newlist = document.querySelector("#myList");
const li = document.getElementsByClassName('list-group-item');
const button = document.getElementById("button");
const button.addEventListener('click' , listName);
const input = document.getElementById("the-filter");
function listName()
const inputVal = input.value;
for (i = 0; i < li.length; i++) {
if ((li[i].innerHTML.toLocaleLowerCase().includes(inputVal) && inputVal!="") ||
(li[i].innerHTML.toUpperCase().includes(inputVal) && inputVal!="")) {
let newItem = document.createElement("li");
li[i].classList.add("list-group-item");
let textnode = document.createTextNode(li[i].innerHTML.toLocaleLowerCase());
newItem.appendChild(textnode);
if((newlist.children[0].innerHTML.toLocaleLowerCase().includes(inputVal))){
newlist.insertBefore(newItem, newlist.childNodes[0]);
}
}
}
}
If I understood the task correct, you need to add items to the list by button click.
If same item exists (case insensitive), then nothing happens.
const list = document.querySelector("#myList");
const button = document.getElementById("button");
button.addEventListener("click", listName);
const input = document.getElementById("the-filter");
function listName() {
const inputVal = input.value;
const [...lis] = document.getElementsByClassName("list-group-item");
const same = lis.find((el) => el.textContent.toLowerCase() === inputVal.toLowerCase());
if (same) {
return;
}
let newItem = document.createElement("li");
newItem.classList.add("list-group-item");
newItem.textContent = inputVal;
list.appendChild(newItem)
}
<input type="text" id="the-filter" placeholder="Search For..." />
<div class="list-container">
<ul id="myList"></ul>
<button id="button">Click</button>
</div>
You're on the right track with event listeners and element creation, but your original code didn't quite seem to match your stated goal.
Here's a solution you might find useful, with some explanatory comments:
// Identifies some DOM elements
const
input = document.getElementById("my-input"),
newList = document.getElementById("my-list"),
items = document.getElementsByClassName('list-group-item'),
button = document.getElementById("my-button");
// Focuses input, and calls addItem on button-click
input.focus();
button.addEventListener('click', addItem);
// Defines the listener function
function addItem(){
// Trims whitespace and sets string to lowerCase
const inputTrimmedLower = input.value.trim().toLocaleLowerCase();
// Clears and refocuses input
input.value = "";
input.focus();
// Ignores empty input
if (!inputTrimmedLower) { return; }
// Ignores value if a list item matches it
for (const li of items) {
const liTrimmedLower = li.textContent.trim().toLocaleLowerCase();
if (liTrimmedLower === inputTrimmedLower) {
console.log(`${inputTrimmedLower} is already listed`);
return;
}
}
// If we got this far, we want to add the new item
let newItem = document.createElement("li");
newItem.classList.add("list-group-item");
newItem.append(inputTrimmedLower); // Keeps lowerCase, as your original code
newList.prepend(newItem); // More modern method than `insertBefore()`
}
<input id="my-input" />
<ul id="my-list"></ul>
<button id="my-button">Click</button>

How can I get my JavaScript delete function to fire more than once?

I'm building a todo app and I use a function to create a list item entered by the user.
There is an event listener added to the output section to listen for a delete button click for each item displayed. My problem is that the delete button is only working for one item and then it stops working.
In the console, it appears that the function is actually called every time I press the button, but the functionality only works for one click. Do I need to add all the list items into an array perhaps?
const todo = document.getElementById('todo');
const enter = document.getElementById('enter');
const output = document.getElementById('output');
enter.addEventListener('click', () => {
listItem(todo);
});
let createListItem;
var deleteBtn;
let checkBtn;
function listItem(todo) {
createListItem = document.createElement('li');
createListItem.innerText = todo.value;
todo.value = '';
output.appendChild(createListItem);
checkBtn = document.createElement('button');
deleteBtn = document.createElement('button');
checkBtn.innerText = 'check';
deleteBtn.innerText = 'delete';
createListItem.append(checkBtn);
createListItem.append(deleteBtn);
checkBtn.classList.add('checkBtn');
deleteBtn.classList.add('deleteBtn');
}
output.addEventListener('click', deleteFunc);
function deleteFunc() {
console.log('function called');
createListItem.remove();
}
<section class="controls">
<div>
<label for="todo">Enter a to-do</label>
<input type="text" name="todo" id="todo">
</div>
<span>
<button id="enter" class = "enter"><i class="fas fa-paper-plane"></i></button>
</span>
</section>
<section>
<ul id="output" class="output">
</ul>
</section>
You need to delegate and use relative addressing because your code only removes the LAST added LI
The variable createListItem pollutes the global scope. Add the keyword var or let in the listItem function too
function deleteFunc(e) {
console.log('function called');
const tgt = e.target;
if (e.target.innerText==="delete") tgt.closest("li").remove()
}
Added benefit from this delegation is that adding the functionality to the "check" button is just
if (e.target.innerText==="check") ...
I would recommend to use a class and testing
if (e.target.classList.contains("delete")
instead of the innerText - especially if you want to change language of the button
const todo = document.getElementById('todo');
const enter = document.getElementById('enter');
const output = document.getElementById('output');
enter.addEventListener('click', () => {
listItem(todo);
});
let createListItem;
var deleteBtn;
let checkBtn;
function listItem(todo) {
let createListItem = document.createElement('li'); // use let or var here
createListItem.innerText = todo.value;
todo.value = '';
output.appendChild(createListItem);
checkBtn = document.createElement('button');
deleteBtn = document.createElement('button');
checkBtn.innerText = 'check';
deleteBtn.innerText = 'delete';
createListItem.append(checkBtn);
createListItem.append(deleteBtn);
checkBtn.classList.add('checkBtn');
deleteBtn.classList.add('deleteBtn');
}
output.addEventListener('click', deleteFunc);
function deleteFunc(e) {
console.log('function called');
const tgt = e.target;
if (e.target.innerText==="delete") tgt.closest("li").remove()
}
<section class="controls">
<div>
<label for="todo">Enter a to-do</label>
<input type="text" name="todo" id="todo">
</div>
<span>
<button id="enter" class = "enter"><i class="fas fa-paper-plane"></i></button>
</span>
</section>
<section>
<ul id="output" class="output">
</ul>
</section>
Your createListItem variable is a global that gets set to the most-recently appended item, so the delete function will always delete the most-recent item. Once an element el has already been removed from the DOM tree, el.remove() is a no-op, so it only works once.
To fix, you can either use event delegation as in #mplungjan's answer or assign a unique id to each list item and pass that as a parameter to the function to determine what to delete.
Example of the second approach:
<ul>
<li id="item-0"></li>
<li id="item-1"></li>
<li id="item-2"></li>
</ul>
const deleteById = id => document.querySelector(`#item-${id}`).remove()
First though:
I think os because you are targeting the "ul" instead the "li" elements. Try adding the event listeners to each "li" element with a querySelectorAll() instead of targeting "output" directly.
When you add another item, you call listItem() which sets a new value to createListItem. This means, when you call deleteFunc(), createListItem.remove(); is only executed on the last element you have added. If this item was already removed, your out of luck as well.
function deleteFunc(e) {
console.log('function called');
const { target } = e; // get target
target.closest("li").remove()
}
Remove the listener on output. (This line: output.addEventListener('click', deleteFunc);)
And add the listener to each deleteBtn:
checkBtn.classList.add('checkBtn');
deleteBtn.classList.add('deleteBtn');
deleteBtn.addEventListener('click', deleteFunc);
This might help you:
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
Find the solution.
const todo = document.getElementById('todo');
const enter = document.getElementById('enter');
const output = document.getElementById('output');
enter.addEventListener('click', () => {
listItem(todo);
});
let createListItem;
var deleteBtn;
let checkBtn;
var index = 0;
function listItem(todo) {
createListItem = document.createElement('li');
createListItem.innerText = todo.value;
createListItem.id= 'li' + index;
todo.value = '';
output.appendChild(createListItem);
checkBtn = document.createElement('button');
deleteBtn = document.createElement('button');
checkBtn.innerText = 'check';
deleteBtn.innerText = 'delete';
deleteBtn.id = 'btn' + index;
createListItem.append(checkBtn);
createListItem.append(deleteBtn);
checkBtn.classList.add('checkBtn');
deleteBtn.classList.add('deleteBtn');
deleteBtn.onclick = function() { deleteFunc(this); };
index += 1;
}
//output.addEventListener('click', deleteFunc);
function deleteFunc(e) {
var rowId = e.id.replace('btn','');
var row = document.getElementById('li'+rowId);
console.log('function called' + rowId);
//alert(rowId);
if (row != null) {
row.remove();
}
//console.log('function called');
//createListItem.remove();
}
<section class="controls">
<div>
<label for="todo">Enter a to-do</label>
<input type="text" name="todo" id="todo">
</div>
<span>
<button id="enter" class = "enter"><i class="fas fa-paper-plane"></i></button>
</span>
</section>
<section>
<ul id="output" class="output">
</ul>
</section>

How to delete an item on click from dynamically generated content javascript

I am working on a very basic task application with vanilla javascript. I have an array and from that, I am rendering the todos dynamically. I want to delete a todo when the checkbox is clicked.
I tried to create a form with the id of the todo in the hidden input and on change event submit the form and delete the todo but I'm generating all of them dynamically. Don't you think it will be a lot of code?
Is there a better way to do this?
Thank you
const todos = ['Complete the todo app', 'Write a script', 'Record a video', 'Publish the video']
const renderTodos = function (todos) {
document.querySelector('#todos').innerHTML = ''
todos.forEach(function (todo, index) {
const divElem = document.createElement('div')
divElem.classList.add(`item`, `item-${index}`)
document.querySelector('#todos').appendChild(divElem)
const checkboxElem = document.createElement('input')
checkboxElem.type = 'checkbox'
checkboxElem.name = 'deleteTodo'
document.querySelector(`.item-${index}`).appendChild(checkboxElem)
const titleElem = document.createElement('p')
titleElem.textContent = todo
document.querySelector(`.item-${index}`).appendChild(titleElem)
})
}
renderTodos(todos)
document.querySelector('#add-todo').addEventListener('submit', function (e) {
e.preventDefault()
if (e.target.elements.newItem.value === '') {
return
}
todos.push(e.target.elements.newItem.value)
e.target.elements.newItem.value = ''
renderTodos(todos)
})
<div id="todos"></div>
<form class="item" id="add-todo" action="">
<input
type="text"
name="newItem"
placeholder="New Item"
autocomplete="off"
/>
<button type="submit" name="list">+</button>
</form>
use remove method :
const DomParser = new DOMParser()
, todoAll = document.getElementById('todos')
, todoForm = document.getElementById('add-todo')
, todos = ['Complete the todo app', 'Write a script', 'Record a video', 'Publish the video']
;
function todoAdding(todo, index)
{
let elmZ = `<div class="item item-${index}"><input type="checkbox" name="deleteTodo"><p>${todo}</p></div>`;
let inZ = DomParser.parseFromString( elmZ, 'text/html');
todoAll.appendChild( inZ.body.firstChild )
}
function renderTodos(todos)
{
document.querySelector('#todos').innerHTML = ''
todos.forEach( todoAdding )
}
todoForm.onclick = e =>
{
e.preventDefault()
if (todoForm.newItem.value === '') return
todos.push(todoForm.newItem.value)
todoForm.newItem.value = ''
renderTodos(todos)
}
todoAll.onclick = e =>
{
if (!e.target.matches('input[type=checkbox]')) return
let suppIndex = todos.findIndex(todo=>todo===e.target.parentNode.querySelector('p').textContent)
todos.splice(suppIndex,1)
e.target.parentNode.remove()
}
renderTodos(todos) // initial case
<div id="todos"></div>
<form class="item" id="add-todo" action="">
<input
type="text"
name="newItem"
placeholder="New Item"
autocomplete="off"
/>
<button type="submit" name="list">+</button>
</form>

How can i add or update products and prices in my list?

Pretty new to javascript, i want to add and update my list but it doesn't work.
I tried adding following code but it didn't work
Product.prototype.addProduct = function() {
var elol = document.getElementById("lijst");
var nieuwNaam = document.createElement("li");
nieuwNaam.textContent= this.naam;
elol.appendChild(nieuwNaam);
var nieuwPrijs = document.createElement("li");
nieuwPrijs.textContent= this.prijs;
elol.appendChild(nieuwPrijs);
}
Product.prototype.getProducten = function() {
return this.naam + "(€ " + this.prijs +")";
}
This is the document i want wish would work propperly
<!DOCTYPE html>
<html>
<head>
<script src="oefwinkel.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
winkel.addProduct("Potlood", 10);
VulLijst();
var elBtn = document.getElementById("btn");
elBtn.onclick = VoegProductToe;
});
function VulLijst() {
var elol = document.getElementById("lijst");
var producten = winkel.getProducten("</li><li>");
if (producten.length > 0) {
elol.innerHTML = "<li>" + producten + "</li>";
} else {
elol.innerHTML = "";
}
}
function VoegProductToe() {
var naam = document.getElementById("txtNaam").value;
var prijs = document.getElementById("txtPrijs").value;
winkel.addProduct(naam, prijs);
VulLijst();
}
function Product(naam, prijs) {
this.naam = naam;
this.prijs = prijs;
}
</script>
</head>
<body>
<div><label for="txtNaam">Naam:</label>
<input type="text" id="txtNaam" /></div>
<div><label for="txtPrijs">Prijs:</label>
<input type="number" id="txtPrijs" /></div>
<input type="button" id="btn" value="Toevoegen/Updaten" />
<ol id="lijst">
</ol>
</body>
</html>
There is no list output how do i correct this?..
I really can't find the solution, what did i miss.. huh?
You had a few things missing,
The HTML code.
The winkel object was undefined.
The VulLijst function was doing nothing... because addProduct was taking care of this already.
You are relying on the instance fields (this.naam and this.prijs), but what you want to do is pass in method parameters (external variables).
As for updating, you will need to store a list of Products, clear the child elements of lijst, and re-add the items that represent the list.
Note: One thing I am confused about is why you named your class—that represents a list—Product, when it should really be an Inventory that allows you to ADD Product objects.
Code
// Uncaught ReferenceError: winkel is not defined
var winkel = new Product();
function Product(naam, prijs) {
this.naam = naam;
this.prijs = prijs;
}
Product.prototype.addProduct = function(naam, prijs) {
naam = naam || this.naam; // Default or instance field
prijs = prijs || this.prijs; // Default or instance field
console.log(naam, prijs);
var elol = document.getElementById("lijst");
var nieuwNaam = document.createElement("li");
nieuwNaam.textContent = naam;
elol.appendChild(nieuwNaam);
var nieuwPrijs = document.createElement("li");
nieuwPrijs.textContent = prijs;
elol.appendChild(nieuwPrijs);
}
Product.prototype.getProducten = function(naam, prijs) {
naam = naam || this.naam; // Default or instance field
prijs = prijs || this.prijs; // Default or instance field
return naam + " (€ " + prijs + ")";
}
document.addEventListener("DOMContentLoaded", function() {
winkel.addProduct("Potlood", 10); // Why are you adding a product to a product?
var elBtn = document.getElementById("btn");
elBtn.onclick = VoegProductToe;
});
function VoegProductToe() {
var naam = document.getElementById("txtNaam").value;
var prijs = document.getElementById("txtPrijs").value;
winkel.addProduct(naam, prijs);
}
label { font-weight: bold; }
<label>Product</label>
<input id="txtNaam" value="Something" />
<input id="txtPrijs"value="1.99" />
<button id="btn">Add</button>
<br/>
<ul id="lijst"></ul>
Explained
I will openly admit, I'm not 100% sure what you're trying to do, I assume that's due to a language barrier my side though, I'm not sure of the natural language that you use on a daily basis, i.e. some of the variable names seem unclear to me, but that's my problem, not yours! :)
Anyway, I used some guess work to figure out what you're trying to achieve, and I assumed that you're simply trying to have some sort of product list where each product has a name and a price attached to it?
You want to be able to add a product to the list, based on two input fields, then some button to add to/update that product list.
I've broken up the code into a couple of simple functions, with this solution you can add/remove as many functions, classes or whatever you want. In this answer you can clearly see that there's some render function, and some onUpdate function, I just went with these generic names for the sake of simplicity.
If you have any issues with this solution, please provide as much feedback as possible! I hope that it's been of some help one way or another.
// A simple product list.
const ProductList = () => {
const products = [];
let el = null;
// What you wish to return, aka an object...
return {
addProduct: (name, price) => {
products.push({
name: name,
price: price
});
onUpdate();
render(el, products);
},
setRoot: root => {
el = root;
},
// removeFromList, etc...
};
};
// A simple on update function.
const onUpdate = () => {
console.clear();
console.log('Update!');
};
// A 'simple' render function.
const render = (el, products) => {
if (el == null) return;
const template = obj => `<li>${obj.name} €${obj.price}</li>`;
let html = '';
products.forEach(product => html += template(product));
el.innerHTML = html;
};
// A function to dispatch some event(s).
const dispatchEvents = products => {
const btn = document.getElementById("btn");
const price = document.getElementById("price");
const name = document.getElementById("name");
// Just an example.
const isValid = () => {
if (price.value != '' && name.value != '') return true;
return false;
};
// Handle the on click event.
btn.onclick = () => {
if (isValid()) {
products.addProduct(name.value, price.value);
name.value = '';
price.value = '';
}
};
};
// A simple dom ready function.
const ready = () => {
const products = ProductList();
products.setRoot(document.getElementById("productList"));
products.addProduct('Demo', 10);
products.addProduct('Other', 19.99);
dispatchEvents(products);
};
document.addEventListener("DOMContentLoaded", ready);
<div>
<label for="name">name:</label>
<input type="text" id="name" />
</div>
<div>
<label for="price">Prijs:</label>
<input type="number" id="price" />
</div>
<input type="button" id="btn" value="Update" />
<ol id="productList">
</ol>

Categories