Implement local storage for todo list - javascript

Having an issue with implementing Local Storage into my To-do list. I watched a ton of videos, understood the syntax etc. but I cannot figure out how to implement it into my code. What am trying to achieve is for the To-do app to save the tasks(make them stay at the site after reload) and store them inside the local storage.
window.addEventListener('load', () => {
const form = document.querySelector("#new-task-form");
const input = document.querySelector("#new-task-input");
const list_el = document.querySelector("#tasks");
const storeTasks = [];
localStorage.setItem('Stored Task', JSON.stringify(storeTasks));
storeTasks.append(input.value);
localStorage.getItem('Stored Task');
console.log(storeTasks);
/*localStorage.setItem('tasks', "list_el");
localStorage.getItem('list_el');*/
console.log(list_el);
form.addEventListener('submit', (e) => {
e.preventDefault();
const task = input.value;
if (!task) {
alert("Add onto the bucketlist!");
return "";
}
const task_el = document.createElement('div');
task_el.classList.add('task');
const task_content_el = document.createElement('div');
task_content_el.classList.add('content');
task_el.appendChild(task_content_el);
const task_input_el = document.createElement('input');
task_input_el.classList.add('text');
task_input_el.type = 'text';
task_input_el.value = task;
task_input_el.setAttribute('readonly', 'readonly');
task_content_el.appendChild(task_input_el);
const task_actions_el = document.createElement('div');
task_actions_el.classList.add('actions');
const task_edit_el = document.createElement('button');
task_edit_el.classList.add('edit');
task_edit_el.innerText = 'Edit';
const task_delete_el = document.createElement('button');
task_delete_el.classList.add('delete');
task_delete_el.innerText = 'Delete';
task_actions_el.appendChild(task_edit_el);
task_actions_el.appendChild(task_delete_el);
task_el.appendChild(task_actions_el);
list_el.appendChild(task_el);
input.value = '';
task_edit_el.addEventListener('click', (e) => {
if (task_edit_el.innerText.toLowerCase() == "edit") {
task_edit_el.innerText = "Save";
task_input_el.removeAttribute("readonly");
task_input_el.focus();
} else {
task_edit_el.innerText = "Edit";
task_input_el.setAttribute("readonly", "readonly");
}
});
task_delete_el.addEventListener('click', (e) => {
list_el.removeChild(task_el);
});
});
});

It appears the problem is that you save the list to the local storage before you add items to it.
And then try to change it.
LocalStorage doesn't "update" when you change the variable you saved. It updates when you tell it to update, so you need to call localStorage.setItem whenever you add new items.
And remember that localStorage saves a String, so you gonna need to JSON.parse your result from localStorage.getItem to turn it back into JS object.

Related

How do I updated an object's value inside an array through user input?

How do I implement/execute where once I click the edit button it will allow the user to input a value then once submitted, the text in the li will render the updated value?
JS code block is written below:
P.S. You can ignore the other functions that are irrelevant.
P.S. I know, the edittask is incomplete but I'm not exactly sure how to implement the functionality I mentioned above.
const alertMsg = document.querySelector('.alert-message');
const inputForm = document.querySelector('.input-section');
const todoInput = document.querySelector('.todo-input');
const addBtn = document.querySelector('.add-btn');
const taskActions = document.querySelector('.task-actions');
const todosList = document.querySelector('.todos-list');
const deleteAllBtn = document.querySelector('.delete-all-btn');
const savedTodos = localStorage.getItem('todos');
let todos = [];
function displayTodos(newTodoObj){
const li = document.createElement('li');
li.id = newTodoObj.id;
li.className = 'task-container'
const task = document.createElement('span');
const checkBtn = document.createElement('button')
const editBtn = document.createElement('button')
const deleteBtn = document.createElement('button')
task.innerText = newTodoObj.text;
checkBtn.innerText = 'Check'
editBtn.innerText = 'Edit';
deleteBtn.innerText = 'Del';
checkBtn.addEventListener('click', (event) => {
const task = event.target.parentElement;
console.log(task);
task.classList.toggle('completed');
})
editBtn.addEventListener('click', editTask)
deleteBtn.addEventListener('click', deleteTask)
li.appendChild(task);
li.appendChild(checkBtn);
li.appendChild(editBtn);
li.appendChild(deleteBtn);
todosList.appendChild(li);
}
function editTask(event){
const li = event.target.parentElement.children[0].innerText;
todoInput.value = li;
}
function deleteTask(event){
const li = event.target.parentElement;
li.remove();
todos = todos.filter((todo) => todo.id !== parseInt(li.id));
saveTodos();
}
function handleTodoSubmit(event){
event.preventDefault();
const newTodo = todoInput.value;
todoInput.value = '';
const newTodoObj = {
text: newTodo,
id: Date.now(),
checked: false
};
todos.push(newTodoObj);
displayTodos(newTodoObj);
saveTodos();
}
function saveTodos(){
localStorage.setItem('todos', JSON.stringify(todos));
}
inputForm.addEventListener('submit', handleTodoSubmit);
if(savedTodos !== null){
const parsedTodos = JSON.parse(savedTodos);
parsedTodos.forEach(displayTodos);
}
window.addEventListener('beforeunload', saveTodos);
This code adds an input element to the DOM when the "Edit" button is clicked, sets its value to the text of the task, and adds an event listener that listens for the "Enter" key. When the "Enter" key is pressed, the code updates the text of the task and replaces the input element with a span element containing the updated text. It also updates the todos array and saves the updated array to local storage.
function editTask(event){
const li = event.target.parentElement;
const task = li.children[0];
const input = document.createElement('input');
input.value = task.innerText;
li.replaceChild(input, task);
input.focus();
input.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
const newTask = document.createElement('span');
newTask.innerText = input.value;
li.replaceChild(newTask, input);
const todoIndex = todos.findIndex((todo) => todo.id === parseInt(li.id));
todos[todoIndex].text = newTask.innerText;
saveTodos();
}
});
}
You can use this code in your existing JavaScript file by replacing the current editTask function with this one.
I don't know if I understood your question very well, but I hope it will at least help guide you. Or maybe it is the complete solution. Best wishes!

JavaScript Class Constructor and DOM (Updated)

I am new to programming, and I don't quite grasp the idea of utilizing class constructor in real life. For instance, let's just say I am trying to create a DOM event handler so I can take user input and push it into CreateTodoList.todos array.
class CreateTodoList {
constructor(list) {
this.todoList = list;
this.todos = [];
}
Then let's just assume that I have built addTodo() function which takes text parameter where an user enters her/his todo.
addTodo(text) {
this.todos.push(text);
this.todoList.appendChild(CreateTodoList.addtoList(text));
}
Here, addtoList creates DOM element that takes value of the user input.
This addTodo function, then pushes the text parameter into the array I made in constructor, while also calling addtoList that makes the DOM element.
Now, let's say I click on "add" button where it takes user input value.
I will build an event handler that responds to click which will add user input to the todoList.
CreateTodoList.eventHandler('click', (e) => {
let userText.todos = document.querySelector(#userInput).value;
addTodo(userText);
})
I am trying to build an eventHandler here, so I can add user input to todoList, and have implemented this several times, but had no luck but receiving reference error.
Below is my full code.
/** #format */
const add = document.querySelector('#btn_add');
let addInput = document.querySelector('#add');
const form = document.querySelector('#form');
class CreateTodoList {
constructor(list) {
this.todoList = list;
this.todos = [];
}
addtoList(text) {
let checkboxEl = document.createElement('span');
checkboxEl.classList.add('round');
let checkboxEl2 = document.createElement('input');
checkboxEl2.id = 'checkbox';
checkboxEl2.type = 'checkbox';
let checkboxEl3 = document.createElement('label');
checkboxEl3.htmlFor = 'checkbox';
checkboxEl.appendChild(checkboxEl2);
checkboxEl.appendChild(checkboxEl3);
let todoTextEl = document.createElement('input');
todoTextEl.value = text;
todoTextEl.disabled = true;
todoTextEl.classList.add('edit_input');
todoTextEl.id = 'edit_input';
todoTextEl.type = 'text';
todoTextEl.name = 'edit_input';
let todoTextEl2 = document.createElement('label');
todoTextEl2.htmlFor = 'edit_input';
let editEl = document.createElement('i');
editEl.classList.add('far');
editEl.classList.add('fa-edit');
let deleteEl = document.createElement('i');
deleteEl.classList.add('far');
deleteEl.classList.add('fa-trash-alt');
let dateEl = document.createElement('small');
dateEl.textContent = timeago.format(new Date());
let liEl = document.createElement('li');
liEl.appendChild(checkboxEl);
liEl.appendChild(todoTextEl);
liEl.appendChild(todoTextEl2);
liEl.appendChild(editEl);
liEl.appendChild(deleteEl);
liEl.appendChild(dateEl);
let list = document.querySelector('ul');
list.appendChild(li);
return liEl;
}
removeFromList(text) {
let list = document.querySelector('ul');
let childs = Array.from(list.childNodes);
let removable = child.find((i) => i.innerText === text);
return item;
}
//todos 배열(todo 데이터에) text를 추가한다.
//todoList 에 liEL(리스트 엘레먼트) 를 append 한다.
addTodo(text) {
this.todos.push(text);
this.todoList.appendChild(CreateTodoList.addtoList(text));
}
removeTodo(text) {
let removed = this.todos.filter((el) => el !== text);
todo.todoList.removeChild(CreateTodoList.removeFromList(text));
this.todos = removed;
}
get getList() {
return this.todos;
}
}
class Handlers {}

How would I use local storage for a to do list?

I am being asked to have a to do list and save each task (that the user supplies as well as original) through local storage. My teacher did a very simple demo on something completely different and I spent a few hours trying to figure it out. When I looked at the solution, I honestly cannot figure it out. It looks really complicated, and I don't even know where to start. If anyone can give me any hints, that would be awesome!
My code:
let ul = document.querySelector('ul');
let newItem = document.querySelector('input[type=text]');
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');
function output() {
let newTodo = document.createElement('li');
newTodo.innerText = newItem.value;
newTodo.classList.add('todo');
let ulAppend = ul.append(newTodo);
ul.append(newTodo);
let checkboxAppend = newTodo.append(checkbox);
newTodo.append(checkbox);
newItem.value = '';
}
let button = document.querySelector('.btn');
button.addEventListener('click', output);
ul.addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
e.target.remove();
} else if (e.target.tagName === 'INPUT') {
e.target.parentElement.classList.toggle('finished');
}
});
My teacher's code/local storage solution:
const todoForm = document.getElementById("newTodoForm");
const todoList = document.getElementById("todoList");
// retrieve from localStorage
const savedTodos = JSON.parse(localStorage.getItem("todos")) || [];
for (let i = 0; i < savedTodos.length; i++) {
let newTodo = document.createElement("li");
newTodo.innerText = savedTodos[i].task;
newTodo.isCompleted = savedTodos[i].isCompleted ? true : false;
if (newTodo.isCompleted) {
newTodo.style.textDecoration = "line-through";
}
todoList.appendChild(newTodo);
}
todoForm.addEventListener("submit", function(event) {
event.preventDefault();
let newTodo = document.createElement("li");
let taskValue = document.getElementById("task").value;
newTodo.innerText = taskValue;
newTodo.isCompleted = false;
todoForm.reset();
todoList.appendChild(newTodo);
// save to localStorage
savedTodos.push({ task: newTodo.innerText, isCompleted: false });
localStorage.setItem("todos", JSON.stringify(savedTodos));
});
todoList.addEventListener("click", function(event) {
let clickedListItem = event.target;
if (!clickedListItem.isCompleted) {
clickedListItem.style.textDecoration = "line-through";
clickedListItem.isCompleted = true;
} else {
clickedListItem.style.textDecoration = "none";
clickedListItem.isCompleted = false;
}
// breaks for duplicates - another option is to have dynamic IDs
for (let i = 0; i < savedTodos.length; i++) {
if (savedTodos[i].task === clickedListItem.innerText) {
savedTodos[i].isCompleted = clickedListItem.isCompleted;
localStorage.setItem("todos", JSON.stringify(savedTodos));
}
}
});
Even though my code is more simpler (at least from what I can tell), it works exactly as his code does.
Local storage saves a JSON object to the user's computer. You should create an array of todos, append that array with every new todo, then set that item to local storage.
let ul = document.querySelector('ul');
const savedTodos = JSON.parse(localStorage.getItem("todos")) || []; // Retrieves local storage todo OR creates empty array if none exist
let newItem = document.querySelector('input[type=text]');
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');
function output() {
let newTodo = document.createElement('li');
newTodo.innerText = newItem.value;
newTodo.classList.add('todo');
ul.append(newTodo);
newTodo.append(checkbox);
savedTodos.push({task: newItem.value, isCompleted: false}); // Appends the new todo to array
localStorage.setItem("todos", JSON.stringify(savedTodos)); //Converts object to string and stores in local storage
newItem.value = '';
}
I've annotated the solution you posted with some comments to help you step through it.
// Retrieve elements and store them in variables
const todoForm = document.getElementById("newTodoForm");
const todoList = document.getElementById("todoList");
// Get data stored in localStorage under the key "todos".
// The data type will be a string (local storage can only store strings).
// JSON is a global object that contains methods for working with data represented as strings.
// The `||` syntax is an OR operator and is used here to set an empty array as a fallback in case `localStorage` is empty
const savedTodos = JSON.parse(localStorage.getItem("todos")) || [];
// Create a loop the same length as the list of todos
for (let i = 0; i < savedTodos.length; i++) {
// Create an <li> element in memory (does not appear in the document yet)
let newTodo = document.createElement("li");
// Set the inner text of that new li with the contents from local storage.
// The savedTodos[i] is accessing data in the localStorage array.
// The [i] is a different number each loop.
// The `.task` is accessing 'task' property on the object in the array.
newTodo.innerText = savedTodos[i].task;
// Create a new property on the element called `isCompleted` and assign a boolean value.
// This is only accessible in code and will not show up when appending to the DOM.
newTodo.isCompleted = savedTodos[i].isCompleted ? true : false;
// Check the value we just set.
if (newTodo.isCompleted) {
// Create a style for the element if it is done (strike it out)
newTodo.style.textDecoration = "line-through";
}
// Actually append the new element to the document (this will make it visible)
todoList.appendChild(newTodo);
}
// `addEventListener` is a function that registers some actions to take when an event occurs.
// The following tells the browser - whenever a form is submitted, run this function.
todoForm.addEventListener("submit", function(event) {
// Don't try to send the form data to a server. Stops page reloading.
event.preventDefault();
// Create a <li> element in memory (not yet visible in the document)
let newTodo = document.createElement("li");
// Find element in the document (probably a input element?) and access the text value.
let taskValue = document.getElementById("task").value;
// Set the text of the <li>
newTodo.innerText = taskValue;
// Set a property on the <li> call `isCompleted`
newTodo.isCompleted = false;
// Empty out all the input fields in the form
todoForm.reset();
// Make the new <li> visible in the document by attaching it to the list
todoList.appendChild(newTodo);
// `push` adds a new element to the `savedTodos` array. In this case, an object with 2 properties.
savedTodos.push({ task: newTodo.innerText, isCompleted: false });
// Overwrite the `todos` key in local storage with the updated array.
// Use the JSON global object to turn an array into a string version of the data
// eg [1,2,3] becomes "[1,2,3]"
localStorage.setItem("todos", JSON.stringify(savedTodos));
});
// This tells the browser - whenever the todoList is clicked, run this function.
// The browser will call the your function with an object that has data about the event.
todoList.addEventListener("click", function(event) {
// the `target` of the event is the element that was clicked.
let clickedListItem = event.target;
// If that element has a property called `isCompleted` set to true
if (!clickedListItem.isCompleted) {
// update the styles and toggle the `isCompleted` property.
clickedListItem.style.textDecoration = "line-through";
clickedListItem.isCompleted = true;
} else {
clickedListItem.style.textDecoration = "none";
clickedListItem.isCompleted = false;
}
// The code above changes the documents version of the data (the elements themselves)
// This loop ensures that the array of todos data is kept in sync with the document
// Loop over the array
for (let i = 0; i < savedTodos.length; i++) {
// if the item in the array has the same text as the item just clicked...
if (savedTodos[i].task === clickedListItem.innerText) {
// toggle the completed state
savedTodos[i].isCompleted = clickedListItem.isCompleted;
// Update the localStorage with the new todos array.
localStorage.setItem("todos", JSON.stringify(savedTodos));
}
}
});
Keep in mind, there are 2 sources of state in your todo list. One is how the document looks, and the other is the array of todos data. Lots of challenges come from making sure these 2 stay in sync.
If somehow the document showed one of the list items as crossed out, but your array of data shows that all the todos are not completed, which version is correct? There is no right answer here, but state management will be something you might consider when designing apps in the future. Redux is a good js library with a well understood pattern that helps solve this problem. Hope this last comment doesn't confuse too much. Best of luck!
The important part is in (de)serializing the data. That means:
reading from localStorage (JSON.parse(localStorage.getItem("todos")) || [])
We add the default [] because if the todos key does not exist, we will get null and we expect a list
saving to localStorage (localStorage.setItem("todos", JSON.stringify(savedTodos)))
We need JSON.parse and its complementary operation JSON.stringify to parse and save strings because localStorage can store only strings.
In your case you need to read the data from localStorage and render the initial list. To save it to localStorage, again, you have to serialize the data. See the below snippets (link to working JSFIDDLE, because the below example does not work in the StackOverflow sandbox environment):
let ul = document.querySelector('ul');
let newItem = document.querySelector('input[type=text]');
const Store = {
serialize () {
return [].slice.call(document.querySelectorAll("li")).map(c => {
return {
text: c.textContent,
finished: c.querySelector("input").checked
}
})
},
get () {
return JSON.parse(localStorage.getItem("todos")) || []
},
save () {
return localStorage.setItem("todos", JSON.stringify(Store.serialize()))
}
}
const firstItems = Store.get()
firstItems.forEach(it => {
output(it.text, it.finished)
})
function output(v, finished) {
let newTodo = document.createElement('li');
newTodo.innerText = v || newItem.value;
newTodo.classList.add('todo');
let ulAppend = ul.append(newTodo);
ul.append(newTodo);
// Create a checkbox for each item
let checkbox = document.createElement('input');
if (finished) {
checkbox.checked = true
}
checkbox.setAttribute('type', 'checkbox');
let checkboxAppend = newTodo.append(checkbox);
newTodo.append(checkbox);
newItem.value = '';
}
let button = document.querySelector('.btn');
button.addEventListener('click', () => {
output()
Store.save()
});
ul.addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
e.target.remove();
} else if (e.target.tagName === 'INPUT') {
e.target.parentElement.classList.toggle('finished');
}
// Update the value in localStorage when you delete or add a new item
Store.save()
});
<ul></ul>
<input type="text" /> <button class="btn">Submit</button>
I have added the Store variable to simplify the way you get and set the data in localStorage.
The serialize method will read the TODOs from the list. document.querySelectorAll("li") returns a NodeList, but by doing [].slice.call(...) we convert it to an Array.

Trouble creating unique ID as it is being overwritten each refresh

This function is called as a form submit, and further calls a new function for rendering the list of divs. After this is done the website is refreshed because of drag and drop functionality. The problem is that I cant seem to find a way to create an unique ID that persists through page refresh and isnt overwritten on page load because of ex: "let taskId = 0".
Any ideas? :)
function createNewTask(event){
if(document.querySelector("[name='description']").value === "") {
alert("Cannot add empty task.");
} else {
event.preventDefault();
let taskId = 0;
const description = document.querySelector("[name='description']").value;
const givenTo = document.querySelector("[name ='givenTo']").value;
const createdByName = document.querySelector("[name = 'workerName']").value;
const task = {taskId, description, givenTo, createdByName, section: 'task-section'};
const taskList = JSON.parse(window.localStorage.getItem("taskList")) || [];
taskId++;
taskList.push(task);
window.localStorage.setItem("taskList", JSON.stringify(taskList));
// renderTaskList();
renderStoredList();
//Reload page after createNewTask to activate draggable
location.reload();
}
}
Use length to get the next taskId.
function createNewTask(event){
if(document.querySelector("[name='description']").value === ""){
alert("Cannot add empty task.");
} else {
event.preventDefault();
const tasklist = JSON.parse(window.localStorage.getItem("taskList")) || []
let taskId = tasklist.length;
const description = document.querySelector("[name='description']").value;
const givenTo = document.querySelector("[name ='givenTo']").value;
const createdByName = document.querySelector("[name = 'workerName']").value;
const task = {taskId, description, givenTo, createdByName, section: 'task-section'};
taskList.push(task);
window.localStorage.setItem("taskList", JSON.stringify(taskList));
// renderTaskList();
renderStoredList();
//Reload page after createNewTask to activate draggable
location.reload();
}
}
As I cannot add a comment yet, I'll post it here as an answer.
What I would do on my end to keep track of the taskId is to also store the latest taskId that was last used in my localStorage, that way, it would persist.
window.localStorage.setItem('lastTaskId', taskId);
And then simply take that each time the page loads.
Hope this helps!
What if you assign taskId based on previous length of the taskList:
const taskList = JSON.parse(window.localStorage.getItem("taskList")) || [];
let taskId = taskList.length

Store In localStorage is showing 2 values as debugger and Undefined

I am Buildig Normal HTML And Materialize Css And Javascript.
When i am storing tasks in my localStorage it was Storing on localStorage But it was Printing Two Values one is My Tasks and These is All Of my code and Please Tell the way i can solve this debugger..
Another Showing As Debugger undefined..
What is Debugger and undefined and Why It was Showing ..?
Please View The Code..!
in Local Storage I am Getting Like
Key Value
tasks ["Walk the Dog"]
debugger undefined
Thank You
const form = document.querySelector('#task-form');
const filter = document.querySelector('#filter');
const taskList = document.querySelector('.collection');
const taskInput = document.querySelector('#task');
const clearBtn = document.querySelector('.clear-tasks');
loadEventListeners();
function loadEventListeners(){
form.addEventListener('submit', addTask);
taskList.addEventListener('click', removeTask);
clearBtn.addEventListener('click',clearTasks);
filter.addEventListener('keyup', filterLi)
}
function addTask(e){
if(taskInput.value ===''){
alert('Please Add Task');
} else {
const li = document.createElement('li');
li.className = 'collection-item';
li.appendChild(document.createTextNode(taskInput.value));
const link = document.createElement('a');
link.className = 'delete-item secondary-content';
link.innerHTML = '<i class="fa fa-remove"></i>'
li.appendChild(link);
taskList.appendChild(li);
storeTaskInLocalStorage(taskInput.value);
e.preventDefault();
taskInput.value="";
}
}
function storeTaskInLocalStorage(task){
let tasks;
if(localStorage.getItem('tasks') === null){
tasks = [];
} else{
tasks = JSON.parse(localStorage.getItem('tasks'));
}
tasks.push(task);
localStorage.setItem('tasks', JSON.stringify(tasks));
}
function removeTask(e){
if(e.target.parentElement.classList.contains('delete-item')){
e.target.parentElement.parentElement.remove();
}
}
function clearTasks(){
while(taskList.firstChild){
taskList.removeChild(taskList.firstChild)
}
}
function filterLi(e){
const filterText = e.target.value.toLowerCase();
document.querySelectorAll('.collection-item').forEach(function(task){
const litem = task.firstChild.textContent;
if(litem.toLowerCase().indexOf(filterText) != -1){
task.style.display = 'block'
} else{
task.style.display = 'none';
}
})
}
You can ignore the key as you are not adding it though your app. I've noticed something similar when using sockets.io but you are not using sockets.io.
If it bothers you, you can remove that key yourself.
// Run this in your console
localStorage.removeItem('debugger');

Categories

Resources