diff --git a/debugging/book-library/index.html b/debugging/book-library/index.html index 23acfa71..2d5ff393 100644 --- a/debugging/book-library/index.html +++ b/debugging/book-library/index.html @@ -1,12 +1,10 @@ - - + + - - + + My Book Library + + @@ -29,22 +27,25 @@

Library

+ - + + + Library name="pages" required /> + + + +
@@ -80,15 +81,7 @@

Library

- - - - - - - - - + diff --git a/debugging/book-library/script.js b/debugging/book-library/script.js index 75ce6c1d..881ee634 100644 --- a/debugging/book-library/script.js +++ b/debugging/book-library/script.js @@ -1,103 +1,142 @@ -let myLibrary = []; +// const is safer than let here because myLibrary is never reassigned — +// we only call .push() and .splice() on it, which modify its contents, not the variable itself +const myLibrary = []; -window.addEventListener("load", function (e) { - populateStorage(); +// Declare all shared DOM references at the top of the file +// These are const because they always point to the same HTML elements +const titleInput = document.getElementById("title"); +const authorInput = document.getElementById("author"); +const pagesInput = document.getElementById("pages"); +const readCheckbox = document.getElementById("check"); + +// Wait for the page to fully load before running any code +window.addEventListener("load", function () { + addDefaultBooks(); render(); }); -function populateStorage() { - if (myLibrary.length == 0) { - let book1 = new Book("Robison Crusoe", "Daniel Defoe", "252", true); - let book2 = new Book( - "The Old Man and the Sea", - "Ernest Hemingway", - "127", - true +// Add two default books so the page is not empty on first load +function addDefaultBooks() { + if (myLibrary.length === 0) { + // Page count is stored as a NUMBER (252, not "252") — correct data type + myLibrary.push(new Book("Robinson Crusoe", "Daniel Defoe", 252, true)); + myLibrary.push( + new Book("The Old Man and the Sea", "Ernest Hemingway", 127, true) ); - myLibrary.push(book1); - myLibrary.push(book2); - render(); } } -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!"); - 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) { +// Book is a constructor function — a template for creating book objects +function Book(title, author, pages, hasBeenRead) { this.title = title; this.author = author; this.pages = pages; - this.check = check; + this.hasBeenRead = hasBeenRead; } -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); +// sanitiseText trims leading and trailing spaces from a string +// Prevents accepting inputs that contain only spaces as valid values +function sanitiseText(value) { + return value.trim(); +} + +// sanitisePages converts the pages input to a safe integer +// Rejects scientific notation like "3e2" which would display strangely +function sanitisePages(value) { + const trimmed = value.trim(); + // If the input contains "e" or "E", it is scientific notation — reject it + if (trimmed.toLowerCase().includes("e")) { + return NaN; } - //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; - render(); - }); - - //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); - render(); - }); + const parsed = Number(trimmed); + if (trimmed === "" || isNaN(parsed) || parsed <= 0) { + return NaN; + } + return Math.floor(parsed); +} + +// Called when the user clicks Submit +function addBook() { + // Step 1: Sanitise the inputs — clean them before checking or using them + const cleanTitle = sanitiseText(titleInput.value); + const cleanAuthor = sanitiseText(authorInput.value); + const cleanPages = sanitisePages(pagesInput.value); + + // Step 2: Validate — reject empty strings and invalid page numbers + if (cleanTitle === "" || cleanAuthor === "" || isNaN(cleanPages)) { + alert("Please fill all fields with valid values!"); + return; + } + + // Step 3: Create and store the new book using the cleaned values + const newBook = new Book( + cleanTitle, + cleanAuthor, + cleanPages, + readCheckbox.checked + ); + myLibrary.push(newBook); + + // Step 4: Clear the form fields after adding the book + titleInput.value = ""; + authorInput.value = ""; + pagesInput.value = ""; + readCheckbox.checked = false; + + render(); +} + +// createReadButton builds and returns a toggle button for a single book +function createReadButton(index) { + const button = document.createElement("button"); + button.className = "btn btn-success"; + button.textContent = myLibrary[index].hasBeenRead ? "Yes" : "No"; + + button.addEventListener("click", function () { + myLibrary[index].hasBeenRead = !myLibrary[index].hasBeenRead; + render(); + }); + + return button; +} + +// createDeleteButton builds and returns a delete button for a single book +function createDeleteButton(index) { + const button = document.createElement("button"); + button.className = "btn btn-warning"; + button.textContent = "Delete"; + + button.addEventListener("click", function () { + // Show an alert with the deleted book's title — restoring original behaviour + alert(`"${myLibrary[index].title}" has been deleted.`); + myLibrary.splice(index, 1); + render(); + }); + + return button; +} + +// render() redraws the entire table from the current myLibrary array +function render() { + const table = document.getElementById("display"); + + // Clear the tbody in one step — simpler and faster than deleting rows one by one + const tbody = table.querySelector("tbody"); + tbody.innerHTML = ""; + + for (let i = 0; i < myLibrary.length; i++) { + const row = tbody.insertRow(); + const titleCell = row.insertCell(0); + const authorCell = row.insertCell(1); + const pagesCell = row.insertCell(2); + const readCell = row.insertCell(3); + const deleteCell = row.insertCell(4); + + titleCell.textContent = myLibrary[i].title; + authorCell.textContent = myLibrary[i].author; + pagesCell.textContent = myLibrary[i].pages; + + readCell.appendChild(createReadButton(i)); + deleteCell.appendChild(createDeleteButton(i)); } }