Search code examples
javascriptdomproject

Type Error for JS library project & DOM Traversal issues


The project is to create a library using vanilla JS and HTML. I have the whole project essentially completed except two functions fail to work. Those being the remove book and change read status, here are their code snippets respectively:

const removeBook = (e) => {
    const title = e.currentTarget.innerHTML.replaceAll(
        '"',
        '',
        'Title: ')
    library.removeBook(title)
    updateLibContainer()
    console.log('remove');
}
const toggleRead = (e) => {
    const title = e.currentTarget.innerHTML.replaceAll(
        '"',
        '',
        'Title: ')
    const book = library.getTitle(title)
    book.isRead = !book.isRead
    console.log('read');
    updateLibContainer()
}

Here is the full write up of the JS:

// Selectors for pop up and closing pop up when x is pressed
const popUp = document.querySelector('.pop-up');
const closePopUp = document.getElementsByTagName('span')[0];
closePopUp.addEventListener('click', () => popUp.style.display = 'none')

const newBookBtn = document.querySelector('.newbtn');
newBookBtn.addEventListener('click', () => popUp.style.display = 'block')


// Constructor for books
class Book {
  constructor(
    title = 'Unkown',
    author = 'Unkown',
    pages = 0,
    isRead = false
  ) {
    this.title = title
    this.author = author
    this.pages = pages
    this.isRead = isRead
  }
}

// Constructor for Library
class Library {
  constructor() {
    this.books = []
  }
  addBook(newBook) {
    this.books.push(newBook)
  }
  removeBook(title) {
    this.books = this.books.filter((book) => book.title !== title)
  }
  getTitle(title) {
    return this.books.find(book => book.title === title)
  }

  isInLibrary(title) {
    return this.books.some(book => book.title === title)
  }
}
const library = new Library()

// Additional UI declarations
const LibContainer = document.getElementById('lib-container')
const addBookForm = document.getElementById('add-form')

// Handling and creating book from form input data
const bookFromInput = () => {
  const title = document.getElementById('title').value
  const author = document.getElementById('author').value
  const pages = document.getElementById('pages').value
  const isRead = document.getElementById('is-read').checked

  return new Book(title, author, pages, isRead)

}
// Function when form is submitted to create new book from input, check if it exists
// and close form
const addNewBook = (e) => {
  e.preventDefault()
  const newBook = bookFromInput()
  if (library.isInLibrary(newBook.title)) {
    alert('Book already exists')
  } else {
    library.addBook(newBook)
    exitForm()
    updateLibContainer()
  }
}
const exitForm = () => {
  addBookForm.reset()
  popUp.style.display = 'none'
}
const resetLibContainer = () => {
  LibContainer.innerHTML = ''
  console.log('reset Container');
}
const updateLibContainer = () => {
  resetLibContainer()
  for (let book of library.books) {
    createBookCard(book)
  }
  console.log('updateContainer');
}


// Creating book card 
const createBookCard = (book) => {
  const bookCard = document.createElement('div')
  const title = document.createElement('p')
  const author = document.createElement('p')
  const pages = document.createElement('p')
  const changeReadStatus = document.createElement('button')
  const removeBookBttn = document.createElement('button')

  bookCard.classList.add('book-card')
  changeReadStatus.classList.add('change-read-status')
  changeReadStatus.onclick = toggleRead
  removeBookBttn.classList.add('remove-book')
  removeBookBttn.onclick = removeBook

  title.textContent = `Title: ${book.title}`
  author.textContent = `Author: ${book.author}`
  pages.textContent = `Pages: ${book.pages}`
  removeBookBttn.textContent = 'Remove'

  if (book.isRead) {
    changeReadStatus.textContent = 'Read'
    changeReadStatus.style.backgroundColor = '#68f364'
  } else {
    changeReadStatus.textContent = 'Not read'
    changeReadStatus.style.backgroundColor = '#d16767'
  }


  bookCard.appendChild(title)
  bookCard.appendChild(author)
  bookCard.appendChild(pages)
  bookCard.appendChild(changeReadStatus)
  bookCard.appendChild(removeBookBttn)
  LibContainer.appendChild(bookCard)
}

const removeBook = (e) => {

  const title = e.currentTarget.innerHTML.replaceAll(
    '"',
    '',
    'Title: ')
  library.removeBook(title)
  updateLibContainer()
  console.log('remove');
}
const toggleRead = (e) => {
  const title = e.currentTarget.innerHTML.replaceAll(
    '"',
    '',
    'Title: ')
  const book = library.getTitle(title)
  book.isRead = !book.isRead
  console.log('read');
  updateLibContainer()
}

addBookForm.onsubmit = addNewBook
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Library</title>
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <div class="header">
    <h1>The Library</h1>
  </div>

  <div class="newbtn-container">
    <button class="newbtn">+ New Book</button>
  </div>
  <div class="pop-up">
    <form class="form" action id="add-form">
      <span>x</span>
      <legend>New book</legend>
      <input type="text" id="title" class="form" placeholder="Title">
      <input type="text" id="author" class="form" placeholder="Author">
      <input type="text" id="pages" class="form" placeholder="Pages">

      <div class="checkbox">
        <label>Read: </label>
        <input type="checkbox" class="form" id="is-read">
      </div>
      <button class="form" id="submit">Add</button>
    </form>
  </div>
  <div id="lib-container">
    <div class="book-card">
      <p>Title: 22</p>
      <p>Author: 22</p>
      <p>Pages: 22</p>
      <button class="change-read-status" style="background-color: rgb(209, 103, 103);">Not read</button>
      <button class="remove-book">Remove</button>
    </div>
  </div>

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

</html>

The book card in the HTML code above is just an example placeholder if a user were to input a book.

Now the exact issue is that both aforementioned functions just dont work at all. The toggle read function produces a Type Error on the line book.isRead = !book.isRead. The remove function just doesnt do its purpose at all. Ive tried the past 3 days to try and trouble shoot using the methods I have learned all to no real avail. Sorry in advance if information is missing or if this isnt formatted properly.


Solution

  • The problem is that the book titles aren't in the innerHTML of the buttons. It's in the title <p> element. So you need to find the title element associated with the clicked button.

    I recommend giving the paragraph a class:

    const title = document.createElement('p');
    title.classList.add("title");
    

    Then you can find the div containing the button and navigate to the corresponding title element.

    There's no need to remove " from the title, you never added them when creating the element. You just need to remove Title: at the beginning.

    const title = e.currentTarget.closest(".book-card").querySelector(".title").innerText.replace("Title: ");
    

    Full code

    // Selectors for pop up and closing pop up when x is pressed
    const popUp = document.querySelector('.pop-up');
    const closePopUp = document.getElementsByTagName('span')[0];
    closePopUp.addEventListener('click', () => popUp.style.display = 'none')
    
    const newBookBtn = document.querySelector('.newbtn');
    newBookBtn.addEventListener('click', () => popUp.style.display = 'block')
    
    
    // Constructor for books
    class Book {
      constructor(
        title = 'Unkown',
        author = 'Unkown',
        pages = 0,
        isRead = false
      ) {
        this.title = title
        this.author = author
        this.pages = pages
        this.isRead = isRead
      }
    }
    
    // Constructor for Library
    class Library {
      constructor() {
        this.books = []
      }
      addBook(newBook) {
        this.books.push(newBook)
      }
      removeBook(title) {
        this.books = this.books.filter((book) => book.title !== title)
      }
      getTitle(title) {
        return this.books.find(book => book.title === title)
      }
    
      isInLibrary(title) {
        return this.books.some(book => book.title === title)
      }
    }
    const library = new Library()
    
    // Additional UI declarations
    const LibContainer = document.getElementById('lib-container')
    const addBookForm = document.getElementById('add-form')
    
    // Handling and creating book from form input data
    const bookFromInput = () => {
      const title = document.getElementById('title').value
      const author = document.getElementById('author').value
      const pages = document.getElementById('pages').value
      const isRead = document.getElementById('is-read').checked
    
      return new Book(title, author, pages, isRead)
    
    }
    // Function when form is submitted to create new book from input, check if it exists
    // and close form
    const addNewBook = (e) => {
      e.preventDefault()
      const newBook = bookFromInput()
      if (library.isInLibrary(newBook.title)) {
        alert('Book already exists')
      } else {
        library.addBook(newBook)
        exitForm()
        updateLibContainer()
      }
    }
    const exitForm = () => {
      addBookForm.reset()
      popUp.style.display = 'none'
    }
    const resetLibContainer = () => {
      LibContainer.innerHTML = ''
      console.log('reset Container');
    }
    const updateLibContainer = () => {
      resetLibContainer()
      for (let book of library.books) {
        createBookCard(book)
      }
      console.log('updateContainer');
    }
    
    
    // Creating book card 
    const createBookCard = (book) => {
      const bookCard = document.createElement('div')
      const title = document.createElement('p')
      title.classList.add("title")
      const author = document.createElement('p')
      const pages = document.createElement('p')
      const changeReadStatus = document.createElement('button')
      const removeBookBttn = document.createElement('button')
    
      bookCard.classList.add('book-card')
      changeReadStatus.classList.add('change-read-status')
      changeReadStatus.onclick = toggleRead
      removeBookBttn.classList.add('remove-book')
      removeBookBttn.onclick = removeBook
    
      title.textContent = `Title: ${book.title}`
      author.textContent = `Author: ${book.author}`
      pages.textContent = `Pages: ${book.pages}`
      removeBookBttn.textContent = 'Remove'
    
      if (book.isRead) {
        changeReadStatus.textContent = 'Read'
        changeReadStatus.style.backgroundColor = '#68f364'
      } else {
        changeReadStatus.textContent = 'Not read'
        changeReadStatus.style.backgroundColor = '#d16767'
      }
    
    
      bookCard.appendChild(title)
      bookCard.appendChild(author)
      bookCard.appendChild(pages)
      bookCard.appendChild(changeReadStatus)
      bookCard.appendChild(removeBookBttn)
      LibContainer.appendChild(bookCard)
    }
    
    const removeBook = (e) => {
    
      const title = e.currentTarget.closest(".book-card").querySelector(".title").innerText.replace('Title: ', '')
      library.removeBook(title)
      updateLibContainer()
      console.log('remove');
    }
    const toggleRead = (e) => {
      const title = e.currentTarget.closest(".book-card").querySelector(".title").innerText.replace('Title: ', '')
      const book = library.getTitle(title)
      book.isRead = !book.isRead
      console.log('read');
      updateLibContainer()
    }
    
    addBookForm.onsubmit = addNewBook
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Library</title>
      <link rel="stylesheet" href="style.css">
    </head>
    
    <body>
      <div class="header">
        <h1>The Library</h1>
      </div>
    
      <div class="newbtn-container">
        <button class="newbtn">+ New Book</button>
      </div>
      <div class="pop-up">
        <form class="form" action id="add-form">
          <span>x</span>
          <legend>New book</legend>
          <input type="text" id="title" class="form" placeholder="Title">
          <input type="text" id="author" class="form" placeholder="Author">
          <input type="text" id="pages" class="form" placeholder="Pages">
    
          <div class="checkbox">
            <label>Read: </label>
            <input type="checkbox" class="form" id="is-read">
          </div>
          <button class="form" id="submit">Add</button>
        </form>
      </div>
      <div id="lib-container">
        <div class="book-card">
          <p>Title: 22</p>
          <p>Author: 22</p>
          <p>Pages: 22</p>
          <button class="change-read-status" style="background-color: rgb(209, 103, 103);">Not read</button>
          <button class="remove-book">Remove</button>
        </div>
      </div>
    
    </body>
    <script src="script.js"></script>
    
    </html>