Home > Blockchain >  How to delete object from array when removed from DOM/HTML
How to delete object from array when removed from DOM/HTML

Time:01-26

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 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. Alternatively, we can add a click handler to the card, instead of the button, and then inside the event handler use this, 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 single p.

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.

  • Related