Skip to content
Advertisement

addEventListener stops after the first execution

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>

Advertisement

Answer

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>
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement