Intro
This is a project from TheOdinProject curriculum, created with HTML, CSS, and JavaScript.
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
}
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;
}
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);
}
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();
}
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 = '';
}
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);
}
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);
}
})
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();
}
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)