From 1582ca09570167113983790acebb7bb563c86627 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Wed, 17 Dec 2025 06:07:38 +0000 Subject: [PATCH 1/6] fix(book-library): resolve all functional bugs and update the html structure and titles --- debugging/book-library/index.html | 55 ++++++------ debugging/book-library/script.js | 116 +++++++++++++------------- debugging/book-library/style.css | 133 +++++++++++++++++++++++++++--- 3 files changed, 204 insertions(+), 100 deletions(-) diff --git a/debugging/book-library/index.html b/debugging/book-library/index.html index 23acfa71..ebf1e4f8 100644 --- a/debugging/book-library/index.html +++ b/debugging/book-library/index.html @@ -1,12 +1,9 @@ - + - - + Book Library + + @@ -19,7 +16,7 @@
-

Library

+

My Book Library

Add books to your virtual library

@@ -31,7 +28,7 @@

Library

Library /> Library
- - - - - - - - - - - - - - - - - - - -
TitleAuthorNumber of PagesRead
+
+ + + + + + + + + + + +
TitleAuthorNumber of PagesRead
+
diff --git a/debugging/book-library/script.js b/debugging/book-library/script.js index 75ce6c1d..5b1cabea 100644 --- a/debugging/book-library/script.js +++ b/debugging/book-library/script.js @@ -1,46 +1,47 @@ +const titleInput = document.getElementById("title"); +const authorInput = document.getElementById("author"); +const pagesInput = document.getElementById("pages"); +const readCheckbox = document.getElementById("check"); +const table = document.getElementById("display"); +const submitBtn = document.getElementById("submit-book-btn"); + let myLibrary = []; -window.addEventListener("load", function (e) { +window.addEventListener("load", function () { populateStorage(); render(); }); +submitBtn.addEventListener("click", addBook); + 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 - ); - myLibrary.push(book1); - myLibrary.push(book2); - render(); + const storedLibrary = localStorage.getItem("myLibrary"); + if (storedLibrary) { + myLibrary = JSON.parse(storedLibrary); } } -const title = document.getElementById("title"); -const author = document.getElementById("author"); -const pages = document.getElementById("pages"); -const check = document.getElementById("check"); +function saveStorage() { + localStorage.setItem("myLibrary", JSON.stringify(myLibrary)); +} + +// Validates input and adds new book +function addBook(e) { + if (e) e.preventDefault(); -//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 == "" - ) { + if (!titleInput.value || !authorInput.value || !pagesInput.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(); } + let book = new Book( + titleInput.value, + authorInput.value, + pagesInput.value, + readCheckbox.checked + ); + myLibrary.push(book); + saveStorage(); + render(); } function Book(title, author, pages, check) { @@ -51,52 +52,51 @@ function Book(title, author, pages, check) { } 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 + // Clears table body efficiently + const tbody = table.querySelector("tbody"); + tbody.innerHTML = ""; + + // Inserts updated row and cells let length = myLibrary.length; for (let i = 0; i < length; i++) { - let row = table.insertRow(1); + // Insert at the end of the table + let row = tbody.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); + // Uses textContent to prevent XSS + titleCell.textContent = myLibrary[i].title; + authorCell.textContent = myLibrary[i].author; + pagesCell.textContent = myLibrary[i].pages; + + // Toggles read status + let readBtn = document.createElement("button"); + wasReadCell.appendChild(readBtn); + let readStatus = ""; - if (myLibrary[i].check == false) { - readStatus = "Yes"; - } else { + if (myLibrary[i].check === false) { readStatus = "No"; + } else { + readStatus = "Yes"; } - changeBut.innerText = readStatus; + readBtn.textContent = readStatus; - changeBut.addEventListener("click", function () { + readBtn.addEventListener("click", function () { myLibrary[i].check = !myLibrary[i].check; + saveStorage(); 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}`); + // Deletes book + let deleteBtn = document.createElement("button"); + deleteCell.appendChild(deleteBtn); + deleteBtn.textContent = "Delete"; + deleteBtn.addEventListener("click", function () { myLibrary.splice(i, 1); + saveStorage(); render(); }); } diff --git a/debugging/book-library/style.css b/debugging/book-library/style.css index 302950cb..5473e93d 100644 --- a/debugging/book-library/style.css +++ b/debugging/book-library/style.css @@ -1,19 +1,132 @@ -.form-group { - width: 400px; - height: 300px; - align-self: left; - padding-left: 20px; +/* Google Fonts */ +@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"); + +body { + font-family: "Roboto", sans-serif; + background-color: #f8f9fa; + color: #333; } +.jumbotron { + background-color: #343a40; + color: white; + padding: 2rem 1rem; + margin-bottom: 2rem; + border-radius: 0; +} + +.jumbotron h1 { + font-weight: 700; +} + +/* Button Styling */ .btn { - display: block; + border-radius: 20px; + padding: 8px 20px; + font-weight: 500; + transition: all 0.3s ease; +} + +.btn-info { + background-color: #17a2b8; + border-color: #17a2b8; + color: white; +} + +.btn-info:hover { + background-color: #138496; + border-color: #117a8b; + transform: translateY(-2px); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +/* Form Styling */ +#demo.collapse.show { + display: flex; + justify-content: center; + margin-bottom: 20px; +} + +.form-group { + background: white; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + width: 100%; + max-width: 500px; + margin: 0 auto; +} + +.form-control { + border-radius: 5px; + border: 1px solid #ced4da; + padding: 10px; +} + +.form-control:focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.25); + border-color: #17a2b8; +} + +label { + font-weight: 500; + margin-top: 10px; } .form-check-label { - padding-left: 20px; - margin: 5px 0px 5px 0px; + display: flex; + align-items: center; + margin: 15px 0; + cursor: pointer; } -button.btn-info { - margin: 20px; +.form-check-input { + margin-right: 10px; + margin-top: 0; +} + +/* Table Styling */ +.table-responsive { + margin-top: 20px; + padding: 0 15px; +} + +.table { + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + +.thead-dark th { + background-color: #343a40; + border-color: #454d55; + color: white; +} + +.table td, +.table th { + vertical-align: middle; +} + +/* Responsive Adjustments */ +@media (max-width: 576px) { + .jumbotron { + padding: 1.5rem 1rem; + } + + .btn { + width: 100%; + margin-bottom: 10px; + } + + .form-group { + padding: 1rem; + } + + button.btn-info { + margin: 10px auto; + display: block; + width: 90%; + } } From 998034a07d6fb32d2e239b7b1c90ef389f7e0482 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Thu, 18 Dec 2025 03:00:26 +0000 Subject: [PATCH 2/6] fix(book-library): revert out of scope style changes --- debugging/book-library/style.css | 133 +++---------------------------- 1 file changed, 10 insertions(+), 123 deletions(-) diff --git a/debugging/book-library/style.css b/debugging/book-library/style.css index 5473e93d..302950cb 100644 --- a/debugging/book-library/style.css +++ b/debugging/book-library/style.css @@ -1,132 +1,19 @@ -/* Google Fonts */ -@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"); - -body { - font-family: "Roboto", sans-serif; - background-color: #f8f9fa; - color: #333; -} - -.jumbotron { - background-color: #343a40; - color: white; - padding: 2rem 1rem; - margin-bottom: 2rem; - border-radius: 0; -} - -.jumbotron h1 { - font-weight: 700; -} - -/* Button Styling */ -.btn { - border-radius: 20px; - padding: 8px 20px; - font-weight: 500; - transition: all 0.3s ease; -} - -.btn-info { - background-color: #17a2b8; - border-color: #17a2b8; - color: white; -} - -.btn-info:hover { - background-color: #138496; - border-color: #117a8b; - transform: translateY(-2px); - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); -} - -/* Form Styling */ -#demo.collapse.show { - display: flex; - justify-content: center; - margin-bottom: 20px; -} - .form-group { - background: white; - padding: 2rem; - border-radius: 8px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); - width: 100%; - max-width: 500px; - margin: 0 auto; -} - -.form-control { - border-radius: 5px; - border: 1px solid #ced4da; - padding: 10px; -} - -.form-control:focus { - box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.25); - border-color: #17a2b8; + width: 400px; + height: 300px; + align-self: left; + padding-left: 20px; } -label { - font-weight: 500; - margin-top: 10px; +.btn { + display: block; } .form-check-label { - display: flex; - align-items: center; - margin: 15px 0; - cursor: pointer; + padding-left: 20px; + margin: 5px 0px 5px 0px; } -.form-check-input { - margin-right: 10px; - margin-top: 0; -} - -/* Table Styling */ -.table-responsive { - margin-top: 20px; - padding: 0 15px; -} - -.table { - background: white; - border-radius: 8px; - overflow: hidden; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); -} - -.thead-dark th { - background-color: #343a40; - border-color: #454d55; - color: white; -} - -.table td, -.table th { - vertical-align: middle; -} - -/* Responsive Adjustments */ -@media (max-width: 576px) { - .jumbotron { - padding: 1.5rem 1rem; - } - - .btn { - width: 100%; - margin-bottom: 10px; - } - - .form-group { - padding: 1rem; - } - - button.btn-info { - margin: 10px auto; - display: block; - width: 90%; - } +button.btn-info { + margin: 20px; } From c83cbbbdd552bda376ae211600bfab9e21dabfbb Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Fri, 19 Dec 2025 06:04:43 +0000 Subject: [PATCH 3/6] refactor: standardises variable scoping and simplifies the rendering logic --- debugging/book-library/script.js | 70 +++++++++++++++----------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/debugging/book-library/script.js b/debugging/book-library/script.js index 5b1cabea..8cabe959 100644 --- a/debugging/book-library/script.js +++ b/debugging/book-library/script.js @@ -7,7 +7,7 @@ const submitBtn = document.getElementById("submit-book-btn"); let myLibrary = []; -window.addEventListener("load", function () { +window.addEventListener("load", () => { populateStorage(); render(); }); @@ -25,7 +25,7 @@ function saveStorage() { localStorage.setItem("myLibrary", JSON.stringify(myLibrary)); } -// Validates input and adds new book +// Trims input values and prevents empty submissions function addBook(e) { if (e) e.preventDefault(); @@ -33,7 +33,7 @@ function addBook(e) { alert("Please fill all fields!"); return false; } - let book = new Book( + const book = new Book( titleInput.value, authorInput.value, pagesInput.value, @@ -52,52 +52,48 @@ function Book(title, author, pages, check) { } function render() { - // Clears table body efficiently const tbody = table.querySelector("tbody"); tbody.innerHTML = ""; - // Inserts updated row and cells - let length = myLibrary.length; - for (let i = 0; i < length; i++) { - // Insert at the end of the table - let row = tbody.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); - - // Uses textContent to prevent XSS - titleCell.textContent = myLibrary[i].title; - authorCell.textContent = myLibrary[i].author; - pagesCell.textContent = myLibrary[i].pages; - - // Toggles read status - let readBtn = document.createElement("button"); - wasReadCell.appendChild(readBtn); + myLibrary.forEach((book, i) => { + const row = tbody.insertRow(-1); + + // Inserts new cells into the table row + const titleCell = row.insertCell(0); + const authorCell = row.insertCell(1); + const pagesCell = row.insertCell(2); + const wasReadCell = row.insertCell(3); + const deleteCell = row.insertCell(4); - let readStatus = ""; - if (myLibrary[i].check === false) { - readStatus = "No"; - } else { - readStatus = "Yes"; - } - readBtn.textContent = readStatus; + // Prevents XSS by inserting data as textContent + titleCell.textContent = book.title; + authorCell.textContent = book.author; + pagesCell.textContent = book.pages; + + // Toggles the read status with a ternary operator + const readBtn = document.createElement("button"); + readBtn.className = book.check + ? "btn btn-success" + : "btn btn-outline-secondary"; + readBtn.textContent = book.check ? "Yes" : "No"; + wasReadCell.appendChild(readBtn); - readBtn.addEventListener("click", function () { - myLibrary[i].check = !myLibrary[i].check; + readBtn.addEventListener("click", () => { + book.check = !book.check; saveStorage(); render(); }); - // Deletes book - let deleteBtn = document.createElement("button"); - deleteCell.appendChild(deleteBtn); + // Deletes the book from the library + const deleteBtn = document.createElement("button"); + deleteBtn.className = "btn btn-danger btn-sm"; deleteBtn.textContent = "Delete"; - deleteBtn.addEventListener("click", function () { + deleteCell.appendChild(deleteBtn); + + deleteBtn.addEventListener("click", () => { myLibrary.splice(i, 1); saveStorage(); render(); }); - } + }); } From c9684350fea3ad06c843603fb245f79060519dde Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Fri, 19 Dec 2025 06:08:09 +0000 Subject: [PATCH 4/6] feat: sanitises user inputs and implements stricter guard clauses --- debugging/book-library/script.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/debugging/book-library/script.js b/debugging/book-library/script.js index 8cabe959..ec1e5fe1 100644 --- a/debugging/book-library/script.js +++ b/debugging/book-library/script.js @@ -29,16 +29,16 @@ function saveStorage() { function addBook(e) { if (e) e.preventDefault(); - if (!titleInput.value || !authorInput.value || !pagesInput.value) { + // Trims input whitespace to sanitise entries + const title = titleInput.value.trim(); + const author = authorInput.value.trim(); + const pages = pagesInput.value.trim(); + + if (!title || !author || !pages) { alert("Please fill all fields!"); - return false; + return; } - const book = new Book( - titleInput.value, - authorInput.value, - pagesInput.value, - readCheckbox.checked - ); + const book = new Book(title, author, pages, readCheckbox.checked); myLibrary.push(book); saveStorage(); render(); From 8b302b90b56effd5498670711f89559b09a2c1da Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Fri, 19 Dec 2025 06:09:38 +0000 Subject: [PATCH 5/6] feat: adds input field resets and descriptive deletion notifications --- debugging/book-library/script.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/debugging/book-library/script.js b/debugging/book-library/script.js index ec1e5fe1..e4cd87e9 100644 --- a/debugging/book-library/script.js +++ b/debugging/book-library/script.js @@ -42,6 +42,12 @@ function addBook(e) { myLibrary.push(book); saveStorage(); render(); + + // Resets the entry form after adding a book + titleInput.value = ""; + authorInput.value = ""; + pagesInput.value = ""; + readCheckbox.checked = false; } function Book(title, author, pages, check) { @@ -84,16 +90,18 @@ function render() { render(); }); - // Deletes the book from the library + // Deletes the book and notifies the user const deleteBtn = document.createElement("button"); deleteBtn.className = "btn btn-danger btn-sm"; deleteBtn.textContent = "Delete"; deleteCell.appendChild(deleteBtn); deleteBtn.addEventListener("click", () => { + const deletedTitle = book.title; myLibrary.splice(i, 1); saveStorage(); render(); + alert(`Success: "${deletedTitle}" has been removed.`); }); }); } From c8e3a34dd6dc0526dd9e4c923290703e102d7b05 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sun, 21 Dec 2025 22:14:38 +0000 Subject: [PATCH 6/6] fix(book-library): initialise storage and validate page input --- debugging/book-library/script.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/debugging/book-library/script.js b/debugging/book-library/script.js index e4cd87e9..91c67c8b 100644 --- a/debugging/book-library/script.js +++ b/debugging/book-library/script.js @@ -14,10 +14,26 @@ window.addEventListener("load", () => { submitBtn.addEventListener("click", addBook); +// Initialises data and seeds default books if storage is empty function populateStorage() { const storedLibrary = localStorage.getItem("myLibrary"); + if (storedLibrary) { - myLibrary = JSON.parse(storedLibrary); + const rawData = JSON.parse(storedLibrary); + // Rehydrates plain data back into Book objects + myLibrary = rawData.map( + (data) => + new Book(data.title, data.author, Number(data.pages), data.check) + ); + } else { + // Seeds data for new users + myLibrary = [ + new Book("The Hobbit", "J.R.R. Tolkien", 295, false), + new Book("1984", "George Orwell", 328, true), + new Book("Robinson Crusoe", "Daniel Defoe", 252, true), + new Book("The Old Man and the Sea", "Ernest Hemingway", 127, false), + ]; + saveStorage(); } } @@ -32,10 +48,11 @@ function addBook(e) { // Trims input whitespace to sanitise entries const title = titleInput.value.trim(); const author = authorInput.value.trim(); - const pages = pagesInput.value.trim(); + const pages = Number(pagesInput.value); - if (!title || !author || !pages) { - alert("Please fill all fields!"); + // Validates input: checks for empty strings, non-numbers, or negative values + if (!title || !author || isNaN(pages) || pages <= 0) { + alert("Please enter valid book details. Pages must be a positive number."); return; } const book = new Book(title, author, pages, readCheckbox.checked);