Hi guys I am trying to save the value of a dynamically created form for my todo App. I am trying to make a new input called newInput. It is for an edit function I am trying to build. But when I add an event listener it says it can't read the value of null. So am I probably doing something very wrong if anyone can help or push me in the right direction I would appreciate it.
#Js
const clear = document.querySelector('.clear-btn');
const list = document.querySelector('.todo-list');
const input = document.getElementById('add-input');
const form = document.getElementById('todoform');
const newInput = document.querySelector('.edit-input ');
let todos = JSON.parse(localStorage.getItem('todos')) || [];
const LINE_THROUGH = 'lineThrough';
// first render
renderTodos();
form.addEventListener('submit', (e) => {
e.preventDefault();
saveTodo();
localStorage.setItem('todos', JSON.stringify(todos));
});
function saveTodo() {
const todoValue = input.value;
todos.push({
value: todoValue,
completed: false,
});
input.value = '';
renderTodos();
}
function renderTodos() {
list.innerHTML = '';
todos.forEach((todo, index) => {
list.innerHTML += `
<div class="todo" id=${index}>
<i class="fa ${
todo.checked ? 'solid fa-check' : 'regular fa-square'
}" data-action="check"
></i>
<p class= "text "${todo.checked ? LINE_THROUGH : ''} data-action="check">${
todo.value
}</p>
<input type="text" class="edit-input hidden" value=${todo.value} />
<i class='fas fa-ellipsis-v edit-task' data-action="edit">
</i>
<i class="fa-solid fa-trash-can trash-btnn hidden " data-action="delete">
</i>
</div>
`;
});
}
// click event listerner for all todos
list.addEventListener('click', (event) => {
const {
target
} = event;
const parentElement = target.parentNode;
if (parentElement.className !== 'todo') return;
const todo = parentElement;
const todoId = Number(todo.id);
// target action
const {
action
} = target.dataset;
action === 'check' && checkTodo(todoId);
// action === 'edit' && checkTodo(todoId);
action === 'delete' && checkTodo(todoId);
});
function checkTodo(todoId) {
todos = todos.map((todo, index) => ({
...todo,
checked: index === todoId ? !todo.checked : todo.checked,
}));
renderTodos();
}
document.body.addEventListener('click', (ev) => {
const el = event.target;
if (el.classList.contains('edit-task')) {
ev.preventDefault();
el.parentNode.querySelector('.trash-btnn').classList.toggle('hidden');
el.parentNode.querySelector('.fa-ellipsis-v').classList.toggle('hidden');
el.parentNode.querySelector('.text').classList.toggle('hidden');
el.parentNode.querySelector('.edit-input').classList.toggle('hidden');
}
if (el.classList.contains('trash-btnn')) {
ev.preventDefault();
todos.splice(el.parentNode.id, 1);
localStorage.setItem('todos', JSON.stringify(todos));
}
if (el.classList.contains('edit-input')) {
newInput.addEventListener('submit', (e) => {
ev.preventDefault();
saveTodo();
});
}
});
clear.addEventListener('click', () => {
todos = [];
localStorage.clear();
renderTodos();
});
document.body.addEventListener('keyup', (ev) => {
const el = event.target;
if (el.classList.contains('edit-input') && ev.keyCode === 13) {
ev.preventDefault();
todos[el.parentNode.id].value = el.value;
localStorage.setItem('todos', JSON.stringify(todos));
renderTodos();
}
});
Related
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);
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 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>
I am trying to remove an item of a ToDo app both from the UI and localStorage when its delete button is clicked. I can remove the item from the UI when e.target.calssName === 'delete'. However, I can't access the index of the array in localStorage and delete the same item from there as well. Here is the code I am working on. Any help would be highly appreciated.
const addTask = document.querySelector('#addItem');
const inputForm = document.querySelector('#inputForm');
const saveButton = document.querySelector('#saveButton');
const output = document.querySelector('#output');
// Add task button
const addItem = () => {
inputForm.style.display = 'block';
taskInput.focus();
};
// Add and update item to localstorage
const checkStorage = () => {
const input = document.querySelector('#taskInput');
if(input.value) {
const task = {
name: input.value
};
if(localStorage.getItem('tasks') === null) {
const tasks = [];
tasks.push(task)
localStorage.setItem('tasks', JSON.stringify(tasks));
} else {
const tasks = JSON.parse(localStorage.getItem('tasks'));
tasks.push(task);
localStorage.setItem('tasks', JSON.stringify(tasks));
}
} else {
return false;
};
};
// Display item on UI
const displayItem = () => {
const tasks = JSON.parse(localStorage.getItem('tasks'));
if(tasks !== null) {
const html = tasks.map((task, index) => {
return `
<ul class="item" id="${index}">
<li>${task.name}</li>
<button class="edit" id="${index}">Edit</button>
<button class="delete" id="${index}">Delete</button>
</ul>
`
}).join('');
output.innerHTML = html;
} else {
checkStorage();
};
};
// Save the task and display the item
const saveTask = (e) => {
e.preventDefault();
checkStorage();
displayItem();
inputForm.reset();
inputForm.style.display = 'none'
};
// Delete item from UI and localStorage
const deleteItem = (e) => {
const tasks = JSON.parse(localStorage.getItem('tasks'));
// Not sure how to loop throuh the tasks array and delete the item which is deleted from the UI
}
document.addEventListener('click', deleteItem);
document.addEventListener('onload', displayItem())
saveButton.addEventListener('click', saveTask);
addTask.addEventListener('click', addItem);
You can use Array#splice to delete an element from an array.
const deleteItem = (e) => {
if(e.target.classList.contains("delete")){
const tasks = JSON.parse(localStorage.getItem('tasks'));
tasks.splice(+(e.target.getAttribute("id")), 1);
localStorage.setItem('tasks', JSON.stringify(tasks));
}
}
many different ways to do this.best way is to user filter. More about filter
var tasks = JSON.parse(localStorage.getItem('tasks'));
tasks = tasks.filter(task=> "your condition");
localStorage.setItem('tasks', JSON.stringify(tasks));
Note: if you have the the id of the task it should go like this.
tasks = tasks.filter(task=> task.id != id );