-
-
Notifications
You must be signed in to change notification settings - Fork 226
Sheffield | 26-ITP-Jan | Mahmoud Shaabo | Sprint 2 | Book Library #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } | ||
|
Comment on lines
+48
to
50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can also just let |
||
| //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; | ||
| } | ||
|
cjyuan marked this conversation as resolved.
|
||
|
|
||
| // 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(); | ||
|
Comment on lines
+111
to
+113
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The alert message is shown before the book is actually deleted; the deletion only occurs after the alert dialog is dismissed. This introduces a risk that the operation may not complete (e.g., if the user closes the browser before dismissing the alert). In general, it’s better to display a confirmation message only after the associated operation has successfully completed. |
||
| }); | ||
|
cjyuan marked this conversation as resolved.
|
||
|
|
||
| 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)); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.