0

I am building a library app that stores user books. It should take user input, store it into an array and then display it on the page. If I manually enter a few object instances in the array, the displayed values are as they should be (eg.title: harry potter, author: JK Rowling, etc), but my object instances created from user input are displayed like so:

function() { return this.title + " by " + this.author + ", " + this.pages + " pages, " + this.read + "."; }

Also, only one instance of object is created even though I've set an event listener on the button. What am I doing wrong?

document.addEventListener("DOMContentLoaded", () => {
    document.getElementById('submit').addEventListener("click", addBookToLibrary);
})

let myLibrary = [];

function Book(title,author,pages,read) {
    this.title = title;
    this.author = author;
    this.pages = pages;
    this.read = read;
    this.info = function() {
      return this.title + " by " + this.author + ", " + this.pages + " pages, " + this.read + ".";
    }
}

function addBookToLibrary() {

    let newBook = new Book();
            newBook.title = document.getElementById("title").value;
            newBook.author =  document.getElementById("author").value;
            newBook.pages = document.getElementById("pages").value;
            newBook.read = document.getElementById("read").value;

        
    myLibrary.push(newBook);
    document.querySelector("form").reset(); // clear the form for the next entries


}
addBookToLibrary();

//loop through an array and display each book
function displayBooks() {

    for (let i=0; i<myLibrary.length; i++) {
        const booksDisplay = document.getElementById("books-display");
        const cardBook = document.createElement('div');
        cardBook.classList.add("grid-item");

        cardBook.innerHTML += Object.values(myLibrary[i]).join(" ");
        booksDisplay.appendChild(cardBook);

    }
}
displayBooks();
<body>
        <header>
        </header>

        <main>
            <div id="page-wrapper">
                <form id="new-book">

                    <label for="title">Title</label><br>
                    <input type="text" id="title" value=""/><br><br>

                    <label for="author">Author</label><br>
                    <input type="text" id="author" value=""/><br><br>

                    <label for="pages">Pages</label><br>
                    <input type="text" id="pages" value=""/><br><br>

                    <label for="read">Read</label><br>
                    <input type="checkbox" id="read" value=""/><br><br>

                    <button id="submit">Click to add</button>
                
                </form> <br><br>

                <div id="books-display"></div>
            </div>
        </main>

    </body>

2
  • You're using Object.values() but .info is a function, so you'd have to call it first. However it already contains all the information, so just do cardBook.innerHTML += myLibrary[i].info(); You're also calling your functions after declaring them but that's nonsense. addBookToLibrary is already called when the submit button is clicked, and the call to displayBooks is supposed to go into your addBookToLibrary function, after pushing the new book into the array. Commented Apr 17, 2021 at 12:27
  • You're also not preventing the regular form submission, and there were a few other minor bugs: jsfiddle.net/yg0w6r54 Commented Apr 17, 2021 at 12:33

2 Answers 2

3

There are several issues. First, you are not preventing the page reload on the form, and therefore, any data that has been set by js will be lost upon page reload. You need to add e.preventDefault() to the addBookToLibrary function.

Secondly, you need to call the displayBooks function when a new record is added in order to re-render the updated list. You can just put the function call inside addBookToLibrary function.

Here is the example:

function addBookToLibrary(e) {
  e.preventDefault()

    let newBook = new Book();
            newBook.title = document.getElementById("title").value;
            newBook.author =  document.getElementById("author").value;
            newBook.pages = document.getElementById("pages").value;
            newBook.read = document.getElementById("read").value;

        
    myLibrary.push(newBook);
    document.querySelector("form").reset(); // clear the form for the next entries
  
    displayBooks();
}

Third, if you want to print the values, you can just call the info function, no need to use Object.values(...) . For example, here myLibrary[i] is a Book object. And since you have already declared a method info inside the Book class, you can just call the method and render it according to that.

Replace

Object.values(myLibrary[i]).join(" ");

With

myLibrary[i].info();

Example:

//loop through an array and display each book
function displayBooks() {

  const booksDisplay = document.getElementById("books-display");
        booksDisplay.innerHTML = "";

    for (let i=0; i<myLibrary.length; i++) {
        const cardBook = document.createElement('div');
        cardBook.classList.add("grid-item");

        cardBook.innerHTML += myLibrary[i].info();
        booksDisplay.appendChild(cardBook);

    }
}

Finally, remove the direct call of addBookToLibrary and displayBooks. addBookToLibrary should only be called when uses submit the form and displayBooks should only be called when a new record is added via form submission.

Here is the complete setup

document.addEventListener("DOMContentLoaded", () => {
    document.getElementById('submit').addEventListener("click", addBookToLibrary);
})

let myLibrary = [];

function Book(title,author,pages,read) {
    this.title = title;
    this.author = author;
    this.pages = pages;
    this.read = read;
    this.info = function() {
      return this.title + " by " + this.author + ", " + this.pages + " pages, " + this.read + ".";
    }
}

function addBookToLibrary(e) {
  e.preventDefault()

    let newBook = new Book();
            newBook.title = document.getElementById("title").value;
            newBook.author =  document.getElementById("author").value;
            newBook.pages = document.getElementById("pages").value;
            newBook.read = document.getElementById("read").value;

        
    myLibrary.push(newBook);
    document.querySelector("form").reset(); // clear the form for the next entries
  
    displayBooks();
}


//loop through an array and display each book
function displayBooks() {
  const booksDisplay = document.getElementById("books-display");
        booksDisplay.innerHTML = ""; //clear the dom first

    for (let i=0; i<myLibrary.length; i++) {
        
        const cardBook = document.createElement('div');
        cardBook.classList.add("grid-item");

        cardBook.innerHTML += myLibrary[i].info();
        booksDisplay.appendChild(cardBook);

    }
}
<main>
            <div id="page-wrapper">
                <form id="new-book">

                    <label for="title">Title</label><br>
                    <input type="text" id="title" value=""/><br><br>

                    <label for="author">Author</label><br>
                    <input type="text" id="author" value=""/><br><br>

                    <label for="pages">Pages</label><br>
                    <input type="text" id="pages" value=""/><br><br>

                    <label for="read">Read</label><br>
                    <input type="checkbox" id="read" value=""/><br><br>

                    <button id="submit">Click to add</button>
                
                </form> <br><br>

                <div id="books-display"></div>
            </div>
        </main>

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

Comments

1

You can save yourself some time writing out all those element ids and property names in the Book function by using a new FormData object.

Some other changes:

  1. Move the info() method to a prototype of Book. Then when you need to serialize the data it won't be included as a property
  2. You need a boolean true/false based on check state of a checkbox not it's value
  3. Use a submit event listener on the form rather than click on the button. Within the callback this will then be the form element to simplify things like this.reset()

initDemo()

document.getElementById('new-book').addEventListener("submit", addBookToLibrary);

let myLibrary = [];

function Book(formData) {
  const checkBoxes = ['read']; 
  for (let [k, v] of formData.entries()) {
    this[k] = v
  }
  // boolean values for checkboxes
  checkBoxes.forEach(k => this[k] = this[k] !== undefined);
}
Book.prototype.info = function() {
  return this.title + " by " + this.author + ", " + this.pages + " pages, " + this.read + ".";
}


function addBookToLibrary(e) {
  e.preventDefault();
  let newBook = new Book(new FormData(this));
  console.clear()
  console.log(JSON.stringify(newBook, null, 4));

  myLibrary.push(newBook);
  this.reset(); // clear the form for the next entries
  displayBooks();
}


//loop through an array and display each book
function displayBooks() {
  const booksDisplay = document.getElementById("books-display");
  booksDisplay.innerHTML = ""; //clear the dom first

  for (let i = 0; i < myLibrary.length; i++) {

    const cardBook = document.createElement('div');
    cardBook.classList.add("grid-item");

    cardBook.innerHTML += myLibrary[i].info();
    booksDisplay.appendChild(cardBook);

  }
}

function initDemo(){
  document.querySelectorAll('[data-value').forEach(el=>el.value = el.dataset.value)
}
<main >
  <div id="page-wrapper">
    <form id="new-book">

      <label for="title">Title</label><br>
      <input type="text" name="title" id="title" data-value="Foo Goes to Bar" /><br><br>

      <label for="author">Author</label><br>
      <input type="text" name="author" id="author" data-value="Mr Foo" /><br><br>

      <label for="pages">Pages</label><br>
      <input type="text" name="pages" id="pages" data-value="3" /><br><br>

      <label for="read">Read</label><br>
      <input type="checkbox" name="read" id="read" value="read" /><br><br>

      <button id="submit">Click to add</button>

    </form> <br><br>

    <div id="books-display"></div>
  </div>
</main>

Comments

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.