output .appendChild on <li> and <span> elements within a map function - javascript

I am storing the todo-items on an array of objects. I want to map the array to get the HTML elements. I am able to do this using innerHTML but I want to do it with createElement.
I am expecting <li><span>TEXT</span></li> output but I am getting only span. It seems append is not working.
HTML code :
<div class="todo">
<h1>Todo</h1>
<form name="todo--form">
<input type="text" name="todo--input" />
<button
type="submit"
name="todo--submit"
class="p-2 bg-slate-500 rounded"
>
Add
</button>
</form>
<ul class="todo--list"></ul>
</div>
JS Code :
const addTodoForm = document.querySelector(`[name="todo--form"]`);
const todoList = document.querySelector('.todo--list');
const todos = [];
// function getLabel() {}
function handleSubmit(e) {
e.preventDefault();
const text = this.querySelector(`[name="todo--input"]`).value;
const item = {
text: text,
finished: false,
};
if (text) {
todos.push(item);
addTodoForm.reset();
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.append(newSpan);
return newEl;
});
console.log(todoItems);
todoList.append(...todoItems);
}
return;
}
addTodoForm.addEventListener('submit', handleSubmit);
I am getting only <span><span> as output.

When the <form> is submitted there's only one item being appended to list at a time so there's no need to build a one item array. BTW event handlers (functions that are bound to an event) don't return values like a normal function can, but there are indirect ways to get values from them (like from your OP code, that array outside of the function pushing objects).
Details are commented in example
// Reference <form>
const form = document.forms.todo;
// Reference <ul>
const list = document.querySelector('.list');
// Define array for storage
let tasks = [];
// Bind "submit" event to <form>
form.onsubmit = handleSubmit;
// Event handlers passes Event Object by default
function handleSubmit(e) {
// Stop default behavior during a "submit"
e.preventDefault();
// Reference all form controls
const IO = this.elements;
/*
Define {item}
Add current timestamp to object {item}
Add <input> text to {item}
Add {item} to [tasks]
Clear <input>
*/
let item = {};
item.log = Date.now();
item.task = IO.data.value;
tasks.push(item);
IO.data.value = '';
/*
Create <time>
Assign timestamp <time>
Convert timestamp into a readable date and time
Add formatted datetime as text to <time>
*/
const log = document.createElement('time');
log.datetime = item.log;
let date = new Date(item.log);
log.textContent = date.toLocaleString();
/*
Create <output>
Add text of <input> to <output>
*/
const txt = document.createElement('output');
txt.value = item.task;
/*
Create <button>
Add type, class, and the text: "Delete" to <button>
*/
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'delete';
btn.textContent = 'Delete';
// Create <li>
const li = document.createElement('li');
// Append like crazy
li.append(log);
li.append(txt);
li.append(btn);
list.append(li);
console.log(tasks);
}
html {
font: 300 2ch/1.2 'Segoe UI';
}
input,
button {
font: inherit;
}
output {
display: inline-block;
margin: 0 8px 8px;
}
button {
cursor: pointer;
}
.as-console-row::after {
width: 0;
font-size: 0;
}
.as-console-row-code {
width: 100%;
word-break: break-word;
}
.as-console-wrapper {
max-height: 30% !important;
max-width: 100%;
}
<form id='todo'>
<fieldset>
<legend>ToDo List</legend>
<input id='data' type="text" required>
<button class="p-2 bg-slate-500 rounded">Add</button>
<ul class="list"></ul>
</fieldset>
</form>

const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.append(newSpan);
return newLi;
});

In this block of code, I believe the newEl variable being created isn't correct. Try changing newLi.append(newSpan) to newLi.appendChild(newSpan)
This is also listed below
if (text) {
todos.push(item);
addTodoForm.reset();
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.appendChild(newSpan);
return newEl;
});
console.log(todoItems);
todoList.append(...todoItems);
}
return;

Thanks for all the answers.
Upon reading on the documentation of append and appendChild, it seems elements cannot be saved into a variable upon using these methods.
I just need to return the append li element.
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newLi.setAttribute('data-index', i);
const todoTextNode = document.createTextNode(todo.text);
newSpan.appendChild(todoTextNode);
newLi.appendChild(newSpan);
return newLi;
});

Related

How do I let a user edit the textcontent of a specific cell in a table?

So I'm new to programming, and I'm creating a website where a user can enter information about their favourite books into a table.
However I've been trying to add an edit button feature where a user can click a button on a specific cell of the table and then be prompted to write in the replacement information.
So for example if they've already entered in the name of an author, they can click the edit button next to the info in the cell and they'll be prompted to enter in the replacement author's name, and it'll then reset the info in the cell in the table.
function addBooks() {
//Below is adding the users input information to the table.
let info = document.getElementById("author").value;
let info2 = document.getElementById("title").value;
let info3 = document.getElementById("genre").value;
let info4 = document.getElementById("review").value;
document.getElementById("author").value = "";
document.getElementById("title").value = "";
document.getElementById("genre").value = "";
document.getElementById("review").value = "";
let obj = {
author: info,
title: info2,
genre: info3,
review: info4,
};
let table = document.getElementById("table");
const row = table.insertRow(1);
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
var cell3 = row.insertCell(2);
var cell4 = row.insertCell(3);
//Below is the delete button which deletes a specific book/row.
var deleteButton = document.createElement("button");
deleteButton.classList.add("delete");
deleteButton.type = "button";
deleteButton.textContent = "Delete Book";
deleteButton.addEventListener("click", (event) => {
var row = deleteButton.parentNode.parentNode;
row.parentNode.removeChild(row);
});
cell1.innerHTML = `${obj.author}<button class="edit">Edit</button>`;
cell2.innerHTML = `${obj.title}<button class="edit">Edit</button>`;
cell3.innerHTML = `${obj.genre}<button class="edit">Edit</button>`;
cell4.innerHTML = `${obj.review}<button class="edit">Edit</button>`;
cell4.appendChild(deleteButton);
//Below here I am trying to addEvent listeners to the edit buttons that activate a function where the user can re-edit and enter new information into a specific cell.
const editButtons = document.getElementsByClassName("edit");
for (var i = 0; i < editButtons.length; i++) {
editButtons[i].addEventListener("click", (e) => {
editButtons.parentNode.innerHTML = prompt("Enter corrected info:");
});
}
}
Above is the Javascript code, but when I click on an edit button I get this error in the console :
books.js:47 Uncaught TypeError: Cannot set properties of undefined (setting 'innerHTML')
at HTMLButtonElement.<anonymous> (books.js:47:40)
I'm not sure what this means, but I was trying to be able to edit the text content of the parentNode. Is this the right way to to access and rewrite the text in the tables cells?
Here is also my html for reference.
<body>
<div class="container">
<h1 class="heading">Your Books</h1>
<p class="subHeading">Author</p>
<input type="text" id="author" />
<p class="subHeading">Title</p>
<input type="text" id="title" />
<p class="subHeading">Genre</p>
<input type="text" id="genre" />
<p class="subHeading">Reviews</p>
<input type="text" id="review" />
</div>
<button class="btn" onclick="addBooks()" id="button">Submit</button>
<table id="table">
<tr>
<th>Author</th>
<th>Title</th>
<th>Genre</th>
<th>Reviews</th>
</tr>
</table>
<script src="books.js"></script>
</body>
I hope that I've phrased things clear enough. Thanks a lot!
Never use innerHTML from unsanitized user inputs. Use textContent instead.
Function addBooks should be named addBook. Singular.
Use <thead> and specifically <tbody> as your target table element to insert rows into
Assign events on Element creation.
Create a separate ELNew_TD function to ease repetitive tasks
(TODO: don't use prompt() )
Here's a quick remake using some nifty DOM helper functions to make the code more readable:
// DOM utility functions
const ELNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
const ELS = (sel, par) => (par || document).querySelectorAll(sel);
const EL = (sel, par) => (par || document).querySelector(sel);
// TASK:
const EL_author = EL("#author");
const EL_title = EL("#title");
const EL_genre = EL("#genre");
const EL_review = EL("#review");
const EL_table = EL("#table tbody");
const EL_add = EL("#button");
const ELNew_TD = (val) => {
const EL_td = ELNew("td");
const EL_span = ELNew("span", {
textContent: val
});
const EL_edit = ELNew("button", {
type: "button",
className: "delete",
textContent: "Edit",
onclick() {
val = prompt("Enter corrected info:");
val && (EL_span.textContent = val);
}
});
EL_td.append(EL_span, EL_edit);
return EL_td;
};
const addBook = () => {
const EL_tr = ELNew("tr");
const EL_btnDel = ELNew("button", {
type: "button",
textContent: "Delete",
onclick() { EL_tr.remove(); },
});
const EL_td5 = ELNew("td");
EL_td5.append(EL_btnDel);
EL_tr.append(
ELNew_TD(EL_author.value),
ELNew_TD(EL_title.value),
ELNew_TD(EL_genre.value),
ELNew_TD(EL_review.value),
EL_td5,
);
EL_table.append(EL_tr);
// Clear form
EL_author.value = "";
EL_title.value = "";
EL_genre.value = "";
EL_review.value = "";
};
EL_add.addEventListener("click", addBook);
label {display: block; padding: 5px 0;}
label span {display: inline-block; min-width: 100px;}
<div class="container">
<h1 class="heading">Your Books</h1>
<label><span>Author</span><input type="text" id="author"></label>
<label><span>Title</span><input type="text" id="title"></label>
<label><span>Genre</span><input type="text" id="genre"></label>
<label><span>Reviews</span><input type="text" id="review"></label>
</div>
<button class="btn" id="button">Add Book</button>
<table id="table">
<thead>
<tr>
<th>Author</th><th>Title</th><th>Genre</th><th>Reviews</th><th></th>
</tr>
</thead>
<tbody></tbody>
</table>
In the end I managed to fix the edit button so that it would remain functional no matter how many times it was used.
const editButtons = document.getElementsByClassName("edit");
for (var i = 0; i < editButtons.length; i++) {
editButtons[i].addEventListener("click", (e) => {
editButtons.parentNode.firstChild.innerHTML = prompt("Enter corrected info:");
});
}
}
This is the code that was used for the edit button function!
In your loop, use the “e” parameter you are passing into the event handler function to reference the elements (e.target).

How to delete a DOM element from an array after you've clicked on it?

I was making a simple to-do list. You submit itens from an input and they go to the To-DO section. When you click over them they go to the 'Done' section. And when you click on them again, they vanish forever. It was all working fine.
But I realized the doneItens array kept growing in length, which I wanted to optimize. So I came up with this line of code
doneItens.splice(i, 1);
which goes inside an onclick event, which you can see in the code inside the deleteDone function.
That gives the error, though,
Error:{
"message": "Uncaught TypeError: doneItens.splice is not a function"
If I put it outside and below the onclick event it also doesn't work. How can I do it?
var input = document.getElementById('play');
var toDo = document.getElementsByTagName('ol')[0];
var done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
const newItem = document.createElement('li');
newItem.setAttribute('class', 'item');
newItem.append(input.value);
toDo.append(newItem);
input.value='';
deleteItem();
}
function deleteItem() {
const toBeDone = document.getElementsByClassName('item');
for(let i = 0; i < toBeDone.length; i++) {
toBeDone[i].onclick = () => {
appendItemDone(toBeDone[i]);
toBeDone[i].style.display = 'none';
deleteDone();
}
}
}
function appendItemDone(item) {
const newDone = document.createElement('li');
newDone.setAttribute('class', 'feito')
newDone.append(item.innerText);
done.append(newDone);
}
function deleteDone() {
const doneItens = document.getElementsByClassName('feito');
console.log('done length', doneItens.length)
for (let i = 0; i < doneItens.length; i++) {
doneItens[i].onclick = () => {
doneItens[i].style.display = 'none';
doneItens.splice(i, 1);
}
}
}
<div id='flex'>
<form class='form' onsubmit='handleSubmit(event)'>
<input placeholder='New item' type='text' id='play'>
<button>Send</button>
</form>
<div id='left'>
<h1 id='todo' >To-do:</h1>
<p class='instruction'><i>(Click over to mark as done)</i></p>
<ol id='here'></ol>
</div>
<div id='right'>
<h1>Done:</h1>
<p class='instruction'><i>(Click over to delete it)</i></p>
<p id='placeholder'></p>
<ol id='done'></ol>
</div>
</div>
With the use of JavaScript DOM API such as Node.removeChild(), Element.remove() and Node.parentNode, your task can be solved with this code:
const input = document.getElementById('play');
const todo = document.getElementById('todo');
const done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
// create new "todo" item
const newTodo = document.createElement('li');
newTodo.textContent = input.value;
todo.append(newTodo);
// clean the input field
input.value = '';
// listen to "click" event on the created item to move it to "done" section
newTodo.addEventListener('click', moveToDone);
}
function moveToDone(event) {
// remove "click"-listener to prevent event listener leaks
event.target.removeEventListener('click', moveToDone);
// move clicked todo-element to "done" section
const newDone = event.target.parentNode.removeChild(event.target);
done.append(newDone);
// listen to "click" event on the moved item to then completely delete it
newDone.addEventListener('click', removeFromDone);
debugElementsLeak();
}
function removeFromDone(event) {
// remove "click"-listener to prevent event listener leaks
event.target.removeEventListener('click', removeFromDone);
// complete remove clicked element from the DOM
event.target.remove();
debugElementsLeak();
}
function debugElementsLeak() {
const todoCount = todo.childElementCount;
const doneCount = done.childElementCount;
console.log({ todoCount, doneCount });
}
<div id="flex">
<form class="form" onsubmit="handleSubmit(event)">
<input placeholder="New item" type="text" id="play">
<button>Add item</button>
</form>
<div id="left">
<h1>To-do:</h1>
<p class="instruction"><em>(Click over to mark as done)</em></p>
<ol id="todo"></ol>
</div>
<div id="right">
<h1>Done:</h1>
<p class="instruction"><em>(Click over to delete it)</em></p>
<p id="placeholder"></p>
<ol id="done"></ol>
</div>
</div>
You'll want to use splice,
and then rather than use hidden, 'refresh' the done element by adding all elements in the spliced array.
I've commented my code where I've made changes and why
var input = document.getElementById('play');
var toDo = document.getElementsByTagName('ol')[0];
var done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
const newItem = document.createElement('li');
newItem.setAttribute('class', 'item');
newItem.append(input.value);
toDo.append(newItem);
input.value='';
deleteItem();
}
function deleteItem() {
const toBeDone = document.getElementsByClassName('item');
for(let i = 0; i < toBeDone.length; i++) {
toBeDone[i].onclick = () => {
appendItemDone(toBeDone[i].cloneNode(true));
toBeDone[i].style.display = 'none';
deleteDone();
}
}
}
function appendItemDone(item) {
const newDone = document.createElement('li');
newDone.setAttribute('class', 'feito')
newDone.append(item.innerText);
done.append(newDone);
}
function deleteDone() {
var doneItens = document.getElementsByClassName('feito');
for (let i = 0; i < doneItens.length; i++) {
doneItens[i].onclick = () => {
var splicedArray = spliceFromArray(doneItens,doneItens[i]);// NEW BIT -CALL NEW SPLICE FUNCTION
done.innerHTML=""; // NEW BIT - SET OVERALL DONE TO BLANK ON DELETE
for(var index in splicedArray){// NEW BIT - fOR EACH RETURNED ELEMENT IN THE SPLICE, ADD IT TO THE OVERALL DONE ELEMENT
done.appendChild(splicedArray[index]);
}
}
}
}
function spliceFromArray(arrayInput,element){// NEW BIT - SPLICE FUNCTION THAT RETURNS SPLICED ARRAY
var array = Array.from(arrayInput);
var index = array.indexOf(element);
if(index!=-1){
if(array.length==1 && index == 0){
array = [];
}
else{
array.splice(index,1);
}
}
return array;
}
<div id='flex'>
<form class='form' onsubmit='handleSubmit(event)'>
<input placeholder='New item' type='text' id='play'>
<button>Send</button>
</form>
<div id='left'>
<h1 id='todo' >To-do:</h1>
<p class='instruction'><i>(Click over to mark as done)</i></p>
<ol id='here'></ol>
</div>
<div id='right'>
<h1>Done:</h1>
<p class='instruction'><i>(Click over to delete it)</i></p>
<p id='placeholder'></p>
<ol id='done'></ol>
</div>
</div>

add comment and reply button to html body using javascript

I want to add html elements to the body of my page as an unordered list. I have used DocumentFragment method to create a fragment of the reply button and comment span. Now I need to add a textbox and a add reply to that ul whenever a user clicks on the reply button and add all the replies as a list next to respective comment. Here is what I've tried:
function comment() {
var my_comment = document.getElementById('comments');
my_comment.innerHTML = "<textarea id='user_comment'> </textarea> <button onclick='addNewItem()'>Post Comment</button>";
}
function addNewItem() {
var thediv = document.getElementById("comments_and_replies");
var listItem = document.createElement("ul");
var replyBox = document.createElement("textbox");
var commentSpan = document.createElement("span");
var user_comment = document.getElementById('user_comment');
var replyButton = document.createElement("button");
listItem.className = "comments-list";
replyButton.innerText = "Reply";
replyButton.className = "reply";
replyButton.addEventListener("click", function() {
var g = document.getElementById('comments_and_replies');
for (var i = 0, len = g.children.length; i < len; i++) {
(function(index) {
g.children[i].onclick = function() {
listItem.insertBefore(replyBox, listItem.children[index]);
}
})(i);
}
})
commentSpan.textContent = user_comment.value;
var documentFragment = document.createDocumentFragment();
documentFragment.appendChild(listItem);
listItem.appendChild(commentSpan);
listItem.appendChild(replyButton);
thediv.appendChild(documentFragment);
}
<section><button onclick="comment()">Leave a comment</button></section>
<div id="comments"></div>
<div id="comments_and_replies"></div>
Event delegation on a single <form> can accommodate an unlimited amount of <button>s even if they are added after the page has loaded.
The example below uses the following:
document.forms
.elements
event.currentTarget
event.target
.matches()
.insertAdjacentHTML()
.previousElementSibling
.parentElement
.remove()
Note: Unless you are submitting data to a server, add type="button" to each <button>
Details are commented in code below
// Refernce <form>
const form = document.forms.commentsReplies;
// Any click on <form> invokes post()
form.onclick = post;
// Pass the event
function post(event) {
/* Reference all <fieldset>
(also <button>, <textarea>, etc) */
const field = event.currentTarget.elements;
// Reference the actual element clicked
const clicked = event.target;
// if element clicked has class postCom
if (clicked.matches('.postCom')) {
/* find <fieldset name="post"> and
insert HTML into it */
field.post.insertAdjacentHTML('beforeEnd', `<fieldset name='commentPost'><textarea></textarea><button class='comTxt' type='button'>Done</button></fieldset>`);
// Otherwise if clicked element has class comTxt
} else if (clicked.matches('.comTxt')) {
/* find the clicked element's element
that is right before it and get it's text */
const text = clicked.previousElementSibling.value;
/* find <fieldset name='comments'> and insert HTML */
field.comments.insertAdjacentHTML('afterBegin', `<fieldset>${text}<button class='postRep' type='button'>Reply</button><ul></ul></fieldset>`);
// Remove <fieldset name='commentPost'>
field.commentPost.remove();
} else if (clicked.matches('.postRep')) {
clicked.insertAdjacentHTML('afterEnd', `<ul><textarea></textarea><button class='repTxt' type='button'>Done</button></ul>`);
} else if (clicked.matches('.repTxt')) {
const text = clicked.previousElementSibling.value;
const list = clicked.parentElement;
list.insertAdjacentHTML('afterBegin', `<li>${text}<button class='postRep' type='button'>Reply</button></li>`);
clicked.previousElementSibling.remove();
clicked.remove();
} else {
return false;
}
}
button {
display: block;
margin-left: 25%;
}
<form id='commentsReplies'>
<fieldset name='post'><button class='postCom' type='button'>Leave a comment</button>
</fieldset>
<fieldset name="comments">
<legend>Comments</legend>
</fieldset>
</form>

Moving elements from one array to another. (JavaScript)

So basically I have a Grocery List and an Inventory List. I want to be able to:
Add an item to either list.
Remove an item from either list.
Move an item from one list to the other.
Each list has its own array, and when you "Add an item" to either list, I have a function (nodeConstructor) that pushes the item to its corresponding array and builds an 'item element' in the desired div (the grocery list or inventory list).
The problem that I'm having is in moving an item from one list to the other. When you click the 'move' button, my 'nodeConstructor' function is supposed to be called and it should basically recreate the selected element in the other list. It kind of does that, except the item value (inputVal) renders as 'undefined' in the div rather than the value of the item that was moved (however it moves from array to array correctly).
The other problem is that moving an item from the Grocery List to the Inventory List correctly splices the item from the Grocery List array and pushes it to the Inventory List array, but moving an item from the Inventory List to the Grocery List neither splices the item from the array nor moves it to the Grocery List array (I'm thinking it has something to do with the order of conditionals being checked but I'm not sure).
If anyone could show me where I'm going wrong it would be greatly appreciated. Also, any suggestions on an easier way to achieve this functionality would be welcomed.
Here is a link to the Codepen that demonstrates my issue: https://codepen.io/TOOTCODER/pen/OJXQKGp?editors=1011
HTML
<label for="groceryInput">Add Grocery Item: </label>
<input id="groceryInput" type="text">
<button id="groceryInputBtn">Submit</button>
<label for="inventoryInput">Add Inventory Item: </label>
<input id="inventoryInput" type="text">
<input id="inventoryInputBtn" type="submit">
<div class="outputDiv" id="arr1Output"><h1>Grocery</h1></div>
<div class="outputDiv" id="arr2Output"><h1>Inventory</h1></div>
CSS
.outputDiv{
width: 200px;
height: 200px;
border: 1px solid black;
margin: 25px 0 0 25px;
}
.outputDiv h1{
text-align: center;
text-decoration: underline;
}
JavaScript
const groceryInput = document.querySelector("#groceryInput");
const groceryInputBtn = document.querySelector("#groceryInputBtn");
const inventoryInput = document.querySelector("#inventoryInput");
const inventoryInputBtn = document.querySelector("#inventoryInputBtn");
const arr1Output = document.querySelector("#arr1Output");
const arr2Output = document.querySelector("#arr2Output");
const groceryList = [];
const inventoryList = [];
//add grocery item
groceryInputBtn.addEventListener("click", function(){
nodeConstructor(groceryInput, groceryList, arr1Output, "newGroceryItemDiv", "newGroceryItem", "groceryDeleteBtnSpan", "groceryMoveBtnSpan")});
arr1Output.addEventListener("click", controlBtns);
//add inventory item
inventoryInputBtn.addEventListener("click", function(){
nodeConstructor(inventoryInput, inventoryList, arr2Output, "newInventoryItemDiv",
"newInventoryItem", "inventoryDeleteBtnSpan", "inventoryMoveBtnSpan")});
arr2Output.addEventListener("click", controlBtns);
//item element builder
function nodeConstructor(inputVal, list, output, divClass,itmClass, deleteClass, moveClass){
const newItmDiv = document.createElement("div");
newItmDiv.classList.add(divClass);
const newItm = document.createElement("span");
newItm.classList.add("itmClass");
const deleteBtnSpan = document.createElement("span");
deleteBtnSpan.innerHTML = "<i class='far fa-trash-alt'></i>";
deleteBtnSpan.classList.add(deleteClass);
const moveBtnSpan = document.createElement("span");
moveBtnSpan.innerHTML = "<i class='fas fa-exchange-alt'></i>";
moveBtnSpan.classList.add(moveClass);
list.push(inputVal.value);
for(let i=0;i<list.length;i++){
newItm.innerText = list[i];
}
newItmDiv.appendChild(newItm);
newItmDiv.appendChild(deleteBtnSpan);
newItmDiv.appendChild(moveBtnSpan);
output.appendChild(newItmDiv);
};
//delete and move buttons
function controlBtns(event){
const clicked = event.target;
for(let i=0;i<groceryList.length;i++){
//grocery delete btn
if(clicked.parentElement.parentElement.innerText==groceryList[i] &&
clicked.parentElement.classList[0]=="groceryDeleteBtnSpan"){
groceryList.splice(i,1);
clicked.parentElement.parentElement.remove();
}
//grocery move btn
if(clicked.parentElement.parentElement.innerText==groceryList[i] &&
clicked.parentElement.classList[0]=="groceryMoveBtnSpan"){
nodeConstructor(groceryList[i], inventoryList, arr2Output, "newInventoryItemDiv", "newInventoryItem", "inventoryDeleteBtnSpan", "inventoryMoveBtnSpan");
inventoryList.push(groceryList[i]);
groceryList.splice(i,1);
clicked.parentElement.parentElement.remove();
}
}
//inventory delete btn
for(let i=0;i<inventoryList.length;i++){
if(clicked.parentElement.parentElement.innerText==inventoryList[i] &&
clicked.parentElement.classList[0]=="inventoryDeleteBtnSpan"){
inventoryList.splice(i,1);
clicked.parentElement.parentElement.remove();
}
//inventory move btn
if(clicked.parentElement.parentElement.value==inventoryList[i] &&
clicked.parentElement.classList[0]=="inventoryMoveBtnSpan"){
nodeConstructor(inventoryList[i], groceryList, arr1Output, "newGroceryItemDiv", "newGroceryItem", "groceryDeleteBtnSpan", "groceryMoveBtnSpan");
groceryList.push(inventoryList[i]);
inventoryList.splice(i,1);
clicked.parentElement.parentElement.remove();
}
}
console.log("InventoryList: "+inventoryList);
console.log("GroceryList: "+groceryList);
}
Hey so the below code will at least work with the switching, the issue you had is you were not calling the nodeConstructor in the move button and since your lists only contain the value, nothing was being updated.
So what I did was updated nodeConstructor to take both input value and value of the item you are moving with this line const nodeValue = typeof inputVal === 'object' ? inputVal.value : inputVal
Then in the move functions I updated both move functions. I grabbed the old value before I delete it from the list, then I call the nodeContructor so both the lists are updated with the value.
const groceryVal = groceryList[i]
groceryList.splice(i,1);
nodeConstructor(groceryVal, inventoryList, arr2Output, "newInventoryItemDiv", "newInventoryItem", "inventoryDeleteBtnSpan", "inventoryMoveBtnSpan");
clicked.parentElement.parentElement.remove();
In terms of of what you could be doing. I think you could probably add a click event to the move buttons that update their own position so you dont have to have that big function.
const groceryInput = document.querySelector("#groceryInput");
const groceryInputBtn = document.querySelector("#groceryInputBtn");
const arr1Output = document.querySelector("#arr1Output");
const inventoryInput = document.querySelector("#inventoryInput");
const inventoryInputBtn = document.querySelector("#inventoryInputBtn");
const arr2Output = document.querySelector("#arr2Output");
const groceryList = [];
const inventoryList = [];
let nodeList = [];
groceryInputBtn.addEventListener("click", function(){
nodeConstructor(groceryInput, groceryList, arr1Output, "newGroceryItemDiv", "newGroceryItem", "groceryDeleteBtnSpan", "groceryMoveBtnSpan");
});
arr1Output.addEventListener("click", controlBtns);
inventoryInputBtn.addEventListener("click", function(){
nodeConstructor(inventoryInput, inventoryList, arr2Output, "newInventoryItemDiv", "newInventoryItem", "inventoryDeleteBtnSpan", "inventoryMoveBtnSpan");
});
arr2Output.addEventListener("click", controlBtns);
function nodeConstructor(inputVal, list, output, divClass,itmClass, deleteClass, moveClass){
const newItmDiv = document.createElement("div");
newItmDiv.classList.add(divClass);
const newItm = document.createElement("span");
newItm.classList.add("itmClass");
const deleteBtnSpan = document.createElement("span");
deleteBtnSpan.innerHTML = "<i class='far fa-trash-alt'></i>";
deleteBtnSpan.classList.add(deleteClass);
const moveBtnSpan = document.createElement("span");
moveBtnSpan.innerHTML = "<i class='fas fa-exchange-alt'></i>";
moveBtnSpan.classList.add(moveClass);
const nodeValue = typeof inputVal === 'object' ? inputVal.value :
inputVal
list.push(nodeValue);
for(let i=0;i<list.length;i++){
newItm.innerText = list[i];
}
newItmDiv.appendChild(newItm);
newItmDiv.appendChild(deleteBtnSpan);
newItmDiv.appendChild(moveBtnSpan);
output.appendChild(newItmDiv);
};
function controlBtns(event){
const clicked = event.target;
for(let i=0;i<groceryList.length;i++){
//groceryDeleteBtn
if(clicked.parentElement.parentElement.innerText==groceryList[i] &&
clicked.parentElement.classList[0]=="groceryDeleteBtnSpan"){
groceryList.splice(i,1);
clicked.parentElement.parentElement.remove();
}
//groceryMoveBtn
if(clicked.parentElement.parentElement.innerText==groceryList[i] &&
clicked.parentElement.classList[0]=="groceryMoveBtnSpan"){
const groceryVal = groceryList[i]
groceryList.splice(i,1);
nodeConstructor(groceryVal, inventoryList, arr2Output, "newInventoryItemDiv", "newInventoryItem", "inventoryDeleteBtnSpan", "inventoryMoveBtnSpan");
clicked.parentElement.parentElement.remove();
}
}
for(let i=0;i<inventoryList.length;i++){
//inventoryDeleteBtn
if(clicked.parentElement.parentElement.innerText==inventoryList[i] &&
clicked.parentElement.classList[0]=="inventoryDeleteBtnSpan"){
inventoryList.splice(i,1);
clicked.parentElement.parentElement.remove();
}
//inventoryMoveBtn
if(clicked.parentElement.parentElement.innerText==inventoryList[i] && clicked.parentElement.classList[0]=="inventoryMoveBtnSpan"){
const inventoryVal= inventoryList[i]
inventoryList.splice(i,1);
nodeConstructor(inventoryVal, groceryList, arr1Output, "newGroceryItemDiv", "newGroceryItem", "groceryDeleteBtnSpan", "groceryMoveBtnSpan");
clicked.parentElement.parentElement.remove();
}
}
console.log("InventoryList: "+inventoryList);
console.log("GroceryList: "+groceryList);
}

How do I save to local storage via vanilla JS

I can't seem to get local storage to work. The goal is to keep the todo list items on the page upon refresh. Every time I refresh the page it goes poof. The syntax seems right.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO LIST</title>
<link rel="stylesheet" href="./styles/style.css">
</head>
<body>
<main id="main">
<h1>THE TO-DO LIST:</h1>
<form action="" id="add-task">
<label for="todo">Add Task:</label>
<input type="text" id="todo">
<button>Add Task</button>
</form>
<p class="center">To complete task, click on text.</p>
<ul id="task-list">
<li class="task-complete">example_1 <button>Remove Task</button></li>
</ul>
</main>
<script src="./script/index.js"></script>
</body>
</html>
const form = document.querySelector('#add-task');
const input = document.querySelector('#todo');
const taskList = document.querySelector('#task-list');
let taskID = 0;
taskList.addEventListener('click', function(e) {
if (e.target.tagName === 'BUTTON') {
e.target.parentElement.remove();
let inputTask = document.getElementById('todo');
localStorage.setItem('email', inputTask.value);
} else if (e.target.tagName === 'LI') {
e.target.classList.toggle('task-complete');
}
});
form.addEventListener('submit', function(e) {
e.preventDefault();
console.log(input.value);
const newTask = document.createElement('li');
const removeBtn = document.createElement('button');
let savedInput = input.value;
removeBtn.innerText = 'Remove Task';
newTask.innerText = input.value;
newTask.appendChild(removeBtn);
taskList.appendChild(newTask);
input.value = '';
console.log(localStorage);
});
.task-complete {
text-decoration: line-through;
}
Joshua, here are a few things from looking at your sample:
First, you're setting the localStorage to a single item, with the current input value, not a collection of tasks like an array
It also seems that you're not getting the saved data on page reload, that's why nothing happens when page reloads
Remember that you can only save strings to localStorage, in a todo list you might want to save an array (a collection of todos), but since you can't do it you need to convert it to a string while saving (JSON.stringify(yourArray) will help you with that), and parse it back to an Array when loading (JSON.parse)
const form = document.querySelector('#add-task');
const input = document.querySelector('#todo');
const taskList = document.querySelector('#task-list');
let taskID = 0;
let tasks = [] // here will hold your current todos collection
// a function that will retrieve the saved todos from local storage
//
// note that 'tasks' can be any string identifier that you want — 'todos'
// would also work — but you need to use the same for localStorage.getItem
// and localStorage.setItem
function getTasksFromLocalStorage(){
// it will return `null` if nothing's there
tasks = localStorage.getItem('tasks') || []
if (tasks) {
// convert it to an array so you can loop over it
tasks = JSON.parse(tasks)
}
}
function addTask(text) {
// CREATE DOM ELEMENTS
const newTask = document.createElement('li');
const removeBtn = document.createElement('button');
removeBtn.innerText = 'Remove Task';
// set the text to the provided value
newTask.innerText = text;
// append the remove button
newTask.appendChild(removeBtn);
// append it to the dom so we can see it
taskList.appendChild(newTask)
}
// on page load get tasks from local storage
// then loop over it, create the DOM elements and append them to
// the taskList
document.addEventListener('DOMContentLoaded', function() {
getTasksFromLocalStorage()
// if we have saved tasks, loop over them and render to the dom
tasks.forEach(function(savedTaskText) {
addTask(savedTaskText)
})
})
// then on your code, you need to update to push
// the current inputed `task` to the `tasks` collection (Array)
// then save the entire collection to the local storage
// then add the new task to the DOM
// and finally reset the input
form.addEventListener('submit', function(e) {
e.preventDefault();
console.log(input.value);
// save it to the current holding list
tasks.push(input.value)
// save a copy of the updated list to the localStorage, so when you
// reload the page you get saved items!
localStorage.setItem('tasks', tasks)
// add it to DOM
addTask(input.value);
// reset the input
input.value = '';
});
There's more things you need to do, if you want tasks to have unique ids (since, so you can remove them later), but the code was simplified for brevity of explanation (and yet you got a long answer anyways).
Here's so docs and suggested reading:
MDN Docs for LocalStorage
MDN Docs for JSON (parse and stringify)
There's plenty vanilla javascript tutorials (written and youtube) for "creating a todo lists using localStorage", that go into more detail than we can go in a SO answer, I suggest you skim through those as well!
Good luck and Happy coding ✌️
There are 2 problems with your code.
First, you are not saving each to-do task entered by user upon form submit. If you want to save each to-do task entered by user in localStorage, then modify the form submit handler as below:
form.addEventListener('submit', function(e) {
e.preventDefault();
const newTask = document.createElement('li');
const removeBtn = document.createElement('button');
let savedInput = input.value;
removeBtn.innerText = 'Remove Task';
newTask.innerText = input.value;
newTask.appendChild(removeBtn);
taskList.appendChild(newTask);
localStorage.setItem('Task'+taskID, input.value);
taskID++;
input.value = '';
});
Second, you are not utilizing the previously saved data in localStorage to show the list of to-dos that were entered by user before the page was loaded. You can achieve that by using below function code:
function showSavedToDos() {
const keys = Object.keys(localStorage);
let i = keys.length;
while (i--) {
const newTask = document.createElement('li');
const removeBtn = document.createElement('button');
removeBtn.innerText = 'Remove Task';
newTask.innerText = localStorage.getItem(keys[i]);
newTask.appendChild(removeBtn);
taskList.appendChild(newTask);
}
}
showSavedToDos();
You are not using de localStorage API, please take a look to this example. here I am using template to display the tasks. In the html file is the only change
<main id="main">
<h1>THE TO-DO LIST:</h1>
<form action="" id="add-task">
<label for="todo">Add Task:</label>
<input type="text" id="todo" />
<button>Add Task</button>
</form>
<p class="center">To complete task, click on text.</p>
<ul id="task-list">
<li class="task-complete">example_1 <button>Remove Task</button></li>
</ul>
</main>
<template id="task">
<li class="task-complete">
<span></span>
<button>Remove task</button>
</li>
</template>
In JavaScript I create a render function that will collect the task stored in localstorage. Populated when calling store(input.value) in the submit handler
const form = document.querySelector("#add-task");
const input = document.querySelector("#todo");
const taskList = document.querySelector("#task-list");
let taskID = 0;
taskList.addEventListener("click", function (e) {
if (e.target.tagName === "BUTTON") {
e.target.parentElement.remove();
let inputTask = document.getElementById("todo");
localStorage.setItem("email", inputTask.value);
} else if (e.target.tagName === "LI") {
e.target.classList.toggle("task-complete");
}
});
form.addEventListener("submit", function (e) {
e.preventDefault();
console.log(input.value);
const newTask = document.createElement("li");
const removeBtn = document.createElement("button");
let savedInput = input.value;
removeBtn.innerText = "Remove Task";
newTask.innerText = input.value;
newTask.appendChild(removeBtn);
taskList.appendChild(newTask);
store(input.value);
input.value = "";
console.log(localStorage);
});
function getTasks() {
return localStorage.tasks ? JSON.parse(localStorage.tasks) : [];
}
function store(task) {
const tasks = getTasks();
tasks.push(task);
localStorage.setItem("tasks", JSON.stringify(tasks));
}
function render() {
const tasks = getTasks();
tasks.forEach((task) => {
const newTask = createTask(task);
taskList.appendChild(newTask);
});
}
function createTask(task) {
const template = document.querySelector("#task");
const taskNode = template.content.cloneNode(true);
taskNode.querySelector("span").innerText = task;
return taskNode;
}
render();
The render function run every first render of the page, so tasks list will be populated

Categories

Resources