Simple library app
Tags: javascript, tutorial, beginners, programming
Published: 2022-04-22 13:23:47

# Intro

This is a project from [TheOdinProject](https://www.theodinproject.com/lessons/node-path-javascript-library) curriculum, created with HTML, CSS, and JavaScript.

[Live Demo](https://mpfdev.github.io/LibraryApp/) | [Github Repo](https://github.com/mpfdev/LibraryApp)

## 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

```javascript
let myLibrary = [];

function Book() {
  // the constructor
}

function addBookToLibrary() {
  // do stuff
}
```

## 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
```javascript
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;
}
```

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
```javascript
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);
}
```

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
```javascript
function addBookToLibrary(book) {
	myLibrary.push(book);
  
  	//What to do after I push the new book to the library
	displayBooks();
  	clearFields();
}
```

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

```javascript
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 = '';
}
```

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

```javascript
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);
}
```

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

```javascript

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);
    }

})
```

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.

```javascript
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();
}
```

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](https://www.twitter.com/mpfdev)!