diff --git a/debugging/book-library/index.html b/debugging/book-library/index.html index 23acfa71..d83c6da2 100644 --- a/debugging/book-library/index.html +++ b/debugging/book-library/index.html @@ -1,96 +1,68 @@ - - - - - - - - - - + - -
-

Library

-

Add books to your virtual library

-
+ + Book Library + + + + + + + - + + +
+
+
+

Library

+

Add books to your virtual library

+
+ + -
-
- - - - - - - - +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
-
+
+
+ +
- - - - - - - - - - - - - - - - - - - -
TitleAuthorNumber of PagesRead
+ + - - - + \ No newline at end of file diff --git a/debugging/book-library/script.js b/debugging/book-library/script.js index 75ce6c1d..764c83ee 100644 --- a/debugging/book-library/script.js +++ b/debugging/book-library/script.js @@ -1,3 +1,21 @@ +class Book { + constructor(title, author, pages, check) { + this.title = title; + this.author = author; + this.pages = pages; + this.check = check; + } +} + +const form = document.getElementById("add-book"); +const submitBtn = document.getElementById("submit-button"); +const title = document.getElementById("title"); +const author = document.getElementById("author"); +const pages = document.getElementById("pages"); +const check = document.getElementById("check"); +const displayBooks = document.querySelector(".display-books"); +const demo = document.getElementById("demo"); + let myLibrary = []; window.addEventListener("load", function (e) { @@ -6,98 +24,150 @@ window.addEventListener("load", function (e) { }); 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 + if (myLibrary.length === 0) { + myLibrary.push(new Book("Robison 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"); +// form submission event listeners - add new books +form.addEventListener("submit", (event) => { + event.preventDefault(); + processEntries(); +}); + +// book field validations +const fields = { + title: { + input: title, + error: document.querySelector(".error-title"), + message: "Title is required", + }, + author: { + input: author, + error: document.querySelector(".error-author"), + message: "Author is required", + }, + pages: { + input: pages, + error: document.querySelector(".error-pages"), + message: "Enter page numbers (whole numbers only)", + }, +}; + +Object.values(fields).forEach(({ input, error }) => { + input.addEventListener("input", () => { + if (input.value.trim() !== "") { + error.textContent = ""; + } + }); +}); + +// check book entry is valid before adding to library +function processEntries() { + let isValid = true; -//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); + Object.entries(fields).forEach(([key, field]) => { + const value = field.input.value.trim(); + + if (value === "") { + field.error.textContent = field.message; + isValid = false; + } else { + field.error.textContent = ""; + } + }); + + if (isValid) { + let book = new Book(title.value, author.value, pages.value, check.checked); + myLibrary.push(book); + demo.classList.remove("show"); render(); + form.reset(); } } -function Book(title, author, pages, check) { - this.title = title; - this.author = author; - this.pages = pages; - this.check = check; +const createBookRow = (book, index) => { + const { title, author, pages, check: hasBeenRead } = book; + const row = document.createElement("tr"); + + const titleCell = document.createElement("td"); + const authorCell = document.createElement("td"); + const pagesCell = document.createElement("td"); + const readCell = document.createElement("td"); + const deleteCell = document.createElement("td"); + + titleCell.textContent = title; + authorCell.textContent = author; + pagesCell.textContent = pages; + + const readButton = document.createElement("button"); + readButton.className = `btn btn-sm ${hasBeenRead ? "btn-success" : "btn-secondary"}`; + readButton.textContent = hasBeenRead ? "Yes" : "No"; + readButton.style.color = "black"; + readCell.appendChild(readButton); + + const deleteButton = document.createElement("button"); + deleteButton.className = "btn btn-sm btn-warning"; + deleteButton.textContent = "Delete"; + deleteCell.appendChild(deleteButton); + + readButton.addEventListener("click", () => toggleReadStatus(index)); + + deleteButton.addEventListener("click", () => deleteBook(index)); + + row.append(titleCell, authorCell, pagesCell, readCell, deleteCell); + return row; +}; + +function toggleReadStatus(index) { + myLibrary[index].check = !myLibrary[index].check; + render(); +} + +function deleteBook(index) { + bookDeleteMessage(`You've deleted title: ${myLibrary[index].title}`); + myLibrary.splice(index, 1); + render(); +} + +function bookDeleteMessage(message, duration = 3000) { + const existingMessage = document.querySelector(".book-delete-message"); + if (existingMessage) existingMessage.remove(); + + const messageBox = document.createElement("div"); + messageBox.className = "book-delete-message"; + messageBox.textContent = message; + document.body.appendChild(messageBox); + + setTimeout(() => { + messageBox.remove(); + }, duration); } 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; - 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(); - }); - } + displayBooks.innerHTML = ""; + + const table = document.createElement("table"); + table.className = "table table-striped"; + + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + ["Title", "Author", "Pages", "Read", "Delete"].forEach((label) => { + const th = document.createElement("th"); + th.textContent = label; + headerRow.appendChild(th); + }); + thead.appendChild(headerRow); + table.appendChild(thead); + + const tbody = document.createElement("tbody"); + const bookRows = myLibrary.map((book, index) => createBookRow(book, index)); + tbody.append(...bookRows); + table.appendChild(tbody); + + displayBooks.appendChild(table); } diff --git a/debugging/book-library/style.css b/debugging/book-library/style.css index 302950cb..f98aad67 100644 --- a/debugging/book-library/style.css +++ b/debugging/book-library/style.css @@ -1,8 +1,9 @@ .form-group { width: 400px; - height: 300px; + min-height: 360px; align-self: left; padding-left: 20px; + padding-bottom: 20px; } .btn { @@ -16,4 +17,78 @@ button.btn-info { margin: 20px; + color: black; +} + +.display-books table { + width: 100%; + margin-top: 20px; + border-collapse: collapse; +} + +.display-books th, +.display-books td { + padding: 0.75rem; + border: 1px solid #dee2e6; + text-align: left; + vertical-align: middle; +} + +.display-books thead th { + background-color: #343a40; + color: white; +} + +.display-books tbody tr:nth-child(even) { + background-color: #f8f9fa; +} + +.display-books .book-delete { + color: #dc3545; + font-weight: 600; +} + +.display-books .book-read { + font-weight: 600; +} + +.display-books .book-read .btn, +.display-books .book-delete .btn { + width: 100%; + max-width: 90px; +} + +.form-control.is-invalid { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.invalid-feedback { + display: none; + color: #dc3545; + font-size: 0.875rem; + margin-bottom: 0.5rem; +} + +.invalid-feedback.show { + display: block; +} + +/* error messages */ +.error { + color: red; + font-size: 0.8rem; +} + +.book-delete-message { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #ff7f7f; + color: white; + padding: 1rem 2rem; + border-radius: 6px; + z-index: 1000; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); }