-
-
Notifications
You must be signed in to change notification settings - Fork 270
London | 26-ITP-May | Zadri Abdule | Sprint 2 | Book - library #520
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
e978b0b
3114094
e597733
66eeeac
8a41b5d
7f5941f
ff58ce9
804cd5a
97e0247
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,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> |
| 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) { | ||
| 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
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. Note: We could also use |
||
|
|
||
| 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
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. We could also obtain the index of the current
Author
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. 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
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. 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 |
||
|
|
||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
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
!pageRawis optional?Why not just check if
pagesis a whole number?