This commit is contained in:
ak 2023-05-04 12:18:30 -07:00
commit cc803fcf76
6 changed files with 420 additions and 0 deletions

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# js-library
Proof-of-concept library/book manager written in ES6 JS

79
assets/css/style.css Normal file
View file

@ -0,0 +1,79 @@
.row {
--bs-gutter-x: 0;
row-gap: 1.5rem;
margin-top: 1.5rem;
margin-left: 1.5rem;
}
.col-sm-6 {
flex: 1;
width: auto;
}
.card, header {
box-shadow: 0.5px 0.5px 5px black;
}
header {
background-color: white;
}
.nav-link.active {
margin-right: 1.5rem;
}
.delete-button, .read-button {
border-radius: .25rem;
color: white;
border: 0;
padding: .5rem 1rem;
text-decoration: none;
display: flex;
align-items: center;
column-gap: .25rem;
height: 100%;
line-height: 16px;
justify-content: center;
}
.read-button {
background-color: green;
line-height: 24px;
}
.delete-button {
background-color: darkred;
}
.card-link {
margin-left: 0px;
}
.read-checkbox {
width: 24px;
height: 24px;
}
.buttons-flex {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
}
#newbookform {
right: 1.5rem;
max-width: 500px;
position: absolute;
z-index: 2;
padding: 1rem 1rem;
}
.form-control {
color: #6c757d !important;
}
#closebutton {
display: flex;
justify-content: flex-end;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z" /></svg>

After

Width:  |  Height:  |  Size: 187 B

307
assets/js/main.js Normal file
View file

@ -0,0 +1,307 @@
let myLibrary = [];
const row = document.querySelector(".row");
const newButton = document.querySelector("#newbutton");
// for setting multiple CSS attributes on an element
Element.prototype.setAttributes = function (attributes) {
Object.keys(attributes).forEach(key => this.setAttribute(key, attributes[key]));
}
// Book object constructor
class Book {
constructor (author, title, pages, read, comment) {
this.author = author;
this.title = title;
this.pages = pages;
this.read = read;
this.comment = comment;
}
}
// pushes completed book (from makeForm() input) into array and adds it to the DOM
function addBookToLibrary(book, readBool) {
// do stuff here
myLibrary.push(book);
// create our divs
// column container
const columnContainer = document.createElement('div');
columnContainer.setAttribute('class', "col-sm-6");
// card
const card = document.createElement('div');
card.setAttributes({class: 'card', style: "width: 18rem"});
// card body
const cardBody = document.createElement('div');
cardBody.setAttribute('class', "card-body");
// book title
const bookTitle = document.createElement('h5');
bookTitle.setAttribute('class', "card-title");
bookTitle.textContent = book.title;
// book author
const bookAuthor = document.createElement('h6');
bookAuthor.setAttribute('class', "card-subtitle mb-2 text-muted");
bookAuthor.textContent = book.author;
// number of pages in book
const bookPages = document.createElement('h6');
bookPages.setAttribute('class', "card-subtitle mb-2 text-muted book-pages");
bookPages.textContent = book.pages + " pages";
// comment, if any
const bookComment = document.createElement('p');
bookComment.setAttribute('class', "card-text");
bookComment.textContent = book.comment;
// create button container
const buttonsFlex = document.createElement('div');
buttonsFlex.setAttribute('class', "buttons-flex");
// delete button
const deleteButton = document.createElement('button');
deleteButton.setAttributes({class: "delete-button", onclick: "removeBookFromLibrary(this)"});
const deleteButtonText = document.createElement('div');
deleteButtonText.textContent = "Delete";
// svg stuff
const trashcanSVGContainer = document.createElementNS("http://www.w3.org/2000/svg","svg");
trashcanSVGContainer.setAttributes({style: "width:24px;height:24px", "viewBox": "0 0 24 24"});
const trashcanSVGPath = document.createElementNS("http://www.w3.org/2000/svg","path");
trashcanSVGPath.setAttributes({fill: "currentColor", d: "M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z"});
// read button
const readButton = document.createElement('button');
readButton.setAttributes({class: "read-button", onclick: "changeBookReadStatus(this)"});
const readButtonText = document.createElement('div');
readButtonText.textContent = "Read?";
// svg stuff
const checkboxSVGContainer = document.createElementNS("http://www.w3.org/2000/svg","svg");
checkboxSVGContainer.setAttributes({style: "width:24px;height:24px", "viewBox": "0 0 24 24"});
const checkboxSVGPath = document.createElementNS("http://www.w3.org/2000/svg","path");
if (readBool === true) {
checkboxSVGPath.setAttributes({fill: "currentColor", d: "M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z"});
}
else {
checkboxSVGPath.setAttributes({fill: "currentColor", d: "M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,5V19H5V5H19Z", class: "unchecked"});
}
// append to DOM
row.appendChild(columnContainer);
columnContainer.appendChild(card);
card.appendChild(cardBody);
cardBody.appendChild(bookTitle);
cardBody.appendChild(bookAuthor);
cardBody.appendChild(bookPages);
cardBody.appendChild(bookComment);
cardBody.appendChild(buttonsFlex);
buttonsFlex.appendChild(deleteButton);
deleteButton.appendChild(deleteButtonText);
deleteButton.appendChild(trashcanSVGContainer);
trashcanSVGContainer.appendChild(trashcanSVGPath);
buttonsFlex.appendChild(readButton);
readButton.appendChild(readButtonText);
readButton.appendChild(checkboxSVGContainer);
checkboxSVGContainer.appendChild(checkboxSVGPath);
}
// removes book from array and updates DOM
function removeBookFromLibrary(element) {
// go up a level
const buttonsFlex = element.parentElement;
const cardBody = buttonsFlex.parentElement;
// get the book name
const bookTitle = cardBody.firstChild.innerHTML;
// remove from array
const index = myLibrary.findIndex(item => item.title == bookTitle);
myLibrary.splice(index, 1);
// go up a few more levels
const card = cardBody.parentElement;
const columnContainer = card.parentElement;
// remove card
row.removeChild(columnContainer);
}
function SVGSwitcheroo (oldpath, container) {
const newSVGPath = document.createElementNS("http://www.w3.org/2000/svg","path");
if (oldpath.getAttribute('class') == "unchecked") {
newSVGPath.setAttributes({fill: "currentColor", d: "M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z"});
}
else {
newSVGPath.setAttributes({fill: "currentColor", d: "M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,5V19H5V5H19Z", class: "unchecked"});
}
container.removeChild(oldpath);
container.appendChild(newSVGPath);
}
// flip the checkmark in DOM and update object in array
function changeBookReadStatus(element) {
// DOM manipulation
// find the checkbox
const checkboxSVGContainer = element.lastChild;
const oldSVGPath = checkboxSVGContainer.firstChild;
// svg switcheroo
SVGSwitcheroo(oldSVGPath, checkboxSVGContainer);
// update read status in internal array
// go up a couple elements
const buttonsFlex = element.parentElement;
const cardBody = buttonsFlex.parentElement;
// get the book name
const bookTitle = cardBody.firstChild.innerHTML;
// find it in array
const index = myLibrary.findIndex(item => item.title == bookTitle);
// toggle read boolean
myLibrary[index].read = myLibrary[index].read ? myLibrary[index].read = false : myLibrary[index].read = true;
}
function makeForm() {
// create our divs
// form body
const newBookForm = document.createElement('form');
newBookForm.setAttributes({class: "card", id: "newbookform"});
// close button (SVG)
const closeButton = document.createElement('div');
closeButton.setAttributes({class: "mb-3", id: "closebutton"});
const closeButtonContainer = document.createElementNS("http://www.w3.org/2000/svg","svg");
closeButtonContainer.setAttributes({style: "width:24px;height:24px", "viewBox": "0 0 24 24"});
const closeButtonSVGPath = document.createElementNS("http://www.w3.org/2000/svg","path");
closeButtonSVGPath.setAttributes({fill: "#6c757d", d: "M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"});
// close the form if button is clicked
function closeForm() {
newBookForm.parentElement.removeChild(newBookForm);
}
closeButtonContainer.addEventListener('click', closeForm);
// set properties for close button
// make divs and input fields
// name field
const bookNameDiv = document.createElement('div');
bookNameDiv.setAttribute('class', "mb-3");
const bookNameInput = document.createElement('input');
bookNameInput.setAttributes({type: "text", class: "form-control", value: "Book Name"});
// author field
const bookAuthorDiv = document.createElement('div');
bookAuthorDiv.setAttribute('class', "mb-3");
const bookAuthorInput = document.createElement('input');
bookAuthorInput.setAttributes({type: "text", class: "form-control", value: "Book Author"});
// page count field
const pageCountDiv = document.createElement('div');
pageCountDiv.setAttribute('class', "mb-3");
const pageCountInput = document.createElement('input');
pageCountInput.setAttributes({type: "text", class: "form-control", value: "Number of Pages"});
// quick number validation because not all browsers support number input fields
function numberValidation (e) {
const numericValue = this.value.replace(new RegExp(/[^\d]/,'ig'), "");
this.value = numericValue;
}
pageCountInput.addEventListener('input', numberValidation);
// comments field
const commentsDiv = document.createElement('div');
commentsDiv.setAttribute('class', "mb-3");
const commentsInput = document.createElement('textarea');
commentsInput.setAttributes({rows: "3", class: "form-control"});
commentsInput.textContent = "Additional Comments";
// create button container
const buttonsFlex = document.createElement('div');
buttonsFlex.setAttribute('class', "buttons-flex");
// read button
const readButton = document.createElement('button');
const readButtonText = document.createElement('div');
readButtonText.textContent = "Read?";
// svg stuff
const checkboxSVGContainer = document.createElementNS("http://www.w3.org/2000/svg","svg");
checkboxSVGContainer.setAttributes({style: "width:24px;height:24px", "viewBox": "0 0 24 24"});
const checkboxSVGPath = document.createElementNS("http://www.w3.org/2000/svg","path");
checkboxSVGPath.setAttributes({fill: "currentColor", d: "M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,5V19H5V5H19Z", class: "unchecked"});
// assign read button to switch SVGs based on click
readButton.setAttribute('class', "read-button");
readButton.addEventListener('click', function(event) {
event.preventDefault();
SVGSwitcheroo(readButton.lastChild.firstChild, readButton.lastChild);
});
// eye candy
// book name
bookNameInput.onfocus = function () {
bookNameInput.setAttributes({value: "", style: "color: black !important"});
}
bookNameInput.onblur = function () {
bookNameInput.setAttributes({value: "Book Name", style: "color: #6c757d !important;"});
}
// book author
bookAuthorInput.onfocus = function () {
bookAuthorInput.setAttributes({value: "", style: "color: black !important"});
}
bookAuthorInput.onblur = function () {
bookAuthorInput.setAttributes({value: "Book Author", style: "color: #6c757d !important;"});
}
// pages
pageCountInput.onfocus = function () {
pageCountInput.setAttributes({value: "", style: "color: black !important"});
}
pageCountInput.onblur = function () {
pageCountInput.setAttributes({value: "Number of Pages", style: "color: #6c757d !important;"});
}
// comments box
commentsInput.onfocus = function () {
commentsInput.textContent = "";
commentsInput.setAttribute('style', "color: black !important");
}
commentsInput.onblur = function () {
commentsInput.textContent = "Additional Comments";
commentsInput.setAttribute('style', "color: #6c757d !important;");
}
// submit button
const submitButton = document.createElement('button');
submitButton.setAttributes({type: "submit", class: "btn btn-primary"});
submitButton.textContent = "Submit";
// prevent default and make new Book with submitted values, then close form
submitButton.addEventListener('click', function(event) {
event.preventDefault();
// some form validation
if (bookNameInput.value === "" || bookNameInput.value === "Book Name") {
alert("Please enter a book name");
}
if (bookAuthorInput.value === "" || bookAuthorInput.value === "Book Author") {
alert("Please enter an author");
}
if (pageCountInput.value === "" || pageCountInput.value === "Number of Pages") {
alert("Please enter a page count");
}
else {
if (commentsInput.value === "" || commentsInput.value === "Additional Comments") {
if (readButton.lastChild.firstChild.getAttribute("class") === "unchecked") {
const book = new Book(bookAuthorInput.value, bookNameInput.value, pageCountInput.value, false, "");
addBookToLibrary(book, false);
}
else {
const book = new Book(bookAuthorInput.value, bookNameInput.value, pageCountInput.value, true, "");
addBookToLibrary(book, true);
}
}
else {
if (readButton.lastChild.firstChild.getAttribute("class") === "unchecked") {
const book = new Book(bookAuthorInput.value, bookNameInput.value, pageCountInput.value, false, commentsInput.value);
addBookToLibrary(book, false);
}
else {
const book = new Book(bookAuthorInput.value, bookNameInput.value, pageCountInput.value, true, commentsInput.value);
addBookToLibrary(book, true);
}
}
closeForm();
}
});
//append to DOM
const pageBody = row.parentElement;
pageBody.insertBefore(newBookForm, row);
newBookForm.appendChild(closeButton);
closeButton.appendChild(closeButtonContainer);
closeButtonContainer.appendChild(closeButtonSVGPath);
newBookForm.appendChild(bookNameDiv);
bookNameDiv.appendChild(bookNameInput);
newBookForm.appendChild(bookAuthorDiv);
bookAuthorDiv.appendChild(bookAuthorInput);
newBookForm.appendChild(pageCountDiv);
pageCountDiv.appendChild(pageCountInput);
newBookForm.appendChild(commentsDiv);
commentsDiv.appendChild(commentsInput);
newBookForm.appendChild(buttonsFlex);
buttonsFlex.appendChild(readButton);
readButton.appendChild(readButtonText);
readButton.appendChild(checkboxSVGContainer);
checkboxSVGContainer.appendChild(checkboxSVGPath);
buttonsFlex.appendChild(submitButton);
}
// calls makeForm() to bring up form for user input of new book
newButton.addEventListener('click', makeForm);

30
index.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="Description" content="Enter your description here"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link rel="stylesheet" href="assets/css/style.css">
<title>Title</title>
</head>
<body style="background-color: darkgray;">
<header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
<svg class="bi me-2" width="40" height="32"><use xlink:href="#bootstrap"/></svg>
<span class="fs-4">Books</span>
</a>
<ul class="nav nav-pills">
<li class="nav-item">
<button class="nav-link active" id="newbutton">New</button></li>
</ul>
</header>
<div class="row">
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.9.2/umd/popper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.0/js/bootstrap.min.js"></script>
<script src="assets/js/main.js"></script>
</body>
</html>