I build my first ToDo App, and its almost done. But i meet some trouble with delete items from list. If i create new task and try delete it, its deleted only from DOM, but if i refresh page i can delete task from local storage and from DOM. Please, explain what i make wrong and how to fix that. Thank you for your attention.
<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>
const taskList = document.querySelector(".todo_tasks-wrapper");
const formTodo = document.querySelector(".control");
const inputTask = document.querySelector(".todo_input");
const taskKeeper = [];
let taskIdCounter = -1;
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();
}
});
taskList.addEventListener("click", (event) => {
for (let el of event.composedPath()) {
if (el.matches && el.matches("button.cancel-task")) {
let uniqueId = +(el.parentNode.getAttribute("id"));
for (let itemId of data) {
if(itemId.id === uniqueId) {
let getIndex = data.indexOf(itemId);
data.splice(getIndex, 1);
localStorage.setItem("tasks", JSON.stringify(data));
}
}
el.parentNode.remove();
}
}
});
if(data !== null) {
for (let item of data) {
updateHtml(item);
}
}
Don't use IDs for your tasks. When deleting an element, you can assign a click handler on creation of your "x" button.
Your taskIdCounter is useless since on page refresh you're starting again from 0. Get rid of it. You know now hot to remove Items from a list
Don't use a form if you don't need one.
Use type="button" on your BUTTONElements
Here's a remake of your code:
<ul id="tasks-list"></ul>
<div>
<input id="tasks-text" id="task" type="text" placeholder="Enter new task" maxlength="30">
<button id="tasks-add" type="button">+</button>
</div>
JavaScript
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const els = (sel, par) => (par || document).querySelectorAll(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Tasks:
const elList = el("#tasks-list");
const elText = el("#tasks-text");
const elAdd = el("#tasks-add");
const tasks = JSON.parse(localStorage.tasks ?? "[]");
const taskRemove = (taskObj, elTask) => {
const idx = tasks.indexOf(taskObj);
tasks.splice(idx, 1);
localStorage.tasks = JSON.stringify(tasks);
elTask && elTask.remove();
};
const taskAdd = (text) => {
const taskObj = { task: text };
tasks.push(taskObj);
localStorage.tasks = JSON.stringify(tasks);
taskInsert(taskObj);
};
const taskInsert = (taskObj) => {
const elTask = elNew("li", {
className: "item-task",
innerHTML: `<span>${taskObj.task}</span>`
});
const elRemove = elNew("button", {
type: "button",
innerHTML: "×",
onclick() {
taskRemove(taskObj, elTask);
}
});
elTask.append(elRemove);
elList.append(elTask);
};
elAdd.addEventListener("click", () => {
const info = elText.value.trim();
if (!info.length) return;
taskAdd(info);
elText.value = "";
elText.focus();
});
// Init
tasks.forEach(taskInsert);
As you can see, to get the index of the task to remove, simply pass the original object reference into const idx = tasks.indexOf(item);
Related
i have an array named todoList, all of my data is in it.
i saved them to localstorage like this:
https://codepen.io/arashkazerouni/pen/YzLxdoQ
const saveToLocal = (array) => {
window.localStorage.setItem("todo", JSON.stringify(array));
};
todoList = JSON.parse(window.localStorage.getItem("todo"));
i used this saveToLocal function after any change in todoList, and it works.
the only problem is when i refresh the page, all items gone.
but if i add new todo, all of them will shown again
and there is page script :
const input = document.querySelector("input");
const button = document.querySelector("button");
const todos = document.querySelector(".todos");
const alertRed = document.querySelector(".alert-red");
let todoList = [];
const saveToLocal = (array) => {
window.localStorage.setItem("todo", JSON.stringify(array));
};
todoList = JSON.parse(window.localStorage.getItem("todo"));
const addToDOM = () => {
// todos.innerHTML = "";
for (let i = 0; i < todoList.length; i++) {
const html = `
<div class="todo" id=${i}>
<p class="todo-text" >${todoList[i]}</p>
<i class="fa-solid fa-check" ></i>
<i class="fa-solid fa-trash"></i>
</div>
`;
todos.insertAdjacentHTML("beforeend", html);
}
};
// Add items to list
button.onclick = (e) => {
e.preventDefault();
// todos.innerHTML = "";
if (!todoList.includes(input.value) && input.value !== "") {
todoList.push(input.value);
saveToLocal(todoList);
}
// console.log(todoList);
addToDOM();
input.value = "";
};
// Handle Enter Press
document.onkeypress = (e) => {
if (e.key === "Enter") {
button.click();
}
};
todos.onclick = (e) => {
e.preventDefault();
const isCheck = e.target.classList.contains("fa-check");
const isTrash = e.target.classList.contains("fa-trash");
if (isCheck) e.target.previousElementSibling.classList.toggle("checked");
if (isTrash) {
const element = e.target.parentElement;
const elementID = parseInt(element.id);
element.classList.add("removed");
todoList = todoList.filter((element) => element !== todoList[elementID]);
saveToLocal(todoList);
setTimeout(() => {
addToDOM();
}, 300);
}
};
I am a beginner in javascript and I have li which is dynamically inserted by the user and is saved in an array of objects, it has its dynamic random id and I want when the user presses the element, it returns the id of the li and compare it with the movie id so I can find the index and then remove it from the array and remove the li. Thank you for your help in advance.
let n;
function deleteFilm() {
const len = listRoot.children.length;
for (let i = 0; i < len; i++) {
const element = listRoot.children[i]; // listRoot is ul
element.addEventListener('click', () => {
const idToRemove = +element.id;
index = movies.map((object) => object.movieId).indexOf(idToRemove);
console.log(index);
n = index;
});
}
console.log(n);
movies.splice(n, 1);
listRoot.children[n].remove();
if (movies[0] === undefined) {
entryText.style.display = 'block';
}
deleteModal.classList.remove('visible');
blackDrop.classList.remove('visible');
}
acceptButton.addEventListener('click', deleteFilm);
Don't assign all over again clicks (specially not inside a for loop).
Your basic logic could be simplified to this couple of lines:
const movies = [
{id:123, title:"Lorem"},
{id:456, title:"Ipsum"},
{id:789, title:"Dolor"},
];
// Retrieve a movie from array by its ID
const getMovie = (id) => movies.find(mov => mov.id === id);
// Remove a movie Object from array
const deleteMovie = (id) => movies.splice(movies.indexOf(getMovie(id)), 1);
// Task:
const movieId = 456; // The movie ID to delete
deleteMovie(movieId);
console.log(movies); // Test
Then, to delete elements from the DOM, knowing the ID was i.e: 456 all you need is to target the elements that have the data-movieid="456" like:
document.querySelectorAll(`[data-movieid="${movieId}"]`).forEach(el => el.remove());
Example:
// Utility functions:
const ELNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
const ELS = (sel, parent) => (parent || document).querySelectorAll(sel);
const EL = (sel, parent) => (parent || document).querySelector(sel);
// Task:
const getMovie = (id) => movies.find(mov => mov.id === id);
const deleteMovie = (id) => movies.splice(movies.indexOf(getMovie(id)), 1);
const createMovie = (movie) => {
const EL_li = ELNew("li", {
className: "list-item movie",
textContent: movie.title,
onclick() {
deleteMovie(movie.id);
EL_li.remove();
ELS(`[data-movieid="${movie.id}"]`).forEach(el => el.remove());
}
});
EL("#movies").append(EL_li);
};
const movies = [
{id:123, title:"Lorem"},
{id:456, title:"Ipsum"},
{id:789, title:"Dolor"},
];
movies.forEach(createMovie);
Click to delete a movie:
<ul id="movies" class="list"></ul>
<div data-movieid="456">If you click on Ipsum I will be removed too!</div>
I am pretty much new to Javascript and working on this simple ToDo app that uses local storage to persist data. However, the Delete function can only delete from local storage when refreshed the page. What could be causing this bug? I have attached my code below
I tried commenting on the e.preventDefault() on the form but the page kept on reloading when a task is submitted.
// Selectors
const ul = document.querySelector('.todo-list');
const todoContainer = document.querySelector('.todo-container');
const clearButton = document.createElement('button');
clearButton.classList.add('clear-button');
clearButton.textContent = 'Clear all Completed';
const form = document.querySelector('.form');
const taskInput = document.querySelector('.todo-input');
let todoTasks = JSON.parse(localStorage.getItem('todo')) || [];
let id = todoTasks.length + 1;
const createElement = ({ description, completed = true, index }) => {
const todoItem = document.createElement('li');
todoItem.classList.add('todo-list-item');
todoItem.setAttribute('id', index);
todoItem.innerHTML = `
<input type="checkbox" class="check" value="${completed}">
<button class="hidden" name="${index}"></button>
<span class="todo-item">${description}</span>
<button name='eclips'><i class="fas fa-ellipsis-v"></i></button>
<button name='delete'><i class="fas fa-trash"></i></button>
`;
ul.appendChild(todoItem);
todoContainer.appendChild(ul);
todoContainer.appendChild(clearButton);
};
// get each todo task
todoTasks.forEach(createElement);
// Function that add todo
const addTask = (description, completed, ind) => {
const input = taskInput.value;
todoTasks.push({
description: input,
completed: false,
index: ind,
});
localStorage.setItem('todo', JSON.stringify(todoTasks));
return { description, completed, ind };
};
const checkTodo = (e) => {
const lineText = e.target.nextElementSibling.nextElementSibling.nextElementSibling;
if (lineText.style.textDecoration === 'line-through') {
lineText.style.textDecoration = 'none';
} else {
lineText.style.textDecoration = 'line-through';
lineText.classList.toggle('completed');
}
};
// Function that delete todo
const handleDeleteAndCheck = (e) => {
const item = e.target;
if (e.target.classList[1] === 'fa-trash') {
const todo = item.parentElement.parentElement;
const targetId = item.parentElement.parentElement.id;
todoTasks = todoTasks.filter((task) => task.index !== +targetId);
localStorage.setItem('todo', JSON.stringify(todoTasks));
todo.remove();
}
if (e.target.classList === 'check') {
checkTodo();
}
};
// Function that Edit todo
const editTask = (e) => {
const item = e.target;
if (item.classList[0] === 'todo-item') {
item.contentEditable = true;
item.style.display = 'block';
}
};
const check = document.querySelectorAll('.fa-check-square');
const index = document.querySelectorAll('#index');
form.addEventListener('submit', (e) => {
e.preventDefault();
const input = taskInput.value;
const checkValue = check.value;
// const indexValue = index.value;
const newTask = addTask(input, checkValue, id);
createElement(newTask);
// location.reload();
taskInput.value = '';
id += 1;
});
ul.addEventListener('click', handleDeleteAndCheck);
ul.addEventListener('click', editTask);
Here please change the statement with this in your handleDeleteAndCheck() method. It will remove the local storage at that time.
localStorage.removeItem('todo');
I am trying to make a leads tracker. When I hit save input btn input value gets rendered. It has a small del button which is created in javascript. I had given it an id and first access it using document.getElementById() and then add an event listener to it. But it is giving an error
" Cannot read property 'addEventListener' of null "
ON clicking that btn I want to delete that li element
let myLeads = []
const inputEl = document.getElementById("input-el")
const inputBtn = document.getElementById("input-btn")
const ulEl = document.getElementById("ul-el")
const deleteBtn = document.getElementById("delete-btn")
const leadsFromLocalStorage = [] // JSON.parse(localStorage.getItem("myLeads") || '[]')
const tabBtn = document.getElementById("tab-btn")
const iconEl = document.getElementById("icon")
if (leadsFromLocalStorage) {
myLeads = leadsFromLocalStorage
render(myLeads)
}
tabBtn.addEventListener("click", function() {
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) {
myLeads.push(tabs[0].url)
// localStorage.setItem("myLeads", JSON.stringify(myLeads))
render(myLeads)
})
})
function render(leads) {
let listItems = ""
for (let i = 0; i < leads.length; i++) {
listItems += `
<li>
<a target='_blank' href='${leads[i]}'>
${leads[i]}
</a><i class="ri-close-circle-line" id="icon"></i>
</li>
`
}
ulEl.innerHTML = listItems
}
deleteBtn.addEventListener("dblclick", function() {
// localStorage.clear()
myLeads = []
render(myLeads)
})
inputBtn.addEventListener("click", function() {
if (inputEl.value) {
myLeads.push(inputEl.value)
inputEl.value = ""
// localStorage.setItem("myLeads", JSON.stringify(myLeads))
render(myLeads)
}
})
/*
iconEl.addEventListener("click", function() {
console.log("icon")
}) */
<input type="text" id="input-el">
<button id="input-btn">SAVE INPUT</button>
<button id="tab-btn">SAVE TAB</button>
<button id="delete-btn">DELETE ALL</button>
<ul id="ul-el"> </ul>
Your html is missing the icon you are trying to access when you tried to access it.
To delete stuff in a list, you need to delegate
I give the icons a class of delete instead of the invalid duplicate ID of icon
ulEl.addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.classList.contains("delete")) {
const task = tgt.closest("li");
const url = task.querySelector("a").getAttribute("href"); // important
const idx = myLeads.findIndex(item => item === url);
myLeads.splice(idx,1)
console.log(myLeads)
localStorage.setItem("myLeads", JSON.stringify(myLeads))
task.remove()
}
})
let myLeads = []
const inputEl = document.getElementById("input-el")
const inputBtn = document.getElementById("input-btn")
const ulEl = document.getElementById("ul-el")
const deleteBtn = document.getElementById("delete-btn")
const leadsFromLocalStorage = ["/Task1", "/Task2"] // JSON.parse(localStorage.getItem("myLeads") || '[]'); // Stacksnippets do not allow localStorage
const tabBtn = document.getElementById("tab-btn")
const iconEl = document.getElementById("icon")
if (leadsFromLocalStorage) {
myLeads = leadsFromLocalStorage
render(myLeads)
}
tabBtn.addEventListener("click", function() {
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) {
myLeads.push(tabs[0].url)
// localStorage.setItem("myLeads", JSON.stringify(myLeads))
render(myLeads)
})
})
function render(leads) {
let listItems = ""
for (let i = 0; i < leads.length; i++) {
listItems += `
<li>
<a target='_blank' href='${leads[i]}'>
${leads[i]}
</a><i class="ri-close-circle-line delete">X</i>
</li>
`
}
ulEl.innerHTML = listItems
}
deleteBtn.addEventListener("dblclick", function() {
// localStorage.clear()
myLeads = []
render(myLeads)
})
inputBtn.addEventListener("click", function() {
if (inputEl.value) {
myLeads.push(inputEl.value)
inputEl.value = ""
// localStorage.setItem("myLeads", JSON.stringify(myLeads))
render(myLeads)
}
})
ulEl.addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.classList.contains("delete")) {
const task = tgt.closest("li");
const url = task.querySelector("a").getAttribute("href"); // important
const idx = myLeads.findIndex(item => item === url);
myLeads.splice(idx,1)
console.log(myLeads)
//localStorage.setItem("myLeads", JSON.stringify(myLeads))
task.remove()
}
})
<input type="text" id="input-el">
<button id="input-btn">SAVE INPUT</button>
<button id="tab-btn">SAVE TAB</button>
<button id="delete-btn">DELETE ALL</button>
<ul id="ul-el">
</ul>
Logic
Add a delete button to the template.
Bind an onclick event to that.
Remove the node from the list and update the localstorage on click.
let todo = []
let inputEl = document.getElementById("input-el")
let inputBtn = document.getElementById("input-btn")
let ulEl = document.getElementById("ul-el")
let deletBtn = document.getElementById("delete-btn")
let savedList = JSON.parse(localStorage.getItem("todo"))
if (savedList) {
todo = savedList
createList()
}
function createList() {
let listItems = ""
for (i = 0; i < todo.length; i++) {
listItems +=
`<li>
${todo[i]}
<button onclick="deleteNode(${i})">delete</button>
</li>` //<------want to add the button here
}
ulEl.innerHTML = listItems
}
function deleteNode(index) {
todo.splice(index, 1);
localStorage.setItem("todo", JSON.stringify(todo))
createList();
}
inputBtn.addEventListener("click", function () {
todo.push(inputEl.value)
inputEl.value = "";
localStorage.setItem("todo", JSON.stringify(todo))
createList();
})
deletBtn.addEventListener("dblclick", function () {
localStorage.clear()
todo = []
createList()
})
<h1>TO-DO</h1>
<input type="text" id="input-el" /><br />
<button id="input-btn">Add</button>
<button id="delete-btn">Delete All</button>
<ul id="ul-el"></ul>
Making a todo app, but got stucked at deleting specific value in array, what am i doing wrong and how should i correct it? splice acts same as shift method.
Also is there any other way or data structure that can be used for todo app.
const form = document.querySelector("form");
const todoInput = document.querySelector(".todoInput");
const list = document.querySelector(".renderListHere");
const todoList = [];
form.addEventListener("submit", (event) => {
event.preventDefault();
const text = todoInput.value.trim();
if (text === "") {
console.log("enter something");
} else {
addTodo(text);
todoInput.value = "";
}
});
const addTodo = (text) => {
const todo = {
id: Date.now(),
text,
};
todoList.push(todo);
renderTodo(todo);
};
const renderTodo = ({ text, id }) => {
const li = document.createElement("li");
li.classList.add("todoListItems");
li.innerHTML = `
<span> ${text} </span>
<button id="${id}" class="del-btn"> x
</button>
`;
list.append(li);
};
list.addEventListener("click", (event) => {
if ((event.target.className = "del-btn")) {
const arr = todoList.filter(
(item) => item.id === parseInt(event.target.id)
);
todoList.splice(arr, 1);
}
});
I think you misunderstood the filter method.
The Array.filter() function returns a new array, so in your case, you could use:
todoList = todoList.filter(
(item) => item.id !== parseInt(event.target.id)
);
So you are filtering todoList with only the items with id different than event.target.id, and applying the result to the same todoList variable.
const arr = todoList.filter(
(item) => item.id !== parseInt(event.target.id)
);
}
its return new array
you do not need splice()