crispy
crispy

Reputation: 5

Problem with deleting object from array of objects

I have a issue where deleting dynamically created dom object from an array of objects. The problem is that when i delete some element from the array the rest of the elements after the spliced element also gets deleted. To my knowledge this is happening due to the index of the next elements gets updated to the one before it and the function deletes the element having the same index over and over.

How can i fix this?(Code uploaded w HTML and CSS incl.) Is this the recommended way to implement this function?

  function populateBooks(myLib, bookView) {
    const bookCards = document.querySelectorAll('.book-card')
    bookCards.forEach(bookCard => bookList.removeChild(bookCard));
    myLib.forEach((book, index) => {
      book.id = index;
      const cardContent = `<div class="book-card" data-index=${book.id}>
                              <div class="card-info-wrapper">
                                  <h2>${book.title}</h2>
                                  <h3>${book.author}</h3>
                                  <h4>${book.pages} Pages</h4>
                                  <p>${book.info()}</p>
                              </div>
                              <div class="card-menu">
                                  <div class="button" id="remove-btn">
                                      Remove
                                  </div>
                              </div>
                      </div>`
      const element = document.createElement('div');
      element.innerHTML = cardContent;
      // element.dataset.indexx = book.id;
      bookView.appendChild(element.firstChild);
      const cards = document.querySelectorAll('[data-index]');
      cards.forEach(card => {
        const removeButton = card.querySelector('.button');
        removeButton.addEventListener('click', () => {
          removeBook(book.id)
        })
      })

    });
  };

function removeBook(id) {
  console.log('deleting', id);
  myLibrary.splice(id, 1);
  console.table(myLibrary);
  populateBooks(myLibrary, bookList);
}

Full code

const form = document.getElementById('input-form');
const formButton = document.getElementById('add-form');
const formView = document.querySelector('.form-card')
const bookList = document.querySelector('.books-wrapper');

let myLibrary = [];
let newBook;

function Book(title, author, pages) {
  this.title = title;
  this.author = author;
  this.pages = pages;
  this.info = function() {
    return `${this.title} is a book by ${this.author}, ${this.pages} pages, not read yet.`
  };
};
Book.prototype.read = false;

function addToLibrary(e) {
  {
    e.preventDefault();
    const title = (document.getElementById('title')).value;
    const author = (document.getElementById('author')).value;
    const pages = (document.getElementById('pages')).value;
    newBook = new Book(title, author, pages);
  } {
    myLibrary.push(newBook);
    populateBooks(myLibrary, bookList);
    formDisplay();
    form.reset();
    console.table(myLibrary)
  }
};


function populateBooks(myLib, bookView) {
  const bookCards = document.querySelectorAll('.book-card')
  bookCards.forEach(bookCard => bookList.removeChild(bookCard));
  myLib.forEach((book, index) => {
    book.id = index;
    const cardContent = `<div class="book-card" data-index=${book.id}>
                                <div class="card-info-wrapper">
                                    <h2>${book.title}</h2>
                                    <h3>${book.author}</h3>
                                    <h4>${book.pages} Pages</h4>
                                    <p>${book.info()}</p>
                                </div>
                                <div class="card-menu">
                                    <div class="button" id="remove-btn">
                                        Remove
                                    </div>
                                </div>
                        </div>`
    const element = document.createElement('div');
    element.innerHTML = cardContent;
    // element.dataset.indexx = book.id;
    bookView.appendChild(element.firstChild);
    const cards = document.querySelectorAll('[data-index]');
    cards.forEach(card => {
      const removeButton = card.querySelector('.button');
      removeButton.addEventListener('click', () => {
        removeBook(book.id)
      })
    })

  });
};

function removeBook(id) {
  console.log('deleting', id);
  myLibrary.splice(id, 1);
  console.table(myLibrary);
  populateBooks(myLibrary, bookList);
}

function formDisplay() {
  form.reset();
  formView.classList.toggle('toggle-on');
};

const theHobbit = new Book('The Hobbit', 'J.R.R. Tolkien', 295);
myLibrary.push(theHobbit)
const harryPotter = new Book('Harry Potter', 'J.K Rowling', 320);
myLibrary.push(harryPotter)
const sangaf = new Book('The Subtle Art of Not Giving a Fuck', 'Mark Manson', 300)
myLibrary.push(sangaf)

document.addEventListener("DOMContentLoaded", function() {
  form.addEventListener("submit", function(e) {
    addToLibrary(e)
  });
});

formButton.addEventListener('click', formDisplay);


populateBooks(myLibrary, bookList);
@font-face {
  font-family: "fanwood";
  font-style: normal;
  font-weight: normal;
  src: url("fonts/Fanwood.otf");
  font-display: swap;
}

:root {
  --color-primary: #e9e2d7;
  --color-primary-alt: #8e6549;
  --color-secondary: #d42257;
  --color-background: #d2fbf7;
  --color-text: #412d86;
  --color-light: #fff;
  --color-anchor: #3a00ff;
  --font-family: "fanwoood";
  --font-weight-strong: 500;
  --font-size-h1: 4rem;
  --font-size-h2: 3rem;
  --font-size-h3: 2rem;
  --font-size-h4: 1.35rem;
  --font-size-text: 1.15rem;
  --border-radius: 8px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}


/* Remove default margin */

body,
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
}

html {
  overflow-x: hidden;
}


/* Set core body defaults */

body {
  font-family: 'fanwood';
  min-height: 100vh;
  font-size: 100%;
  line-height: 1.5;
  text-rendering: optimizeSpeed;
  overflow-x: hidden;
}


/* Make images easier to work with */

img {
  display: block;
  max-width: 100%;
}


/* Inherit fonts for inputs and buttons */

input,
button,
textarea,
select {
  font: inherit;
}

body {
  background-color: var(--color-primary);
}

button {
  background-color: var(--color-primary);
  border: none;
  margin: 0;
}

input {
  width: 100%;
  margin-bottom: 10px 0;
}

.site-wrapper {
  margin: 0 4%;
}

.card-info-wrapper {
  margin: 4% 4%;
  text-align: left;
}

.card-menu {
  align-self: flex-end;
  margin: 4% 4%;
}

.header {
  color: var(--color-primary);
  background-color: var(--color-primary-alt);
  height: 84px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.tool-bar {
  margin-top: 20px;
}

.tools {
  display: flex;
}

.button {
  cursor: pointer;
  display: inline-flex;
  padding: 2px 8px;
  color: var(--color-primary-alt);
  background-color: var(--color-primary);
}

.button.add {
  display: inline-flex;
  padding: 2px 8px;
  background-color: var(--color-primary-alt);
  color: var(--color-primary);
}

.books-wrapper {
  margin-top: 20px;
  /* border: 1px solid white; */
  display: flex;
  flex-wrap: wrap;
}

.book-card {
  word-wrap: normal;
  background-color: var(--color-primary-alt);
  color: var(--color-primary);
  width: 300px;
  height: 350px;
  margin-right: 10px;
  margin-bottom: 10px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.form-card {
  display: none;
  word-wrap: normal;
  background-color: var(--color-primary-alt);
  color: var(--color-primary);
  width: 300px;
  height: 350px;
  margin-right: 10px;
  margin-bottom: 10px;
}

.toggle-on {
  display: block;
}
<!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</title>
  <link rel="stylesheet" href="style.css">
</head>

<body>

  <div class="header">
    <div class="site-wrapper">
      <div class="header-logo-container">
        <h1>Library</h1>
      </div>
    </div>
  </div>

  <div class="tool-bar">
    <div class="site-wrapper">
      <div class="tools">
        <div class="button add" id="add-form">
          Add Book
        </div>
      </div>
    </div>
  </div>
  <div class="books">
    <div class="site-wrapper">
      <div class="books-wrapper">
        <!-- TEMPLATE FOR BOOK CARD -->
        <!-- <div class="book-card">
                    <div class="card-info-wrapper">
                        <h2>Title</h2>
                        <h3>Author</h3>
                        <h4>Pages</h4>
                        <p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsum fugit officiis animi soluta et, sit aliquid.</p>
                    </div>
                    <div class="card-menu">
                        <div class="button">
                            Remove
                        </div>
                    </div>
                </div> -->
        <div class="form-card">
          <div class="card-info-wrapper">
            <form id="input-form">
              <label for="title"><h3>Title</h3></label>
              <input type="text" id="title" name="title" placeholder="Name of the Book" required>

              <label for="author"><h3>Author</h3></label>
              <input type="text" id="author" name="author" placeholder="Name of the Author" required>

              <label for="pages"><h3>Pages</h3></label>
              <input type="number" id="pages" name="pages" placeholder="Number of Pages" required>

              <button type="submit" class="button" id="addBook">Add Book</button>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script src="app.js"></script>
</body>

</html>

Upvotes: 0

Views: 63

Answers (1)

Jamiec
Jamiec

Reputation: 136104

The problem with your code is that for every card aded in populateBook, you loop all the previous cards and add a click event listener, which means the second book gets 2 copies of this handler, the third 3 etc.

Instead of doing that, add a single event handler for clicking and handle appropriately:

document.querySelector(".books-wrapper").addEventListener("click", (e) => {
    if(e.target.classList.contains("button")){
      const index = e.target.parentElement.parentElement.dataset.index;
      removeBook(index);
  }    
});

Live example:

const form = document.getElementById('input-form');
const formButton = document.getElementById('add-form');
const formView = document.querySelector('.form-card')
const bookList = document.querySelector('.books-wrapper');

let myLibrary = [];
let newBook;

function Book(title, author, pages) {
    this.title = title;
    this.author = author;
    this.pages = pages;
    this.info = function() {
        return `${this.title} is a book by ${this.author}, ${this.pages} pages, not read yet.`
    };
};
Book.prototype.read = false;

function addToLibrary(e) {
    {
        e.preventDefault();
        const title = (document.getElementById('title')).value;
        const author = (document.getElementById('author')).value;
        const pages = (document.getElementById('pages')).value;
        newBook = new Book(title, author, pages);
    } {
        myLibrary.push(newBook);
        populateBooks(myLibrary, bookList);
        formDisplay();
        form.reset();
        console.table(myLibrary)
    }
};


function populateBooks(myLib, bookView) {
    const bookCards = document.querySelectorAll('.book-card')
    bookCards.forEach(bookCard => bookList.removeChild(bookCard));
    myLib.forEach((book, index) => {
        book.id = index;
        const cardContent = `<div class="book-card" data-index=${book.id}>
                                <div class="card-info-wrapper">
                                    <h2>${book.title}</h2>
                                    <h3>${book.author}</h3>
                                    <h4>${book.pages} Pages</h4>
                                    <p>${book.info()}</p>
                                </div>
                                <div class="card-menu">
                                    <div class="button" id="remove-btn">
                                        Remove
                                    </div>
                                </div>
                        </div>`
        const element = document.createElement('div');
        element.innerHTML = cardContent;
        // element.dataset.indexx = book.id;
        bookView.appendChild(element.firstChild);      
    });
};

function removeBook(id) {
    console.log('deleting', id);
    myLibrary.splice(id, 1);
    console.table(myLibrary);
    populateBooks(myLibrary, bookList);
}

function formDisplay() {
    form.reset();
    formView.classList.toggle('toggle-on');
};

const theHobbit = new Book('The Hobbit', 'J.R.R. Tolkien', 295);
myLibrary.push(theHobbit)
const harryPotter = new Book('Harry Potter', 'J.K Rowling', 320);
myLibrary.push(harryPotter)
const sangaf = new Book('The Subtle Art of Not Giving a Fuck', 'Mark Manson', 300)
myLibrary.push(sangaf)

document.addEventListener("DOMContentLoaded", function() {
    form.addEventListener("submit", function(e) {
        addToLibrary(e)
    });
    
    document.querySelector(".books-wrapper").addEventListener("click", (e) => {
        if(e.target.classList.contains("button")){
          const index = e.target.parentElement.parentElement.dataset.index;
        removeBook(index);
      }    
    })
});

formButton.addEventListener('click', formDisplay);


populateBooks(myLibrary, bookList);
@font-face {
    font-family: "fanwood";
    font-style: normal;
    font-weight: normal;
    src: url("fonts/Fanwood.otf");
    font-display: swap;
}

:root {
    --color-primary: #e9e2d7;
    --color-primary-alt: #8e6549;
    --color-secondary: #d42257;
    --color-background: #d2fbf7;
    --color-text: #412d86;
    --color-light: #fff;
    --color-anchor: #3a00ff;
    --font-family: "fanwoood";
    --font-weight-strong: 500;
    --font-size-h1: 4rem;
    --font-size-h2: 3rem;
    --font-size-h3: 2rem;
    --font-size-h4: 1.35rem;
    --font-size-text: 1.15rem;
    --border-radius: 8px;
}

*,
*::before,
*::after {
    box-sizing: border-box;
}


/* Remove default margin */

body,
h1,
h2,
h3,
h4,
h5,
h6 {
    margin: 0;
}

html {
    overflow-x: hidden;
}


/* Set core body defaults */

body {
    font-family: 'fanwood';
    min-height: 100vh;
    font-size: 100%;
    line-height: 1.5;
    text-rendering: optimizeSpeed;
    overflow-x: hidden;
}


/* Make images easier to work with */

img {
    display: block;
    max-width: 100%;
}


/* Inherit fonts for inputs and buttons */

input,
button,
textarea,
select {
    font: inherit;
}

body {
    background-color: var(--color-primary);
}

button {
    background-color: var(--color-primary);
    border: none;
    margin: 0;
}

input {
    width: 100%;
    margin-bottom: 10px 0;
}

.site-wrapper {
    margin: 0 4%;
}

.card-info-wrapper {
    margin: 4% 4%;
    text-align: left;
}

.card-menu {
    align-self: flex-end;
    margin: 4% 4%;
}

.header {
    color: var(--color-primary);
    background-color: var(--color-primary-alt);
    height: 84px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.tool-bar {
    margin-top: 20px;
}

.tools {
    display: flex;
}

.button {
    cursor: pointer;
    display: inline-flex;
    padding: 2px 8px;
    color: var(--color-primary-alt);
    background-color: var(--color-primary);
}

.button.add {
    display: inline-flex;
    padding: 2px 8px;
    background-color: var(--color-primary-alt);
    color: var(--color-primary);
}

.books-wrapper {
    margin-top: 20px;
    /* border: 1px solid white; */
    display: flex;
    flex-wrap: wrap;
}

.book-card {
    word-wrap: normal;
    background-color: var(--color-primary-alt);
    color: var(--color-primary);
    width: 300px;
    height: 350px;
    margin-right: 10px;
    margin-bottom: 10px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.form-card {
    display: none;
    word-wrap: normal;
    background-color: var(--color-primary-alt);
    color: var(--color-primary);
    width: 300px;
    height: 350px;
    margin-right: 10px;
    margin-bottom: 10px;
}

.toggle-on {
    display: block;
}
<!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</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>

    <div class="header">
        <div class="site-wrapper">
            <div class="header-logo-container">
                <h1>Library</h1>
            </div>
        </div>
    </div>

    <div class="tool-bar">
        <div class="site-wrapper">
            <div class="tools">
                <div class="button add" id="add-form">
                    Add Book
                </div>
            </div>
        </div>
    </div>
    <div class="books">
        <div class="site-wrapper">
            <div class="books-wrapper">
                <!-- TEMPLATE FOR BOOK CARD -->
                <!-- <div class="book-card">
                    <div class="card-info-wrapper">
                        <h2>Title</h2>
                        <h3>Author</h3>
                        <h4>Pages</h4>
                        <p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ipsum fugit officiis animi soluta et, sit aliquid.</p>
                    </div>
                    <div class="card-menu">
                        <div class="button">
                            Remove
                        </div>
                    </div>
                </div> -->
                <div class="form-card">
                    <div class="card-info-wrapper">
                        <form id="input-form">
                            <label for="title"><h3>Title</h3></label>
                            <input type="text" id="title" name="title" placeholder="Name of the Book" required>

                            <label for="author"><h3>Author</h3></label>
                            <input type="text" id="author" name="author" placeholder="Name of the Author" required>

                            <label for="pages"><h3>Pages</h3></label>
                            <input type="number" id="pages" name="pages" placeholder="Number of Pages" required>

                            <button type="submit" class="button" id="addBook">Add Book</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="app.js"></script>
</body>

</html>

Upvotes: 1

Related Questions