addEventListener stops after the first execution - javascript

I'm running into an issue with my current JS project. It's a simple library where the user inputs the info and spits it out onto the page. I have a delete button that I add to each new div, which I've added event listeners to the buttons. When I click delete, it will delete the first one - but that's it. It stops working if i want to delete more than one book.
I think my problem is how I'm targeting the data-set/index values that i assign each div, but I'm not sure. I've tried for loops, for each, etc and can't seem to figure it out.
Any help would be appreciated.
const book1 = new Book('inserttitlehere', 'His name?', 63, false)
const book2 = new Book('kill bill', 'author2', 653, false)
const book3 = new Book('oh yeah baby', 'author3', 323, false)
const book4 = new Book('kill me now', 'author4', 132, true)
library.push(book1, book2, book3, book4)
// Book constructor
function Book(title, author, pages, completed) {
this.title = title
this.author = author
this.pages = pages
this.completed = completed
}
const main = document.querySelector('main');
const form = document.querySelector('.form');
//Function to add books to the DOM
function displayBooks() {
let dataIndex = 0;
main.innerHTML = '';
library.forEach(book => {
const bookElement = document.createElement('div')
bookElement.classList.add('book');
bookElement.setAttribute('data-index', dataIndex);
bookElement.innerHTML = `
<h3> ${book.title} </h3>
<p> Author: ${book.author}</p>
<p> Pages: ${book.pages}</p>
Completed: <input type="checkbox"> ${book.completed} <br>
<button class="delete">Delete</button>
`
main.appendChild(bookElement);
dataIndex++;
})
}
displayBooks();
//Add new book to library
function addBookToLibrary(title, author, pages, completed) {
const newBook = new Book(title, author, pages, completed)
library.push(newBook);
}
//Deleting a book from the array
let deleteBtns = document.querySelectorAll('.book .delete');
deleteBtns.forEach(button => {
button.addEventListener('click', () => {
const index = button.parentNode.dataset['data-index'];
deleteBook(index);
})
});
function deleteBook(index) {
library.splice(index, 1);
displayBooks();
}
form.addEventListener('submit', (e) => {
e.preventDefault();
const title = document.querySelector('#title').value;
const author = document.querySelector('#author').value;
const pages = document.querySelector('#pages').value;
addBookToLibrary(title, author, pages, false);
document.forms[0].reset();
})
console.log(library);
console.log(deleteBtns);
!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<title>Library</title>
</head>
<body>
<nav>
<h1>Library</h1>
<button class="add-book-btn">NEW BOOK</button>
</nav>
<main></main>
<div class="modal">
<form class="form">
<p class="close"></p>
<h2>Add Book</h2>
<div class="user-input">
<label for="">Title</label><br>
<input type="text" id="title" required>
</div>
<div class="user-input">
<label for="">Author</label><br>
<input type="text" id="author" required>
</div>
<div class="user-input">
<label for="">Pages</label><br>
<input type="number" id="pages" required>
</div>
<div class="user-input">
<label for="">Read?</label>
<input type="checkbox" id="read">
</div>
<button type="submit" id="submit">Submit</button>
</form>
</div>
<script src="script.js"></script>
</body>
</html>

When you are building an app like this it's often best to remove the parts that aren't relevant such as the form and all it;s associated functions in order to work on specific parts of it like these user interactions.
Here's a scaled down version with a completely different approach that adds event listeners to the book elements individually as you create them.
Then instead of worrying about indexing, use array methods to find the book object in the library. So rather than rebuild all the elements when you remove one you simply remove both the element and the object in the array.
It's broken down into smaller functions like addBookEvents() then within each different event handler uses either Array.prototype.find() or Array.prototype.findIndex() to modify library.
Your approach of rebuilding all the elements just to change the indexing is not very scalable or efficient. In order to create the library array used here I just used your library.push(book1,book2...) and then ran console.log(JSON.stringify(library)) and pasted it into this code to keep it lean for this stage.
Note that I changed data-index to data-title on the elements with assumption that titles will be unique in the array. That then allows searching array to find the specific book object. Using a unique book id is more reliable in case of title duplications
const main = document.getElementById('books-list')
library.forEach(createBookElement)
function handleDeleteClick(event) {
const bookElem = event.target.closest('.book');
const title = bookElem.dataset.title;
bookElem.remove();
removeFromLibrary(title)
console.log(title)
}
function handleCompleteChange(event){
const bookElem = event.target.closest('.book');
const title = bookElem.dataset.title;
const checked = event.target.checked;
toggleLibraryComplete(title, checked)
}
function removeFromLibrary(title) {
const idx = library.findIndex(book => book.title === title);
if (idx > -1) {
library.splice(idx, 1);
console.log('library length =', library.length)
}
}
function toggleLibraryComplete(title, checked){
const book = library.find(book => book.title === title);
book.completed = checked;
console.log(JSON.stringify(book))
}
function addBookEvents(bookElement){
const button = bookElement.querySelector('.delete');
button.addEventListener('click', handleDeleteClick);
const checkbox = bookElement.querySelector('.book-complete');
checkbox.addEventListener('change', handleCompleteChange);
}
function createBookElement(book) {
const bookElement = document.createElement('div')
bookElement.classList.add('book');
bookElement.setAttribute('data-title', book.title);
bookElement.innerHTML = `
<h3> ${book.title} </h3>
<p> Author: ${book.author}</p>
<p> Pages: ${book.pages}</p>
Completed: <input class="book-complete" type="checkbox" ${book.completed && 'checked'}> ${book.completed} <br>
<button class="delete">Delete</button>
`;
// add the event listeners for delete and completed
addBookEvents(bookElement);
main.appendChild(bookElement);
}
//console.log(JSON.stringify(library))
<main id="books-list"></main>
<script>
const library =
[{"title":"inserttitlehere","author":"His name?","pages":63,"completed":true},{"title":"kill bill","author":"author2","pages":653,"completed":false},{"title":"oh yeah baby","author":"author3","pages":323,"completed":false},{"title":"kill me now","author":"author4","pages":132,"completed":true}];
</script>

Try this:
var library = []
const book1 = new Book('inserttitlehere', 'His name?', 63, false)
const book2 = new Book('kill bill', 'author2', 653, false)
const book3 = new Book('oh yeah baby', 'author3', 323, false)
const book4 = new Book('kill me now', 'author4', 132, true)
library.push(book1, book2, book3, book4)
// Book constructor
function Book(title, author, pages, completed) {
this.title = title
this.author = author
this.pages = pages
this.completed = completed
}
const main = document.querySelector('main');
const form = document.querySelector('.form');
//Function to add books to the DOM
function displayBooks() {
let dataIndex = 0;
main.innerHTML = '';
library.forEach(book => {
const bookElement = document.createElement('div')
bookElement.classList.add('book');
bookElement.setAttribute('data-index', dataIndex);
bookElement.innerHTML = `
<h3> ${book.title} </h3>
<p> Author: ${book.author}</p>
<p> Pages: ${book.pages}</p>
Completed: <input type="checkbox"> ${book.completed} <br>
<button class="delete">Delete</button>
`
main.appendChild(bookElement);
dataIndex++;
})
}
displayBooks();
//Add new book to library
function addBookToLibrary(title, author, pages, completed) {
const newBook = new Book(title, author, pages, completed)
library.push(newBook);
displayBooks();
}
function addListener() {
//Deleting a book from the array
let deleteBtns = document.querySelectorAll('.book .delete');
deleteBtns.forEach(button => {
button.addEventListener('click', () => {
const index = button.parentNode.dataset['data-index'];
deleteBook(index);
})
});
}
addListener();
function deleteBook(index) {
library.splice(index, 1);
displayBooks();
addListener();
}
form.addEventListener('submit', (e) => {
e.preventDefault();
const title = document.querySelector('#title').value;
const author = document.querySelector('#author').value;
const pages = document.querySelector('#pages').value;
addBookToLibrary(title, author, pages, false);
document.forms[0].reset();
})
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<title>Library</title>
</head>
<body>
<nav>
<h1>Library</h1>
<button class="add-book-btn">NEW BOOK</button>
</nav>
<main></main>
<div class="modal">
<form class="form">
<p class="close"></p>
<h2>Add Book</h2>
<div class="user-input">
<label for="">Title</label><br>
<input type="text" id="title" required>
</div>
<div class="user-input">
<label for="">Author</label><br>
<input type="text" id="author" required>
</div>
<div class="user-input">
<label for="">Pages</label><br>
<input type="number" id="pages" required>
</div>
<div class="user-input">
<label for="">Read?</label>
<input type="checkbox" id="read">
</div>
<button type="submit" id="submit">Submit</button>
</form>
</div>
<script src="script.js"></script>
</body>
</html>
First off, library was not defined so I added an array library at the top. Then, I addressed the problem.
The problem was that every time you delete a book, deleteBtn and the button event listeners were being altered, because the buttons they reference were being deleted. So, just reinitialize them every time you delete a book. I did this by wrapping it in a function and recalling it every time it is needed.
Note that I also added an extra line of displayBooks under addBookToLibrary, to make sure that the page is "refreshed" when a book is added.

Related

Novice coder question - JS User-generated shopping list - How to update number property of list objects and avoid duplicates

e.g. if a user submits "tacos" twice, instead of having two lines, each containing "tacos", I want to have one line with "tacos x 2". Question 2 - is it possible to create a variable that selects every item in an array except another variable? e.g.
for (let i = 0; i < items.length; i++)
{let j = !i;
}
(I am aware the above code is incorrect, I included it only to illustrate my question)
Thank you in advance.
// key function out of context
if (!items.includes(item.text)) {
items.push(item);
} else {
items.item.number =+1
}
//entire html file with script including key function in context
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LocalStorage</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="wrapper">
<h2>LOCAL TAPAS</h2>
<p></p>
<ul class="plates">
<li>Loading Tapas...</li>
</ul>
<form class="add-items" autocomplete="off">
<input type="text" name="item" placeholder="Item Name" required>
<input type="submit" value="+ Add Item">
</form>
<button type="reset" value="reset"">clear all</button>
<button class="select">Select all</button>
</div>
<script>
const addItems = document.querySelector('.add-items');
const itemsList = document.querySelector('.plates');
const selectAll = document.querySelector('.select');
const items = [];
const mySet = new Set();
const userInput = document.querySelector('[type="text"]');
// add items to list
// populate list with html
function add(e) {
e.preventDefault();
console.dir(e.currentTarget)
const text = e.currentTarget.item.value;
const item = {
text,
done:false,
number: 1,
};
console.dir(item)
if (!items.includes(item.text)) {
items.push(item);
} else {
items.item.number =+1
}
e.currentTarget.reset();
itemsList.dispatchEvent(new CustomEvent('itemsUpdated'))
}
function displayItems(item, i) {
const html = items.map((item, i) => `
<li>
<input type="checkbox">
<label name="${item}" id="${i}"><strong>${item.text}</strong> x${item.number} </label>
</li>`).join('');
itemsList.innerHTML = html;
};
addItems.addEventListener('submit', add)
itemsList.addEventListener('itemsUpdated', displayItems)
</script>
</body>
</html>
The problem lies with how you're checking if your item object is in the items array.
Since the elements of your array are objects, you would need to modify that checkup - includes and indexOf won't work.
What you would need to do is:
let indx = items.findIndex(element => element.text === item.text);
array.findIndex will let you find an element within an array which satisfies the given condition. In this case, you want to find a specific product by name. That's why we're doing the element.text === item.text comparison.
Check the updated example below, to see it in action.
const addItems = document.querySelector('.add-items');
const itemsList = document.querySelector('.plates');
const selectAll = document.querySelector('.select');
const items = [];
const mySet = new Set();
const userInput = document.querySelector('[type="text"]');
// add items to list
// populate list with html
function add(e) {
e.preventDefault();
const text = e.currentTarget.item.value;
const item = {
text,
done:false,
number: 1,
};
/* these are the key changes */
let indx = items.findIndex(element => element.text === item.text);
if (indx < 0) {
items.push(item);
} else {
items[indx].number += 1;
}
/* */
e.currentTarget.reset();
itemsList.dispatchEvent(new CustomEvent('itemsUpdated'))
}
function displayItems(item, i) {
const html = items.map((item, i) => `
<li>
<input type="checkbox">
<label name="${item}" id="${i}"><strong>${item.text}</strong> x${item.number}</label>
</li>
`).join('');
itemsList.innerHTML = html;
};
addItems.addEventListener('submit', add)
itemsList.addEventListener('itemsUpdated', displayItems)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LocalStorage</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="wrapper">
<h2>LOCAL TAPAS</h2>
<p></p>
<ul class="plates">
<li>Loading Tapas...</li>
</ul>
<form class="add-items" autocomplete="off">
<input type="text" name="item" placeholder="Item Name" required>
<input type="submit" value="+ Add Item">
</form>
<button type="reset" value="reset">clear all</button>
<button class="select">Select all</button>
</div>
</body>
</html>
EDIT There were also minor syntax error edits:
in your original code, your reset button had this as its value - value="reset"". I've removed the extra quote.
your initial incrementing of item.number was also erroneous - instead of items.item.number =+1 it should have been (as it is now) items[indx].number += 1. Note that it's += and not =+.

how to refresh only a form or fieldset not a page

in my SPRING project, I have an index.html file that has two forms. When you write a name in the first one and click submit the name is transferred to another one. The other form is connected with DB and displays it in a fieldset.
I have two buttons:
1)ADD to DB - when I ADD an item I can see it right away
2)DELETE an item from DB by ID - it works and deletes from DB, but the view isn't refreshed/reloaded. I need to refresh the whole page to see the results of DELETED item.
When I refresh the page it is going back to the first form with the name...
I wonder how to solve it.
I know something must be written inside the DELETE function.
PLEASE HELP.
<!DOCTYPE html>
<html lang="en" xmlns:https="http://www.w3.org/1999/xhtml" xmlns:http="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>SHOPPING LIST</title>
<link rel="stylesheet" href="https://unpkg.com/purecss#1.0.0/build/pure-min.css"
integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
</head>
<body>
<main style="width: 40%; margin: 0 auto">
<div id="welcome" style="text-align: center">
<h1>Introduce yourself</h1>
</div>
<form id="welcomeForm" class="pure-form pure-g pure-form-aligned">
<input class="pure-input-rounded pure-u-1" name="name" placeholder="name" id="text_name">
<button id="welcomeFormBtn" class="pure-button pure-button-primary pure-u-1">Submit</button>
</form>
<form id="AddForm" class="pure-form" style="text-align: center; display: none">
<fieldset>
<input id="name" class="pure-input-rounded pure-input-2-3" style="width: available" placeholder="name">
<input id="amount" class="pure-input-rounded pure-input-2-3" style="width: available" placeholder="amount">
<input id="uom" class="pure-input-rounded pure-input-2-3" style="width: available" placeholder="unit of measure">
<input id="idToDel" type="number" class="pure-input-rounded pure-input-2-3" style="width: available" placeholder="Please provide an id to delete">
</fieldset>
<fieldset>
<button id="addProduct" class="pure-button pure-button-primary">POST</button>
<button id="delProduct" class="pure-button pure-button-primary">DELETE</button>
<br>
<button id="print-btn" style="width: 50px; height: 50px; border-radius: 50%"><img style="width: 40px; height: 40px" src="https://cdn-icons-png.flaticon.com/512/3233/3233446.png" alt="PRINT"></button>
</fieldset>
<fieldset id="allProducts" >
</fieldset>
</form >
</main>
<script>
const API_URL = 'http://localhost:8080';
const API_URL_ADD = `${API_URL}/api`;
const API_URL_ALL = `${API_URL_ADD}/list`;
const pName = document.getElementById('name');
const pUom = document.getElementById('uom');
const pAmount = document.getElementById('amount');
AddFunction();
fetch(API_URL_ALL)
.then(processOkResponse)
.then(list => list.forEach(createNewProduct))
document.getElementById('addProduct').addEventListener('click', (event) => {
event.preventDefault();
fetch(API_URL_ALL, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: pName.value, type : pUom.value, amount: pAmount.value })
})
.then(processOkResponse)
.then(createNewProduct)
.then(() => pName.value = '')
.then(() => pAmount.value = '')
.then(() => pUom.value = '')
.catch(console.warn);
});
function createNewProduct(product) {
const label = document.createElement('label');
const l1 = document.createElement('label');
const l2 = document.createElement('label');
const l3 = document.createElement('label');
const l4 = document.createElement('label');
label.classList.add('label');
l1.appendChild(document.createTextNode(` ID:${product.id}. `));
l2.appendChild(document.createTextNode(` ${product.name} `));
l3.appendChild(document.createTextNode(` ${product.amount} `));
l4.appendChild(document.createTextNode(` ${product.type} `));
label.appendChild(l1).appendChild(l2).appendChild(l3).appendChild(l4)
document.getElementById('allProducts').appendChild(label);
label.style.display= 'table';
label.style.paddingLeft='40%';
label.style.wordSpacing='30%';
}
document.getElementById('delProduct').addEventListener('click', (event) => {
event.preventDefault();
removeTodo();
});
function removeTodo() {
const d = document.getElementById('idToDel').value;
fetch(`${API_URL_ALL}/${d}`, { method: 'DELETE' })
.then(processOkResponse)
.catch(console.info)
}
function AddFunction(){
const welcomeForm = document.getElementById('welcomeForm');
document.getElementById('welcomeFormBtn').addEventListener('click', (event) => {
event.preventDefault();
const formObj = {
name: welcomeForm.elements.name.value,
};
fetch(`${API_URL_ADD}?${new URLSearchParams(formObj)}`)
.then(response => response.text())
.then((text) => {
document.getElementById('welcome').innerHTML = `
<h1>${text}</h1>
`;
welcomeForm.remove();
document.getElementById('AddForm').style.display = 'block';
});
});
}
document.getElementById('print-btn').addEventListener('click', (event) => {
event.preventDefault();
const f = document.getElementById("allProducts").innerHTML;
const a = window.open();
a.document.write(document.getElementById('welcome').innerHTML);
a.document.write(f);
a.print();
})
function processOkResponse(response = {}) {
if (response.ok) {
return response.json();
}
throw new Error(`Status not 200 (${response.status})`);
}
</script>
</body>
</html>
just like you've done .then(processOkResponse).then(createNewProduct) for adding a product, you should do the same for deletion as well.
.then(processOkResponse).then(deleteProduct) in your removeToDo function as well.
You should add a specific id to the label you're creating in createNewProduct(product) function like this
function createNewProduct(product) {
const label = document.createElement('label');
label.setAttribute('id', `pid-${product.id}`); // just this line to be added
//.... rest of your code
function deleteProduct(deleteApiResponse) {
// make sure to send id value of the deleted product
const {id} = deleteApiResponse;
const idToDel = `pid-${id}`;
// this does remove from HTML. just sets display: none
document.getElementById(idToDel).style.display = 'none';
// or remove from HTML like this. but for this you need the parentId
// to effectively remove the style(if any) on the id to be delete
//document.getElementById(idToDelParent).innerHTML = '';
}

How to create a delete function for vanillajs todo list app?

Good day! I've been trying to figure out how to create a delete function in my todo app. I dont know what to do next and the delete function as well as the eventListener is not correct. I hope you guys can help me. I want to fully understand every small projects that I make. Thank you in advance! :)
const inputBox = document.querySelector('.input')
const addBtn = document.querySelector('.input-button')
const todoMain = document.querySelector('.todo-list')
const deleteBtn = document.querySelector('.delete-button')
const deleteAllBtn = document.querySelector('.clear-all')
//Event listeners
inputBox.addEventListener("keyup", function(){
let userInput = inputBox.value;
if (userInput.trim() != 0) {
addBtn.classList.add("active")
} else {
addBtn.classList.remove("active");
}
})
addBtn.addEventListener("click", todoAdd);
todoMain.addEventListener("click", todoDelete);
// Functions
function todoAdd(event){
event.preventDefault();
const todoLi = document.createElement('li');
todoLi.innerText = inputBox.value;
const todoDeleteBtn = document.createElement('button');
todoDeleteBtn.innerHTML = `<i class="fas fa-trash-alt"></i>`;
todoDeleteBtn.classList.add('delete-button')
todoLi.appendChild(todoDeleteBtn);
todoMain.appendChild(todoLi);
inputBox.value = '';
addBtn.classList.remove("active");
};
function todoDelete(e){
const item = e.target;
if (item.classList[0] === 'delete-button'){
todoMain.removeChild(todoLi);
}
}
<link crossorigin rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" />
<div class="container">
<h1>TODO list</h1>
<div class="input-container">
<input type="text" class="input" placeholder="Input Text Here">
<button class="input-button"><i class="fas fa-plus"></i></button>
</div>
<ul class="todo-list">
</ul>
<div class="footer">
<span>You have<span class="pending">0</span>pending task left</span>
<button class="clear-all">Clear All</button>
</div>
</div>
I have implemented it quite simply as an example.
When creating the ToDo item, I add a key for the text and a data attribute for the delete button and an onclick event.
The key is important to have the relation between button and text. First i used new Date() but i updated with an random Method. (Math.random()+1).toString().split('.')[1];
For deletAll() you can get the entire Parent Node and set html to an empty string.
const inputBox = document.querySelector('.input')
const addBtn = document.querySelector('.input-button')
const todoMain = document.querySelector('.todo-list')
const deleteBtn = document.querySelector('.delete-button')
const deleteAllBtn = document.querySelector('.clear-all')
//Event listeners
inputBox.addEventListener("keyup", function(){
let userInput = inputBox.value;
if (userInput.trim() != 0) {
addBtn.classList.add("active")
} else {
addBtn.classList.remove("active");
}
})
addBtn.addEventListener("click", todoAdd);
todoMain.addEventListener("click", todoDelete);
// Functions
function todoAdd(event){
event.preventDefault();
const todoLi = document.createElement('li');
const key =(Math.random()+1).toString().split('.')[1];
todoLi.innerText = inputBox.value;
todoLi.setAttribute("id", key);
const todoDeleteBtn = document.createElement('button');
todoDeleteBtn.innerHTML = `<i class="fas fa-trash-alt"></i>`;
todoDeleteBtn.classList.add('delete-button')
todoDeleteBtn.onclick = function() {
const _key = this.getAttribute('data-key')
document.getElementById(_key).remove();
this.remove()
}
todoDeleteBtn.setAttribute("data-key", key);
todoLi.appendChild(todoDeleteBtn);
todoMain.appendChild(todoLi);
inputBox.value = '';
addBtn.classList.remove("active");
};
function todoDelete(e){
const item = e.target;
if (item.classList[0] === 'delete-button'){
todoMain.removeChild(todoLi);
}
}
<link crossorigin rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" />
<div class="container">
<h1>TODO list</h1>
<div class="input-container">
<input type="text" class="input" placeholder="Input Text Here">
<button class="input-button"><i class="fas fa-plus"></i></button>
</div>
<ul class="todo-list">
</ul>
<div class="footer">
<span>You have<span class="pending">0</span>pending task left</span>
<button class="clear-all">Clear All</button>
</div>
</div>
There are a couple of issues that might be stopping this from working:
Your function todoDelete tries to access the variable todoLi, which doesn't exist in its scope. You define this inside todoAdd, but its scope is limited so you can't access the variable from outside the function.
I suspect what you might want to be doing is passing item instead.
You attach the event listener that triggers todoDelete to your todoMain element, which means that e.target for the function will always be the ul element, not your list element. Your if is then always false so the code never runs.
To fix this, attach the event listener to the todoDeleteBtn in your todoAdd function instead.

How to remove book and change status of book in the library using javascript?

These were the instructions:
Add a button on each book’s display to remove the book from the
library. You will need to associate your DOM elements with the actual
book objects in some way. One easy solution is giving them a
data-attribute that corresponds to the index of the library array.
Add a button on each book’s display to change its read status. To
facilitate this you will want to create the function that toggles a
book’s read status on your Book prototype instance.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>
</style>
</head>
<body>
<div id="enter">
<!-- Add a modal when Add book button is clicked -->
<label for="bookInfo"> Enter book info </label></br>
<input type="text" id="inputTitle" placeholder="Title"></br>
<input type="text" id="inputAuthor" placeholder="Author"></br>
<input type="text" id="inputPages" placeholder="Pages"></br>
<select id="readingProgress">
<option disabled>--Select--</option>
<option>Read</option>
<option>In Progress</option>
<option>Not Read</option>
</select>
<div id=submit>
<button id="submit">Submit</button>
</div>
<table>
<thead>
<tr>
<!--Try to make this hidden until after the first book is added, but remain viewed after first book-->
<th>Title</th>
<th>Author</th>
<th>Pages</th>
<th>Reading Status</th>
</tr>
<thead>
<tbody id="bookList">
</tbody>
</table>
<script>
let myLibrary = []
const submit = document.querySelector('#submit')
//const tableHeading = document.querySelector('th')
//const table = document.querySelector('table')
const list = document.querySelector("#bookList")
const bookTitle = document.querySelector('#inputTitle')
const bookAuthor = document.querySelector('#inputAuthor')
const bookPages = document.querySelector('#inputPages')
const readOptions = document.querySelector('select')
//Constructor to create book objects:
function Book(title, author, pages, read) {
this.title = title
this.author = author
this.pages = pages
this.read = read
}
//New book objects are stored in an array:
function addBookToLibrary() {
if (bookTitle.value && bookAuthor.value && bookPages.value && readOptions.value) {
myLibrary.push(new Book(bookTitle.value, bookAuthor.value, bookPages.value, readOptions.value))
} else {
alert("Please enter all information")
}
console.log(myLibrary)
}
//Display book:
function displayBooks(book) {
const row = document.createElement('tr')
const createTitle = document.createElement('td')
const createAuthor = document.createElement('td')
const createPages = document.createElement('td')
const createStatus = document.createElement('td')
createTitle.innerHTML = book.title
createAuthor.innerHTML = book.author
createPages.innerHTML = book.pages
createStatus.innerHTML = book.read
//row.innerHTML = `<td>${book.author}<td><td>${book.pages}<td><td>${book.read}<td>`
//above code I formatting was weird, will try back using this code
row.appendChild(createTitle)
row.appendChild(createAuthor)
row.appendChild(createPages)
row.appendChild(createStatus)
list.appendChild(row)
createTitle.classList.add('deleteRow')
}
//Remove books:
list.addEventListener('click', function removeBook(e) {
if (e.target.classList.contains('deleteRow')) {
let eachIndex = e.target.parentElement.rowIndex - 1
console.log(eachIndex)
e.target.parentElement.remove()
//displayBooks(myLibrary[myLibrary.length-1])
myLibrary.forEach((book, index) => {
if (index === eachIndex) {
myLibrary.splice[eachIndex, 1]
}
})
}
console.log(myLibrary)
})
//Event Listeners:
submit.addEventListener('click', (e) => {
addBookToLibrary()
displayBooks(myLibrary[myLibrary.length - 1])
})
</script>
</body>
</html>

Identified the correct element and it's parent yet I am unable to delete it

Set up ES6 classes correctly. Identified the correct element to be removed but nothing happens when remove button is clicked.
Used console.log to make sure the event was registered and trigger. It shows up but I cannot remove an element from the DOM.
Other methods in the class works perfectly, only the deleteBook() method inside the class UI gives me trouble. The debugger console does not show me any fault.
class UI {
static displayBooks() {
const storedBooks = [{
title: 'Book One',
author: 'John Doe',
isbn: '3434434'
},
{
title: 'Book Two',
author: 'Jane Doe',
isbn: '4554576'
},
];
const books = storedBooks;
books.forEach((book) => UI.addBookToList(book));
}
// Now we grab HTML section to display the book list we submit - create HTML - append / render it to the DOM
static addBookToList(book) {
const list = document.querySelector('#book-list'); // This section displays the book's list we submitted.
const row = document.createElement('tr');
row.innerHTML = `
<td>${book.title}</td>
<td>${book.author}</td>
<td>${book.isbn}</td>
<td>X</td>
`;
list.appendChild(row);
}
// NOTE: we target the parentElement of parentElement so the entire row can be removed
// Otherwise only click button will be removed when we click on it.
static deleteBook(el) {
if (el.classList.contains('delete')) {
el.parentElement.parentElement.remove();
}
}
// This class is utilized later on for us to clear out the input fields
static clearField() {
document.querySelector('#title').value = '';
document.querySelector('#author').value = '';
document.querySelector('#isbn').value = '';
}
}
// EVENT: ADD A BOOK
document.querySelector('#book-form').addEventListener('submit', (e) => {
// Fist we need to prevent actual submit
e.preventDefault();
// Now we will grab the form values
const title = document.querySelector('#title').value;
const author = document.querySelector('#author').value;
const isbn = document.querySelector('#isbn').value;
// Instantiate book Class
const book = new Book(title, author, isbn);
// Now books get added each time we click the submit button by accessing UI.addBookToList();
UI.addBookToList(book);
// Once submited we want all existing texts in the input field to disappear
UI.clearField();
});
// EVENT: We would like to remove a book if we don't want it anymore
document.querySelector('#book-list').addEventListener('click', (e) => {
// Remove book from UI
UI.deleteBook(e.target);
})
// EVENT: Display list of BOOKS to the DOM...
document.addEventListener('DOMContentLoaded', UI.displayBooks);
<form id="book-form">
<div class="form-group">
<label for="title">Title</label>
<input type="text" id="title" class="form-control">
</div>
<div class="form-group">
<label for="author">Author</label>
<input type="text" id="author" class="form-control">
</div>
<div class="form-group">
<label for="isbn">ISBN#</label>
<input type="text" id="isbn" class="form-control">
</div>
<input type="submit" value=" Add Book" class="btn btn-primary btn-block">
<table class="table table-striped mt-5">
<thead>
<th>Title</th>
<th>Author</th>
<th>ISBN#</th>
<th></th>
</thead>
<tbody id="book-list"></tbody>
</table>
</form>
Fully Working Solution With Mistakes highlighted
<div class="form-group">
<label for="title">Title</label>
<input type="text" id ="title" class="form-control">
</div>
<div class="form-group">
<label for="author">Author</label>
<input type="text" id ="author" class="form-control">
</div>
<div class="form-group">
<label for="isbn">ISBN#</label>
<input type="text" id ="isbn" class="form-control">
</div>
<input type="submit" id="addBook" value=" Add Book" class="btn btn-primary btn-block">
<table class="table table-striped mt-5">
<thead>
<th>Title</th>
<th>Author</th>
<th>ISBN#</th>
<th></th>
</thead>
<tbody id="book-list"></tbody>
</table>
</form>
JAVASCRIPT HERE
<script>
//You had no script tag
class Book {//you had no book class
constructor(title, author,isbn) {
this.title = title;
this.author = author;
this.isbn = isbn;
}
}
class UI {
static displayBooks () {
const storedBooks = [
{
title: 'Book One',
author: 'John Doe',
isbn: '3434434'
},
{
title: 'Book Two',
author: 'Jane Doe',
isbn: '4554576'
},
];
const books = storedBooks;
books.forEach((book) => UI.addBookToList(book));
}
// Now we grab HTML section to display the book list we submit - create HTML - append / render it to the DOM
static addBookToList(book) {
const list = document.querySelector('#book-list'); // This section displays the book's list we submitted.
const row = document.createElement('tr');
row.innerHTML = `
<td>${book.title}</td>
<td>${book.author}</td>
<td>${book.isbn}</td>
<td>X</td>
`;
list.appendChild(row);
}
// NOTE: we target the parentElement of parentElement so the entire row can be removed
// Otherwise only click button will be removed when we click on it.
static deleteBook(el) {
debugger;//fix here
if(el.classList.contains("btn-smdelete")) {
el.parentElement.parentElement.remove();
}
}
// This class is utilized later on for us to clear out the input fields
static clearField() {
document.querySelector('#title').value = '';
document.querySelector('#author').value = '';
document.querySelector('#isbn').value = '';
}
}
// EVENT: ADD A BOOK
document.getElementById('addBook').addEventListener('click', (e) => {
// Fist we need to prevent actualy submit
e.preventDefault();
// Now we will grab the form values
const title = document.querySelector('#title').value;
const author = document.querySelector('#author').value;
const isbn = document.querySelector('#isbn').value;
// Instatiate book Class
const book = new Book(title, author, isbn);
// Now books get added each time we click the submit button by accessing UI.addBookToList();
UI.addBookToList(book);
// Once submited we want all existing texts in the input field to disappear
UI.clearField();
});
// EVENT: We would like to remove a book if we don't want it anymore
document.querySelector('#book-list').addEventListener('click', (e) => {
// Remove book from UI
UI.deleteBook(e.target);
})
// EVENT: Display list of BOOKS to the DOM...
document.addEventListener('DOMContentLoaded', UI.displayBooks);
</script>

Categories