0

I created a constructor function name Book and an empty array named myLibrary as a container for these new book object instances. A separate function to create a book createBook calls a function addBookToLibrary which pushes the book into the myLibrary array, clears the form to add books, then renders the book list via the viewBookList function.

Each time I call createBook and create a new instance of Book, I'm running into a problem where the rendered list of books that show up on the page repeat all instances of Book objects created prior to the new book being added plus the one new book. For instance, I have book1, book2 and book3 that I previously created. I then add book4 so now the array has [ {book1...}, {book2...}, {book3...}, {book4...} ] objects correctly in place, but what's being rendered on the page is a scattered mess of book1, book1, book2, book1, book2, book3, book1, book2, book3, book4 as you see in the attached image. You'll see the array in the console is fine.

image of book container

I initially cleared out the myLibrary array in createBook which correctly rendered the books to the page, but obviously wipes out my array which won't work because I need to be able to delete these at some point from the array. So my question is, how do I display what's in my array onto the page each time without rendering duplicates of books that were already added?

const submitButton = document.querySelector('#submit');
const btnOpenModal = document.querySelector('.open-modal');
const btnCloseModal = document.querySelector('.close-modal');
const modalElement = document.querySelector('.modal');

// let myLibrary = [
//   new Book('Down and Out in Paris and London', 'George Orwell', 232, true),
//   new Book('Homage to Catalonia', 'George Orwell', 202, true),
//   new Book('Shooting an Elephant', 'George Orwell', 368, false),
// ];
// let count = myLibrary.length - 3;

let myLibrary = [];
let count = myLibrary.length;

// Constructor...
function Book(title, author, pages, read) {
  this.title = title;
  this.author = author;
  this.pages = Number(pages);
  this.read = Boolean(read);

  // Methods
  this.bookInfo = function () {
    const wasRead = this.read === true ? 'read' : 'not read';
    return `${this.title} written by ${this.author}, ${this.pages} pages in length was ${wasRead}.`;
  };

  return this.bookInfo();
}

function createBook(e) {
  e.preventDefault();
  // myLibrary = [];

  let title = document.querySelector('#title').value;
  let author = document.querySelector('#author').value;
  let pages = document.querySelector('#pages').value;
  let read = document.querySelector('#read').value;

  // Instantiate new Book object
  const newBook = new Book(title, author, pages, read); // Add ID when instantiating
  addBookToLibrary(newBook);
  clearForm();
  viewBookList(myLibrary);
}

function addBookToLibrary(book) {
  // Hide modal
  removeClass();
  // Add book to array
  return myLibrary.push(book);
}

function clearForm() {
  document.querySelector('#form').reset();
}

function setCardStyle(element, details) {
  element.setAttribute(
    'style',
    'display: flex; flex-direction: column; justify-content: space-between; text-align: center; background-color: #fff; padding: 1em; margin: 1em 1em 1em 0; border-radius: 5px; height: 250px; width: 250px; line-height: 1.5; box-shadow: 0 5px 10px 2px rgba(0, 0, 0, 0.2);',
  );

  element.innerHTML = `
    <h3 class="title">${details.title}</h3>
    <br>
    <hr>
    <br>
    <p>${details.author}</p>
    <p>${details.pages} pages</p>
    <p>${details.read === true ? 'Read' : 'Unread'}</p>`;
}

function createCard() {
  let bookCard = document.createElement('div');
  bookCard.classList.add('card');
  bookCard.setAttribute('data-target', `${count++}`); // Set target ID

  return bookCard;
}

function removeBookBtn() {
  let btn = document.createElement('button');
  // Style button
  btn.setAttribute(
    'style',
    'color: red; height: 2.5em; width: 50%; border-radius: 5px; margin: 0 auto; font-weight: bold; text-transform: uppercase; cursor: pointer;',
  );
  btn.innerHTML = 'Delete';
  return btn;
}

function handleDelete(e) {
  // Get book's data-target
  let bookIndex = parseInt(e.path[1].attributes[1].value);
  // console.log(bookIndex);

  myLibrary.splice(bookIndex, 1);
  // console.log(newArray);
  // myLibrary = myLibrary.filter((book, index) => index !== bookIndex);
  return viewBookList(myLibrary);
}

function clearDOM(element) {
  while (element.firstElementChild) {
    element.firstElementChild.remove();
  }
}

function viewBookList(list) {
  const bookDiv = document.querySelector('.book-list');
  clearDOM(bookDiv);

  for (book in list) {
    let bookDetails = list[book];
    let renderCard = createCard();
    const deleteButton = removeBookBtn();

    deleteButton.addEventListener('click', handleDelete);
    setCardStyle(renderCard, bookDetails);

    renderCard.appendChild(deleteButton);
    bookDiv.appendChild(renderCard);
  }

  return bookDiv;
}

function addClass() {
  return modalElement.classList.add('open');
}

function removeClass() {
  return modalElement.classList.remove('open');
}

// Event listeners
btnOpenModal.addEventListener('click', addClass);
btnCloseModal.addEventListener('click', removeClass);
submitButton.addEventListener('click', createBook);
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');

:root {
  --roboto: 'Roboto', sans-serif;
  --pt: "PT Sans", sans-serif;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-family: var(--roboto);
}

body {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  background-image: linear-gradient(to top, #a6c1ee 0%, #fbc2eb 100%);
}

.header {
  text-align: center;
}

.header h1 {
  color: #fff;
  font-size: 4rem;
  text-shadow: 1px 2px 5px #000;
}

.modal {
  padding: 1em;
  background-color: #fff;
}

.label {
  text-transform: capitalize;
}

.btn {
  font-family: "PT Sans", sans-serif;
  font-size: 1.2rem;
  padding: 1rem 2.5rem;
  cursor: pointer;
  border: none;
}

.modal {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.7);
}

.open-modal {
  background: #fff;
  border-radius: 5px;
  box-shadow: 0 5px 10px 2px rgba(0, 0, 0, 0.2);
  transition: all 0.2s linear;
}

.open-modal:hover {
  box-shadow: none;
  background: rgba(255, 255, 255, 0.8);
}


.modal.open {
  display: flex;
}

.form {
  padding: 5em;
}


.modal-container {
  background-color: #fff;
  width: 90%;
  max-width: 450px;
  position: relative;
}

.modal.open .modal-container {
  animation: move 0.6s ease-out;
}

@keyframes move {
  from {
    transform: translateY(-50px);
  }

  to {
    transform: translateY(0px);
  }
}

.close-modal {
  font-size: 3rem;
  position: absolute;
  top: 0;
  right: 0;
  background-color: #fff;
  height: 40px;
  width: 40px;
  text-align: center;
  line-height: 40px;
  cursor: pointer;
  transition: color 0.15s linear;
}

.close-modal:hover {
  color: #f00;
}

.book-list {
  display: flex;
  flex-wrap: wrap;
  margin-top: 1.5em;
  gap: 2em;
}

/* Created in app.js */
/* .card {
} */
<!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">
  <link rel="stylesheet" href="styles/style.css">
  <script src="scripts/app.js" defer></script>
  <title>Odin Library</title>
</head>
<body>
  <div class="container">
    <header class="header">
      <h1>Library</h1>
    </header>
    <section class="modal open">
      <div class="modal-container">
        <span id="close-modal" class="close-modal">&times;</span>
        <form action="" method="POST" name="bookForm" id="form" class="form">
          <div class="form-row">
            <label for="title" class="label">title: </label>
            <input type="text" name="title" id="title" placeholder="Animal Farm" required>
          </div>
          <div class="form-row">
            <label for="author" class="label">author: </label>
            <input type="text" name="author" id="author" placeholder="George Orwell" required>
          </div>
          <div class="form-row">
            <label for="pages" class="label">pages: </label>
            <input type="number" name="pages" id="pages" placeholder="232" required>
          </div>
          <div class="form-row">
            <label for="read" class="label">read: </label>
            <input type="checkbox" name="read" id="read" required>
          </div>
          <div class="button-container">
            <input type="submit" value="Create" id="submit" class="submit">
          </div>
        </form>
      </div>
    </section class="card-section">
    <section>
      <!-- Add plus icon -->
      <button class="btn open-modal" id="open-modal">Add Book</button> 
      <div class="book-list">
        <!-- Container for cards -->
      </div>
    </section>
  </div>
</body>
</html>

3
  • 1
    Do you ever clear your book list DOM? I only see appending new elelemnts. Perhaps viewBookList should first clear out the container of all children? Commented Oct 17, 2022 at 17:48
  • 1
    Nothing to do with constructors, nothing to do with array pushing. viewBookList simply always adds all items you give it to the page. The previous things on the page are not cleared. Commented Oct 17, 2022 at 17:49
  • @CollinD I took your advice and added a separate function clearDOM to clear the DOM and called it in viewBookList. Updated the code snippet as well. Thanks. Commented Oct 17, 2022 at 18:02

1 Answer 1

1

Like the others already said on the comment, the problem is that each time you add a new book, when you try to display ti on the page you are "appending" all the books after the already existing books.

So you get something like this: Add book 1: (1) Add book 2: (1) (1 2) Add book 3: (1) (1 2) (1 2 3)

The solution is to clear the container div before you update it.

Something like this (JS line 107):

const submitButton = document.querySelector('#submit');
const btnOpenModal = document.querySelector('.open-modal');
const btnCloseModal = document.querySelector('.close-modal');
const modalElement = document.querySelector('.modal');

// let myLibrary = [
//   new Book('Down and Out in Paris and London', 'George Orwell', 232, true),
//   new Book('Homage to Catalonia', 'George Orwell', 202, true),
//   new Book('Shooting an Elephant', 'George Orwell', 368, false),
// ];
// let count = myLibrary.length - 3;

let myLibrary = [];
let count = myLibrary.length;

// Constructor...
function Book(title, author, pages, read) {
  this.title = title;
  this.author = author;
  this.pages = Number(pages);
  this.read = Boolean(read);

  // Methods
  this.bookInfo = function() {
    const wasRead = this.read === true ? 'read' : 'not read';
    return `${this.title} written by ${this.author}, ${this.pages} pages in length was ${wasRead}.`;
  };

  return this.bookInfo();
}

function createBook(e) {
  e.preventDefault();
  // myLibrary = [];

  let title = document.querySelector('#title').value;
  let author = document.querySelector('#author').value;
  let pages = document.querySelector('#pages').value;
  let read = document.querySelector('#read').value;

  // Instantiate new Book object
  const newBook = new Book(title, author, pages, read); // Add ID when instantiating
  addBookToLibrary(newBook);
  clearForm();
  viewBookList(myLibrary);
}

function addBookToLibrary(book) {
  // Hide modal
  removeClass();
  // Add book to array
  return myLibrary.push(book);
}

function clearForm() {
  document.querySelector('#form').reset();
}

function setCardStyle(element, details) {
  element.setAttribute(
    'style',
    'display: flex; flex-direction: column; justify-content: space-between; text-align: center; background-color: #fff; padding: 1em; margin: 1em 1em 1em 0; border-radius: 5px; height: 250px; width: 250px; line-height: 1.5; box-shadow: 0 5px 10px 2px rgba(0, 0, 0, 0.2);',
  );

  element.innerHTML = `
    <h3 class="title">${details.title}</h3>
    <br>
    <hr>
    <br>
    <p>${details.author}</p>
    <p>${details.pages} pages</p>
    <p>${details.read === true ? 'Read' : 'Unread'}</p>`;
}

function createCard() {
  let bookCard = document.createElement('div');
  bookCard.classList.add('card');
  bookCard.setAttribute('data-target', `${count++}`); // Set target ID

  return bookCard;
}

function removeBookBtn() {
  let btn = document.createElement('button');
  // Style button
  btn.setAttribute(
    'style',
    'color: red; height: 2.5em; width: 50%; border-radius: 5px; margin: 0 auto; font-weight: bold; text-transform: uppercase; cursor: pointer;',
  );
  btn.innerHTML = 'Delete';
  return btn;
}

function handleDelete(e) {
  // Get book's data-target
  let bookIndex = parseInt(e.path[1].attributes[1].value);
  // console.log(bookIndex);

  myLibrary.splice(bookIndex, 1);
  // console.log(newArray);
  // myLibrary = myLibrary.filter((book, index) => index !== bookIndex);
  return viewBookList(myLibrary);
}

function viewBookList(list) {
  const bookDiv = document.querySelector('.book-list');
  bookDiv.innerHTML = "";
  console.log(myLibrary);

  for (book in list) {
    let bookDetails = list[book];
    let renderCard = createCard();
    const deleteButton = removeBookBtn();

    deleteButton.addEventListener('click', handleDelete);
    setCardStyle(renderCard, bookDetails);

    renderCard.appendChild(deleteButton);
    bookDiv.appendChild(renderCard);
  }

  return bookDiv;
}

function addClass() {
  return modalElement.classList.add('open');
}

function removeClass() {
  return modalElement.classList.remove('open');
}

// Event listeners
btnOpenModal.addEventListener('click', addClass);
btnCloseModal.addEventListener('click', removeClass);
submitButton.addEventListener('click', createBook);
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
:root {
  --roboto: 'Roboto', sans-serif;
  --pt: "PT Sans", sans-serif;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-family: var(--roboto);
}

body {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  background-image: linear-gradient(to top, #a6c1ee 0%, #fbc2eb 100%);
}

.header {
  text-align: center;
}

.header h1 {
  color: #fff;
  font-size: 4rem;
  text-shadow: 1px 2px 5px #000;
}

.modal {
  padding: 1em;
  background-color: #fff;
}

.label {
  text-transform: capitalize;
}

.btn {
  font-family: "PT Sans", sans-serif;
  font-size: 1.2rem;
  padding: 1rem 2.5rem;
  cursor: pointer;
  border: none;
}

.modal {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.7);
}

.open-modal {
  background: #fff;
  border-radius: 5px;
  box-shadow: 0 5px 10px 2px rgba(0, 0, 0, 0.2);
  transition: all 0.2s linear;
}

.open-modal:hover {
  box-shadow: none;
  background: rgba(255, 255, 255, 0.8);
}

.modal.open {
  display: flex;
}

.form {
  padding: 5em;
}

.modal-container {
  background-color: #fff;
  width: 90%;
  max-width: 450px;
  position: relative;
}

.modal.open .modal-container {
  animation: move 0.6s ease-out;
}

@keyframes move {
  from {
    transform: translateY(-50px);
  }
  to {
    transform: translateY(0px);
  }
}

.close-modal {
  font-size: 3rem;
  position: absolute;
  top: 0;
  right: 0;
  background-color: #fff;
  height: 40px;
  width: 40px;
  text-align: center;
  line-height: 40px;
  cursor: pointer;
  transition: color 0.15s linear;
}

.close-modal:hover {
  color: #f00;
}

.book-list {
  display: flex;
  flex-wrap: wrap;
  margin-top: 1.5em;
  gap: 2em;
}


/* Created in app.js */


/* .card {
} */
<!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">
  <link rel="stylesheet" href="styles/style.css">
  <script src="scripts/app.js" defer></script>
  <title>Odin Library</title>
</head>

<body>
  <div class="container">
    <header class="header">
      <h1>Library</h1>
    </header>
    <section class="modal open">
      <div class="modal-container">
        <span id="close-modal" class="close-modal">&times;</span>
        <form action="" method="POST" name="bookForm" id="form" class="form">
          <div class="form-row">
            <label for="title" class="label">title: </label>
            <input type="text" name="title" id="title" placeholder="Animal Farm" required>
          </div>
          <div class="form-row">
            <label for="author" class="label">author: </label>
            <input type="text" name="author" id="author" placeholder="George Orwell" required>
          </div>
          <div class="form-row">
            <label for="pages" class="label">pages: </label>
            <input type="number" name="pages" id="pages" placeholder="232" required>
          </div>
          <div class="form-row">
            <label for="read" class="label">read: </label>
            <input type="checkbox" name="read" id="read" required>
          </div>
          <div class="button-container">
            <input type="submit" value="Create" id="submit" class="submit">
          </div>
        </form>
      </div>
    </section class="card-section">
    <section>
      <!-- Add plus icon -->
      <button class="btn open-modal" id="open-modal">Add Book</button>
      <div class="book-list">
        <!-- Container for cards -->
      </div>
    </section>
  </div>
</body>

</html>

Sign up to request clarification or add additional context in comments.

3 Comments

I took your advice but slightly modified it to include a function clearDOM to clear out the DOM. Marked your answer as the accepted answer either way.
Nice. I think it would be a good idea to change the function Book into a class Book. In the end it doesn't make a big difference, it is just a cleaner and more modern way of doing it
took your advice and converted it to a class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.