DEV Community

Cover image for Simple library app
Matheus πŸ‡§πŸ‡·
Matheus πŸ‡§πŸ‡·

Posted on

Simple library app

Intro

This is a project from TheOdinProject curriculum, created with HTML, CSS, and JavaScript.

Live Demo | Github Repo

Objective

Create a small Library App, which is expected to:

  • A constructor to create the book object.
  • A function that can take user's input and store the new book objects into an array
  • An array to store the books
let myLibrary = [];

function Book() {
  // the constructor
}

function addBookToLibrary() {
  // do stuff
}
Enter fullscreen mode Exit fullscreen mode

About HTML and CSS

I'll provide my template, where I used the Spectre-CSS framework to create the tables and the form. It is really simple, and it's a modest layout that serves only the purpose to create this library app.

For this challenge, I was really interested on the JavaScript.

Hands-On

First, I started completing my constructor Function.

Book Constructor

function Book(title, author, pages, status) {
  const id = Math.floor(Math.random() * 10000);

  this.title = title;
  this.author = author;
  this.pages = pages;
  this.status = status;
  this.id = id;
}
Enter fullscreen mode Exit fullscreen mode

The whole idea here, is to create a unique id for each book entry.
Why? Because I want to add a feature in the future to delete unique items from the array. and I'll need this id to locate the index in the array.

Now that I can create new books using the Book Constructor. I can manipulate DOM using .addEventListener to capture the input entries and create a new book.

Event Listener: Add a new book

document.querySelector('form').addEventListener('submit', e => {
    e.preventDefault();

    //Create a new book
    const title = document.querySelector('#bookTitle').value;
    const author = document.querySelector('#bookAuthor').value;
    const pages = document.querySelector('#bookPages').value;
    const status = document.querySelector('input[name="readStatus"]:checked').value;

    const book = new Book(title, author, pages, status);

    //Add new book to library
    addBookToLibrary(book);
}
Enter fullscreen mode Exit fullscreen mode

And now, I need to add this book to myLibrary array and loop through the array to render it.

Array push: Add the book to the library

function addBookToLibrary(book) {
    myLibrary.push(book);

    //What to do after I push the new book to the library
    displayBooks();
    clearFields();
}
Enter fullscreen mode Exit fullscreen mode

The function displayBooks is going to render the table's rows with information about each book. And the function clearFields() is going to clear my input values on the screen.

Rendering table rows

function displayBooks() {
  const tBody = document.querySelector('#bookRow');
  tbody.querySelectorAll('tr').forEach(el => el.remove());

  myLibrary.forEach(book => {
    createRow(book)
  });
}

function clearFields() {
    document.querySelector('#bookTitle').value = '';
    document.querySelector('#bookAuthor').value = '';
    document.querySelector('#bookPages').value = '';
}
Enter fullscreen mode Exit fullscreen mode

I need to talk about this line of code: tbody.querySelectorAll('tr').forEach(el => el.remove());

I was having a hard time at the first, second, third times while writing this code.
Every time I re-rendered my table, it became duplicated.
So this is the solution that I found functional:

  • Remove all the tr from my <tbody>
  • Create everything again

I don't know if it is the best solution, but it worked for me and for this project.

Creating the table rows

function createRow(book) {
    const tbody = document.querySelector('#bookRow');
    const row = document.createElement('tr');

    if (book.status === 'true') {
        row.innerHTML = `
        <td>${book.title}</td>
        <td>${book.author}</td>
        <td>${book.pages}</td>
        <td>Read</td>
        <td id="delete">Delete</td>
    `
    } else {
        row.innerHTML = `
        <td>${book.title}</td>
        <td>${book.author}</td>
        <td>${book.pages}</td>
        <td>Not Read</td>
        <td id="delete">Delete</td>
    `
    }



    row.classList.add(`${book.id}`)
    tbody.appendChild(row);
}
Enter fullscreen mode Exit fullscreen mode

This if-else condition is descriptive.
It checks the current status of each book, and evaluates a boolean expression. It compares to a string because input returns a string.
It's also adding a class name for each <tr>, with that unique id generated in the Book Constructor.

Event Listener: Delete a book and change their status


document.querySelector('table').addEventListener('click', e => {
    if (e.target.textContent === 'Delete') {
        const id = e.target.parentElement.className;
        deleteBook(id);
    }

    if (e.target.textContent === 'Read' || e.target.textContent === 'Not Read') {
        const id = e.target.parentElement.className;
        changeStatus(id);
    }

})
Enter fullscreen mode Exit fullscreen mode

Here, I can actually interact with my table. Since I'm rendering with string values, I'm looking for the exactly .textContent to trigger the functions that I want to execute.

function deleteBook(id) {
    myLibrary.forEach((book, i) => {
        if (book.id == id) {
            myLibrary.splice(i, 1);
        }
    })

    displayBooks();
}

function changeStatus(id) {
    myLibrary.forEach((book, i) => {
        if (book.id == id) {
            if (book.status === 'true') {
                book.status = 'false'
            } else {
                book.status = 'true'
            }
        }
    })

    displayBooks();
}
Enter fullscreen mode Exit fullscreen mode

After each change, I call the displayBooks()again. It re-renders my table (removing everything inside my tbody), and renders my new mΜ€yLibrary array with their new length or updated book.status.

That's it.

I hope you enjoyed this tutorial and how I solved this challenge!

Follow me on my Twitter!

Top comments (0)