Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 69 additions & 83 deletions debugging/book-library/index.html
Original file line number Diff line number Diff line change
@@ -1,96 +1,82 @@
<!DOCTYPE html>
<html>
<!doctype html>
<html lang="en">
<head>
<title> </title>
<meta
charset="utf-8"
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>My Library</title>
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" type="text/css" href="style.css" />
<style>
#toast {
position: fixed;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
background: #333;
color: #fff;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
#toast.show {
opacity: 1;
}
</style>
</head>

<body>
<div class="jumbotron text-center">
<h1>Library</h1>
<p>Add books to your virtual library</p>
</div>
<div class="container my-4">
<h1>My Library</h1>

<button data-toggle="collapse" data-target="#demo" class="btn btn-info">
Add new book
</button>

<div id="demo" class="collapse">
<div class="form-group">
<label for="title">Title:</label>
<input
type="title"
class="form-control"
id="title"
name="title"
required
/>
<label for="author">Author: </label>
<input
type="author"
class="form-control"
id="author"
name="author"
required
/>
<label for="pages">Pages:</label>
<input
type="number"
class="form-control"
id="pages"
name="pages"
required
/>
<label class="form-check-label">
<form id="bookForm" class="row g-2 align-items-end">
<div class="col">
<label for="title">Title:</label>
<input type="text" class="form-control" id="title" name="title" required />
</div>
<div class="col">
<label for="author">Author:</label>
<input type="text" class="form-control" id="author" name="author" required />
</div>
<div class="col">
<label for="pages">Pages:</label>
<input
type="checkbox"
class="form-check-input"
id="check"
value=""
/>Read
</label>
<input
type="submit"
value="Submit"
class="btn btn-primary"
onclick="submit();"
/>
</div>
type="number"
class="form-control"
id="pages"
name="pages"
min="1"
step="1"
required
/>
</div>
<div class="col-auto form-check mb-2">
<input type="checkbox" class="form-check-input" id="check" name="check" />
<label class="form-check-label" for="check">Read</label>
</div>
<div class="col-auto">
<input type="submit" value="Submit" id="submitBtn" class="btn btn-primary" />
</div>
</form>

<table id="display" class="table table-striped mt-4">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Pages</th>
<th>Read</th>
<th>Delete</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>

<table class="table" id="display">
<thead class="thead-dark">
<tr>
<th>Title</th>
<th>Author</th>
<th>Number of Pages</th>
<th>Read</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<div id="toast" aria-live="polite"></div>

<script src="script.js"></script>
<script src="script.js" type="module"></script>
</body>
</html>
186 changes: 106 additions & 80 deletions debugging/book-library/script.js
Original file line number Diff line number Diff line change
@@ -1,103 +1,129 @@
let myLibrary = [];
const myLibrary = [];

window.addEventListener("load", function (e) {
populateStorage();
render();
});
const bookForm = document.getElementById("bookForm");
const titleInput = document.getElementById("title");
const authorInput = document.getElementById("author");
const pagesInput = document.getElementById("pages");
const checkInput = document.getElementById("check");

function Book(title, author, pages, read) {
this.title = title;
this.author = author;
this.pages = pages;
this.read = !!read;
}

function populateStorage() {
if (myLibrary.length == 0) {
let book1 = new Book("Robison Crusoe", "Daniel Defoe", "252", true);
let book2 = new Book(
if (myLibrary.length === 0) {
const book1 = new Book("Robinson Crusoe", "Daniel Defoe", 252, true);
const book2 = new Book(
"The Old Man and the Sea",
"Ernest Hemingway",
"127",
127,
true
);
myLibrary.push(book1);
myLibrary.push(book2);
render();
myLibrary.push(book1, book2);
}
}

const title = document.getElementById("title");
const author = document.getElementById("author");
const pages = document.getElementById("pages");
const check = document.getElementById("check");

//check the right input from forms and if its ok -> add the new book (object in array)
//via Book function and start render function
function submit() {
if (
title.value == null ||
title.value == "" ||
pages.value == null ||
pages.value == ""
) {
alert("Please fill all fields!");
function addBook() {
const title = titleInput.value.trim();
const author = authorInput.value.trim();
const pagesRaw = pagesInput.value.trim();
const pages = Number(pagesRaw);

// Native "required"/"min"/"step" already ran before this handler fires
// (see bookForm's submit listener below), so this is a second layer
// for things HTML attributes can't express, like whitespace-only text.
if (!title || !author || !pagesRaw || !Number.isFinite(pages) || pages <= 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Do you know why checking !pageRaw is optional?

  • Why not just check if pages is a whole number?

alert("Please fill all fields with valid values!");
return false;
} else {
let book = new Book(title.value, title.value, pages.value, check.checked);
library.push(book);
render();
}
}

function Book(title, author, pages, check) {
this.title = title;
this.author = author;
this.pages = pages;
this.check = check;
const book = new Book(title, author, pages, checkInput.checked);
myLibrary.push(book);

bookForm.reset();

render();
return true;
}

function render() {
let table = document.getElementById("display");
let rowsNumber = table.rows.length;
//delete old table
for (let n = rowsNumber - 1; n > 0; n-- {
table.deleteRow(n);
}
//insert updated row and cells
let length = myLibrary.length;
for (let i = 0; i < length; i++) {
let row = table.insertRow(1);
let titleCell = row.insertCell(0);
let authorCell = row.insertCell(1);
let pagesCell = row.insertCell(2);
let wasReadCell = row.insertCell(3);
let deleteCell = row.insertCell(4);
titleCell.innerHTML = myLibrary[i].title;
authorCell.innerHTML = myLibrary[i].author;
pagesCell.innerHTML = myLibrary[i].pages;

//add and wait for action for read/unread button
let changeBut = document.createElement("button");
changeBut.id = i;
changeBut.className = "btn btn-success";
wasReadCell.appendChild(changeBut);
let readStatus = "";
if (myLibrary[i].check == false) {
readStatus = "Yes";
} else {
readStatus = "No";
}
changeBut.innerText = readStatus;

changeBut.addEventListener("click", function () {
myLibrary[i].check = !myLibrary[i].check;
const table = document.getElementById("display");
const tbody = table.getElementsByTagName("tbody")[0];
Comment on lines +53 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: We could also use document.querySelector() to select the specific tbody element directly.


tbody.innerHTML = "";

myLibrary.forEach((book) => {
const row = document.createElement("tr");

const titleCell = document.createElement("td");
titleCell.textContent = book.title;
row.appendChild(titleCell);

const authorCell = document.createElement("td");
authorCell.textContent = book.author;
row.appendChild(authorCell);

const pagesCell = document.createElement("td");
pagesCell.textContent = book.pages;
row.appendChild(pagesCell);

const readCell = document.createElement("td");
const toggleReadBtn = document.createElement("button");
toggleReadBtn.className = "btn btn-sm btn-outline-primary";
toggleReadBtn.textContent = book.read ? "Yes" : "No";
toggleReadBtn.addEventListener("click", () => {
book.read = !book.read;
render();
});
readCell.appendChild(toggleReadBtn);
row.appendChild(readCell);

const deleteCell = document.createElement("td");
const deleteBtn = document.createElement("button");
deleteBtn.className = "btn btn-sm btn-danger";
deleteBtn.textContent = "Delete";
deleteBtn.addEventListener("click", () => {
const idx = myLibrary.indexOf(book);
if (idx === -1) return;
Comment on lines +89 to +90

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also obtain the index of the current book from the second parameter of the function pass to forEach() on line 66.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback. I have updated both files.


//add delete button to every row and render again
let delButton = document.createElement("button");
delBut.id = i + 5;
deleteCell.appendChild(delBut);
delBut.className = "btn btn-warning";
delBut.innerHTML = "Delete";
delBut.addEventListener("clicks", function () {
alert(`You've deleted title: ${myLibrary[i].title}`);
myLibrary.splice(i, 1);
myLibrary.splice(idx, 1);
render();
showToast(`Deleted "${book.title}"`);
});
deleteCell.appendChild(deleteBtn);
row.appendChild(deleteCell);

tbody.appendChild(row);
});
}

function showToast(message, duration = 2000) {
const toast = document.getElementById("toast");
if (!toast) {
console.log(message);
return;
}
toast.textContent = message;
toast.classList.add("show");
clearTimeout(showToast._timer);
showToast._timer = setTimeout(() => {
toast.classList.remove("show");
}, duration);
}

bookForm.addEventListener("submit", (event) => {
event.preventDefault();
addBook();
});

document.addEventListener("DOMContentLoaded", () => {
populateStorage();
render();
});
Comment on lines +117 to +125

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job in placing all code that runs once on page load at the end of the file. Doing so could make locating and managing them easier.

A better practice would be to keep them in a single function. For example, you could put it inside the page load callback or create a function named init() or setup() and call it once when the page loads.





Loading