I am making a simple to do app and am stuck on updating the data array when a new task is added. Right now, if you add a new task, the entire array re-populates on the display and duplicate values are listed. How do I resolve this so that only one iteration of the array displays any time it is updated on the page?
const completedDivs = document.getElementById("completed");
const taskDivs = document.getElementById("tasks");
const addBtn = document.querySelector(".fa-plus");
const input = document.getElementById("newTask");
// SET UP DEFAULT TO DO TASKS
let todos = [
{ completed: false, task: "Make Coffee", id: 1 },
{ completed: false, task: "Walk the dog", id: 2 },
{ completed: true, task: "Make calculator app", id: 3 },
];
// SET UP REQUIRED VARIABLES
let taskNo = (completedDivsLength + taskDivsLength) + 1;
// MAP OUT DEFAULT TASKS TO DISPLAY
function generateToDo() {
todos.map(todo => {
// CREATE A NEW DIV
let newTask = document.createElement("div");
// GIVE THE DIV AN ID
newTask.id = `div${taskNo}`;
if(todo.completed) {
// FORMAT THE DIV
newTask.innerHTML = `<input type="checkbox" id="task${taskNo}" checked>
<label for="task${taskNo}">${todo.task}</label>
<i class="far fa-trash-alt" id="trash${taskNo}"></i>`
// Check the item off the list
newTask.classList.add("checked");
// Add the task to the completed list
completedDivs.appendChild(newTask);
} else if (!todo.completed) {
// FORMAT THE DIV
newTask.innerHTML = `<input type="checkbox" id="task${taskNo}">
<label for="task${taskNo}">${todo.task}</label>
<i class="far fa-trash-alt" id="trash${taskNo}"></i>`
// Uncheck the task from the list
newTask.classList.remove("checked");
// Move the task to the To Do list
taskDivs.appendChild(newTask);
}
// INCREMENT THE TASK NO BY 1
taskNo++;
});
}
// ADD NEW TASKS
addBtn.addEventListener("click", () => {
todos.push({ completed: false, task: `${input.value}`, id: taskNo });
generateToDo();
// RESET THE INPUT FIELD TO NOTHING
input.value = "";
});
// AUTOMATICALLY LOAD THE DEFAULT TO DOS
window.onload = generateToDo();
You should not iterate on the whole array each time a task is added.
Just create an element for the new task.
Only in on load iterate over the array.
// MAP OUT DEFAULT TASKS TO DISPLAY
function generateToDo(todo) {
// CREATE A NEW DIV
let newTask = document.createElement("div");
// GIVE THE DIV AN ID
newTask.id = `div${taskNo}`;
if(todo.completed) {
// FORMAT THE DIV
newTask.innerHTML = `<input type="checkbox" id="task${taskNo}" checked>
<label for="task${taskNo}">${todo.task}</label>
<i class="far fa-trash-alt" id="trash${taskNo}"></i>`
// Check the item off the list
newTask.classList.add("checked");
// Add the task to the completed list
completedDivs.appendChild(newTask);
} else if (!todo.completed) {
// FORMAT THE DIV
newTask.innerHTML = `<input type="checkbox" id="task${taskNo}">
<label for="task${taskNo}">${todo.task}</label>
<i class="far fa-trash-alt" id="trash${taskNo}"></i>`
// Uncheck the task from the list
newTask.classList.remove("checked");
// Move the task to the To Do list
taskDivs.appendChild(newTask);
}
// INCREMENT THE TASK NO BY 1
taskNo++;
}
// ADD NEW TASKS
addBtn.addEventListener("click", () => {
const newTodo = { completed: false, task: `${input.value}`, id: taskNo };
todos.push(newTodo);
generateToDo(newTodo);
// RESET THE INPUT FIELD TO NOTHING
input.value = "";
});
// AUTOMATICALLY LOAD THE DEFAULT TO DOS
window.onload = todos.forEach( todo => generateToDo(todo));
maybe you need these couple of lines - delete what is already there and then update
// MAP OUT DEFAULT TASKS TO DISPLAY
function generateToDo() {
[].forEach.call(completedDivs.children,(x)=>x.remove());
[].forEach.call(taskDivs.children,(x)=>x.remove());
todos.map(todo => {
// CREATE A NEW DIV
let newTask = document.createElement("div");
...
Related
When a user enters a title and amount, it gets pushed to an array. If the item is an expense, it gets pushed to an expense array of objects, if it's an income, it gets pushed to an income array of objects. Then either the displayIncome or displayExpense functions render a component so that it's displayed. The issue that i'm having is that every time I press the submit button, the app displays the previous item as one item and also the previous item with the new item as another individual item. If there are two items in the array, the app will display (item1) ((item1) + (item2)). The picture shows my issue. How do I get the app to only display one item for each item in the array?
let expense_list = []
let income_list = []
addExpense.addEventListener('click', () =>{
if(expenseTitle.value == '' || expenseAmount.value == ''){
return;
}
let expense = {
type: 'expense',
title: expenseTitle.value,
amount: expenseAmount.value,
id: Date.now()
}
expense_list.push(expense)
console.log(expense_list)
clearExpense()
displayExpense()
})
addIncome.addEventListener('click', () =>{
if(incomeTitle.value == '' || incomeAmount.value == ''){
return;
}
let income = {
type: 'income',
title: incomeTitle.value,
amount: incomeAmount.value,
id: Date.now()
}
income_list.push(income)
console.log(income_list)
clearIncome()
displayIncome()
})
const clearExpense = () =>{
expenseTitle.value = '';
expenseAmount.value = '';
}
const clearIncome = () =>{
incomeTitle.value = ''
incomeAmount.value = ''
}
const displayExpense = () =>{
expense_list.map((entry) =>{
return expenseList.innerHTML += `<li id = "${entry.id}" class= "${entry.type}">
<div class = "entry">${entry.title}: $${entry.amount}</div>
<div class="icon-container">
<div class = "edit" id="${entry.id}"></div>
<div class ="delete" id="${entry.id}"></div>
</div>
</li>`
})
}
const displayIncome = () =>{
income_list.map((entry) =>{
return incomeList.innerHTML += `<li id = "${entry.id}" class= "${entry.type}">
<div class = "entry">${entry.title}: $${entry.amount}</div>
<div class="icon-container">
<div class = "edit" id="${entry.id}"></div>
<div class ="delete" id="${entry.id}"></div>
</div>
</li>`
})
}
I've tried just using one array for expenses and incomes, and using forEach but every time I get the same result.
Clear the expenseList.innerHTML before mapping.
Something like that
const displayExpense = () =>{
expenseList.innerHTML = ''
expense_list.map((entry) =>{ ....
I am almost done with my to-do app, what is left is to do the local storage for the completed list and edited task.
The local storage I have done for when the task is added and removed. But I am not sure how to do the local storage for when the task is set to complete and when the task has been edited.
HTML
<div class="form">
<input class="user-input" type="text">
<input class="date" type="date">
<input class="time" type="time">
<button onclick="addTask()" class="add" id="add">+</button>
</div>
<div class="list"></div>
JS
//local storage key
const STORAGE_KEY = "tasks-storage-key";
// variables object
const el = {
form: document.querySelector(".form"),
input: document.querySelector(".user-input"),
list: document.querySelector(".list"),
date: document.querySelector(".date"),
time: document.querySelector(".time"),
};
const updateEl = {
formUpdate: document.querySelector(".form-update"),
inputUpdate: document.querySelector(".user-input"),
modal: document.querySelector(".modal"),
dateUpdate: document.querySelector(".date-update"),
timeUpdate: document.querySelector(".time-update"),
};
//Create ID
const createId = () =>
`${Math.floor(Math.random() * 10000)}${new Date().getTime()}`;
//variable of empty array that gets new task
let taskList = JSON.parse(window.localStorage.getItem(STORAGE_KEY) ?? "[]");
renderList();
function makeNewTask() {
const data = {
id: createId(),
taskNew: el.input.value,
taskDate: el.date.value,
taskTime: el.time.value,
};
return data;
}
function updateTask() {
const dataUpdate = {
id: createId(),
inputUpdate: updateEl.inputUpdate.value,
dateUpdate: updateEl.dateUpdate.value,
timeUpdate: updateEl.timeUpdate.value,
};
return dataUpdate;
}
function renderList() {
// This resets the list innerHTML to the new list
el.list.innerHTML = taskList.map(function (data) {
return `<div class="task">
<div class="task-content">
<div class="task" data-id="${data.id}">
<input class="new-task-created" value="${data.taskNew}" readonly></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)" class="edit" data-id="${data.id}">Edit</button>
<button onclick="deleteItem(event)" class="delete" data-id="${data.id}">Delete</button>
<button onclick="completeItem(event)" class="complete" data-id="${data.id}">Complete</button>
</div>`;
})
el.input.value = "";
}
//event listner that listens for add button.
function addTask() {
taskList.push(makeNewTask());
// store the list on localstorage because data changed
storeList();
// render list again because you've added a new entry
renderList();
}
//function that removes task from array with delete button.
function deleteItem(event) {
taskList.splice(taskList.indexOf(event.target.dataset.id), 1);
// store the list on localstorage because data changed
storeList();
// render list again because entry was removed
renderList();
}
//function that stores task list.
function storeList() {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(taskList));
}
//function that that edits tasks with date and time.
function editItem(event) {
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";
}
}
//function that that completes task.
function completeItem(event) {
const element = event.target.closest(".task-content");
let taskItem = element.querySelector(".new-task-created");
let dateItem = element.querySelector(".due-date");
let timeItem = element.querySelector(".due-time");
// style..
taskItem.style.textDecoration = "line-through";
dateItem.style.textDecoration = "line-through";
timeItem.style.textDecoration = "line-through";
}
I have added a screenshot of the section of the code I am focusing on, I just put the entire code so you can see the flow.
here in some changes in your js file look into it.
in editItem function on behalf of i we change value in taskList.
in completeItem function on behalf of i we add one more prop in object textDecoration: true and in renderList function we use prop to add style.
pass index i to each button action in function like editItem, deleteItem, completeItem .
//local storage key
const STORAGE_KEY = "tasks-storage-key";
// variables object
const el = {
form: document.querySelector(".form"),
input: document.querySelector(".user-input"),
list: document.querySelector(".list"),
date: document.querySelector(".date"),
time: document.querySelector(".time"),
};
const updateEl = {
formUpdate: document.querySelector(".form-update"),
inputUpdate: document.querySelector(".user-input"),
modal: document.querySelector(".modal"),
dateUpdate: document.querySelector(".date-update"),
timeUpdate: document.querySelector(".time-update"),
};
//Create ID
const createId = () =>
`${Math.floor(Math.random() * 10000)}${new Date().getTime()}`;
//variable of empty array that gets new task
let taskList = JSON.parse(window.localStorage.getItem(STORAGE_KEY) ?? "[]");
renderList();
function makeNewTask() {
const data = {
id: createId(),
taskNew: el.input.value,
taskDate: el.date.value,
taskTime: el.time.value,
};
return data;
}
function updateTask() {
const dataUpdate = {
id: createId(),
inputUpdate: updateEl.inputUpdate.value,
dateUpdate: updateEl.dateUpdate.value,
timeUpdate: updateEl.timeUpdate.value,
};
return dataUpdate;
}
function renderList() {
// This resets the list innerHTML to the new list
// <input class="new-task-created" value="${data.taskNew}" readonly style="text-decoration: 'line-through'"></input>
el.list.innerHTML = taskList.map(function (data, i) {
return `<div class="task">
<div class="task-content">
<div class="task" 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>`;
})
el.input.value = "";
}
//event listner that listens for add button.
function addTask() {
taskList.push(makeNewTask());
// store the list on localstorage because data changed
storeList();
// render list again because you've added a new entry
renderList();
}
//function that removes task from array with delete button.
function deleteItem(event, i) {
taskList.splice(i, 1);
// store the list on localstorage because data changed
storeList();
// render list again because entry was removed
renderList();
}
//function that stores task list.
function storeList() {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(taskList));
}
//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();
}
}
//function that that completes task.
function completeItem(event, i) {
const element = event.target.closest(".task-content");
let taskItem = element.querySelector(".new-task-created");
let dateItem = element.querySelector(".due-date");
let timeItem = element.querySelector(".due-time");
// style..
taskItem.style.textDecoration = "line-through";
dateItem.style.textDecoration = "line-through";
timeItem.style.textDecoration = "line-through";
taskList[i] = {
...taskList[i],
textDecoration: true
}
console.log('taskList', taskList)
// store the list on localstorage because data changed
storeList();
// render list again because you've added a new entry
renderList();
}
For edited tasks I would first modify the updateTask() function so that it uses the same id as the originally created task:
//...
const taskData = {
id: createId(),
completed: false, // added
contents: el.input.value,
date: el.date.value,
time: el.time.value,
};
//...
You can modify the contents and the date/time to keep track of the last time it was modified. From there, you would do the simply call storeList() and renderList() to save to local storage and update the view.
For completed tasks I would either add a boolean property in the task data (something like completed) or a completion date (initially set to zero or null). From there you simply update and check for that value. And as for saving to local storage you would do the same: call storeList() and renderList().
The function addTask() is the function that when the user clicks the "+" button it adds the list to the array. I have a render function that renders the to-do list when the "+" is added. I sat and thought about how I could do this.
doing the taskList.push()``` in the ```function addTask()``` might work, but the issue I am having is I am not sure how to push the elements of the ```function renderList(list)``` into the taskList.push()```.
I am missing something in this approach, but I am not sure what. I know I need to access the ```let newtask```` and add that bit to the array.
HTML
<div class="form">
<input class ="user-input" type="text">
<input class="date" type="date">
<input class="time" type="time">
<button onclick="addTask()" class="add" id="add">+</button>
</div>
<div class="list"></div>
JS
//local storage key
const STORAGE_KEY = "tasks-storage-key";
// variables object
const el = {
form: document.querySelector(".form"),
input: document.querySelector(".user-input"),
list: document.querySelector(".list"),
date: document.querySelector(".date"),
time: document.querySelector(".time"),
};
//Create ID
const createId = () =>
`${Math.floor(Math.random() * 10000)}${new Date().getTime()}`;
//variable of empty array that gets new task
let taskList = JSON.parse(window.localStorage.getItem(STORAGE_KEY) ?? "[]");
function makeNewTask() {
const data = {
id: createId(),
taskNew: el.input.value,
taskDate: el.date.value,
taskTime: el.time.value,
};
return data;
}
//function that creates new tasks with date and time
function display() {
data = makeNewTask();
taskList.push(data);
renderList(taskList);
storeList();
}
function renderList(list) {
list.forEach(function (data) {
const tasks = document.createElement("div");
let newtask = (tasks.innerHTML = `
<div class="task-content">
<div class="task" data-id="${data.id}">
<div class="new-task-created">${data.taskNew}</div>
<label class="due-date">${data.taskDate}</label>
<label class="due-time">${data.taskTime}</label>
</div>
<div class="action-buttons">
<button onclick="editItem(event)" class="edit" data-id="${data.id}">Edit</button>
<button onclick="deleteItem(event)" class="delete" data-id="${data.id}">Delete</button>
<button onclick="completeItem(event)" class="complete" data-id="${data.id}">Complete</button>
</div>
</div>`);
el.list.appendChild(tasks);
});
}
//event listner that listens for add button.
function addTask() {
taskList.push(list);
}
//function that stores task list.
function storeList() {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(taskList));
}
//function that removes task from array with delete button.
function deleteItem() {
let removeitem = document.querySelector(".task-content");
removeitem.parentNode.removeChild(removeitem);
window.localStorage.removeItem(STORAGE_KEY);
}
//function that removes stored task when deleted.
//function that that edits tasks with date and time.
function editItem() {}
//function that that completes task.
function completeItem(event) {
const element = event.target.closest(".task-content");
console.log(element);
let taskItem = element.querySelector(".new-task-created");
let dateItem = element.querySelector(".due-date");
let timeItem = element.querySelector(".due-time");
// style..
taskItem.style.textDecoration = "line-through";
dateItem.style.textDecoration = "line-through";
timeItem.style.textDecoration = "line-through";
}
This is the error I get.
There are a few issues with your code, but let's start with the error:
Well the issue is that addTask function does not receive a list parameter that you are trying to push. I think you want to call makeNewTask() there instead of passing that list that is undefined.
//event listner that listens for add button.
function addTask() {
taskList.push(makeNewTask());
}
The next problem is that display() is never called. Also, why are you creating an empty task when calling display? The display function is redundant, you should only call renderList(taskList). You should call storeList() only when it changes (creation of new task or removal of one).
Next problem is in your renderList() function. You are assigning that newTask variable that you never actually use. Also if you're appending children to the main element in a forEach on that list, it will keep adding new lists of elements. I don't think you want that.
function renderList() {
el.list.innerHTML = taskList.map(function (data) {
return `<div class="task">
<div class="task-content">
<div class="task" data-id="${data.id}">
<div class="new-task-created">${data.taskNew}</div>
<label class="due-date">${data.taskDate}</label>
<label class="due-time">${data.taskTime}</label>
</div>
<div class="action-buttons">
<button onclick="editItem(event)" class="edit" data-id="${data.id}">Edit</button>
<button onclick="deleteItem(event)" class="delete" data-id="${data.id}">Delete</button>
<button onclick="completeItem(event)" class="complete" data-id="${data.id}">Complete</button>
</div>
</div>`;
});
}
Also, renderList should use the taskList instead of receiving a list parameter since you're already storing the taskList globally (which is not great either).
The final working code should be something like this (added some comments as well -- I suggest using classes for structuring your data but I'll leave that up to you):
//local storage key
const STORAGE_KEY = "tasks-storage-key";
// variables object
const el = {
form: document.querySelector(".form"),
input: document.querySelector(".user-input"),
list: document.querySelector(".list"),
date: document.querySelector(".date"),
time: document.querySelector(".time"),
};
//Create ID
const createId = () => `${Math.floor(Math.random() * 10000)}${new Date().getTime()}`;
//variable of empty array that gets new task
let taskList = JSON.parse(window.localStorage.getItem(STORAGE_KEY) ?? "[]");
// This should be called initially after you read the data from localstorage in order to display the initial data if you have any.
renderList();
function makeNewTask() {
const data = {
id: createId(),
taskNew: el.input.value,
taskDate: el.date.value,
taskTime: el.time.value,
};
return data;
}
function renderList() {
// actually reset the list innerHTML to the new list (in order to facilitate removing / adding -- not very efficient)
el.list.innerHTML = taskList.map(function (data) {
return `<div class="task">
<div class="task-content">
<div class="task" data-id="${data.id}">
<div class="new-task-created">${data.taskNew}</div>
<label class="due-date">${data.taskDate}</label>
<label class="due-time">${data.taskTime}</label>
</div>
<div class="action-buttons">
<button onclick="editItem(event)" class="edit" data-id="${data.id}">Edit</button>
<button onclick="deleteItem(event)" class="delete" data-id="${data.id}">Delete</button>
<button onclick="completeItem(event)" class="complete" data-id="${data.id}">Complete</button>
</div>
</div>`;
});
}
//event listner that listens for add button.
function addTask() {
taskList.push(makeNewTask());
// store the list on localstorage because data changed
storeList();
// render list again because you've added a new entry
renderList();
}
//function that removes task from array with delete button.
function deleteItem(event) {
taskList.splice(taskList.indexOf(event.target.dataset.id), 1);
// store the list on localstorage because data changed
storeList();
// render list again because you've removed an entry
renderList();
}
//function that stores task list.
function storeList() {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(taskList));
}
//function that that edits tasks with date and time.
function editItem() { }
//function that that completes task.
function completeItem(event) {
const element = event.target.closest(".task-content");
let taskItem = element.querySelector(".new-task-created");
let dateItem = element.querySelector(".due-date");
let timeItem = element.querySelector(".due-time");
// style..
taskItem.style.textDecoration = "line-through";
dateItem.style.textDecoration = "line-through";
timeItem.style.textDecoration = "line-through";
}
I'd like to add that since completeItem function only changes the current DOM, it cannot be stored on localStorage so on refresh you won't know which tasks are completed. I would suggest adding a property completed to each taskList entry so you can recreate the completed state when refreshing.
as it was mentioned by Cristian the error that you are getting is because the function is not receiving the parameter ‘list’, you can simply try to put this instead:
function addTask() {
display();
}
Then you should see the task on the UI but there are more things that you will need to improve related with your code logic
I am trying to make the tasks that have been created save to the user's local storage within the browser. Is it also possible to show me how to make the delete button remove the created task?
/************************************
* creates an object of elements needed *
************************************/
const elements = {
form: document.querySelector("#new-task-form"),
input: document.querySelector("#new-task-input"),
list: document.querySelector("#tasks"),
cal: document.querySelector("#calendar")
}
/****************************
* Generates an ID for task *
****************************/
const createId = () => `${Math.floor(Math.random() * 10000)}-${new Date().getTime()}`
/**********************************************
* function that creates the HTML elements *
**********************************************/
const createTask = () => {
const id = createId()
const task = elements.input.value;
const date = elements.cal.value;
if(!task && !date) return alert("Please fill in task and select date");
if(!task) return alert("Please fill in task");
if(!date) return alert("Please select date");
const tasks = document.createElement("div");
tasks.innerHTML = `
<button class = "sort">Sort</button>
<div class="task" data-id = "${id}">
<div class="content">
<input type ="checkbox" class="tick">
<input type ="text" class = "text" id = "text" value="${task}" readonly>
<label class = "due-date" for ="text">${date}</label>
<input type ="date" class = "date" id = "date">
</div>
<div class = "actions">
<button class="edit" data-id="${id}">Edit</button>
<button class="delete" data-id="${id}">Delete</button>
</div>
</div>
`
elements.list.appendChild(tasks)
listen()
return tasks
}
/********************************************
* Marks tasks as complete with checkbox *
********************************************/
function listen(){
let allCheckboxes = document.querySelectorAll('.tick')
allCheckboxes.forEach(checkbox =>{
checkbox.addEventListener('change',(e)=>{
let parentElem=e.target.parentElement
if(e.target.checked){
parentElem.style.textDecoration = "line-through"
}
else{
parentElem.style.textDecoration = "none"
}
});
});
}
/**************************************************************
* Event that listens for the edit,save and delete buttons *
**************************************************************/
elements.list.addEventListener('click',event => {
const {target} = event;
const {id} = target.dataset;
const task = id ? document.querySelector(`[data-id="${id}"]`):null;
const type = {
edit: event.target.classList.contains('edit'),
delete: event.target.classList.contains('delete')
}
const isFromSaveLabel = target.innerText.toLowerCase() === 'save'
//Checking to see if buttons are pressed
if(task && type.edit && isFromSaveLabel){
const text = task.querySelector('text')
target.innerText = 'Edit'
text.setAttribute('readonly', true)
return
};
if(task && type.edit){
const text = task.querySelector('text')
target.innerText = 'save'
text.removeAttribute('readonly')
text.focus()
return
};
if(task && type.delete){
return
}
});
/*******************************************************************
* Submits the HTML elements to have the lists submited and created*
*******************************************************************/
const submitHandler = (event) =>{
event.preventDefault();
createTask();
}
elements.form.addEventListener("submit", submitHandler);
The end result that I would like to achieve is to make the created list local storage so that if the page refreshes the tasks remain. and having the task delete when clicking the delete button
You can find the jsfiddler here: https://jsfiddle.net/blaze92/seLvzd1h/2/
I Had Done The Exact Same Project In The Past My Project. So How You Can Do It Can Be Like The Following:
Since You Already Have A Method For Creating A Task createTask. modify this function in a way such that you can pass custom parameters. This allows you to create a method to add notes from the localstorage to your page.
Then You Need To Take All The Tasks get their info using document.getElementByClassName or document.getElementByID and then add it to a JSON Object/Array.
Then You Can Convert this JSON Object/Array Into String Using JSON.Stringify. Now You Can Store This String Into Localstorage using localStorage.setItem()
Now You Can Add A Event Listener To Detect Page Load and then get this String from localhost as localStorage.getItem() and then convert it back to a JSON Object.
Then Create A For Loop Which Iterates Through all the elements of this object/array and call the method to createTask with the parameters from the object.
I have created a todo-apps with js but I have a problem : when I am clicking on the check button to do someting or on the edit button or the time button all tasks are changed : for example when I click on the check button on « learn js » I want that just this task changed ( underline) but when I do that all my tasks become underline. I know this is a beginner question sorry. This is my HTML code :
<h1>To Do List</h1>
<input type="text" placeholder="Name..." id="Name">
<input type="button" id="addItem" value="Add Item" />
<div class="choices">
<p id="p"></p>
</div>
<button id="btn" type="submit"> Clear Items</button>
This is my JS code :
let clear = document.getElementById("btn");
let add = document.getElementById("addItem");
let choices = [];
let vide = document.getElementById('p');
var choice = document.getElementById("Name").value;
let invalid = document.getElementById("invalid");
function main() {
add.addEventListener('click', function() {
addItems();
})
}
function addItems() {
choice = document.getElementById("Name").value;
vide.innerHTML += choice;
choices.push(choice);
document.getElementById('p').insertAdjacentHTML('beforeend', `<i id="check" class="far fa-check-circle"></i>`);
document.getElementById('p').insertAdjacentHTML( 'beforeend', `<i id="null" class="far fa-times-circle"></i>`);
document.getElementById('p').insertAdjacentHTML( 'beforeend', `<i. id="edit" class="far fa-edit"></i>`);
vide.insertAdjacentHTML('beforeend', `<br/><br/>`);
document.getElementById('p').classList.add('listClass');
document.getElementById('check').onclick = function() {
document.getElementById('p').classList.toggle("done");
document.getElementById('check').classList.toggle('opacity');
};
document.querySelector('#null').onclick = function() {
vide.innerHTML ='';
};
document.getElementById('edit').onclick = function() {
// I have not finished this part
}
}
}
main();
This is a picture of the result :
Depsite the fact that you have many mistakes(especially code redundancy) in your code, the main issue is that your IDs are not unique in the page.
As I said, the code is not that flexible to allow us building on it, so I took sometime to provide you a more modern, readable, performant solution (regarding yours of course !).
The code is commented to allow you understand easily what I'm doing.
// selecting the elements from the DOM
const todoList = document.getElementById("todo-list"),
newTodoForm = document.getElementById("new-todo-form"),
todoInp = document.getElementById("todo"),
clearBtn = document.getElementById("clear"),
choices = [],
/**
* createElement: a function that creates an HTML element with the specified attributes and events
* #param nodeName (string) the HTML element tag name
* #param opts (object) the attributes object which can contain :
* content: (object) an object to specify the element's content and it has two fields :
* html: (boolean) is the content should be inserted as HTML or a simple text. Defaults to false, pass true to treat the content as HTML
* value: (string) the actual content
* classList: (string) specifies a space-separated classes to be assigned to the element
* id: (string) the elemnt's ID
* data: (object) an object for the "data-TOKEN=VAL" attributes where each key (in camelCase) is the "TOKEN" and its value is the "VAL", example: {someDataOne: 'some value one', someDataTwo: 'some value two'} will be transformed into "data-some-data-one="some value one" data-some-data-two="some value two""
* events: (object) the keys are event names and the values are the events callbacks. Example {click: () => alert('clicked !')}
**/
createElement = (nodeName, opts) => {
// create the requested element
const el = document.createElement(nodeName);
// merge the options with the default ones
opts = Object.assign({
content: {
value: "",
html: !1
},
classList: "",
id: "",
data: {},
events: {}
},
opts
);
// apply the classes if the "opts.classList" is not empty
!!opts.classList && (el.classList = opts.classList);
// apply the ID if the "opts.id" is not empty
!!opts.id && (el.id = opts.id);
// apply the content if the "opts.content.value" is not empty and check if we want the content to be treated as HTML
!!opts.content.value &&
(el[opts.content.html === !0 ? "innerHTML" : "textContent"] =
opts.content.value);
// apply the data-* if the "opts.data" is not empty
if (Object.getOwnPropertyNames(opts.data).length) {
for (let p in opts.data)
if (opts.data.hasOwnProperty(p)) el.dataset[p] = opts.data[p];
}
// assign the events if the "opts.events" is not empty
if (Object.getOwnPropertyNames(opts.events).length) {
for (let p in opts.events)
if (opts.events.hasOwnProperty(p))
el.addEventListener(p, opts.events[p]);
}
// return the created element after applying the requested attributes and events
return el;
},
/*
* a function that generates a list-item template (HTML code that contains the todo text, buttons for edit, delete and so on...)
* #param txt (string) the todo text
*/
todoItemTpl = txt => {
// "item" is the list-item wrapper that contains all the buttons and todo text for only one todo item
// as you can see, we're using the "createElement" function so the code doesn't get redundant and also it become more readable and flexible
const item = createElement("div", {
classList: "todo-item row col-12 py-2 px-0 m-0 my-1 align-items-center"
}),
// the "p" element that shows the todo text
txtWrapper = createElement("p", {
content: {
value: txt
},
classList: "todo-text col-auto m-0"
}),
// a wrapper for the options (delete, edit and check) buttons of a todo item
btnsWrapper = createElement("div", {
classList: "todo-btns col-auto"
}),
// an array that holds the 3 option buttons so later we can loop through it and append each button to "btnsWrapper"
// every button here has its click event so the browser won't get confused which todo item should it alter
// !!: the edit functionality is not implemented it simply prints some text to the console when the edit button is clicked
optionBtns = [
createElement("button", {
content: {
value: '<i class="far fa-check-circle"></i>',
html: !0
},
classList: "option-btn check-btn ml-1",
events: {
click: function() {
this.closest('div.todo-item').classList.add('finished');
this.disabled = !0;
}
}
}),
createElement("button", {
content: {
value: '<i class="far fa-edit"></i>',
html: !0
},
classList: "option-btn edit-btn ml-1",
events: {
click: function() {
console.log('Edit functionnality not yet implemented !');
}
}
}),
createElement("button", {
content: {
value: '<i class="fa fa-times"></i>',
html: !0
},
classList: "option-btn del-btn ml-1",
events: {
click: function() {
const todoItem = this.closest('div.todo-item'),
txt = todoItem.querySelector('.todo-text').textContent;
todoItem.remove();
choices.splice(choices.indexOf(txt), 1);
}
}
})
];
// append the option buttons to the buttons wrapper
optionBtns.forEach((b) => btnsWrapper.appendChild(b));
// append the todo text to the todo-item
item.appendChild(txtWrapper);
// append the option buttons wrapper text to the todo-item
item.appendChild(btnsWrapper);
// return the newly created todo-item
return item;
};
// listen for the form submission
newTodoForm.addEventListener("submit", e => {
// store the trimmed input (the todo name) value
const inpVal = todoInp.value.trim();
// prevent form submission to disable page reload
e.preventDefault();
// stop execution if the todo item name is empty
if (!inpVal) return;
// if the todo text is not empty then :
// append the entered value to the "choices" array
choices.push(inpVal);
// append the todo-item to the todo list (that is initially empty) using "todoItemTpl" function and by passing the entered value for the todo name
todoList.appendChild(todoItemTpl(inpVal));
// finnaly, empty up the todo name input field
todoInp.value = "";
});
// listen for the click event of the clear button
// when clicked, remove all the todo-items and empty up the "choices" array
clearBtn.addEventListener('click', () => (todoList.innerHTML = '', choices.length = 0));
// styling for the demo, nothing fancy !
.todo-list * {
transition: all .4s 0s ease;
}
.todo-list {
background-color: #f5f5f5;
}
.todo-list .todo-item {
border-bottom: 1px solid #c5c5c5;
}
.todo-list .todo-item.finished {
background-color: #65d4a8;
}
.todo-list .todo-item.finished .todo-text {
text-decoration: line-through;
}
<!-- importing Bootsrap and Font-Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" />
<!-- some transformations to use Bootsrap's classes -->
<div class="todo-form-wrapper">
<form action="#" id="new-todo-form">
<h1>To Do List</h1>
<input type="text" placeholder="Name..." id="todo">
<button type="submit" value="Add Item">Add</button>
</form>
</div>
<!-- the todo list wrapper is initially empty -->
<div id="todo-list" class="todo-list row m-0"></div>
<button id="clear" type="button">Clear Items</button>
Eventhough the code looks good, I don't recommand using it in production as it has some issues, it doesn't support old browsers like IE for example.
I guess you are adding all the contents inside one single - p - tag and when you change the class- i.e: toggling the class of that - p - tag to "done", it is getting applied to all inner texts.
You need separate wrapper for each task assertion to handle this problem.