Scenario: Just created a simple book List app, which can accept some inputs and display it on the UI and has other functionalities such as the use of localStorage.
Just as I added more custom alerts (e.g. show alert - on adding a book, - if author name contain numbers etc.) The whole alert stuff has gone AWOL. (Previously defined alerts worked fine before)
Also none of the questions that showed similarity proved useful to me
Method which handles all the alerts in the page
static showAlert(msg, className) {
const alertBox = document.createElement('div');
alertBox.className = `alert alert-${className} mb-3 p-2`;
alertBox.appendChild(document.createTextNode(msg));
const container = document.querySelector('.container');
const form = document.querySelector('#book-form');
// check whether if an alertBox already exist in the UI
if(document.querySelector('.alert')) {
// remove alertBox after some time
setTimeout(() => {
document.querySelector('.alert').remove();
}, 3000);
} else {
container.insertBefore(alertBox, form);
}
}
And this is where I added new custom alerts:
if(bookName === '' || authorName === '' || bookId === '') {
Dom.showAlert('All fields are required', 'danger');
} else if (!isNaN(bookName)) {
Dom.showAlert('Name of the book should not contain numbers', 'danger');
document.querySelector('#title').value = '';
} else if (!isNaN(authorName)) {
Dom.showAlert('Author Name should not contain numbers', 'danger');
document.querySelector('#author').value = '';
} else if (isNaN(bookId)) {
Dom.showAlert('Reference Id should only consist of numbers', 'danger');
document.querySelector('#bookId').value = '';
Problems facing:
Quick note: I have set the setTimeout delay to 3000
Existing alert is not removed after 3 seconds (which worked fine before)
If there is a need for another alert to exist in dom, then the alert showed previously should be removed and the new alert should be displayed; even if that happens in under 3 seconds.
I have added the code as snippet as I had some trouble setting JSFiddle
// Book class
class Book {
constructor(title, author, id) {
this.bookName = title;
this.authorName = author;
this.bookId = id;
}
}
// Dom class : handle user interface / dom
class Dom {
static displayBooks() {
//dummy data to mimic data in localStorage
const savedBooks = [
{
bookName: 'Vikings',
authorName: 'Michael Hirst',
bookId: 9764363,
},
{
bookName: 'The Soldier',
authorName: 'David Gary',
bookId: 5764363,
},
]
//const books = Store.getBooks();
const books = savedBooks;
books.forEach((book) => Dom.addBookToList(book))
}
static addBookToList(bookInfo) {
const tbody = document.querySelector('#book-list')
const bookRow = document.createElement('tr');
bookRow.innerHTML =
`
<tr>
<td>${bookInfo.bookName}</td>
<td>${bookInfo.authorName}</td>
<td>${bookInfo.bookId}</td>
<td>Delete</td>
</tr>
`
tbody.appendChild(bookRow);
}
static deleteBook(target) {
if(target.classList.contains('deleteBtn')) {
target.parentElement.parentElement.remove()
// show alert if user deletes a book
Dom.showAlert('Book deleted successfully', 'warning');
}
}
static showAlert(msg, className) {
const alertBox = document.createElement('div');
alertBox.className = `alert alert-${className} mb-3 p-2`;
alertBox.appendChild(document.createTextNode(msg));
const container = document.querySelector('.container');
const form = document.querySelector('#book-form');
// check whether if an alertBox already exist in the UI
if(document.querySelector('.alert')) {
// remove alerBox after an interval
setTimeout(() => {
document.querySelector('.alert').remove();
}, 3000);
} else {
container.insertBefore(alertBox, form);
}
}
static clearFields() {
document.querySelector('#title').value = '';
document.querySelector('#author').value = '';
document.querySelector('#bookId').value = '';
}
}
// Store class - deals with localStorage
/*class Store {
static getBooks() {
let books;
if(localStorage.getItem('books') === null) {
// if there is no book in the localstorage, then we initialize an empty array (of objects);
books = [];
} else {
// since the data is in the format of string, we need to change it to a javascript object in order to to things with it.Therefore we use JSON.parse
books = JSON.parse(localStorage.getItem('books'));
}
return books;
}
static addBooks(book) {
const books = Store.getBooks();
books.push(book);
localStorage.setItem('books', JSON.stringify(books))
}
static removeBooks(bookId) {
const books = Store.getBooks();
books.forEach((book, index) => {
if(book.bookId === bookId) {
books.splice(index, 1);
}
});
localStorage.setItem('books', JSON.stringify(books));
}
}*/
// Events in Dom
document.addEventListener('DOMContentLoaded', Dom.displayBooks);
// Add new book to dom/ui
document.querySelector('#book-form').addEventListener('submit', event => {
event.preventDefault();
const bookName = document.querySelector('#title').value;
const authorName = document.querySelector('#author').value;
const bookId = document.querySelector('#bookId').value;
// input validation
if(bookName === '' || authorName === '' || bookId === '') {
Dom.showAlert('All fields are required', 'danger');
} else if (!isNaN(bookName)) {
Dom.showAlert('Name of the book should not contain numbers', 'danger');
document.querySelector('#title').value = '';
} else if (!isNaN(authorName)) {
Dom.showAlert('Author Name should not contain numbers', 'danger');
document.querySelector('#author').value = '';
} else if (isNaN(bookId)) {
Dom.showAlert('Reference Id should only consist of numbers', 'danger');
document.querySelector('#bookId').value = '';
} else {
// object instantiation of book
const book = new Book(bookName, authorName, bookId);
// Add submitted book to the list
Dom.addBookToList(book);
// Add Submitted bookk to the local Storage
//Store.addBooks(book);
// show alert after adding the book
Dom.showAlert('Book added successfully', 'success');
// Clear input fields after submitting
Dom.clearFields();
}
})
// Remove / Delete a book from the list
document.querySelector('#book-list')
.addEventListener('click', event => {
// Remove book from the Dom
Dom.deleteBook(event.target);
// Remove book from the local storage
//Store.removeBooks(event.target.parentElement.previousElementSibling.textContent);
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Book List app</title>
<!-- fontAwesome script -->
<script src="https://kit.fontawesome.com/39350fd9df.js"></script>
<!-- bootstrap css -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.6.0/dist/css/bootstrap.min.css" type="text/css">
</head>
<body>
<div class="container mt-4">
<h1 class="display-4 text-center mb-4"><i class="fas fa-book text-success"></i> <span class="text-success">BlueStock</span> <span class="text-muted">Book Shop</span></h1>
<form action="#" id="book-form">
<div class="form-group mb-2">
<label for="title">Book Title</label>
<input type="text" name="title" id="title" class="form-control">
</div>
<div class="form-group mb-2">
<label for="author">Author Name</label>
<input type="text" name="author" id="author" class="form-control">
</div>
<div class="form-group">
<label for="book-id">Reference Id</label>
<input type="text" name="title" id="bookId" class="form-control">
</div>
<input type="submit" value="Add Book" class="btn btn-primary w-100 mt-3" id="submit">
</form>
<table class="table table-striped mt-4">
<thead>
<tr>
<th>Book Title</th>
<th>Author Name</th>
<th>Reference Id</th>
<th class="text-warning"><small>Delete book</small></th>
</tr>
</thead>
<tbody id="book-list"></tbody>
</table>
</div>
<script src="app.js" type="text/javascript"></script>
</body>
</html>
The issue I notice with your showAlert:
showAlert(msg, className) {
// ...
if (document.querySelector('.alert')) {
/*
#1. At the first time this function called, this will always be null. Since '.alert' is not in the DOM yet (it wil be added to the DOM at the following 'else' block.
Therefore, setTimeout here won't be called, since the code never enters here at the first time.
#2. At the second time and forth, this will be called.
However, it will not enter the `else` block which is to add the alert into the DOM
*/
setTimeout(() => {
document.querySelector('.alert').remove();
}, 3000);
} else {
container.insertBefore(alertBox, form);
}
}
If we break it down your expected behavior, the code should probably somewhere like this;
static showAlert(msg, className) {
// If there's an existing alert, instantly remove it.
if (document.querySelector('.alert')) {
document.querySelector('.alert').remove();
}
// Create the alert
const alertBox = document.createElement('div');
alertBox.className = `alert alert-${className} mb-3 p-2`;
alertBox.appendChild(document.createTextNode(msg));
const container = document.querySelector('.container');
const form = document.querySelector('#book-form');
// Insert alert to the DOM.
container.insertBefore(alertBox, form);
// Remove the alert above after 3s
setTimeout(() => {
alertBox.remove();
}, 3e3);
}
Example final code as following;
// Book class
class Book {
constructor(title, author, id) {
this.bookName = title;
this.authorName = author;
this.bookId = id;
}
}
// Dom class : handle user interface / dom
class Dom {
static displayBooks() {
//dummy data to mimic data in localStorage
const savedBooks = [{
bookName: 'Vikings',
authorName: 'Michael Hirst',
bookId: 9764363,
},
{
bookName: 'The Soldier',
authorName: 'David Gary',
bookId: 5764363,
},
]
//const books = Store.getBooks();
const books = savedBooks;
books.forEach((book) => Dom.addBookToList(book))
}
static addBookToList(bookInfo) {
const tbody = document.querySelector('#book-list')
const bookRow = document.createElement('tr');
bookRow.innerHTML =
`
<tr>
<td>${bookInfo.bookName}</td>
<td>${bookInfo.authorName}</td>
<td>${bookInfo.bookId}</td>
<td>Delete</td>
</tr>
`
tbody.appendChild(bookRow);
}
static deleteBook(target) {
if (target.classList.contains('deleteBtn')) {
target.parentElement.parentElement.remove()
// show alert if user deletes a book
Dom.showAlert('Book deleted successfully', 'warning');
}
}
static showAlert(msg, className) {
// If there's an existing alert, instantly remove it.
if (document.querySelector('.alert')) {
document.querySelector('.alert').remove();
}
// Create the alert
const alertBox = document.createElement('div');
alertBox.className = `alert alert-${className} mb-3 p-2`;
alertBox.appendChild(document.createTextNode(msg));
const container = document.querySelector('.container');
const form = document.querySelector('#book-form');
// Insert alert to the DOM.
container.insertBefore(alertBox, form);
// Remove the alert above after 3s
setTimeout(() => {
alertBox.remove();
}, 3e3);
}
static clearFields() {
document.querySelector('#title').value = '';
document.querySelector('#author').value = '';
document.querySelector('#bookId').value = '';
}
}
// Store class - deals with localStorage
/*class Store {
static getBooks() {
let books;
if(localStorage.getItem('books') === null) {
// if there is no book in the localstorage, then we initialize an empty array (of objects);
books = [];
} else {
// since the data is in the format of string, we need to change it to a javascript object in order to to things with it.Therefore we use JSON.parse
books = JSON.parse(localStorage.getItem('books'));
}
return books;
}
static addBooks(book) {
const books = Store.getBooks();
books.push(book);
localStorage.setItem('books', JSON.stringify(books))
}
static removeBooks(bookId) {
const books = Store.getBooks();
books.forEach((book, index) => {
if(book.bookId === bookId) {
books.splice(index, 1);
}
});
localStorage.setItem('books', JSON.stringify(books));
}
}*/
// Events in Dom
document.addEventListener('DOMContentLoaded', Dom.displayBooks);
// Add new book to dom/ui
document.querySelector('#book-form').addEventListener('submit', event => {
event.preventDefault();
const bookName = document.querySelector('#title').value;
const authorName = document.querySelector('#author').value;
const bookId = document.querySelector('#bookId').value;
// input validation
if (bookName === '' || authorName === '' || bookId === '') {
Dom.showAlert('All fields are required', 'danger');
} else if (!isNaN(bookName)) {
Dom.showAlert('Name of the book should not contain numbers', 'danger');
document.querySelector('#title').value = '';
} else if (!isNaN(authorName)) {
Dom.showAlert('Author Name should not contain numbers', 'danger');
document.querySelector('#author').value = '';
} else if (isNaN(bookId)) {
Dom.showAlert('Reference Id should only consist of numbers', 'danger');
document.querySelector('#bookId').value = '';
} else {
// object instantiation of book
const book = new Book(bookName, authorName, bookId);
// Add submitted book to the list
Dom.addBookToList(book);
// Add Submitted bookk to the local Storage
//Store.addBooks(book);
// show alert after adding the book
Dom.showAlert('Book added successfully', 'success');
// Clear input fields after submitting
Dom.clearFields();
}
})
// Remove / Delete a book from the list
document.querySelector('#book-list')
.addEventListener('click', event => {
// Remove book from the Dom
Dom.deleteBook(event.target);
// Remove book from the local storage
//Store.removeBooks(event.target.parentElement.previousElementSibling.textContent);
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Book List app</title>
<!-- fontAwesome script -->
<script src="https://kit.fontawesome.com/39350fd9df.js"></script>
<!-- bootstrap css -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.6.0/dist/css/bootstrap.min.css" type="text/css">
</head>
<body>
<div class="container mt-4">
<h1 class="display-4 text-center mb-4"><i class="fas fa-book text-success"></i> <span class="text-success">BlueStock</span> <span class="text-muted">Book Shop</span></h1>
<form action="#" id="book-form">
<div class="form-group mb-2">
<label for="title">Book Title</label>
<input type="text" name="title" id="title" class="form-control">
</div>
<div class="form-group mb-2">
<label for="author">Author Name</label>
<input type="text" name="author" id="author" class="form-control">
</div>
<div class="form-group">
<label for="book-id">Reference Id</label>
<input type="text" name="title" id="bookId" class="form-control">
</div>
<input type="submit" value="Add Book" class="btn btn-primary w-100 mt-3" id="submit">
</form>
<table class="table table-striped mt-4">
<thead>
<tr>
<th>Book Title</th>
<th>Author Name</th>
<th>Reference Id</th>
<th class="text-warning"><small>Delete book</small></th>
</tr>
</thead>
<tbody id="book-list"></tbody>
</table>
</div>
<script src="app.js" type="text/javascript"></script>
</body>
</html>
Related
i'm studying JS and at the moment i'm not really good with it. I created a page (a kind of social network) and i need to add an image from an URL when i fill a form. The form has 2 fields: Image title and URL
the initial cards that i have on the page, i handle to insert them from an array. But i can't understand how to add a single photo from a form.
The new photo should appear as first image, the previous 1st image should be at the 2nd place and so on, cards can be deleted when i click on a button but i didn't really got how to do it, and the like buttons should work for every single cards... i've was looking for it on google and i found some stuffs but they didn't work for me.
how can i solve it?
my code:
HTML
<section class="cards" id="cards">
<!-- images will be added here-->
<div class="cards__add-form-overlay">
<form class="cards__add-form">
<button class="cards__add-form-close-icon"></button>
<p class="cards__add-form-text">New place</p>
<input type="text" placeholder="Name" class="cards__add-form-first-field" id="ImageName" value= "">
<input type="text" placeholder="Image URL" class="cards__add-form-second-field" id="URL" value= "">
<button type="submit" class="cards__add-form-submit">create</button>
</form>
</div>
</section>
<template class="elements" id="elements">
<div class="element">
<div class="element__card">
<img class="element__photo" src="" alt="">
<div class="element__button-container">
<button type="button" class="element__trash-button" id="trashbutton">
</div>
<div class="element__text">
<p class="element__place"></p>
<button type="button" class="element__like-button" id="likebutton" onclick="toggle()"></button>
</div>
</div>
</template>
<script type="text/javascript" src="./script.js"></script>
</body>
</html>
JS
// IMAGES //
// description: adding photos in the page from an array with JS //
const initialCards = [
{ name:'', link:''},
{ name:'', link:''},
{ name:'', link:''},
{ name:'', link:''},
{ name:'', link:''},
{ name:'', link:''}
];
initialCards.forEach(card => cardItem(card));
function cardItem(cardData) {
const container = document.getElementById("cards");
const cardTemplate = document.getElementById("elements").content;
const newCard = cardTemplate.cloneNode(true);
const elementImage = newCard.querySelector('.element__photo');
const elementText = newCard.querySelector('.element__place');
elementImage.src = cardData.link;
elementText.textContent = cardData.name;
container.append(newCard);
}
----- Until here all good
// description: adding popup when clicking on + button, and close with X button //
document.querySelector('.profile__add-button').addEventListener('click', function () {
document.querySelector('.cards__add-form-overlay').style.visibility = 'visible';
});
document.querySelector('.cards__add-form-close-icon').addEventListener('click', function () {
document.querySelector('.cards__add-form-overlay').style.visibility = 'hidden';
});
document.querySelector('.cards__add-form-submit').addEventListener('click', function () {
document.querySelector('.cards__add-form-overlay').style.visibility = 'hidden';
});
document.querySelector('.profile__add-button').addEventListener('click', function () {
document.querySelector('.cards__add-form').style.visibility = 'visible';
});
document.querySelector('.cards__add-form-close-icon').addEventListener('click', function () {
document.querySelector('.cards__add-form').style.visibility = 'hidden';
});
document.querySelector('.cards__add-form-submit').addEventListener('click', function () {
document.querySelector('.cards__add-form').style.visibility = 'hidden';
});
// description: adding photo through popup with 2 fields, name and URL //
const addPhoto = document.querySelector('.cards__add-form');
const imageNameInput = document.querySelector('.cards__add-form-first-field');
const imageUrlInput = document.querySelector('.cards__add-form-first-field');
function handleAddCardFormSubmit(evt) {
evt.preventDefault();
const element = createCard(imageNameInput.value, imageUrlInput.value);
elements.prepend(element);
imageNameInput.value = '';
imageUrlInput.value = '';
closePopup(evt.target.closest('.cards__add-form'));
}
function createCard(name, link) {
const elementTemplate = document.querySelector('#element-template').content;
const element = elementTemplate.querySelector('.element').cloneNode(true);
const elementImage = element.querySelector('.element__photo');
const elementTitle = element.querySelector('.element__place');
elementImage.src = link;
elementTitle.textContent = name;
//like button//
const likeButton = element.querySelector('.element__like-button');
likeButton.addEventListener('click', () => likeButton.classList.toggle('element__like-button_active'));
//delete cards //
element.addEventListener('click', function (evt) {
if (evt.target.classList.contains('element__trash-button')) {
evt.currentTarget.remove();
}
if (evt.target.classList.contains('element__photo')) {
openImagePopup(name, link);
}
});
return element;
}
initialCards.forEach(({name, link}) => elements.append(createCard(name, link)));
with this code, the new image doesn't appear, about like button console says that toogle() is not defined, and delete button don't delete the image but no error in the console
I'm trying to build a video chat webapp using Twilio following https://www.twilio.com/blog/build-video-chat-application-python-javascript-twilio-programmable-video, but I keep getting the error listed in the title. From what I've gathered, I'm trying to call upon the attributes of an object (sid, name) that was never really defined (participant), but I'm not sure where in my code to define it.
<body>
<h1>join existing jam</h1>
<form>
<label for="username">Name: </label>
<input type="text" name="username" id="username">
<button id="join_leave">join</button>
</form>
<p id="count"></p>
<div id="container" class="container">
<div id="local" class="participant"><div></div><div>Me</div></div>
<div id="{{ participant.sid }}" class="participant">
<div></div> <!-- the video and audio tracks will be attached to this div -->
<div>{{participant.name}}</div>
</div>
</div>
<script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>
<script>
let connected=false;
const usernameInput = document.getElementById('username');
const button = document.getElementById('join_leave');
const container = document.getElementById('container');
const count = document.getElementById('count');
let room;
function addLocalVideo() {
Twilio.Video.createLocalVideoTrack().then(track => {
let video = document.getElementById('local').firstChild;
video.appendChild(track.attach());
});
};
function connectButtonHandler(event) {
event.preventDefault();
if (!connected) {
let username = usernameInput.value;
if (!username) {
alert('Enter your name before connecting');
return;
}
button.disabled = true;
button.innerHTML = 'connecting...';
connect(username).then(() => {
button.innerHTML = 'leave';
button.disabled = false;
}).catch(() => {
alert('Connection failed. Is the backend running?');
button.innerHTML = 'join';
button.disabled = false;
});
}
else {
disconnect();
button.innerHTML = 'join';
connected = false;
}
};
function connect(username) {
let promise = new Promise((resolve, reject) => {
// get a token from the back end
fetch('/login', {
method: 'POST',
body: JSON.stringify({'username': username})
}).then(res => res.json()).then(data => {
// join video call
return Twilio.Video.connect(data.token);
}).then(_room => {
room = _room;
room.participants.forEach(participantConnected);
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
connected = true;
updateParticipantCount();
resolve();
}).catch(() => {
reject();
});
});
return promise;
};
function updateParticipantCount() {
if (!connected)
count.innerHTML = 'Disconnected.';
else
count.innerHTML = (room.participants.size + 1) + ' participants online.';
};
function participantConnected(participant) {
let participantDiv = document.createElement('div');
participantDiv.setAttribute('id', participant.sid);
participantDiv.setAttribute('class', 'participant');
let tracksDiv = document.createElement('div');
participantDiv.appendChild(tracksDiv);
let labelDiv = document.createElement('div');
labelDiv.innerHTML = participant.identity;
participantDiv.appendChild(labelDiv);
container.appendChild(participantDiv);
participant.tracks.forEach(publication => {
if (publication.isSubscribed)
trackSubscribed(tracksDiv, publication.track);
});
participant.on('trackSubscribed', track => trackSubscribed(tracksDiv, track));
participant.on('trackUnsubscribed', trackUnsubscribed);
updateParticipantCount();
};
function participantDisconnected(participant) {
document.getElementById(participant.sid).remove();
updateParticipantCount();
};
function trackSubscribed(div, track) {
div.appendChild(track.attach());
};
function trackUnsubscribed(track) {
track.detach().forEach(element => element.remove());
};
function disconnect() {
room.disconnect();
while (container.lastChild.id != 'local')
container.removeChild(container.lastChild);
button.innerHTML = 'Join call';
connected = false;
updateParticipantCount();
};
addLocalVideo();
button.addEventListener('click', connectButtonHandler);
</script>
</body>
Also, if it helps, this is the app.py that I'm calling from terminal:
import os
from dotenv import load_dotenv
from flask import Flask, render_template, request, abort
from twilio.jwt.access_token.grants import VideoGrant
load_dotenv()
twilio_account_sid=os.environ.get("TWILIO_ACCOUNT_SID")
twilio_api_key_sid = os.environ.get('TWILIO_API_KEY_SID')
twilio_api_key_secret = os.environ.get('TWILIO_API_KEY_SECRET')
app=Flask(__name__)
#app.route('/')
def index():
return render_template('joinJam.html')
#app.route('/login',methods=['POST'])
def login():
username=request.get_json(force=True).get('username')
if not username:
abort(401)
token=AccessToken(twilio_account_sid, twilio_api_key_sid, twilio_api_key_secret, identity=username)
token.add_grant(VideoGrant(room='My Room'))
return {'token': token.to_jwt().decode()}
Twilio developer evangelist here.
Your issue is in the HTML here:
<div id="container" class="container">
<div id="local" class="participant"><div></div><div>Me</div></div>
<div id="{{ participant.sid }}" class="participant">
<div></div> <!-- the video and audio tracks will be attached to this div -->
<div>{{participant.name}}</div>
</div>
</div>
You are trying to refer to a participant object that does not exist.
In this case you are trying to render the participant information for the local participant. Instead of doing so directly in the HTML, you need to do this in the JavaScript once you have successfully requested the media of your local participant.
Your HTML should be:
<div id="container" class="container">
<div id="local" class="participant"><div></div><div>Me</div></div>
</div>
Then the showing of your media will be handled by the addLocalVideo method.
const expenseForm = document.querySelector(".tracker-form"),
expenseName = document.querySelector(".tracker-name"),
expenseDate = document.querySelector(".tracker-date"),
expenseAmount = document.querySelector(".tracker-amount"),
expenseTable = document.querySelector(".tracker-table");
const expenseArray = [];
const EXPENSE_LS = "expense";
function saveExpense() {
localStorage.setItem(EXPENSE_LS, JSON.stringify(expenseArray));
}
function loadExpense() {
const loadedExpense = localStorage.getItem(EXPENSE_LS);
if (loadedExpense !== null) {
const parsedExpense = JSON.parse(loadedExpense);
createRow();
parsedExpense.forEach(expense => {
paintExpense(expense);
});
}
}
function createRow() {
const tr = document.createElement("tr");
expenseTable.appendChild(tr);
}
function paintExpense(text) {
const expenseObj = {};
function createData(text) {
const td = document.createElement("td");
const tr = document.querySelector("tr");
expenseTable.lastChild.appendChild(td);
td.innerHTML = text;
if (text === expenseName.value && text !== "") {
expenseObj.name = text;
} else if (text === expenseDate.value && text !== "") {
expenseObj.date = text;
} else if (text === expenseAmount.value && text !== "") {
expenseObj.amount = text;
}
}
createRow();
createData(expenseName.value);
createData(expenseDate.value);
createData(expenseAmount.value);
expenseArray.push(expenseObj);
saveExpense();
}
function handleSubmit(event) {
event.preventDefault();
paintExpense();
expenseName.value = "";
expenseDate.value = "";
expenseAmount.value = "";
}
function init() {
loadExpense();
expenseForm.addEventListener("submit", handleSubmit);
}
init();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Expense Tracker</title>
<link href="expense-tracker.css" rel="stylesheet">
</head>
<body>
<h1>Expense Tracker</h1>
<h3>Add A New Item</h3>
<form class="tracker-form">
<label for="tracker-name">Name: </label>
<input class="tracker-name" type="text" placeholder="What did you spend?">
<label for="tracker-date">Date: </label>
<input class="tracker-date" type="date">
<label for="tracker-amount">Amount: </label>
<input class="tracker-amount" type="text">
<input class="tracker-button" type="submit" value="Add Expense">
</form>
<table class="tracker-table">
<tr>
<th scope="col">Name</th>
<th scope="col">Date</th>
<th scope="col">Amount</th>
<th scope="col">Delete</th>
</tr>
</table>
<script src="expense-tracker.js"></script>
</body>
</html>
When i submit form, three input values are set in localStorage as an object in array. But when i refresh the page, all the value is clear and only the object itself left. I think loadExpense function has a problem but i don't know why. I googled about this problem and almost of them says stringify array when set it in local storage and parse it when i get it so i did it but it doesn't solve this problem. Why is this happen?
Problem:
When doing init() it call loadExpense() which reads data from local storage and iterates on each item and then paintExpense(expense); is getting called with expense as the parameter.
Now in paintExpense method, the passed expense object is not getting used to populate the tr and td rather you are calling
createData(expenseName.value);
createData(expenseDate.value);
createData(expenseAmount.value);
expenseArray.push(expenseObj) // <-- Empty object since the fields are empty on load
In this case all these expenseName, expenseDate, expenseAmount are empty that is they don't have value on page refresh. So inside createData the value for expenseObj is not getting set which means it remains empty.
Now the line expenseArray.push(expenseObj); is pushing empty object in array i.e [{}] which ultimately getting saved in you localstorage hence in localstoray empty objects are getting stored.
Solution:
Use passed expenseObj in paintExpense to populate object.
Sample Code
function loadExpense() {
const loadedExpense = localStorage.getItem(EXPENSE_LS);
if (loadedExpense !== null) {
const parsedExpense = JSON.parse(loadedExpense);
parsedExpense.forEach(expense => {
createRow(expense); //<-- Changed to createRow
});
}
}
// Added new method to create column
function createColumn(text) {
const td = document.createElement("td");
td.innerHTML = text;
return td;
}
// Changed createRow to create row and add columns (Replacement of paintExpense)
function createRow(expenseObj) {
const tr = document.createElement("tr");
tr.append(createColumn(expenseObj.name));
tr.append(createColumn(expenseObj.date));
tr.append(createColumn(expenseObj.amount));
expenseTable.appendChild(tr);
}
// Added new method to create object from html fields
function createData() {
const expenseObj = {};
expenseObj.name = expenseName.value;
expenseObj.date = expenseDate.value;
expenseObj.amount = expenseAmount.amount;
return expenseObj;
}
function handleSubmit(event) {
event.preventDefault();
const expenseObj = createData(); //<-- Create data from fields
expenseArray.push(expenseObj) //<-- Push into expense array
createRow(expenseObj); //<-- Create complete row with columns
saveExpense(); //<-- Moved Save from paint
expenseName.value = "";
expenseDate.value = "";
expenseAmount.value = "";
}
You should stringify your data and then put it into localStorage.
Example:
var testObject = { 'one': 1, 'two': 2, 'three': 3 };
// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));
I have been working on a vanilla javascript TODO list with variou functionality like editing , adding deleting todos.
But I am failing to edit it because i cannot fetch the required HTML to the input tag.
Here is the relatede HTML code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Grocery Bud</title>
<!-- font-awesome -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css"
/>
<!-- styles -->
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<section class="section-center">
<!-- form -->
<form class="grocery-form">
<p class="alert"></p>
<h3>Grocery Bud</h3>
<div class="form-control">
<input type="text" id="grocery" placeholder="e.g. eggs">
<button type="submit" class="submit-btn">
Submit
</button>
</div>
</form>
<!-- list -->
<div class="grocery-container">
<div class="grocery-list">
</div>
<!-- button -->
<button type="button" class="clear-btn">clear items</button>
</div>
</section>
<!-- javascript -->
<script src="app.js"></script>
</body>
</html>
And here is my javascript code.
// ****** SELECT ITEMS **********
const alert = document.querySelector('.alert');
const form = document.querySelector('.grocery-form');
const grocery = document.getElementById('grocery');
const submitBtn = document.querySelector('.submit-btn');
const container = document.querySelector('.grocery-container');
const list = document.querySelector('.grocery-list');
const clearBtn = document.querySelector('.clear-btn');
// edit option
let editElement;
let editFlag = false;
let editID = "";
// display alert
const displayAlert = (text,action)=>{
alert.textContent = text;
alert.classList.add(`alert-${action}`);
// remove alert
setTimeout(() => {
alert.textContent = '';
alert.classList.remove(`alert-${action}`);
}, 1000);
}
// clear item
const clearItems = () => {
const items = document.querySelectorAll('.grocery-item');
if (items.length > 0) {
items.forEach(item => {
list.removeChild(item);
})
}
container.classList.remove('show-container');
displayAlert('Empty list', 'danger');
setBackToDefault();
// localStorage.removeItem('list');
}
// remove from local storage item
const removeFromLocalStorage = (id) => {
}
// delete function
const deleteItem = e => {
// console.log("Item Deleted.");
const element = e.currentTarget.parentElement.parentElement;
const id = element.dataset.id;
list.removeChild(element);
if (list.children.length === 0) {
container.classList.remove('show-container');
}
displayAlert("Item removed", "danger");
setBackToDefault();
// remove from local storage
// removeFromLocalStorage(id);
}
// edit function
const editItem = (e) => {
// console.log("Item Edited.");
const element = e.currentTarget.parentElement.parentElement;
// set edit item
editElement = e.currentTarget.parentElement.previousElementSiblings;
// set from value
grocery.value = editElement.innerHTML;
editFlag = true;
}
// local storage
const addTolocalStorage = (id, value) => {
console.log('added to local storage.');
}
// set back to default
const setBackToDefault = () => {
// console.log('Setted back to default.');
grocery.value = "";
editFlag = false;
editID = "";
submitBtn.textContent = "submit";
}
// ****** FUNCTIONS **********
const addItem = (e) => {
e.preventDefault();
// console.log(grocery.value);
const value = grocery.value.trim();
const id = new Date().getTime().toString();
if (value && !editFlag) {
const element = document.createElement('article');
// add class
element.classList.add('grocery-item');
// add id
const attr = document.createAttribute('data-id');
attr.value = id;
element.setAttributeNode(attr);
element.innerHTML = ` <p class="title">${value}</p>
<div class="btn-container">
<button type="button" class="edit-btn">
<i class="fas fa-edit"></i>
</button>
<button type="button" class="delete-btn">
<i class="fas fa-trash"></i>
</button>
</div>`;
const deleteBtn = element.querySelector('.delete-btn');
const editBtn = element.querySelector('.edit-btn');
deleteBtn.addEventListener('click', deleteItem);
editBtn.addEventListener('click', editItem);
// append chiled
list.appendChild(element);
// display alert
displayAlert('item added to the list.', 'success');
// show container
container.classList.add('show-container');
// add to localstorage
addTolocalStorage(id, value);
// set back to default
setBackToDefault();
}
else if (!value && editFlag) {
console.log("Editing...");
}
else {
displayAlert('PLease enter velue...', 'danger');
}
}
// ****** EVENT LISTENERS **********
// submit form
form.addEventListener('submit', addItem);
// clear items
clearBtn.addEventListener('click', clearItems);
The arrow function named editItem has a line with innerHTML. Here i have to get the innerHTML in the input in order to edit it.
Please help meout someone.
// edit function
const editItem = (e) => {
// console.log("Item Edited.");
const element = e.currentTarget.parentElement.parentElement;
// set edit item
editElement = e.currentTarget.parentElement.previousElementSiblings;
// set from value
grocery.value = editElement.innerHTML;
editFlag = true;
}
If you are trying to get the value of an input try with this:
grocery.value = editElement.value
Just change the editElement to element.firstElementChild and youre good
const editItem = (e) => {
// console.log("Item Edited.");
const element = e.currentTarget.parentElement.parentElement;
// set edit item
editElement = element.firstElementChild;
// set from value
grocery.value = editElement.innerHTML;
editFlag = true;
}
Here is a sample of index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Students</title>
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/design.css">
</head>
<body>
<div class="page">
<div class="page-header cf">
<h2>Students</h2>
<!-- dynamically insert search form here (optional) -->
</div>
<ul class="student-list">
<li class="student-item cf">
<div class="student-details">
<img class="avatar" src="https://randomuser.me/api/portraits/thumb/women/67.jpg">
<h3>iboya vat</h3>
<span class="email">iboya.vat#example.com</span>
</div>
<div class="joined-details">
<span class="date">Joined 07/15/15</span>
</div>
</li>
</ul>
Here is App.js
const studentList=document.querySelector('.student-list');
const page=document.querySelector(".page");
//constants used in displayPage function
//listItems ===54
const listItems=document.querySelector(".student-list").children;
const numberOfItems=10;
displayPage=(list,pageNumber)=> {
const SI=(pageNumber*numberOfItems)-numberOfItems;
const EI=pageNumber*numberOfItems;
Array.from(list).forEach((item,index)=> {
if (index>= SI && index<EI) {
item.style.display="block";
} else {
item.style.display="none";
}
})
}
//number of pages to display
addPaginationLinks=(list)=> {
const pages=Math.floor(list.length/10)
let html=``;
for (let i=0; i<pages; i++) {
if (i===0) {
html+=`
<li>
<a class="active" href="#">${i+1}</a>
</li>`;
} else {
html+=`
<li>
${i+1}
</li>`;
}
}
const ul=document.createElement("ul");
ul.innerHTML=html;
const div=document.createElement("div");
div.classList.add("pagination");
div.appendChild(ul);
page.appendChild(div);
}
displayPage(listItems,1);
addPaginationLinks(listItems);
addEventListener=()=> {
const a=document.querySelectorAll("a");
a.forEach(item=> {
item.addEventListener('click', (e)=> {
a.forEach(item=> {
if (item.classList.contains("active")) {
item.classList.remove("active")
e.target.classList.add("active");
}
})
const pageNumber=parseInt(e.target.textContent);
displayPage(listItems,pageNumber);
})
})
}
addEventListener();
addSearchComponent=()=> {
const pageHeader=document.querySelector(".page-header.cf")
let html=`
<div class="student-search">
<input placeholder="Search for students...">
<button>Search</button>
</div>`;
pageHeader.insertAdjacentHTML('beforeend', html);
}
addSearchComponent()
const search=document.querySelector("input");
const studentDetails=document.getElementsByClassName("student-details");
noResultsItem=()=> {
const item=`
<li class="no-results" style="display: none;">
<h3>No Results Shown Found</h3>
</li>`;
studentList.insertAdjacentHTML('beforeend', item);
}
noResultsItem()
//select no results list item
//filtering student list based on keyup search value
//array length is 0 if no match was found
search.addEventListener('keyup', (e)=> {
const noResults=document.querySelector(".no-results");
const array=Array.from(studentDetails).filter(student=>student.children[1].textContent.includes(e.target.value))
//no items in filter
//make every student item dissppear, make noresults show
if (array.length==0) {
Array.from(studentDetails).forEach(student=>{
student.parentNode.style.display="none";
});
noResults.style.display="block";
//show ones that match, hide ones that don't
} else if (array.length>0) {
noResults.style.display="none";
Array.from(studentDetails).forEach(student=>{
if (student.children[1].textContent.includes(e.target.value)) {
student.parentNode.style.display="block";
} else {
student.parentNode.style.display="none";
}
});
}
});
Here is a link to my JSfiddle:
https://jsfiddle.net/dt7q9h5x/1/
The index.html shown above actually includes, 54 li list items, but I only added one for the sample.
Heres my issue:
Although the number of list items changes depending on the value in the input field, when I delete what I typed in and the search input becomes empty, all 54 items re-appear, which isn't supposed to happen because only 10 items are supposed to appear on each page.
I want it to go back to the original pagination setting, which was initially set before I typed anything into the search input. This was originally set by the function displayPages(listItems,1).
When you go to JSfiddle, you can see there are 10 items listed on each page. Once you start typing, things seem to work, but once you delete everthing, then all the items just re-appear.
I think the code at the bottom of app.js where I'm writing code for the event handler must be the culprit here.
Any suggestions?
Thanks in advance.
I checked your code, you didn't specify what to happen if the value of your search input becomes empty and this is where the problem occurs.
I changed your code a little and add a simple if statement and everything works just fine :)
search.addEventListener('keyup', (e) => {
// **************
// Check to see if search term is empty or not
// **************
if (e.target.value.length > 0) {
const noResults = document.querySelector(".no-results");
const array = Array.from(studentDetails).filter(student => student.children[1].textContent.includes(e.target.value))
//no items in filter
//make every student item dissppear, make noresults show
if (array.length == 0) {
Array.from(studentDetails).forEach(student => {
student.parentNode.style.display = "none";
});
noResults.style.display = "block";
//show ones that match, hide ones that don't
} else if (array.length > 0) {
noResults.style.display = "none";
Array.from(studentDetails).forEach(student => {
if (student.children[1].textContent.includes(e.target.value)) {
student.parentNode.style.display = "block";
} else {
student.parentNode.style.display = "none";
}
});
}
}
// **************
// Display the initial state if search term is empty
// **************
else {
displayPage(listItems, 1);
}
});