I've been trying for hours to successfully remove items from a list through the localStorage API. I have no problem with inserting the items into the list and view them as they're being pushed into the view. How can I delete items from an array using localStorage? The code that I wrote is below. One other small issue, the form validation is not kicking in, I can't seem to spot the bug that's allowing null inputs.
index.html
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="shoutout-box-pos">
<div class="shoutout-box">
<div class="shoutout-box-header">
<div class="text-center">
<div class="shoutout-header-text">
<h4>Post a shoutout</h4>
</div>
<div class="shoutout-form-container">
<form id='shoutout-form'>
<div class="form-group">
<label>Name</label>
<input class="form-control" type="text" placeholder="Customer Name" id="name" />
</div>
<div class="form-group">
<label>Tell us about your stay!</label>
<input class="form-control" type="text" placeholder="What would you like to say?" id="message" />
</div>
<div class="form-group">
<label>Which session did you take part in?</label>
<input class="form-control" type="text" id="tour_session" placeholder="Give us the date or session title" />
</div>
<div class="form-group">
<button class="bttn-bordered bttn-md bttn-primary" type="submit" value="Submit">Post Message</button>
</div>
</form>
</div>
<div class="shoutout-messages">
<ul class="messageListContainer" id="messageList">
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
app.js
// ES6 Shoutout Messages
class Shoutout {
constructor(name, message, tour_session) {
this.name = name;
this.message = message;
this.tour_session = tour_session;
}
}
class UI {
addMessageToList(Shoutmessage) {
const list = document.getElementById('messageList');
// create li element
const li = document.createElement('li');
//Insert li into ul
li.innerHTML = `
<li><strong>Customer Name: </strong> <p class='lead'>${Shoutmessage.name}</p></li>
<li><strong>Message: </strong> <p class='lead'> ${Shoutmessage.message}</p></li>
<li><strong>Tour Session: </strong> <p class='lead'> ${Shoutmessage.tour_session} </p></li>
<li><a href="#" class="delete bttn-minimal bttn-sm">Remove Shoutout<a></li>
`;
list.appendChild(li);
}
showAlert(message, className) {
// create div
const div = document.createElement('div');
// Add classes
div.className = `alert ${className}`;
// Add text
div.appendChild(document.createTextNode(message));
// Get Parent Class
const container = document.querySelector('.shoutout-form-container');
// Get form Id
const form = document.querySelector('#shoutout-form');
// Insert alert
container.insertBefore(div, form);
// Timeout after 3 seconds
setTimeout(function () {
document.querySelector('.alert').remove();
}, 3000);
}
deleteShout(target) {
if (target.className === 'delete') {
target.parentElement.parentElement.remove();
}
}
clearFields() {
document.getElementById('name').value = '';
document.getElementById('message').value = '';
document.getElementById('tour_session').value = '';
}
}
// Local Storage Class Object
class Store {
static getShoutouts() {
let messages;
if (localStorage.getItem('messages') === null) {
messages = [];
} else {
messages = JSON.parse(localStorage.getItem('messages'));
}
return messages;
}
static displayShoutouts() {
const messages = Store.getShoutouts();
messages.reverse().forEach(function (message) {
const ui = new UI;
ui.addMessageToList(message);
});
}
static addMessage(message) {
const messages = Store.getShoutouts();
messages.push(message);
localStorage.setItem('messages', JSON.stringify(messages));
}
static removeMessage(tour_session) {
const messages = Store.getShoutouts();
messages.forEach(function (message, index) {
if (message.tour_session === tour_session) {
messages.splice(index, 1);
}
});
localStorage.setItem('messages', JSON.stringify(messages));
}
}
// DOM Load Event
document.addEventListener('DOMContentLoaded', Store.displayShoutouts);
// Event Listener for add message
document.getElementById('shoutout-form').addEventListener('submit', function (e) {
//Grab form values
const name = document.getElementById('name').value,
message = document.getElementById('message').value,
tour_session = document.getElementById('tour_session').value
// Instantiate message
const msg = new Shoutout(name, message, tour_session);
// Instantiate UI
const ui = new UI();
console.log(ui);
// Validate
if (name === '' || message === '' || tour_session === '') {
//Error alert
ui.showAlert('Please fil in all fields', 'error');
} else {
// Add message to list
ui.addMessageToList(msg);
// Add to Local Storage
Store.addMessage(msg);
// Show success
ui.showAlert('Shoutout Message Added!', 'success');
// Clear fields
ui.clearFields();
}
e.preventDefault();
});
// Event Listener for delete
document.getElementById('messageList').addEventListener('click', function (e) {
// Instantiate UI
const ui = new UI();
// Delete message
ui.deleteShout(e.target);
// Remove from Local Storage
Store.removeMessage(e.target.parentElement.previousElementSibling.textContent);
// Show message
ui.showAlert('Message Removed!', 'success');
e.preventDefault();
});
The problem here in your code is that
e.target.parentElement.previousElementSibling.textContent probably returns string like
"Tour Session: ${tour_session}"
while exact tour session, which you need, is inside the element in p.lead
so try to change your tour_session selector to
...
// Remove from Local Storage
Store.removeMessage(e.target.parentElement.previousElementSibling.querySelector(".lead").textContent);
...
UPD
Take a look at this CodePen.
DEMO: https://codepen.io/anon/pen/ajpwYR?editors=0011
In your addMessageToList() method I've added an attribute tour_session to the Remove Shoutout link.
addMessageToList(Shoutmessage) {
const list = document.getElementById('messageList');
// create li element
const li = document.createElement('li');
//Insert li into ul
li.innerHTML = `
<li><strong>Customer Name: </strong> <p class='lead'>${Shoutmessage.name}</p></li>
<li><strong>Message: </strong> <p class='lead'> ${Shoutmessage.message}</p></li>
<li><strong>Tour Session: </strong> <p class='lead'> ${Shoutmessage.tour_session} </p></li>
<li><a href="#" class="delete bttn-minimal bttn-sm" tour_session=${Shoutmessage.tour_session}>Remove Shoutout<a></li>
`;
list.appendChild(li);
}
In the event listener, pass the value of the tour_session attribute to the removeMessage method.
document.getElementById('messageList').addEventListener('click', function(e) {
// Instantiate UI
const ui = new UI();
// Delete message
ui.deleteShout(e.target);
// Remove from Local Storage
// console.log(e.target.getAttribute('tour_session'));
Store.removeMessage(e.target.getAttribute('tour_session'));
// Show message
ui.showAlert('Message Removed!', 'success');
e.preventDefault();
});
In the deleteShout method, the className would return the names of all the classes you've specified. So, your check fails everytime and the node is not removed from the DOM.
deleteShout(target) {
if (target.className.split(' ').indexOf('delete') > -1) {
target.parentElement.parentElement.remove();
}
}
Related
i got two copies of the same code with slight variation but seems the second one isnt working.
I have tried using ChatGPT to identify issues however, nothing was found. But as far as i can see, it should work.
But, everything i refresh, its removed.
I want the buttons to stay there.
working code:
const todoList = document.querySelector(".todo-list");
const todoForm = document.querySelector(".add-todo");
const removeList = document.querySelector(".remove-List");
let items = JSON.parse(localStorage.getItem("todoList")) || [
{
title: "Write on, 'do not forget to', and press '+' to add",
done: false,
},
{
title: "press 'X' to remove",
done: false,
},
{
title: "search via 'search box'",
done: false,
},
{
title: "filter via 'all'",
done: false,
},
];
function addTodo(e) {
e.preventDefault();
const title = this.querySelector("[name=item]").value;
const todo = {
title,
done: false,
};
items.push(todo);
saveTodos();
this.reset();
}
function createList(list = [], listTarget) {
listTarget.innerHTML = list
.map((item, i) => {
return `<li>
<input type="checkbox" id="todo${i}" data-index="${i}"
${item.done ? "checked" : ""} />
<label for="todo${i}">${item.title}
<span class="font-size:30px" data-index="${i}">X</span>
</label>
</li>`;
})
.join("");
}
function toggleDone(e) {
if (!e.target.matches("input")) return;
const el = e.target;
const index = el.dataset.index;
items[index].done = !items[index].done;
saveTodos();
}
function removeSingle(e) {
if (!e.target.matches("span")) return;
const el = e.target;
const index = el.dataset.index;
items.splice(index, 1);
saveTodos();
if (items.length === 0) {
removeList.classList.add("hidden");
}
}
function saveTodos() {
localStorage.setItem("todoList", JSON.stringify(items));
createList(items, todoList);
showRemoveButton();
}
function removeData() {
items = [];
localStorage.removeItem("todoList");
createList(items, todoList);
removeList.classList.add("hidden");
}
function showRemoveButton() {
if (items.length > 1) return;
removeList.classList.remove("hidden");
}
todoList.addEventListener("click", toggleDone);
todoList.addEventListener("click", removeSingle);
todoForm.addEventListener("submit", addTodo);
removeList.addEventListener("click", removeData);
// Init list
createList(items, todoList);
<div class="ToDo-container">
<header class="app-header">
<div class="app-header1">
<div class="title">
<h1 class="app-title">ToDo List</h1>
</div>
<div class="select-button">
<select id="filter">
<option value="all">All</option>
<option value="completed">Completed</option>
<option value="incomplete">Incomplete</option>
</select>
</div>
</div>
<div class="search-header">
<input type="text" id="search" placeholder="Search Todos">
<button type="button" id="search-button" class="btn">Search</button>
</div>
</header>
<section class="app-body">
<div id="toDo">
<ul class="todo-list"></ul>
<form class="add-todo">
<input
type="text"
placeholder="Don't Forget to..."
name="item"
required
/>
<input type="submit" value="+" />
</form>
</div>
<button class="remove-List btn">Remove All</button>
</section>
</div>
// Add button function
function addButton() {
// Prompt for button link and name
var link = prompt("Enter the button link:");
var name = prompt("Enter the button name:");
// Create new button element
var newButton = document.createElement("button");
newButton.innerHTML = name;
newButton.setAttribute("onclick", "location.href='" + link + "'");
// Append new button to button container
document.getElementById("button-container").appendChild(newButton);
// Save buttons to local storage
saveButtons();
}
// Remove buttons function
function removeButtons() {
// Clear button container
document.getElementById("button-container").innerHTML = "";
// Save buttons to local storage
saveButtons();
}
// Save buttons to local storage
function saveButtons() {
// Get button container HTML
var buttonHTML = document.getElementById("button-container").innerHTML;
// Save button HTML to local storage
localStorage.setItem("buttons", buttonHTML);
}
// Load buttons from local storage on page load
window.onload = function () {
// Get button HTML from local storage
var buttonHTML = localStorage.getItem("buttons");
// Set button container HTML
document.getElementById("button-container").innerHTML = buttonHTML;
};
<div class="shortcuts-container">
<header class="shortcut-header">
<h1 class="shortcut-title">Quick Links</h1>
</header>
<section class="shortcut-body">
<form id="button-form">
<div id="button-container"></div>
<button type="button" onclick="addButton()">Add Button</button>
<button type="button" onclick="removeButtons()">Remove Buttons</button>
</form>
</section>
</div>
i want some help with identifying the bug in my code
As you have mentioned that your "second one" is not working, I have focused on the second piece of code. This peice of code adds a button for which we need to provide a link and name via alert box, which is technically supposed to be a hyperlink. I have added 2 buttons using that and refreshed page multiple times but the newly added buttons always stay. Local storage is working just fine.
Also, looking at your code, there is NO localStorage.removeItem() present. Hence the window.onload works fine. However, if you have any error logs in console of dev tools, please provide them for further debugging.
Also, if there is a way for you attach a video link of testing with dev tools console visible that would really help to understand why it doesn't work for you.
I've created an HTML form with JS validation. I've called the function through a button via onclick but it is firing multiple times resulting in unnecessary blank form data. Here's the code.
I'm pushing the data to firebase realtime db.
I'm new to JS, any help would be appreciated.
HTML
<form class="subscribe-form" id="subscribe-form">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<input class="email-input" name="email" id="email" type="text" placeholder="Email">
</div>
<div class="col-lg-2 col-md-2 col-sm-2 col-xs-2">
<button onclick="updateSubscribeEmail();" class="sitebtn btn-submit"
type="submit">Subscribe</button></div>
</form>
JS
function updateSubscribeEmail() {
// Reference messages collection
var messagesRef = firebase.database().ref("Subscription Emails");
var resetForm = document.getElementById("subscribe-form");
// Listen for form submit
document.getElementById("subscribe-form").addEventListener("submit", submitSubscribeForm);
// Submit form
function submitSubscribeForm(e) {
// To avoid entire page getting refreshed
e.preventDefault();
var email = getInputVal("email");
// Get values
var text;
console.log("Before Validation");
if (email.indexOf("#") === -1 || email.length < 6) {
console.log("After Validation");
text = "Please enter a valid email address!";
red_inner_html.innerHTML = text;
red_div.style.display = "block";
setTimeout(function () {
red_div.style.display = "none";
}, 5000);
} else {
saveMessage(email);
console.log("Before reset");
resetForm.reset();
console.log("after reset");
text = "You have successfully subscribed!";
green_inner_html.innerHTML = text;
green_div.style.display = "block";
setTimeout(function () {
green_div.style.display = "none";
}, 5000);
}
}
// Function to get get form values
function getInputVal(id) {
return document.getElementById(id).value;
}
// Save message to firebase
function saveMessage(email) {
var newMessageRef = messagesRef.push();
newMessageRef.set({
Email: email
});
}
}
For the sake of practice I am trying to render an element on submitting the form but each time I click the button element renders on the page but it should render only once in the case of an invalid value.
My question is how to execute function renderError() only once, when Submit button is clicked?
The code I'm trying to accomplish this with is:
const form = document.querySelector('.form')
const renderError = () => {
const newElement = document.createElement('div')
newElement.className = 'error'
newElement.innerHTML = `
<img class="error__icon" src="images/icon-error.svg" alt="error icon" />
<p class="error__message">Please provide a valid email</p>
`
const rootElement = document.getElementById('error__container')
rootElement.append(newElement)
}
const validateForm = () => {
const isValid = /^[^\s#]+#[^\s#]+\.[^\s#]+$/
if (isValid.test(email.value)) {
// return something
} else {
renderError()
}
}
form.addEventListener('submit', (e) => {
e.preventDefault()
validateForm()
})
<div class="form__container">
<form class="form" novalidate>
<input type="email" name="email" id="email" aria-label="Email" placeholder="Email Address" />
<div id="error__container"></div>
<button class="submit">
<img src="images/icon-arrow.svg" alt="submit icon">
</button>
</form>
</div>
Don't create a new error element each time. Try to find the one created before (by id, for example). Create it only if you need it. This is often called "lazy" initialization.
// lazily return the error element (create it if we can't find it)
const errorElement = () => {
let element = document.getElementById('errorElement')
if (!element) {
element = document.createElement('div')
element.className = 'error'
element.id = 'errorElement';
const rootElement = document.getElementById('error__container')
rootElement.append(element)
}
return element
}
let count = 0
const validateForm = () => {
const isValid = /^[^\s#]+#[^\s#]+\.[^\s#]+$/
if (isValid.test(email.value)) {
// return something
} else {
const element = errorElement();
element.innerHTML = `something went wrong ${++count} times`
}
}
const form = document.getElementById('myform')
form.addEventListener('submit', (e) => {
e.preventDefault()
validateForm()
})
<div class="form__container">
<form id="myform" class="form" novalidate>
<input type="email" name="email" id="email" aria-label="Email" placeholder="Email Address" />
<div id="error__container"></div>
<button class="submit">Button
</button>
</form>
</div>
One solution: you can clear the contents of the rootElement before re-rendering:
const renderError = () => {
const newElement = document.createElement('div')
newElement.className = 'error'
newElement.innerHTML = `
<img class="error__icon" src="images/icon-error.svg" alt="error icon" />
<p class="error__message">Please provide a valid email</p>
`
const rootElement = document.getElementById('error__container')
rootElement.innerHTML = ""; // <-- Empty before appending
rootElement.append(newElement)
}
Another solution would be to add the newElement in the HTML, hide it using visibility: hidden and then toggle a class on/off that will turn the visibility value to visible and hidden. This way, when the render error runs, it will just add a special class that will display the error element and upon clicking again the element will just get the class enabled again without re-appearing.
this is my javascript code the onclick functions do not trigger when i push the button I have tried with an event listener that listen only for the parent of the button aka the form but nothing in that case it fires once and it does not keep listening for further button clicks:
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port);
document.addEventListener('DOMContentLoaded', () =>{
const room__message = Handlebars.compile(document.querySelector('#room__message').innerHTML);
document.querySelector('#send__button').onclick = () =>{
console.log('hola el boton fue pulsado')
let message = document.querySelector('#message__input').value
let user = localStorage.getItem('user')
let channel = localStorage.getItem('channel')
console.log(message)
socket.emit('send message', {'message':message, 'user':user, 'room':channel})
document.querySelector('#message__input').value = '';
}
socket.on('connect', () =>{
socket.emit('join', { 'channel':localStorage.getItem('channel'), 'user':user })
load__list();
load_messages(localStorage.getItem('channel'))
});
document.querySelector('#add__room').onclick = () => {
let list = JSON.parse(localStorage.getItem('channel__list'));
let name_ = prompt("Please enter you'r new Channel's name", "");
while (name_ in list || name != null){
name_ = prompt("this name is already in the database", "");
}
if (name_ != null){
list.push(name_)
}
socket.emit('new room', {'name':name_})
};
socket.on('broadcast', data =>{
let message = data.message;
let user = data.user;
let timestamp = data.timestamp;
const msj = room__message({'message':message, 'user':user, 'timestamp':timestamp})
document.querySelector('.message__cont').innerHTML += msj;
});
});
the html looks like this:
<body>
<ul id="channel__list">
<li>
<button id="add__room">+</button>
</li>
</ul>
<div id="chanel__container">
<form id="channel__form" action="" >
<input type="text" id="message__input" autocomplete="off" placeholder="message">
<input type="submit" id="send__button" value="send">
</form>
</div>
</body>
it does run in a flask server I dont know if that may be an issue
First of all, there are quite a few syntax errors in your code.
Inside your HTML code
<from> tag is to be changed to <form>
Inside your JS code
ID room__message is not declared hence this will never return anything.
You have opened a function socket.io('connect') function but never closed.
Coming back to why the on click buttons are not getting triggered *
The possible reason this could be happening is that by the time your document readyState is already completed in this case the event DOMContentLoaded will not be fired at any point in time. This can be avoided by providing/checking document ready state.
Below are two sample codes (I am still not sure why you are using on click function inside a listener while you can use JS shorthand and directly use it)
Proper HTML code
<body>
<ul id="channel__list">
<li>
<button id="add__room">+</button>
</li>
</ul>
<div id="chanel__container">
<form id="channel__form">
<input
type="text"
id="message__input"
autocomplete="off"
placeholder="message"
/>
<input type="submit" id="send__button" value="send" />
</form>
</div>
</body>
Your actual JS code (a bit modification)
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadDomContent());
} else {
console.log("Current document load state: ", document.readyState);
loadDomContent();
}
function loadDomContent() {
const room__message = Handlebars.compile(
document.querySelector("#room__message").innerHTML
);
document.querySelector("#send__button").onclick = () => {
/* do something */
};
socket.on("connect", () => {
socket.emit("join", {
/* do other stuff }); */
});
});
document.querySelector("#add__room").click = () => {
console.log("I am working");
/* do something else */
};
socket.on("broadcast", data => {
/* send some stuff over */
});
}
Also, you can write something like this
<button onclick="addNewRoom()" id="add__room">+</button>
An declaring that as:
function addNewRoom(){
// do something
}
My Solution
the issue was in deed in the html, because it was changing the position of th ebutton by adding more elements to its container im guessing it changed the address to which the event was pointing at, thanks everybody for their input.
I add a new contact in the method: addToBook(). First I check the fields, if they are not empty, then I create the instance of the class LocalStorage and pass the field values and make JSON from it.
I want to see the new product in the array and LocalStorage but I get the error:
Uncaught TypeError: Can not read property 'push' of undefined"
Help me fix it.
class Contacts {
constructor() {
// Storage Array
this.contacts = [];
}
addToBook() {
let isNull = forms.name.value != '' && forms.phone.value != '' &&
forms.email.value != '';
if (isNull) {
// format the input into a valid JSON structure
let obj = new LocalStorage(forms.name.value,
forms.phone.value, forms.email.value);
this.contacts.push(obj);
localStorage["addbook"] = JSON.stringify(this.contacts);
console.log(this.contacts);
}
console.log(this.contacts);
}
}
let contacts = new Contacts();
class Forms {
constructor() {
// Blocks
this.addNewContact = document.getElementById("addNewContact");
this.registerForm = document.querySelector('.addNewContact-form');
// Forms
this.fields = document.forms.register.elements;
this.name = this.fields[0].value;
this.phone = this.fields[1].value;
this.email = this.fields[2].value;
// Buttons
this.cancelBtn = document.getElementById("Cancel");
this.addBtn = document.getElementById("Add");
this.BookDiv = document.querySelector('.addbook');
// display the form div
this.addNewContact.addEventListener("click", (e) => {
this.registerForm.style.display = "block";
if (this.registerForm.style.display = "block") {
this.BookDiv.style.display = "none";
}
});
this.cancelBtn.addEventListener("click", (e) => {
this.registerForm.style.display = "none";
if (this.registerForm.style.display = "none") {
this.BookDiv.style.display = "block";
}
});
this.addBtn.addEventListener("click", contacts.addToBook);
}
}
let forms = new Forms();
class LocalStorage {
constructor(name, phone, email) {
this.name = name;
this.phone = phone;
this.email = email;
}
}
<div class="AddNewContact">
<button id="addNewContact" type="button">Add new contact</button>
<i class="fas fa-search "></i>
<input type="text" placeholder="SEARCH BY NAME">
<button id="ImportData" type="button">Import data to book</button>
</div>
<div class="addNewContact-form">
<form name="register">
<label for="name">Name</label><input type="text" id="name" class="formFields">
<label for="phone">Phone</label><input type="text" id="phone" class="formFields">
<label for="email">E-Mail</label><input type="text" id="email" class="formFields">
<br><br>
<button id="Add" type="button">Add Now</button><button id="Cancel" type="button">Cancel</button>
</form>
</div>
When you pass a reference to a function like this:
this.addBtn.addEventListener("click", contacts.addToBook);
you loose the binding to this. Which you depend on when you call this.contacts.push(obj); in addToBook()
You can hard bind the reference you want this to be with:
this.addBtn.addEventListener("click", contacts.addToBook.bind(contacts);
You could also pass in a function that explicitly calls addToBook with the correct context:
this.addBtn.addEventListener("click", () => contacts.addToBook());