localStorage data does not show when window reloads - javascript

I am building a mock-up RSVP app, and I can't get the localStorage data to come up when the page refreshes. I am aiming to be able to insert a name and have the name get appended to the invitation list. Then, the user can either repeat those steps for multiple names or edit the names in the list. I have this part down, but if I were to refresh the page, the invitees are no longer in the list below the input bar. I need it to where it will keep the names in the list, and the buttons on the list items (edit, remove) will still work.
With the 'main code' below, the item is added to the localStorage and set as 'rsvp', but the visible list is not updated until I refresh the page. I need it to update every time I hit the submit button. I have tried adding
if (rsvp != null) {
ul.outerHTML = rsvp;
}
right below
console.log(rsvp);
but when I click submit, the list is not updated and in the console you see the data that was loaded the previous time you used the app.
For example, if I type in 'Test', click submit, type in 'Test2', click submit then type in 'Test3' and click submit again - the list is not visibly updated, you get an error in the console saying 'Uncaught DOMException: Failed to set the 'outerHTML' property on 'Element': This element has no parent node.', and the list is never updated until you refresh the page, type in another name and click submit. Again, if you do this, the list is not updated until you repeat the same process.
Main code (without the rsvp 'if' statement in the handler)
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('registrar');
const input = form.querySelector('input');
const mainDiv = document.querySelector('.main');
const ul = document.getElementById('invitedList');
const div = document.createElement('div');
const filterLabel = document.createElement('label');
const filterCheckbox = document.createElement('input');
filterLabel.textContent = "Hide those who haven't responded";
filterCheckbox.type = 'checkbox';
div.appendChild(filterLabel);
div.appendChild(filterCheckbox);
mainDiv.insertBefore(div, ul);
// Creates the list item for the RSVP list
function createLI(text) {
function createElement(elementName, property, value) {
const element = document.createElement(elementName);
element[property] = value;
return element;
}
function appendToLI(elementName, property, value) {
const element = createElement(elementName, property, value);
li.appendChild(element);
return element;
}
const li = document.createElement('li');
appendToLI('span', 'textContent', text);
appendToLI('label','textContent', 'Confirm')
.appendChild(createElement('input', 'type', 'checkbox'));
appendToLI('button', 'textContent', 'edit');
appendToLI('button', 'textContent', 'remove');
return li;
}
form.addEventListener('submit', (e) => {
e.preventDefault();
const text = input.value;
input.value = '';
// Checks for empty string in the input area
if (text === '') {
alert("You have not entered a name, please try again.");
return;
}
// Checks for duplicate names
for (i = 0; i < ul.children.length; i++) {
if (text === ul.children[i].children[0].textContent) {
alert("This name has already been entered. Please enter a different name.");
return;
}
}
const li = createLI(text);
ul.appendChild(li);
localStorage.setItem('rsvp', JSON.stringify(ul.outerHTML));
});
const rsvp = JSON.parse(localStorage.getItem('rsvp'));
if (rsvp != null) {
ul.outerHTML = rsvp;
}
// Changes list item from confirm to confirmed
ul.addEventListener('change', (e) => {
const checkbox = event.target;
const checked = checkbox.checked;
const label = checkbox.parentNode;
const listItem = checkbox.parentNode.parentNode;
if (checked) {
listItem.className = 'responded';
label.childNodes[0].textContent = 'Confirmed';
} else {
listItem.className = '';
label.childNodes[0].textContent = 'Confirm';
}
});
ul.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
const button = e.target;
const li = button.parentNode;
const ul = li.parentNode;
const action = button.textContent;
const nameActions = {
remove: () => {
ul.removeChild(li);
},
edit: () => {
const span = li.firstElementChild;
const input = document.createElement('input');
input.type = 'text';
input.value = span.textContent;
li.insertBefore(input, span);
li.removeChild(span);
button.textContent = 'save';
},
save: () => {
const input = li.firstElementChild;
const span = document.createElement('span');
span.textContent = input.value;
li.insertBefore(span, input);
li.removeChild(input);
button.textContent = 'edit';
}
};
// select and run action in button's name
nameActions[action]();
}
});
// Filters out those who have not yet responded
filterCheckbox.addEventListener('change', (e) => {
const isChecked = e.target.checked;
const lis = ul.children;
if (isChecked) {
for (let i = 0; i < lis.length; i++) {
let li = lis[i];
if (li.className === 'responded') {
li.style.display = '';
} else {
li.style.display = 'none';
}
}
} else {
for (let i = 0; i < lis.length; i++) {
let li = lis[i];
li.style.display = '';
}
}
});
});

const rsvp = JSON.parse(localStorage.getItem('rsvp'))
is called within DOMContentLoaded event handler
if (rsvp != null) {
ul.outerHTML = rsvp;
}
is called immediately following .addEventListener(); also the code at Question does not indicate where localStorage.getItem('rsvp') is set before const rsvp = JSON.parse(localStorage.getItem('rsvp')) is called.
You can check is localStorage has the property key "rsvp" before defining rsvp, and use if condition and statement with DOMContentLoaded event handler.

Related

How can I write a code in JS that deletes the existing output by disappearing it letter by letter, than writing the another one

Here's the code i've written, where when i write a word into e search field, it appends it in the element "word" by displaying it letter by letter. But, the problem is, that i don't know how to write the code that when i write another word in the search field, it deletes the word that appear to element "Word", then writes the new one i've written.
let text = document.getElementById("txt");
let elem = document.getElementsByClassName("target")[0];
let word = elem.querySelector(".word");
let btn = document.getElementsByClassName("btn")[0];
let error = document.querySelector('#error');
i = 0;
word.style.color = "#ffe100";
btn.addEventListener("click", function init() {
if (text.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
let save = word.textContent += text.value.charAt(i);
i++;
}
if (i < text.value.length) {
window.setTimeout(init, 100);
}
});
I've try many of alternatives, but there's no result.
I will iteratively change and/or improve your code in this answer, and will try to comment on each change in the code.
Refactor
First off, I'll have an easier time explaining the different approaches after refactoring your code:
// Prefer `const` over `let` when applicable
const input = document.querySelector("input"); // Previously: text
const output = document.querySelector("output"); // Previously: word
const button = document.querySelector("button"); // Previously: btn
const error = document.getElementById("error");
// You forgot `let`. Always declare your variables!
let i = 0;
button.addEventListener("click", () => {
if (input.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
reveal();
}
});
// Moved code from listener to here
function reveal() {
// Removed `save`; was unused
output.textContent += input.value[i];
++i;
// Moved here because it only runs if `input.value !== ""` anyways
if (i < input.value.length) {
setTimeout(reveal, 100);
}
}
/* Prefer to use CSS for static styles! */
.word {
color: #ffe100;
}
<!-- I assume your HTML to have looked similar to this: -->
<input><button>Submit</button>
<div>
<output></output>
</div>
<p id="error">Please submit (actual) text.</p>
Now let's take a look at your refactored code from above:
There is no resetting: Revealing can only continue (text can only be added).
The value of input is referenced directly: When its value changes...
Then revealing may stop prematurely.
Then the further revealed text may not represent the entered text upon button-press.
Allow reusability
The issue of point 1 can be solved by (re-)setting i and output.textContent in the listener. To solve point 2, we need to use some buffer for the text:
const input = document.querySelector("input");
const output = document.querySelector("output");
const button = document.querySelector("button");
const error = document.getElementById("error");
let i = 0;
let text = ""; // The buffer
button.addEventListener("click", () => {
if (input.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
// (Re-)setting
i = 0;
text = input.value;
output.textContent = "";
reveal();
}
});
function reveal() {
output.textContent += text[i];
++i;
if (i < text.length) {
setTimeout(reveal, 100);
}
}
.word {
color: #ffe100;
}
<input><button>Submit</button>
<div>
<output></output>
</div>
<p id="error">Please submit (actual) text.</p>
With these two small changes, your code now successfully deletes the revealed text in place for new text!
Adding states
But the deletion doesn't happen letter-by-letter. That would require some way to keep track of whether we are deleting or revealing.
Let's use a state-machine that –upon prompting– deletes the already revealed text (if any) and then reveals the new text:
const input = document.querySelector("input");
const output = document.querySelector("output");
const button = document.querySelector("button");
const error = document.getElementById("error");
let i = 0;
let text = "";
let state = "nothing"; // The state
button.addEventListener("click", () => {
if (input.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
// Remember previous state (for below)
const previousState = state;
// Issue deletion and update text-to-reveal
state = "delete";
text = input.value;
if (previousState === "nothing") {
// Start state-machine
nextCharacter();
}
}
});
// Rename function
function nextCharacter() {
if (state === "nothing") return;
if (state === "delete") {
output.textContent = output.textContent.slice(0, i);
// Finished deleting?
if (i === 0) {
const requiresRevealing = i < text.length;
state = requiresRevealing ? "reveal" : "nothing";
} else {
--i;
}
} else if (state === "reveal") {
output.textContent += text[i];
++i
// Finished revealing?
if (i === text.length) {
state = "nothing";
}
}
// Requires continuing?
if (state !== "nothing") {
setTimeout(nextCharacter, 100);
}
}
.word {
color: #ffe100;
}
<input><button>Submit</button>
<div>
<output></output>
</div>
<p id="error">Please submit (actual) text.</p>
Code quality refactoring
The code now works, but(!) the logic is scattered everywhere, and you need to know what variables to update for the revealing to work correctly. Instead, we could make use of classes:
const input = document.querySelector("input");
const output = document.querySelector("output");
const button = document.querySelector("button");
const error = document.getElementById("error");
// Move related variables and functions into class
class Revealer {
output;
index = 0;
text = "";
state = "nothing";
constructor(output) {
this.output = output;
}
// Moved from listener
reveal(text) {
const previousState = this.state;
this.state = "delete";
this.text = text;
if (previousState === "nothing") {
this.next();
}
}
// Previously nextCharacter()
next() {
if (this.state === "nothing") return;
if (this.state === "delete") {
this.deleteCharacter();
} else if (this.state === "reveal") {
this.revealCharacter();
}
if (this.state !== "nothing") {
setTimeout(() => this.next(), 100);
}
}
// Use more specific functions for readability
deleteCharacter() {
this.output.textContent = this.output.textContent.slice(0, this.index);
if (this.index === 0) {
const requiresRevealing = this.index < this.text.length;
this.state = requiresRevealing ? "reveal" : "nothing";
} else {
--this.index;
}
}
revealCharacter() {
this.output.textContent += this.text[this.index];
++this.index;
if (this.index === this.text.length) {
this.state = "nothing";
}
}
}
const revealer = new Revealer(output);
button.addEventListener("click", () => {
if (input.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
// Use simple call
revealer.reveal(input.value);
}
});
.word {
color: #ffe100;
}
<input><button>Submit</button>
<div>
<output></output>
</div>
<p id="error">Please submit (actual) text.</p>
Ideas for practicing
While the above code works, there are still ways to improve it or implement it differently:
Only delete until the remaining text is the same as the leading substring of the text-to-add.
You can use promises (with async/await) instead of using setTimeout() directly.
You can implement the revealing as functionality of a custom element.
...
Try implementing (one of) these as practice!

How to auto grow text on input value?

I have an input form field that outputs text on submit to another created input, essentially an editable todo list. I have tried to make the input text value auto grow, but cannot figure out how to do it. Right now the user has to scroll over to see the rest of the text on each list item. This should not be.
What I tried:
I have tried creating a span and attaching editableContent but that makes my input text disappear.
I have tried setting an attribute on max-length on the created input but cannot get it to work. What is the best way to accomplish auto growing the text input value?
Here is the full codepen
const createTodoText = (todo) => {
const itemText = document.createElement("INPUT");
// const itemText = document.createElement("span");
// itemText.contentEditable
// itemText.contentEditable = 'true'
itemText.classList.add("todoText");
itemText.value = todo.name;
itemText.addEventListener("click", (e) => {
e.currentTarget.classList.add("active");
});
// update todo item when user clicks away
itemText.addEventListener("blur", (e) => {
todo.name = e.currentTarget.value;
renderTodos();
});
return itemText;
};
There you go: -
// select DOM elements
const todoForm = document.querySelector(".todo-form");
const addButton = document.querySelector(".add-button");
const input = document.querySelector(".todo-input");
const ul = document.getElementById("todoList");
let todos = [];
todoForm.addEventListener("submit", function (e) {
e.preventDefault();
addTodo(input.value);
});
const addTodo = (input) => {
if (input !== "") {
const todo = {
id: Date.now(),
name: input,
completed: false
};
todos.push(todo);
renderTodos();
todoForm.reset();
}
};
const renderTodos = (todo) => {
ul.innerHTML = "";
todos.forEach((item) => {
let li = document.createElement("LI");
// li.classList.add('item');
li.setAttribute("class", "item");
li.setAttribute("data-key", item.id);
const itemText = createTodoText(item);
const cb = buildCheckbox(item);
const db = buildDeleteButton(item);
// if (item.completed === true) {
// li.classList.add('checked');
// }
li.append(cb);
li.append(db);
li.append(itemText);
ul.append(li);
});
};
const createTodoText = (todo) => {
const itemText = document.createElement("span");
itemText.setAttribute('role','textbox');
itemText.setAttribute('contenteditable',"true");
itemText.classList.add("todoText");
itemText.innerHTML = todo.name;
itemText.addEventListener("click", (e) => {
e.currentTarget.classList.add("active");
});
// update todo item when user clicks away
itemText.addEventListener("blur", (e) => {
todo.name = e.target.textContent;
renderTodos();
});
return itemText;
};
const buildCheckbox = (todo) => {
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.name = 'checkbox';
cb.classList.add('checkbox');
cb.checked = todo.completed;
// checkbox not staying on current state ??
cb.addEventListener('click', function (e) {
if (e.target.type === 'checkbox') {
// todo.completed = e.target.value;
todo.completed = e.currentTarget.checked
e.target.parentElement.classList.toggle('checked');
}
});
return cb;
};
const buildDeleteButton = (todo) => {
const deleteButton = document.createElement("button");
deleteButton.className = "delete-button";
deleteButton.innerText = "x";
deleteButton.addEventListener("click", function (e) {
// duplicates children sometimes ??
const div = this.parentElement;
div.style.display = "none";
todos = todos.filter((item) => item.id !== todo.id);
});
return deleteButton;
};
// //------ Local Storage ------
function addToLocalStorage(todos) {}
function getFromLocalStorage() {}
// getFromLocalStorage();
This is the Javscript code part. In createTodoText, you can see the changes i've made. It's working according to what you want. What i've done is simple used 'span' instead of 'input'.
How about trying something like
if (todo.name.length) {itemText.size = todo.name.length;}

Local storage in JS not loading items problem

I have a problem with the local storage it seems the items are getting saved to local storage but I cannot make it work to load at start.
Any tips and advice much appreciated.
I am posting the code below.
const input = document.getElementById('input');
const list = document.getElementById('list');
const addButton = document.getElementById('addButton');
const completed = document.getElementById("completed");
let LIST;
let id;
let loadSTORAGE = localStorage.getItem("STORAGE");
if (loadSTORAGE) {
LIST = JSON.parse(loadSTORAGE);
id = LIST.length;
loadList(LIST);
} else {
LIST = [];
id = 0;
}
function loadList() {
LIST.forEach(function() {
addTask();
});
}
addButton.addEventListener("click", addTask);
input.addEventListener("keyup", function(event) {
(event.keyCode === 13 ? addTask() : null)
})
function addTask() {
const newTask = document.createElement("li");
const delBtn = document.createElement("button");
const checkBtn = document.createElement("button");
delBtn.innerHTML = "<button>Reset</button>"
checkBtn.innerHTML = "<button>Done</button>"
if (input.value !== "") {
newTask.textContent = input.value;
list.appendChild(newTask);
newTask.appendChild(checkBtn);
newTask.appendChild(delBtn);
LIST.push({
name: input.value,
id: id,
});
id++
input.value = "";
console.log(LIST);
localStorage.setItem("STORAGE", JSON.stringify(LIST));
}
checkBtn.addEventListener("click", function() {
const parent = this.parentNode
parent.remove();
completed.appendChild(parent);
});
delBtn.addEventListener("click", function() {
const parent = this.parentNode
parent.remove();
});
}
You need to break out the logic of building the item and getting the value. Something like the following where the addTask just makes sure there is input and calls a method that builds an item. Now with the localstorage call, you can call just the code that builds the item.
const input = document.getElementById('input');
const list = document.getElementById('list');
const addButton = document.getElementById('addButton');
const completed = document.getElementById("completed");
const loadSTORAGE = localStorage.getItem("STORAGE");
const LIST = loadSTORAGE ? JSON.parse(loadSTORAGE) : [];
let id = LIST.length;
loadList(LIST);
function loadList() {
LIST.forEach(function(data) {
addTaskElement(data);
});
}
function addTask() {
if (input.value !== "") {
cons newItem = {
name: input.value,
id: id,
};
LIST.push(newItem);
id++;
localStorage.setItem("STORAGE", JSON.stringify(LIST));
input.value = "";
addTaskElement(newItem);
}
}
function addTaskElement(data) {
const newTask = document.createElement("li");
const delBtn = document.createElement("button");
const checkBtn = document.createElement("button");
delBtn.textContent = "Reset"
checkBtn.textContent = "Done"
newTask.textContent = data.name;
newTask.appendChild(checkBtn);
newTask.appendChild(delBtn);
list.appendChild(newTask);
}

Todo list wont refresh after pushing to array

Whenever I add a todo to my array, it's not being refreshed in html, what do I need to sort this out? Also, how can I connect the Delete button which is being created in loop to a function?
const form = document.querySelector('form')
const input = document.querySelector('input')
const btnAdd = document.querySelector('button')
let ul = document.querySelector('ul')
let todos = ["one", "two", "three"];
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
console.log(todos);
}
for(let i of todos) {
createList = document.createElement("li");
createList.innerHTML+=i + '<button>Delete</delete>';
console.log(todos);
ul.appendChild(createList);
}
form.addEventListener('submit', pushTodos)
The problem you have is that you only update DOM only once on document load. You don't do anything after updating the list. To fix this, create a sync function to synchronize your memory with the DOM and call it every time you update the list:
let todos = ["one", "two", "three"];
const syncTodosWithDOM = () => {
ul.innerHTML = "";
for (let i of todos) {
createList = document.createElement("li");
createList.innerHTML+=i + '<button>Delete</delete>';
console.log(todos);
ul.appendChild(createList);
}
};
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
console.log(todos);
syncTodosWithDOM();
}
syncTodosWithDOM();
form.addEventListener('submit', pushTodos)
Iterate todos inside pushTodos function. Create a separate function createDomElem which will create dom element and call this function again when the element is pushed in the array
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
console.log(todos);
createDomElem()
}
createDomElem = () => {
for (let i of todos) {
createList = document.createElement("li");
createList.innerHTML += i + '<button>Delete</delete>';
console.log(todos);
ul.innerHTML = ''; // remove previous list
ul.appendChild(createList);
}
}
createDomElem() // will create initial list of todos
In pushTodos you also need to refresh HTML.
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
createList = document.createElement("li");
createList.innerHTML+=input.value + '<button>Delete</delete>';
ul.appendChild(createList);
}

how to loop through each list and add local stored item in it

I have a Bookmark Page where I add edit and delete bookmarks. and I have stored these items in localStorage. the issue is in loaddata function where I get the stored data and save it back in newly created li. the li tag is storing all the inputs that I typed in just one list. what I want is each bookmark should be within its own list just like additem function. but I don't know how to achieve this
const search = document.querySelector('form input');
const input = document.querySelector('.add-text');
const container = document.querySelector('ul');
let items = null;
let currentItem = null;
let array = [];
const searchItems = function(e) {
if (items) {
let word = e.target.value.toLowerCase();
for (let item of items) {
if (item.firstChild.textContent.toLowerCase().indexOf(word) !== -1) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
}
}
}
const deleteItem = function(e) {
currentItem = null;
e.target.parentNode.remove();
input.value = '';
}
const editItem = function(e) {
currentItem = e.target.parentNode.firstChild;
input.value = currentItem.textContent;
}
const updateItem = function(e) {
if (currentItem) {
currentItem.textContent = input.value;
input.value = '';
}else{
alert('No Selected Text Here to Update');
return;
}
}
const addItem = function() {
let val = input.value;
if (val) {
let li = document.createElement('li');
let inner = '<h1 class="text">' + val + '</h1>';
inner += '<button class="delete">Delete</button>';
inner += '<button class="edit">Edit</button>';
array.push(inner);
let stringified = JSON.stringify(array);
localStorage.setItem('list', stringified);
li.innerHTML = inner;
container.appendChild(li);
input.value = '';
currentItem = li.firstChild;
items = document.querySelectorAll('li');
for (let del of document.querySelectorAll('.delete')) {
del.addEventListener('click', deleteItem);
}
for (let edit of document.querySelectorAll('.edit')) {
edit.addEventListener('click', editItem);
}
} else {
alert('please add some text');
return;
}
}
function loaddata(){
let li = document.createElement('li');
let stringified = localStorage.getItem('list');
let listitems = JSON.parse(stringified);
li.innerHTML = listitems;
container.appendChild(li);
console.log(li);
}
loaddata();
search.addEventListener('keyup', searchItems);
document.querySelector('#add').addEventListener('click', addItem);
document.querySelector('#update').addEventListener('click', updateItem);
Considering your list is an array, you need to loop through it and create adn populate elements within that loop. Try to edit your loaddata function this way:
// Mock content
let container = document.body
localStorage.setItem('list', JSON.stringify(['<h1>Foo</h1>', '<h1>Bar</h1>', '<h1>Baz</h1>']))
loaddata()
// Edited 'loaddata'
function loaddata() {
let stringified = localStorage.getItem('list');
console.log(stringified)
let listitems = JSON.parse(stringified);
for (let i = 0; i < listitems.length; i++) {
let li = document.createElement('li');
li.innerHTML = listitems[i];
container.appendChild(li);
console.log(li);
}
}
It can't be run like a code snippet in Stack Overflow sandbox due to security reasons (accessing Local Storage), so if you want to test it, consider copying to JSFiddle or so.

Categories

Resources