I am a beginner in javascript and I have li which is dynamically inserted by the user and is saved in an array of objects, it has its dynamic random id and I want when the user presses the element, it returns the id of the li and compare it with the movie id so I can find the index and then remove it from the array and remove the li. Thank you for your help in advance.
let n;
function deleteFilm() {
const len = listRoot.children.length;
for (let i = 0; i < len; i++) {
const element = listRoot.children[i]; // listRoot is ul
element.addEventListener('click', () => {
const idToRemove = +element.id;
index = movies.map((object) => object.movieId).indexOf(idToRemove);
console.log(index);
n = index;
});
}
console.log(n);
movies.splice(n, 1);
listRoot.children[n].remove();
if (movies[0] === undefined) {
entryText.style.display = 'block';
}
deleteModal.classList.remove('visible');
blackDrop.classList.remove('visible');
}
acceptButton.addEventListener('click', deleteFilm);
Don't assign all over again clicks (specially not inside a for loop).
Your basic logic could be simplified to this couple of lines:
const movies = [
{id:123, title:"Lorem"},
{id:456, title:"Ipsum"},
{id:789, title:"Dolor"},
];
// Retrieve a movie from array by its ID
const getMovie = (id) => movies.find(mov => mov.id === id);
// Remove a movie Object from array
const deleteMovie = (id) => movies.splice(movies.indexOf(getMovie(id)), 1);
// Task:
const movieId = 456; // The movie ID to delete
deleteMovie(movieId);
console.log(movies); // Test
Then, to delete elements from the DOM, knowing the ID was i.e: 456 all you need is to target the elements that have the data-movieid="456" like:
document.querySelectorAll(`[data-movieid="${movieId}"]`).forEach(el => el.remove());
Example:
// Utility functions:
const ELNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
const ELS = (sel, parent) => (parent || document).querySelectorAll(sel);
const EL = (sel, parent) => (parent || document).querySelector(sel);
// Task:
const getMovie = (id) => movies.find(mov => mov.id === id);
const deleteMovie = (id) => movies.splice(movies.indexOf(getMovie(id)), 1);
const createMovie = (movie) => {
const EL_li = ELNew("li", {
className: "list-item movie",
textContent: movie.title,
onclick() {
deleteMovie(movie.id);
EL_li.remove();
ELS(`[data-movieid="${movie.id}"]`).forEach(el => el.remove());
}
});
EL("#movies").append(EL_li);
};
const movies = [
{id:123, title:"Lorem"},
{id:456, title:"Ipsum"},
{id:789, title:"Dolor"},
];
movies.forEach(createMovie);
Click to delete a movie:
<ul id="movies" class="list"></ul>
<div data-movieid="456">If you click on Ipsum I will be removed too!</div>
Related
I build my first ToDo App, and its almost done. But i meet some trouble with delete items from list. If i create new task and try delete it, its deleted only from DOM, but if i refresh page i can delete task from local storage and from DOM. Please, explain what i make wrong and how to fix that. Thank you for your attention.
<div class="todo_wrapper">
<ul class="todo_tasks-wrapper"></ul>
<form class="control" action="">
<label class="todo_label-form" for="task">
<input class="todo_input" id="task" type="text" placeholder="Enter new task" maxlength="30">
<input class="todo_submit" type="submit" value="+">
</label>
</form>
</div>
const taskList = document.querySelector(".todo_tasks-wrapper");
const formTodo = document.querySelector(".control");
const inputTask = document.querySelector(".todo_input");
const taskKeeper = [];
let taskIdCounter = -1;
const data = JSON.parse(localStorage.getItem("tasks"));
const updateHtml = (taskObj) => {
const newLi = document.createElement("li");
newLi.innerHTML = `<li id="${taskObj.id}" class="item-task">
<span>${taskObj.task}</span>
<button class="cancel-task">
<img src="assets/todo-cancel.png" alt="Cancel">
</button>
</li>`;
taskList.append(newLi);
}
const newTask = (info) => {
taskIdCounter += 1;
const taskObj = {
task: info,
id: taskIdCounter,
};
taskKeeper.push(taskObj);
localStorage.setItem("tasks", JSON.stringify(taskKeeper));
updateHtml(taskObj);
};
formTodo.addEventListener("submit", event => {
event.preventDefault();
const info = inputTask.value.trim();
if(info.length !== 0) {
newTask(info);
inputTask.value = "";
inputTask.focus();
}
});
taskList.addEventListener("click", (event) => {
for (let el of event.composedPath()) {
if (el.matches && el.matches("button.cancel-task")) {
let uniqueId = +(el.parentNode.getAttribute("id"));
for (let itemId of data) {
if(itemId.id === uniqueId) {
let getIndex = data.indexOf(itemId);
data.splice(getIndex, 1);
localStorage.setItem("tasks", JSON.stringify(data));
}
}
el.parentNode.remove();
}
}
});
if(data !== null) {
for (let item of data) {
updateHtml(item);
}
}
Don't use IDs for your tasks. When deleting an element, you can assign a click handler on creation of your "x" button.
Your taskIdCounter is useless since on page refresh you're starting again from 0. Get rid of it. You know now hot to remove Items from a list
Don't use a form if you don't need one.
Use type="button" on your BUTTONElements
Here's a remake of your code:
<ul id="tasks-list"></ul>
<div>
<input id="tasks-text" id="task" type="text" placeholder="Enter new task" maxlength="30">
<button id="tasks-add" type="button">+</button>
</div>
JavaScript
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const els = (sel, par) => (par || document).querySelectorAll(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Tasks:
const elList = el("#tasks-list");
const elText = el("#tasks-text");
const elAdd = el("#tasks-add");
const tasks = JSON.parse(localStorage.tasks ?? "[]");
const taskRemove = (taskObj, elTask) => {
const idx = tasks.indexOf(taskObj);
tasks.splice(idx, 1);
localStorage.tasks = JSON.stringify(tasks);
elTask && elTask.remove();
};
const taskAdd = (text) => {
const taskObj = { task: text };
tasks.push(taskObj);
localStorage.tasks = JSON.stringify(tasks);
taskInsert(taskObj);
};
const taskInsert = (taskObj) => {
const elTask = elNew("li", {
className: "item-task",
innerHTML: `<span>${taskObj.task}</span>`
});
const elRemove = elNew("button", {
type: "button",
innerHTML: "×",
onclick() {
taskRemove(taskObj, elTask);
}
});
elTask.append(elRemove);
elList.append(elTask);
};
elAdd.addEventListener("click", () => {
const info = elText.value.trim();
if (!info.length) return;
taskAdd(info);
elText.value = "";
elText.focus();
});
// Init
tasks.forEach(taskInsert);
As you can see, to get the index of the task to remove, simply pass the original object reference into const idx = tasks.indexOf(item);
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.
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()
i have a search system on my website it works but i can not click on items i search. its a simple script that creates li elements dephended on my search input but how can i add this elements a custom links? because if you can not click on item you searched that does not make any sense... i want to add this const people elements custom links.code:
const people = [
{name:'გადაარჩინე დედამიწა'},
{name:'ანტიმელა'},
{name:'mr.capsule'},
{name:'capsule battle royale'}
];
const list = document.getElementById('list');
function setlist (group) {
clearlist();
for (const person of group) {
const item = document.createElement("li");
item.classList.add("list-group-item");
const text = document.createTextNode(person.name);
item.appendChild(text);
list.appendChild(item);
}
if (group.length === 0) {
setnoresults();
}
}
function clearlist () {
while (list.firstChild) {
list.removeChild(list.firstChild);
}
}
function getrelevency (value, searchTerm) {
if (value === searchTerm) {
return 2;
}else if (value.startsWith(searchTerm)) {
return 1;
}else if (value.includes(searchTerm)) {
return 0;
}
}
function setnoresults () {
const item = document.createElement('li');
item.classList.add('list-group-item');
const text = document.createTextNode('შედეგები ვერ მოიძებნა... სცადეთ თავიდან');
item.appendChild(text);
list.appendChild(item);
}
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (event) => {
let value = event.target.value;
if (value && value.trim().length > 0) {
value = value.trim().toLowerCase();
setlist(people.filter(person => {
return person.name.includes(value);
}).sort((personA, personB) => {
return getrelevency(personB.name, value) - getrelevency(personA.name, value);
}));
}else {
clearlist();
}
});
You should add it in setlist function.
function setlist (group) {
clearlist();
for (const person of group) {
const item = document.createElement("li");
item.classList.add("list-group-item");
const link = document.createElement("a");
link.href = "http://..."; // put here where the link should point
item.appendChild(link);
const text = document.createTextNode(person.name);
link.appendChild(text);
list.appendChild(item);
}
if (group.length === 0) {
setnoresults();
}
}
i got solution with this way checking what search input is and if its targeted search it will add a link on custom search. if statemant is doing everything!
function setlist (group) {
clearlist();
for (const person of group) {
const item = document.createElement("li");
item.classList.add("list-group-item");
if (person.name === "გადაარჩინე დედამიწა") {
const link = document.createElement("a");
link.href = "https://google.com"; // put here where the link should point
link.text = "გადაარჩინე დედამიწა"
item.appendChild(link);
}
const text = document.createTextNode(person.name);
item.appendChild(text);
list.appendChild(item);
}
if (group.length === 0) {
setnoresults();
}
}
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 = "";