0

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 class="card"><p>${book.title}</p><p>${book.author}</p><p>${book.pages}</p><p>${book.haveRead}</p><button class="remove-btn" 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.

Armen Michaeli
  • 8,625
  • 8
  • 58
  • 95
Woah
  • 3
  • 1
  • "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" - what did it do? This is probably a good method to take, as "implement a function that deletes the object from the array if it's not found in the DOM" is probably not a good method to take. – Tom Jan 25 '23 at 22:14
  • Can you elaborate on the "how to implement a function that deletes object from the array if it's not found in the document" part? Why would you want to delete the object if it is _not_ in the document? I'd think one would want to remove the book from the array when the card is removed, and vice versa -- the card is conceptually a visual representation of the book in the array, is it not? – Armen Michaeli Jan 25 '23 at 22:20
  • It looks like you are using `title` as an index, to uniquely identify an item in the library, based on the fact that that is what you pass to `alreadyInLibrary`? So you should be able to [find the object's array index](https://stackoverflow.com/questions/7364150/find-object-by-id-in-an-array-of-javascript-objects) using the `title`, and then [delete it by index](https://stackoverflow.com/questions/5767325/how-can-i-remove-a-specific-item-from-an-array)? – Don't Panic Jan 25 '23 at 22:21
  • I guess I just don't know how to implement this function. – Woah Jan 25 '23 at 22:54

2 Answers2

0

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 class="card" data-title="${book.title}"><p>${book.title}</p><p>${book.author}</p><p>${book.pages}</p><p>${book.haveRead}</p><button class="remove-btn" 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.

RAllen
  • 1,235
  • 1
  • 7
  • 10
  • This helps, I've not used dataset before, but el.dataset.title is coming back undefined? – Woah Jan 25 '23 at 23:11
0

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 with addEventListeners 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 in alreadyInLibrary(), so we'll use that. Let's add a class to the p that displays the title so we can do that: <p class='title'>...</p>. Now we can search with .getElementsByClassName('title') to get the p, 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. There are a few options:

I've gone with the 2nd option in the code below, but either are fine.

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 class="card">
        <p class='title'>Harry Potter</p>
        <p>J.K. Rowling</p>
        <p>123</p>
        <p>Unread</p>
        <button class="remove-btn">Delete</button>
    </div>

    <div class="card">
        <p class='title'>LotR</p>
        <p>J.R.R. Tolkien</p>
        <p>4214</p>
        <p>Read</p>
        <button class="remove-btn">Delete</button>
    </div>
    
    <div class="card">
        <p class='title'>No Country for Old Men</p>
        <p>Cormac McCarthy</p>
        <p>575</p>
        <p>Unread</p>
        <button class="remove-btn">Delete</button>
    </div>
</div>
Don't Panic
  • 13,965
  • 5
  • 32
  • 51
  • Thank you for all the tips. This has me on the right track, although it's still not working with my code. I'll have to review my functions tomorrow and ensure nothing is re-entering the books into the array, preventing them from being deleted. – Woah Jan 26 '23 at 02:56