Rendering array items after making changes to its properties - javascript

I'm having an issue re-rendering items in an array after changes are made to elements in the array. Whether I add by pushing or remove by splicing, when the array is rendered again on the page, it like more items are being added to the array. So if I push onto the array, the item is added, but the old items are then duplicated into the array. Something similar happens when I remove items. The item looks to be removed, but the elements that were in the array show on the page, they are then duplicated and the item that was spliced is gone.
I'm trying to avoid a location.reload('/edit.html') to refresh the page. Kind of cheating. It seems to work, but I'm trying to get the page to refresh with my renderIngredients function. The toggleIngredient function is also duplicating the list of items when I check an item.
import { initializeEditPage, generateLastEdited } from './views'
import { updateRecipe, removeRecipe, saveRecipes, getRecipes, createIngredient } from './recipes'
const titleElement = document.querySelector('#recipe-title')
const bodyElement = document.querySelector('#recipe-body')
const removeElement = document.querySelector('#remove-recipe')
const addElement = document.querySelector('#add-recipe')
const dateElement = document.querySelector('#last-updated')
const addIngredient = document.querySelector('#new-ingredient')
const recipeStatus = document.querySelector('#recipe-status')
const recipeId = location.hash.substring(1)
const recipeOnPage = getRecipes().find((item) => item.id === recipeId)
titleElement.addEventListener('input', (e) => {
const recipe = updateRecipe(recipeId, {
title: e.target.value
})
dateElement.textContent = generateLastEdited(recipe.updatedAt)
})
bodyElement.addEventListener('input', (e) => {
const recipe = updateRecipe(recipeId, {
body: e.target.value
})
dateElement.textContent = generateLastEdited(recipe.updatedAt)
})
addElement.addEventListener('click', () => {
saveRecipes()
location.assign('/index.html')
})
removeElement.addEventListener('click', () => {
removeRecipe(recipeId)
location.assign('/index.html')
})
addIngredient.addEventListener('submit', (e) => {
const text = e.target.elements.text.value.trim()
e.preventDefault()
if (text.length > 0) {
createIngredient(recipeId, text)
e.target.elements.text.value = ''
}
renderIngredients(recipeId)
saveRecipes()
//location.reload('/edit.html')
})
const removeIngredient = (text) => {
const ingredientIndex = recipeOnPage.ingredients.findIndex((ingredient)=> ingredient.text === text)
if (ingredientIndex > -1) {
recipeOnPage.ingredients.splice(ingredientIndex, 1)
}
saveRecipes()
renderIngredients(recipeId)
//location.reload('/edit.html')
}
const toggleIngredient = (text) => {
const ingredient = recipeOnPage.ingredients.find((ingredient) => ingredient.text === text)
if (ingredient.included) {
ingredient.included = false
} else {
ingredient.included = true
}
//location.reload('/edit.html')
}
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} 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.textContent = 'remove'
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')
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.forEach((ingredient) => {
const recipeDOM = generateIngredientDOM(ingredient)
ingredientList.appendChild(recipeDOM)
})
}
renderIngredients(recipeId)
I believe the issue stems from my renderIngredients function but I can't figure out how to fix it. Again, when I refresh the page, the results I want display, but I want to avoid using location.reload. I'm expecting the removeIngredient function to remove the ingredient with a button click and the page refreshes with the renderIngredients function. Also expecting the toggleIngredient function to just display a checkbox next to the ingredient I checked off, but that's not what's happening. The Same thing is happening when I use the addIngredient function, the ingredient is being added, but the ingredient that was already on the page is being duplicated.

I guess you want to clear the list before adding the elements again:
ingredientList.innerHTML = "";

Related

Getting Duplicates on To Do List, yet local storage unaffected

I'm new to javascript and I am trying to use local storage to save user input. With the code as it is right now, I get the user input saved to local storage, but the list does not save when refreshing the page. Also, the user input appends to the to do list multiple times. Thanks for taking a look.
window.addEventListener('load', () => {
const form = document.querySelector('#new-task-form');
const input = document.querySelector('#new-task-input');
const listElement = document.querySelector('#tasks');
todos = JSON.parse(localStorage.getItem('todos')) || [];
console.log(todos)
form.addEventListener('submit', (e) => {
e.preventDefault();
const task = input.value;
if(!task) {
alert('You definitely have something to do...')
return
}
const todo = {
content: task,
id: Math.floor(Math.random() * 100000)
}
todos.push(todo);
localStorage.setItem('todos', JSON.stringify(todos));
todos.forEach(todo => {
const taskElement = document.createElement('div');
taskElement.classList.add('task');
const taskContentElement = document.createElement('div');
taskContentElement.classList.add("content");
taskElement.appendChild(taskContentElement);
const taskInputElement = document.createElement("input")
taskInputElement.classList.add("text");
taskInputElement.type = "text";
taskInputElement.value = task;
taskInputElement.setAttribute('readonly', 'readonly');
taskContentElement.appendChild(taskInputElement);
const taskActionsElement = document.createElement('div');
taskActionsElement.classList.add("actions");
const taskEditElement = document.createElement('button');
taskEditElement.classList.add('edit');
taskEditElement.innerHTML = "Edit";
const taskDeleteElement = document.createElement('button');
taskDeleteElement.classList.add('delete');
taskDeleteElement.innerHTML = "Delete";
taskActionsElement.appendChild(taskEditElement);
taskActionsElement.appendChild(taskDeleteElement);
taskElement.appendChild(taskActionsElement);
listElement.appendChild(taskElement);
input.value = '';
taskEditElement.addEventListener('click', () => {
if (taskEditElement.innerText.toLowerCase() == 'edit') {
taskInputElement.removeAttribute('readonly');
taskInputElement.focus();
taskEditElement.innerText = "Save";
localStorage.setItem("todos", JSON.stringify(todos));
} else {
taskInputElement.setAttribute('readonly', 'readonly');
taskEditElement.innerText = 'Edit';
}
});
taskDeleteElement.addEventListener('click', () => {
todos = todos.filter(t => t != todo);
localStorage.setItem('todos', JSON.stringify(todos));
listElement.removeChild(taskElement);
});
});
});
});
The user input comes up multiple times when added to the list, depending on when added to the array. I was hoping for it to come up one time, and for it to be a normal list.

Why isn't my classlist removing when using this constructor function

So i'm trying to remove my classlist of selected items when they are clicked again:
const todo = []
const elements = []
const todoList = document.getElementById('todoList')
const addItem = document.getElementById('textBox')
const submit = document.getElementById('submit')
const todoListItem = document.querySelectorAll('.todoListItem')
const todoListItemArr = Array.from(todoListItem)
const deleteItem = document.getElementById('deleteButton')
let selected
class Div {
constructor(content) {
this.content = content
}
addToScreen(){
const element = document.createElement('div')
element.innerHTML = this.content
element.classList.add('todoListItem')
todoList.appendChild(element)
}
select(){
const element = document.querySelectorAll('.todoListItem')
element.forEach(item => {
item.addEventListener('click', () => {
item.classList.add('selectedItem')
selected = item
})
})
}
deselect() {
const elements = document.querySelectorAll('.selectedItem')
elements.forEach(item => {
item.addEventListener('click', () => {
item.classList.remove('selectedItem')
})
})
}
}
function createItem() {
const todoItem = new Div(addItem.value)
todoItem.addToScreen()
todo.push(todoItem.content)
console.log(todo)
addItem.value = ''
todoItem.select()
todoItem.deselect()
}
addItem.addEventListener('keypress', (e) => {
if(e.key == 'Enter') {
createItem()
}
})
submit.addEventListener('click', createItem)
also I'm trying to add a delete function, but everytime i try to remove the element in the constructor by adding a delete function, my delete button does nothing, i've removed those features for now.

Local storage not deleting data until page is refreshed

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');

Taking out specific value from array with objects

Making a todo app, but got stucked at deleting specific value in array, what am i doing wrong and how should i correct it? splice acts same as shift method.
Also is there any other way or data structure that can be used for todo app.
const form = document.querySelector("form");
const todoInput = document.querySelector(".todoInput");
const list = document.querySelector(".renderListHere");
const todoList = [];
form.addEventListener("submit", (event) => {
event.preventDefault();
const text = todoInput.value.trim();
if (text === "") {
console.log("enter something");
} else {
addTodo(text);
todoInput.value = "";
}
});
const addTodo = (text) => {
const todo = {
id: Date.now(),
text,
};
todoList.push(todo);
renderTodo(todo);
};
const renderTodo = ({ text, id }) => {
const li = document.createElement("li");
li.classList.add("todoListItems");
li.innerHTML = `
<span> ${text} </span>
<button id="${id}" class="del-btn"> x
</button>
`;
list.append(li);
};
list.addEventListener("click", (event) => {
if ((event.target.className = "del-btn")) {
const arr = todoList.filter(
(item) => item.id === parseInt(event.target.id)
);
todoList.splice(arr, 1);
}
});
I think you misunderstood the filter method.
The Array.filter() function returns a new array, so in your case, you could use:
todoList = todoList.filter(
(item) => item.id !== parseInt(event.target.id)
);
So you are filtering todoList with only the items with id different than event.target.id, and applying the result to the same todoList variable.
const arr = todoList.filter(
(item) => item.id !== parseInt(event.target.id)
);
}
its return new array
you do not need splice()

How would I delete the same item from UI and localStorage

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 );

Categories