I'm creating a library Web application that allows you to click a button that brings up a form to add a book via title, author, pages, and if you've read it or not. Each form input gets added to a "card" in the document via createElement
/appendChild
and also gets added to the myLibrary
array via a constructor function. Here's my script:
const modal = document.getElementById("myModal");
const btn = document.getElementById("newBook");
const modalBtn = document.getElementById("modal-btn");
const title = document.getElementById("title");
const author = document.getElementById("author");
const pages = document.getElementById("pages");
const haveRead = document.getElementById("have-read");
const span = document.getElementsByClassName("close");
const cards = document.getElementById("cards");
let myLibrary = [];
// Book Constructor
function Book(title, author, pages, haveRead) {
(this.title = title),
(this.author = author),
(this.pages = pages),
(this.haveRead = haveRead);
if (alreadyInLibrary(title)) {
return alert("Sorry, it looks like this book is already in your library");
}
addBooKToLibrary(this);
}
// Adds book to array
const addBooKToLibrary = function (book) {
myLibrary.push(book);
};
const book1 = new Book("Harry Potter", "J.K. Rowling", 123, "Unread");
const book2 = new Book("LotR", "J.R.R. Tolkien", 4214, "Read");
const book3 = new Book("No Country for Old Men", "Cormac McCarthy", 575, "Unread");
// Creates books for each card in the DOM
createCard = function () {
cards.innerHTML = "";
myLibrary.forEach((book) => {
let html = `<div ><p>${book.title}</p><p>${book.author}</p><p>${book.pages}</p><p>${book.haveRead}</p><button onclick="deleteBook(this)">Delete</div>`;
cards.innerHTML = html;
});
};
// Checks the array for already registered books
function alreadyInLibrary(title) {
return myLibrary.some(function (el) {
return el.title === title;
});
}
modalBtn.addEventListener("click", function (event) {
event.preventDefault();
const book = new Book(title.value, author.value, pages.value, haveRead.value);
modal.style.display = "none";
createCard();
});
I've added a "Delete" button to each book's card that calls a function to remove itself from the document:
function deleteBook(el) {
const element = el;
element.parentNode.remove();
}
However, the book stays in the array even after the card is deleted, and I can't figure out how to implement a function that deletes the object from the array if it's not found in the document.
I've tried adding a unique ID to each book object in the myLibrary
array to target the object with to delete it from the array, but couldn't get that to work. I've tried looping through the array and using an if
statement to see if myLibrary.title === book.title
, else remove it from the array, but that's not working either.
CodePudding user response:
Here's a working snippet.
Notes
It is considered good practice to separate your JS and HTML, here that means removing the
onclick()
s in your HTML, and replacing them withaddEventListener
s in your JS.When a button is clicked, we need to identify the book it represents. You are already using
title
to uniquely identify a book inalreadyInLibrary()
, so we'll use that. Let's add a class to thep
that displays the title so we can do that:<p class='title'>...</p>
. Now we can search with.getElementsByClassName('title')
to get thep
, and here's how to get the text of an element.But how to find the title of the specific button that was clicked? We need to find the parent
card
, and then the title inside that. Alternatively, we can add a click handler to thecard
, instead of thebutton
, and then inside the event handler usethis
, which now refers to the whole card. We can search for the title inside the clicked card like this:this.getElementsByClassName('title')
. That returns an array, but we know there will only be 1 element, so we can add[0]
to get just the singlep
.
let myLibrary = [];
const cards = document.querySelectorAll(".card");
// Book Constructor
function Book(title, author, pages, haveRead) {
(this.title = title),
(this.author = author),
(this.pages = pages),
(this.haveRead = haveRead);
addBooKToLibrary(this);
}
// Adds book to array
const addBooKToLibrary = function (book) {
myLibrary.push(book);
};
const book1 = new Book("Harry Potter", "J.K. Rowling", 123, "Unread");
const book2 = new Book("LotR", "J.R.R. Tolkien", 4214, "Read");
const book3 = new Book("No Country for Old Men", "Cormac McCarthy", 575, "Unread");
// We want to add an event handler for each card. cards is a nodelist,
// we need an array to iterate over:
// https://stackoverflow.com/questions/12330086/how-to-loop-through-selected-elements-with-document-queryselectorall
Array.from(cards).forEach(function (card) {
// Add event handler for each card
card.addEventListener("click", function (event) {
// Since the handler is for the card, we need to ignore clicks
// everywhere except directly on buttons:
// https://stackoverflow.com/questions/49680484/how-to-add-one-event-listener-for-all-buttons
if (event.target.nodeName !== 'BUTTON') {
return;
}
// Find the title of the book being deleted by searching inside
// the card that registered this click
// https://stackoverflow.com/questions/7815374/get-element-inside-element-by-class-and-id-javascript
// https://stackoverflow.com/questions/6743912/how-to-get-the-pure-text-without-html-element-using-javascript
let p = this.getElementsByClassName('title')[0];
let title = p.textContent;
// console.log(title);
// Find the index of array element for this book
// https://stackoverflow.com/questions/7364150/find-object-by-id-in-an-array-of-javascript-objects
let index = myLibrary.findIndex(x => x.title === title);
// Now remove this book from the array
// https://stackoverflow.com/questions/5767325/how-can-i-remove-a-specific-item-from-an-array
myLibrary.splice(index, 1);
// Just for debugging, show it really is removed from myLibrary
console.dir(myLibrary);
// And remove it from the page
this.remove();
});
});
.card {
border: 1px solid black;
}
<div id="cards">
<div >
<p class='title'>Harry Potter</p>
<p>J.K. Rowling</p>
<p>123</p>
<p>Unread</p>
<button >Delete</button>
</div>
<div >
<p class='title'>LotR</p>
<p>J.R.R. Tolkien</p>
<p>4214</p>
<p>Read</p>
<button >Delete</button>
</div>
<div >
<p class='title'>No Country for Old Men</p>
<p>Cormac McCarthy</p>
<p>575</p>
<p>Unread</p>
<button >Delete</button>
</div>
</div>
CodePudding user response:
You can use the data-
attribute to store the title and then delete the book by it.
To do this, you will need to add a data-title
attribute to a card like so
let html = `<div data-title="${book.title}"><p>${book.title}</p><p>${book.author}</p><p>${book.pages}</p><p>${book.haveRead}</p><button onclick="deleteBook(this)">Delete</div>`;
and then read the data-title
attribute in your delete function:
function deleteBook(el) {
// removing book by title
const bookTitle = el.getAttribute("data-title");
myLibrary = myLibrary.filter((book) => book.title !== bookTitle);
const element = el;
element.parentNode.remove();
}
Please let me know if this helps.