I am trying to create a Todo list and wonder how I would go about keeping the completed todos from vanishing each time I add a new task. I am aware that this is happening because I clear my div each time a new task is added, however I am unsure on how to proceed for me to keep using arrays on this and still show completed tasks along with new tasks.
Codepen: https://codepen.io/martinkariuki7-the-looper/pen/OJvQXRW
const newToDo = document.getElementById('todo-new')
const addBtn = document.getElementById('addToDo')
const main= document.getElementById('toDoList')
const taskDone = document.getElementsByClassName('task')
let toDoList = []
// populate to do list
function populateToDo(){
let todo = newToDo.value
todo !== null && todo !== '' ? toDoList.push(todo) : alert('You must write something')
updateDOM()
}
//Update DOM
function updateDOM(){
// Clear main div
main.innerHTML = `<h1>To do list </h1>`
// Show tasks on the front
toDoList.forEach(item =>{
let task = document.createElement('div')
task.classList.add('task')
task.innerHTML = `<label><input type="checkbox" >${item}</label><br>`
main.append(task)
task.addEventListener('change', () => task.classList.toggle('task-complete'))
newToDo.value = ''
})
}
// Event listeners
// Add Tasks
addBtn.addEventListener('click', populateToDo)
newToDo.addEventListener('keydown', (e) => {
if(e.code === 'Enter'){
e.preventDefault()
populateToDo()
}
})
If you want to use an array to update the list, you need to compare what you have with what will be changed.
If there aren't items with the same text, you could do something like this:
function updateNodes()
{
const nodesMap = new Map();
[...document.querySelectorAll('div label')]
.forEach(node => {
nodesMap.add(node.innerText, node);
});
const nodesUpdate = [];
toDoList.forEach(item => {
if (nodesMap.has(item)) {
nodesUpdate.push(nodesMap[item]);
} else {
const newNode = document.createElement('div');
newNode.classList.add('task');
newNode.innerHTML = `<label><input type="checkbox" >${item}</label>`;
nodesUpdate.push(newNode);
}
});
return nodesUpdate;
}
Otherwise, each item (or task) needs to have an identification. Otherwise, if there are two items with the same name, you won't know which one was removed or moved. Besides, without identifications, you can't know if an item was renamed.
The code here was not tested and, of course, you need to adapt it to your needs.
Related
i know that the problem is that let todoList is an empty array, but i dont know how to solve it.
the id tags in my created html is so e can create a delete button later
heres my code:
const textArea = document.querySelector("textarea");
const button = document.querySelector("button");
const listContainer = document.querySelector(".list-container");
let id = 0;
let todoList = [];
button.onclick = function () {
const listItem = {
title: textArea.value,
};
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
};
function addToStorage(items) {
const stringify = JSON.stringify(items);
localStorage.setItem("list", stringify);
}
function getFromStorage() {
const data = localStorage.getItem("list");
const unstrigified = JSON.parse(data);
return unstrigified;
}
const createHtml = (data) => {
id++;
listContainer.innerHTML = "";
data.forEach((item) => {
listContainer.innerHTML += `<div class="list-item" data-id=${id}><p>${item.title} </p><button class="remove" data-id=${id}>Delete</button></div>`;
});
};
The problem here is you just forgot to load the data from localStorage when the page loaded like this
window.onLoad = () => {
const dataFromStorage = getFromStorage();
if(dataFromStorage){
createHtml(dataFromStorage);
} else {
createHtml([]);
}
}
The problem in the code is as follows
Initially the todolist will be an empty array. so when you do the below
todoList.push(listItem);
// adding to local storage which will override the existing todos when page is refreshed
addToStorage(todoList);
// So when the below line is executed only the latest todo will be returned
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
Fix:
Initialise the todos from localstorage instead of an empty array
let todoList = [];
// change it as below
let todoList = getFromStorage();
Now Modify the getFromStorage() as below
// If the data is present in the localStorage then return it, else return empty array
function getFromStorage() {
const data = localStorage.getItem("list");
if (!data) return [];
const unstrigified = JSON.parse(data);
return unstrigified;
}
Now when the page is loaded, we need to display the todos. Add the below lines of code
window.onload = function () {
createHtml(todoList);
};
That's it. This will fix the issue.
Few minor improvements can be made as well.
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage(); // this line is not necessary, remove it
createHtml(dataFromStorage); // change this to createHtml(todoList)
Codepen
Thanks.
I'm trying to make a kind of catalog and once someone presses the "show more" button it should show the description from the corresponding data I got from a API/JSON file. I tried using e.target but I'm stuck at this point.
function getCocktailItemsSuccessHandler(data) {
for(item of data){
const cardDiv = document.createElement("div")
cardDiv.classList.add("card")
cocktailGallery.appendChild(cardDiv)
const nameCocktail = document.createElement("h2")
nameCocktail.classList.add("listName")
nameCocktail.innerText = item.name
cardDiv.appendChild(nameCocktail)
const img = document.createElement("img")
img.src = item.image
cardDiv.appendChild(img)
const detailsButton = document.createElement("button")
detailsButton.classList.add("detailsButton")
detailsButton.innerHTML = 'More'
detailsButton.addEventListener('click', detailsClickHandler)
cardDiv.appendChild(detailsButton)
}
}
function detailsClickHandler(e) {
let details = e.target
detailsContainer.innerHTML = item.
}
If I am understanding your structure, you can store the incoming data in a variable that you can reference after the fact. See the lines with the // <--- add this line and then the whole detailsClickHandler function.
Another option of course is to create a div and insert the description text in it, hide it with display:none and toggle it on with the button click. The way I've presented below is more dynamic, but not neccesarily better.
let theData = [] // <-- add this line
function getCocktailItemsSuccessHandler(data) {
theData = data; // <-- add this line
for (item of data) {
const cardDiv = document.createElement("div")
cardDiv.classList.add("card")
cardDiv.setAttribute('data-id', item.id); // <-- add this line
cocktailGallery.appendChild(cardDiv)
const nameCocktail = document.createElement("h2")
nameCocktail.classList.add("listName")
nameCocktail.innerText = item.name
cardDiv.appendChild(nameCocktail)
const img = document.createElement("img")
img.src = item.image
cardDiv.appendChild(img)
const detailsButton = document.createElement("button")
detailsButton.classList.add("detailsButton")
detailsButton.innerHTML = 'More'
detailsButton.addEventListener('click', detailsClickHandler)
cardDiv.appendChild(detailsButton)
}
}
function detailsClickHandler(e) {
let details = e.target
// get the relative id
let id = details.closest('.card').dataset.id;
// get the item from the object
let item = theData.filter(item => item.id.toString().trim() === id.toString().trim());
detailsContainer.innerHTML = item[0].description; // or whatever field the html is in
}
You can pass the item dynamically to the detailsClickHandler() function and then use it there to update details.
detailsButton.addEventListener('click', (event) => detailsClickHandler(event, item))
function detailsClickHandler(event, item) {
detailsContainer.innerHTML = item
}
Making a todo list for a class project - able to get the item to "add" and append to the list, but when you refresh the page, the item returns as undefined. Checking localStorage.todo, I see it pulling part of the values I need, but aren't in use yet, but not the actual innerText. I started adding bits for a line-through to check off completed items and a remove button, but that was commented out and hopefully isn't impacting things - was trying to isolate the error.
I'm guessing my issue is something in the localStorage.setItem, but I'm not sure what it would be. I've tried changing the newTodo.innerText value to other variables in use but nothing returns. Below returns an input value I enter, but again, is lost when the page refreshes.
const todoForm = document.querySelector('#todoForm');
const todoList = document.querySelector('#todoList');
let todoItem = document.querySelector('#todoItem');
// Pull from storage
const savedList = JSON.parse(localStorage.getItem('todo')) || [];
for (let i = 0; i < savedList.length; i++)
{
let newTodo = document.createElement('li');
newTodo.innerText = savedList[i].task;
todoList.appendChild(newTodo);
}
// Add Item
todoForm.addEventListener('submit', function(e){
e.preventDefault();
let newTodo = document.createElement('li');
let newItem = document.querySelector('#todoItem').value;
newTodo.innerText = newItem;
todoForm.reset();
todoList.appendChild(newTodo);
// Clear Form Field on submit and storage
savedList.push({ task: newTodo.innertext, isCompleted: false });
localStorage.setItem('todo', JSON.stringify(savedList));
});
// Add Remove Button
// let removeItem = document.createElement('button');
// removeItem.innerText = "Remove";
// newTodo.append(removeItem);
// Strike through item or remove item
// todoList.addEventListener('click', function(e){
// if (e.target.tagName === 'BUTTON'){
// e.target.parentElement.remove();
// }
// else if (e.target.tagName === 'LI'){
// e.target.style.textDecoration = 'line-through';
// }
// });
camelcase is wrong not innertext but innerText
savedList.push({ task: newTodo.innerText, isCompleted: false })
I've already asked that question but my explanation was pretty bad, so I decided to ask again with a better explanation and with actual code (I'll ask moderators to delete one of the posts). So let's consider the problem.
Following snippet represents rendering notes from array. However, during the adding note part, I mutate a state. So the question is: how can I add a new note in notes array without mutating? In other words, I want to remove replaceNotes and remain the same functionality. I know that it's possible to add notes without array at all, but I do need to update array with notes in due to the future reference. The ting is, in my original application I've got lists with notes, and while I switch between lists, I should get rendered notes that relies to the list I switch on. That's why I should keep the reference to notes array.
At the same time I'm wondering, would it be okay, if I just store notes in localStorage and then take notes from that data? Is it a good practice in functional programming?
const button = document.getElementById('button');
const notesContainer = document.querySelector('.notes');
const pipe = (f, g) => (...args) => f(g(...args));
let notes = [];
const createNote = (...fns) => fns.reduceRight(pipe);
const handleEvent = () =>
createNote(gatherContent, renderContent, replaceNotes)(notes);
function gatherContent(notes) {
const name = prompt('How do you want to name a note?');
return [...notes, { name }];
}
function renderContent(notes) {
function render(note) {
const noteEl = document.createElement('div');
noteEl.innerHTML = `<p>${note.name}</p>`;
notesContainer.append(noteEl);
}
notesContainer.innerHTML = '';
notes.map(render);
return notes;
}
const replaceNotes = newNotes => (notes = newNotes);
button.addEventListener('click', handleEvent);
<button id="button">Click me!</button>
<section class="notes"></section>
Here is how to create a simple task list app without mutating anything except for the DOM.
const button = document.getElementById("button");
const section = document.getElementById("notes");
const template = document.getElementById("template");
template.parentNode.removeChild(template);
const render = notes => {
button.onclick = event => {
const name = prompt("How do you want to name a note?");
render([...notes, { name }]);
};
while (section.lastChild) {
section.removeChild(section.lastChild);
}
for (const note of notes) {
const node = template.cloneNode(true);
node.firstChild.firstChild.nodeValue = note.name;
section.appendChild(node);
}
};
render([]);
<button id="button">Click me!</button>
<section id="notes"></section>
<div id="template"><p>name</p></div>
For a detailed explanation, read my previous answer. https://stackoverflow.com/a/58642199/783743
You can use this pattern with localStorage too.
const button = document.getElementById("button");
const section = document.getElementById("notes");
const template = document.getElementById("template");
template.parentNode.removeChild(template);
const render = notes => {
localStorage.setItem("notes", notes); // set notes
button.onclick = event => {
const name = prompt("How do you want to name a note?");
render([...notes, { name }]);
};
while (section.lastChild) {
section.removeChild(section.lastChild);
}
for (const note of notes) {
const node = template.cloneNode(true);
node.firstChild.firstChild.nodeValue = note.name;
section.appendChild(node);
}
};
render(localStorage.getItem("notes") || []); // get notes
Note that localStorage should only be used to save state that you want to use across sessions. It's not recommended to use localStorage as your application store. That would result in both bad performance and bad code structure.
I'm trying to return a new array by adding an event listener to my remove button. Connected to that event listener is a function which runs an array.filter() function. I'm not sure why it's not working. I can console.log the new array and that's fine, but when I try to return the new array, nothing happens.
I was splicing the item out of the array and that worked fine, but I wanted to try using filter. It seems that the logic is there and there's no error being thrown. But it just doesn't return a new array. It's the removeIngredient function that I'm trying to remove the item from via its index.
const removeIngredient = text => {
const ingredientIndex = recipeOnPage.ingredients.findIndex(
ingredient => ingredient.text === text
)
const ingredientList = recipeOnPage.ingredients
const ingredient = ingredientList.filter(
item => ingredientList.indexOf(item) !== ingredientIndex
)
return ingredient
}
const toggleIngredient = text => {
const ingredient = recipeOnPage.ingredients.find(
ingredient => ingredient.text === text
)
if (ingredient.included) {
ingredient.included = false
} else {
ingredient.included = true
}
}
const ingredientSummary = recipe => {
let message
const allUnchecked = recipeOnPage.ingredients.every(
ingredient => ingredient.included === false
)
const allChecked = recipeOnPage.ingredients.every(
ingredient => ingredient.included === true
)
if (allUnchecked) {
message = `none`
} else if (allChecked) {
message = `all`
} else {
message = `some`
}
return `You have ${message} of the ingredients for this recipe`
}
const generateIngredientDOM = ingredient => {
const ingredientEl = document.createElement('label')
const containerEl = document.createElement('div')
const checkbox = document.createElement('input')
const ingredientText = document.createElement('span')
const removeButton = document.createElement('button')
recipeStatus.textContent = ingredientSummary(recipeOnPage)
// Setup ingredient container
ingredientEl.classList.add('list-item')
containerEl.classList.add('list-item__container')
ingredientEl.appendChild(containerEl)
// Setup ingredient checkbox
checkbox.setAttribute('type', 'checkbox')
checkbox.checked = ingredient.included
containerEl.appendChild(checkbox)
// Create checkbox button in ingredient div
checkbox.addEventListener('click', () => {
toggleIngredient(ingredient.text)
saveRecipes()
renderIngredients(recipeId)
})
// Setup ingredient text
ingredientText.textContent = ingredient.text
containerEl.appendChild(ingredientText)
// Setup the remove button
removeButton.innerHTML = '<i class="far fa-trash-alt"></i>'
removeButton.classList.add('button', 'button--text')
ingredientEl.appendChild(removeButton)
// Create remove button in ingredient div
removeButton.addEventListener('click', () => {
removeIngredient(ingredient.text)
saveRecipes()
renderIngredients(recipeId)
})
return ingredientEl
}
const renderIngredients = recipeId => {
// Grab the ingredient display from the DOM
const ingredientList = document.querySelector('#ingredients-display')
ingredientList.innerHTML = ''
const recipe = getRecipes().find(item => {
return item.id === recipeId
})
// Iterate through the list of ingredients on the page and render all items from recipeDOM
recipe.ingredients.map(ingredient => {
const ingredientDisplay = generateIngredientDOM(ingredient)
ingredientList.appendChild(ingredientDisplay)
})
saveRecipes()
}
Nothing is happening, as I mentioned above. When I console.log the variable ingredient, I get the new array with the removed item gone, but when I return it, nothing happens.
If I understand the structure correctly, your removeIngredient can probably be trimmed down to:
const removeIngredient = text => {
recipeOnPage.ingredients = recipeOnPage.ingredients.filter(i => i.text !== text);
}
This removes the ingredient which has it's text property same as the text parameter. You are doing of a lot of unnecessary findIndex to get the filtered ingredients and return it. But you're nowhere setting it back to the recipeOnPage object.
Since you aren't using the return value of removeIngredient, you need not return anything