Unable to delete multiple li from ul - javascript

I'm creating a to-do list for class:
Lab 4: Todo List Let's make a simple todo-list which supports the
following operations: add an item to the list remove an item from the
list mark an item as completed Removed items should disappear
entirely. Completed items should appear at the bottom (or in a
separate list) with a line through them.
I'm unable to remove multiple li from my ul. I get an error after removing the first.
lab-04.js:16 Uncaught DOMException: Failed to execute 'removeChild' on
'Node': The node to be removed is not a child of this node. at
HTMLButtonElement.removeBtn.onclick
(http://127.0.0.1:5500/js/lab-04/lab-04.js:16:18)
Oddly, enough the buttons are removed without much fuss.
The code (js):
let manipulateDom = () => {
let container = document.getElementsByClassName('container')
let toDoList = document.getElementById('to-do')
let removeBtn = document.createElement('button')
content = document.getElementById('userInput').value
listItem = document.createElement('li')
listItem.className = 'list-item'
listItem.textContent = (content)
removeBtn.appendChild(document.createTextNode('remove'))
removeBtn.onclick = function() {
toDoList.removeChild(removeBtn)
toDoList.removeChild(listItem)
}
toDoList.appendChild(listItem)
toDoList.appendChild(removeBtn)
}
(html):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lab-04</title>
<script src='/js/lab-04/lab-04.js'></script>
</head>
<body>
<h4>To Do List</h4>
<input type='text' id='userInput' placeholder="item to add">
<input type="submit" onclick="manipulateDom()">
<ul id='to-do'>
</ul>
<ul id='done'>
</ul>
</body>
</html>
Any help is greatly appreciated, ready to pull my hair out

You are putting your listItem in the window scope, and what removeBtn.onclick does is removing the listItem in the window scope, that's why the remove button only works once and only works on the last element created.
Declare listItem in the block scope and it should be working again
let manipulateDom = () => {
let container = document.getElementsByClassName('container');
let toDoList = document.getElementById('to-do');
let removeBtn = document.createElement('button');
let content = document.getElementById('userInput').value;
let listItem = document.createElement('li');
listItem.className = 'list-item';
listItem.textContent = (content);
removeBtn.appendChild(document.createTextNode('remove'))
removeBtn.onclick = function() {
toDoList.removeChild(removeBtn);
toDoList.removeChild(listItem);
};
toDoList.appendChild(listItem);
toDoList.appendChild(removeBtn);
}
<h4>To Do List</h4>
<input type='text' id='userInput' placeholder="item to add">
<input type="submit" onclick="manipulateDom()">
<ul id='to-do'>
</ul>
<ul id='done'>
</ul>

Related

Having trouble with ToDo List App with saving to localStorage

Super new to all of this so this might be some beginner troubleshooting. The list seems to be working where I'm adding a list element to the UL with a checkbox and delete button. When checkbox is checked it puts a line through the text and when the delete button is clicked it deletes the list element. The assignment asks to save to localStorage so that when refreshed, the list items still remain, and I'm getting super confused by this. What I have now seems to be saving my list elements to an array but I don't understand how to get them to save and stay on the page.
const form = document.querySelector('form');
const input = document.querySelector('#todoInput');
const newElement = document.querySelector('ul');
const savedToDos = JSON.parse(localStorage.getItem('todos')) || [];
newElement.addEventListener('click', function(e) {
if(e.target.tagName === 'BUTTON') {
e.target.parentElement.remove()
}
})
function addToList(text) {
const li = document.createElement('li');
const checkbox = document.createElement('input');
const button = document.createElement('button');
button.innerText = "Delete";
checkbox.type = 'checkbox';
checkbox.addEventListener('change', function() {
li.style.textDecoration = checkbox.checked ? 'line-through' : 'none';
})
li.innerText = text;
li.insertBefore(checkbox, li.firstChild);
li.appendChild(button);
return li;
};
form.addEventListener('submit', function(e) {
e.preventDefault();
const newListItem = addToList(input.value);
input.value = '';
newElement.append(newListItem);
savedToDos.push(newListItem.innerText);
localStorage.setItem('todos', JSON.stringify(savedToDos));
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ToDo App</title>
<link rel="stylesheet" href="app.css">
</head>
<body>
<div>
<h1>Todo List</h1>
<form action="">
<input type="text" id="todoInput" placeholder="Add To Todo List">
<button class="add-button">Add</button>
</form>
<ul id="todoList">
</ul>
</div>
<script src="app.js"></script>
</body>
</html>
It looks like you're failing to populate the DOM when the page loads.
After you retrieve the items from local storage (which you're already doing), loop through the list and add each of them to the DOM:
// After this line, which you've already written:
const savedToDos = JSON.parse(localStorage.getItem('todos')) || [];
// Loop through savedToDos, and for each one, insert a new list:
savedToDos.forEach(function(value) {
const newListItem = addToList(value);
newElement.append(newListItem);
});
Every browser has local storage where we can store data and cookies. just go to the developer tools by pressing F12, then go to the Application tab. In the Storage section expand Local Storage.
this piece of code might help you
// Store Task
function storeTaskInLocalStorage(task) {
let tasks;
if(localStorage.getItem('tasks') === null){
tasks = [];
} else {
tasks = JSON.parse(localStorage.getItem('tasks'));
}
tasks.push(task);
localStorage.setItem('tasks', JSON.stringify(tasks));
}

Currently working on a todo app excerise but STUCKK in saving and pulling localStorage logic

I don't have coding experience, am in the process of learning.
I'm very very confuse as to what/how I should proceed setItem to localStorage and getItem from localStorage . So when webpage refresh, saved todo items would still be there.
I seen quite a few youtube videos and blog posts, but cant quite seem to understand .
I know I need to
-push input value into an array
-save that to localStorage with JSON.stringify
-when page refresh, check if there's data in localStorage
-if true, getItem from localStorage with JSON.parse
-if false, do nothing.
Can someone please explain like I'm five.
const toDoForm = document.querySelector('#todo-form');
const toDoInput = document.querySelector('#todo');
const ulList = document.querySelector('#ulList');
let dataArray = [];
toDoForm.addEventListener('submit', function (e) {
//stop submit event from refreshing
e.preventDefault();
//when submit -> create a element <li> link it with variable newLi
//Fill new <li>innerText</li> with toDoInput's value
const newLi = document.createElement('li');
newLi.innerText = toDoInput.value;
//when submit -> create a element <button></button> link it with variable btn
//<button>x</button>
//append <button>x</button> to newLi <li><button>x</button></li>
const btn = document.createElement('button');
btn.innerText = 'x';
newLi.appendChild(btn);
//add newLi <li><button>x</button></li> to ulList<ul></ul>
ulList.appendChild(newLi);
//push input into an empty array called dataArray
dataArray.push(toDoInput.value);
localStorage.setItem('localData', JSON.stringify(dataArray));
//when submit -> after all the above is done, we will set the input field to empty string
toDoInput.value = '';
});
ulList.addEventListener('click', function (e) {
if (e.target.tagName === 'BUTTON') {
e.target.parentElement.remove();
} else if (e.target.tagName === 'LI') {
e.target.classList.toggle('line');
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>TO DO LIST</h1>
<ul id="ulList">
</ul>
<form action="" id="todo-form">
<label for="todo">Write down things to do</label>
<input type="text" id="todo" name="todo">
<input type="submit">
</form>
<script src="app.js"></script>
</body>
</html>
You can extract the code to add a TODO item to a function and then call that function for each element in the array stored in localStorage if it is found.
let dataArray = localStorage.getItem('localData') ? JSON.parse(localStorage.getItem('localData')): [];
dataArray.forEach(addTodo);
function addTodo(todo){
const newLi = document.createElement('li');
newLi.innerText = todo;
//when submit -> create a element <button></button> link it with variable btn
//<button>x</button>
//append <button>x</button> to newLi <li><button>x</button></li>
const btn = document.createElement('button');
btn.innerText = 'x';
newLi.appendChild(btn);
//add newLi <li><button>x</button></li> to ulList<ul></ul>
ulList.appendChild(newLi);
}
toDoForm.addEventListener('submit', function (e) {
//stop submit event from refreshing
e.preventDefault();
addTodo(toDoInput.value);
dataArray.push(toDoInput.value);
localStorage.setItem('localData', JSON.stringify(dataArray));
//when submit -> after all the above is done, we will set the input field to empty string
toDoInput.value = '';
});
Demo

MDL upgrade all dynamic list elements on each tab

I have 4 mdl tabs where I dynamically create a list of mdl cards in each one. Each card has a mdl-menu created like this:
// job settings dropdown :
settingsButton.setAttribute('id', 'jobSettings');
var jobUl = document.createElement('ul');
jobUl.className = 'mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect';
jobUl.setAttribute('for', 'jobSettings');
var jobLi = document.createElement('li');
var jobLi2 = document.createElement('li');
var jobLi3 = document.createElement('li');
jobLi.className = 'mdl-menu__item';
jobLi2.className = 'mdl-menu__item';
jobLi3.className = 'mdl-menu__item';
jobLi.textContent = 'Edit';
jobLi2.textContent = 'Delete';
jobLi3.textContent = 'Pay';
jobUl.appendChild(jobLi);
jobUl.appendChild(jobLi2);
jobUl.appendChild(jobLi3);
Which would show up in html something like this:
<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect"
for="demo-menu-lower-right">
<li class="mdl-menu__item">Edit</li>
<li class="mdl-menu__item">Delete</li>
<li class="mdl-menu__item">Pay</li>
Then I do componentHandler.upgradeDom(); hoping to upgrade the mdl-menu items in each unordered list (ul).
This only seems to work for ONE list element (li) in the entire website. it doesn't work for any other element on any other tab.
How do I make my mdl-menu work on each dynamically created list element in each dynamically created ul?
The issue could be that you are duplicating the id value across multiple menu elements (MDL will get confused if there is more than one button with an id value that matches the for value on your menu element). You could add a numerical increment to your id and for values to ensure that each menu is uniquely identified. See the following example.
const fragment = document.createDocumentFragment();
const addCard = (n) => {
const card = document.createElement('div');
const menu = document.createElement('div');
const button = document.createElement('button');
const icon = document.createElement('i');
const ul = document.createElement('ul');
card.className = 'mdl-card mdl-shadow--2dp';
menu.className = 'mdl-card__menu';
button.id = `menu${n}`;
button.className = 'mdl-button mdl-js-button mdl-button--icon';
icon.className = 'material-icons';
icon.textContent = 'more_vert';
ul.className = 'mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect';
ul.setAttribute('for', `menu${n}`);
fragment.appendChild(card);
card.appendChild(menu);
menu.appendChild(button);
button.appendChild(icon);
menu.appendChild(ul);
for (const action of ['Action 1', 'Action 2', 'Action 3']) {
const li = document.createElement('li');
li.textContent = action;
li.className = 'mdl-menu__item';
ul.appendChild(li);
}
};
for (let i = 0; i < 2; i++) {
addCard(i);
}
document.querySelector('#container').appendChild(fragment);
componentHandler.upgradeDom();
.mdl-card {
margin: 8px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Material Design Lite Cards / Menus</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
</head>
<body>
<div id="container"></div>
<script src="https://code.getmdl.io/1.3.0/material.min.js"></script>
</body>
</html>

JavaScript DOM Issue

I'm having trouble retrieving the textContent of some of my elements. The issue arises from line 88-91. Line 91 retrieves a value of null. I don't understand why this is so. I was able to verify this by setting line 91 to a variable and console.log() to the console and the value was returned as null. Additionally, at line 99 my console is giving me an error of, "Uncaught TypeError: Cannot read property 'firstChild' of undefined". I'm assuming this error is related to the issue I'm having at lines 88-91, but I'm not entirely sure because it says undefined and not null. The HTML elements have text inside of them so I don't know why it would be returning null. I'm not sure what I'm missing. Any insight is greatly appreciated. Thanks in advance for you guys insight!
HTML Markup:
(function(){
const taskList = document.querySelector('ul');
const completedList = document.querySelector('#completed-task');
const editTask = document.querySelector('#edit-task');
const cover = document.querySelector('.cover');
const editedInfo = document.querySelector('#edited-info');
const editedDate = document.querySelector('#edited-date');
const editedName = document.querySelector('edited-item');
let editedListItem;
document.querySelector('button').addEventListener('click', function(e) {
/* Preventing Page Default Page Refresh*/
e.preventDefault();
/* Grabbing User Input */
let userInput = document.querySelector('input').value;
let userDate = document.getElementById('date').value;
let userInfo = document.getElementById('userInfo').value;
/* Creating Elements */
let listItem = document.createElement('li');
let container = document.createElement('div');
let item = document.createElement('span');
let deletebutton = document.createElement('span');
/* Update: February 4, 2019 --> Creating Info Box */
let infoBox = document.createElement('div');
let dueDate = document.createElement('p');
let info = document.createElement('p');
let span = document.createElement('span');
/* Update: February 5, 2019 --> Creating Edit button */
let edit = document.createElement('span');
/* Adding Attributes */
container.setAttribute('class', 'container');
deletebutton.setAttribute('class','delete');
infoBox.setAttribute('class','info');
edit.setAttribute('class', 'edit');
/* Setting User Input */
item.textContent = userInput;
span.textContent = userDate;
info.textContent = userInfo;
deletebutton.textContent = "Discard";
dueDate.textContent = "Due: ";
edit.textContent = "Edit";
/* Adding Elements to List */
taskList.appendChild(listItem);
listItem.appendChild(container);
container.appendChild(item);
container.appendChild(deletebutton);
listItem.appendChild(infoBox);
infoBox.appendChild(dueDate);
infoBox.appendChild(info);
dueDate.appendChild(span);
infoBox.appendChild(edit);
});
document.querySelector('#task-list').addEventListener('click',function(event){
if(event.target.className == 'delete') {
const li = event.target.parentElement;
li.parentElement.removeChild(li);
}
});
/* Update: February 4, 2019 --> Hide Tasks Checkbox */
document.forms['hide-task'].addEventListener('change', function(event){
const checkbox = document.querySelector('input[type="checkbox"]');
if (checkbox.checked === true) {
taskList.style.display = 'none';
} else {
taskList.style.display = 'block';
}
});
taskList.addEventListener('click', function(event){
if (event.target.className == 'complete') {
let parent = event.target.parentElement.parentElement;
completedList.appendChild(parent);
}
/******* ISSUE OCCURS IN THIS CODE BLOCK *************/
if (event.target.className == 'edit') {
event.preventDefault();
editTask.style.display = 'flex';
cover.style.display = 'block';
editedDate.value = event.target.previousSibling.previousSibling.textContent;
editedInfo.value = event.target.previousSibling.textContent;
editedName.value = event.target.parentElement.previousSibling.firstChild.textContent;
editedListItem = event.target.parentElement.parentElement;
}
});
document.querySelector('#change').addEventListener('click', function(event){
editedListItem.firstChild.firstChild.textContent = editedName.value;
editedListItem.lastChild.firstChild.textContent = editedDate.value;
editedListItem.lastChild.lastChild.textContent = editedInfo.value;
editTask.style.display = 'none';
cover.style.display = 'none';
});
})()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>To Do List</title>
</head>
<body>
<div class="cover"></div>
<h1>To do List</h1>
<form>
<div id="input-button">
<input type="text" placeholder="Add an item..." class="item">
<button type="submit" class="add">+</button>
</div>
<div>
<input type="text" placeholder="Due Date..." id="date">
<input type="text" placeholder="About the task..." id="userInfo">
</div>
</form>
<hr>
<ul id="task-list">
<li>
<div class="container">
<span class="title">Example Task</span>
<span class="delete">Discard</span>
</div>
<!-- Update: Feb #, 2019 -->
<div class="info">
<p>Due: <span>January 15, 2019</span></p>
<p>This is some example text that adds additional context or information for the task</p>
<span class="edit">Edit</span>
</div>
</li>
</ul>
<!-- Update: February 5, 2019 -->
<ul id="completed-task">
<li id="title">Completed Tasks</li>
</ul>
<!-- Update: February 4, 2019 -->
<form id="hide-task">
<div>
<input type="checkbox" name="hide-task">
<label for="hide-task">Hide Tasks</label>
</div>
</form>
<form id="edit-task">
<input type="text" placeholder="Edit item..." id="edited-item">
<input type="text" placeholder="Edit Due Date..." id="edited-date">
<input type="text" placeholder="Edit about the task..." id="edited-info">
<button id="change" type="button">Change</button>
</form>
</body>
</html>
After a break and some dinner, I started debugging with a fresh set of eyes and an unfrustrated mind and found the bugs. Everything is now working properly.
1st Issue:
A Typo in the querySelector was responsible for one of the variables to return
textContent error.
Solution:
const editedName = document.querySelector('#edited-item');
2nd Issue:
For the most part previousSibling and previousElementSibling return the element, but
previousSibling will return any kind of sibling. Some browsers add white space around
the elements and so previousSibling will grab the white space around the element
instead, but previousElementSibling always grabs the Element. My initial code was
grabbing the white space around the element.
Solution:
taskList.addEventListener('click', function(event){
if (event.target.className == 'complete') {
let parent = event.target.parentElement.parentElement;
completedList.appendChild(parent);
}
if (event.target.className == 'edit') {
event.preventDefault();
editTask.style.display = 'flex';
cover.style.display = 'block';
editedDate.value = event.target.previousElementSibling.previousElementSibling.firstElementChild.textContent;
editedInfo.value = event.target.previousElementSibling.textContent;
editedName.value = event.target.parentElement.previousElementSibling.firstElementChild.textContent;
editedListItem = event.target.parentElement.parentElement;
}
});
document.querySelector('#change').addEventListener('click', function(event){
editedListItem.firstElementChild.firstElementChild.textContent = editedName.value;
editedListItem.lastElementChild.firstElementChild.firstElementChild.textContent = editedDate.value;
editedListItem.lastElementChild.firstElementChild.nextElementSibling.textContent = editedInfo.value;
editTask.style.display = 'none';
cover.style.display = 'none';
});

AppendChild to JS dynamically created element without redundant code

I'm trying to build a simple todo list. I would like to add a (x) to each item in the list. The list has two existing items, and the rest will be added from user input.
My current code can only add (x) to existing items. I followed the following tutorial, but i think the way it adds (x) to both existing items and new input items are redundant. (note it basically uses "var span = document.createElement("SPAN"); ..." twice.
Is there a way that I can append the (x) in the end to all li items in the document?
// Create a "close" button and append it to each list item
var allListItems = document.getElementsByTagName("li");
function appendClose(x){
var close = document.createElement("span");
var text = document.createTextNode("(\u00D7)");
close.appendChild(text);
return x.appendChild(close);
}
// Turn object into array.
const peopleArray = Object.keys(allListItems).map(i => allListItems[i]);
console.log(peopleArray);
peopleArray.map(appendClose);
// Create new list item after button click.
function createNewElement(){
var li = document.createElement("li"); // create <li>
var v_userInput = document.getElementById("myInput");
var content = document.createTextNode(v_userInput.value);
li.appendChild(content);
document.getElementById("myUL").appendChild(li);
document.getElementById("myInput").value = ""; //fresh the input box;
};
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Work to-do</title>
</head>
<body>
<h1> Work to-do </h1>
<div id="myDiv">
<input id="myInput" type="text" placeholder="New item..." maxlength="27">
<button id="enter" onclick="createNewElement()">Add</button>
</div>
<ul id="myUL">
<li>Gym</li>
<li>Food</li>
</ul>
</body>
<script type="text/javascript" src="7_todo.js"></script>
</html>
You just have to write one more line inside createNewElement() to do that:
appendClose(li);
Further, you should also prevent empty values being appended to todo-list by checking the length of input value. I've added an example of that.
// Create a "close" button and append it to each list item
var allListItems = document.getElementsByTagName("li");
function appendClose(x){
var close = document.createElement("span");
var text = document.createTextNode("(\u00D7)");
close.appendChild(text);
return x.appendChild(close);
}
// Turn object into array.
const peopleArray = Object.keys(allListItems).map(i => allListItems[i]);
// console.log(peopleArray);
peopleArray.map(appendClose);
// Create new list item after button click.
function createNewElement(){
var li = document.createElement("li"); // create <li>
var v_userInput = document.getElementById("myInput");
// Prevents empty task in todo list
if(v_userInput.value.length === 0) {
alert('Enter something!');
return ;
}
var content = document.createTextNode(v_userInput.value);
li.appendChild(content);
appendClose(li); // Edited here
document.getElementById("myUL").appendChild(li);
document.getElementById("myInput").value = ""; //fresh the input box;
};
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Work to-do</title>
</head>
<body>
<h1> Work to-do </h1>
<div id="myDiv">
<input id="myInput" type="text" placeholder="New item..." maxlength="27">
<button id="enter" onclick="createNewElement()">Add</button>
</div>
<ul id="myUL">
<li>Gym</li>
<li>Food</li>
</ul>
</body>
<script type="text/javascript" src="7_todo.js"></script>
</html>

Categories

Resources