Compare commits
2 Commits
913ec3d05e
...
b52949f3e9
Author | SHA1 | Date | |
---|---|---|---|
b52949f3e9 | |||
4a02014bef |
195
frontend/files/app.js
Normal file
195
frontend/files/app.js
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
var sortState = {
|
||||||
|
sortBy: "sortAuthor",
|
||||||
|
sortOrder: "asc",
|
||||||
|
};
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
fetch("/api")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((books) => {
|
||||||
|
// prepare response
|
||||||
|
books.forEach(apiResponseParsing);
|
||||||
|
document.getElementById("search").addEventListener("input", (e) => {
|
||||||
|
renderTable(search(books, e.target.value));
|
||||||
|
});
|
||||||
|
renderTable(books);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function apiResponseParsing(book) {
|
||||||
|
book.sortTitle = titleCleaner(book.title);
|
||||||
|
if (!book["isbn-10"] && book["isbn-13"]) {
|
||||||
|
book["isbn-10"] = ISBNfromEAN(book["isbn-13"]);
|
||||||
|
}
|
||||||
|
if (!book.coverurl && book["isbn-10"]) {
|
||||||
|
book.coverurl =
|
||||||
|
`https://images-na.ssl-images-amazon.com/images/P/` +
|
||||||
|
book["isbn-10"] +
|
||||||
|
`.01.LZZ.jpg`;
|
||||||
|
}
|
||||||
|
return book;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTable(books, sortField) {
|
||||||
|
if (sortField) {
|
||||||
|
if (sortState.sortBy === sortField && sortState.sortOrder === "asc") {
|
||||||
|
sortState.sortOrder = "desc";
|
||||||
|
} else {
|
||||||
|
sortState.sortOrder = "asc";
|
||||||
|
}
|
||||||
|
sortState.sortBy = sortField;
|
||||||
|
}
|
||||||
|
books.sort((one, two) =>
|
||||||
|
(one[sortState.sortBy] + one["sortTitle"]).localeCompare(
|
||||||
|
two[sortState.sortBy] + two["sortTitle"]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (sortState.sortOrder === "desc") {
|
||||||
|
books.reverse();
|
||||||
|
}
|
||||||
|
books.forEach((e, i) => (e.rowNumber = i)); // re-key
|
||||||
|
|
||||||
|
// rendering
|
||||||
|
var bookElement = document.getElementById("books");
|
||||||
|
bookElement.innerHTML = TableTemplate(books);
|
||||||
|
|
||||||
|
// add listeners for selecting book to view
|
||||||
|
Array.from(bookElement.querySelectorAll("tbody tr"))
|
||||||
|
.slice(1) // remove header from Array
|
||||||
|
.forEach((row) => {
|
||||||
|
row.addEventListener("click", (e) => {
|
||||||
|
// add listener to swap current book
|
||||||
|
document.getElementById("current").innerHTML = BookTemplate(
|
||||||
|
books[e.currentTarget.id]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// add sorting callbacks
|
||||||
|
Array.from(bookElement.querySelectorAll("tbody tr th[data-sort-by]")).forEach(
|
||||||
|
(row) => {
|
||||||
|
row.addEventListener("click", function (e) {
|
||||||
|
renderTable(books, e.target.dataset.sortBy); // only add callback when there's a sortBy attribute
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// mark currently active column
|
||||||
|
bookElement
|
||||||
|
.querySelector("tbody tr th[data-sort-by=" + sortState.sortBy + "]")
|
||||||
|
.classList.add(sortState.sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
function search(books, searchBy) {
|
||||||
|
searchBy = searchCleaner(searchBy);
|
||||||
|
if (searchBy !== "") {
|
||||||
|
books = books.filter(
|
||||||
|
({ title, authors, genre, publisher, series, year }) => {
|
||||||
|
return Object.values({
|
||||||
|
title,
|
||||||
|
authors: authors.join(" "),
|
||||||
|
genre,
|
||||||
|
publisher,
|
||||||
|
series,
|
||||||
|
year,
|
||||||
|
}).find((field) => searchCleaner(field).indexOf(searchBy) !== -1);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return books;
|
||||||
|
}
|
||||||
|
|
||||||
|
function titleCleaner(title) {
|
||||||
|
return title
|
||||||
|
.replace('"', "")
|
||||||
|
.replace(":", "")
|
||||||
|
.replace(/^(An?|The)\s/i, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchCleaner(str) {
|
||||||
|
return str
|
||||||
|
.toLowerCase()
|
||||||
|
.replaceAll('"', "")
|
||||||
|
.replaceAll(":", "")
|
||||||
|
.replaceAll("'", "")
|
||||||
|
.replaceAll(" ", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function ISBNfromEAN(EAN) {
|
||||||
|
ISBN = EAN.slice(3, 12);
|
||||||
|
var checkdigit =
|
||||||
|
(11 - (ISBN.split("").reduce((s, n, k) => s + n * (10 - k), 0) % 11)) % 11;
|
||||||
|
return ISBN + (checkdigit === 10 ? "X" : checkdigit);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BookTemplate({
|
||||||
|
"isbn-13": isbn13,
|
||||||
|
authors,
|
||||||
|
coverurl,
|
||||||
|
description,
|
||||||
|
format,
|
||||||
|
notes,
|
||||||
|
onLoan,
|
||||||
|
publisher,
|
||||||
|
series,
|
||||||
|
signed,
|
||||||
|
title,
|
||||||
|
volume,
|
||||||
|
year,
|
||||||
|
}) {
|
||||||
|
return `${coverurl ? `<img src="${coverurl}"/>` : ""}
|
||||||
|
<h1 ${onLoan ? "class='onLoan' " : ""}>${title}</h1>
|
||||||
|
<h2>${authors}</h2>
|
||||||
|
<span>${isbn13}</span><br/>
|
||||||
|
<span>${publisher}, ${year}</span><br/>
|
||||||
|
${
|
||||||
|
series
|
||||||
|
? `<span>${series}${volume ? `, Volume ${volume}` : ""}</span><br/>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
${signed ? "<span>Signed by the author ✒</span><br/>" : ""}
|
||||||
|
<span>${format}</span>
|
||||||
|
${onLoan ? `<h2 class="onLoan">On loan to ${onLoan}</h2>` : ""}
|
||||||
|
<div class="description">
|
||||||
|
<p>${description}</p>
|
||||||
|
${notes ? `<span>Notes:</span><p>${notes}</p>` : ""}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableRowTemplate({
|
||||||
|
"isbn-13": isbn13,
|
||||||
|
authors,
|
||||||
|
onLoan,
|
||||||
|
publisher,
|
||||||
|
rowNumber,
|
||||||
|
signed,
|
||||||
|
title,
|
||||||
|
year,
|
||||||
|
}) {
|
||||||
|
return `<tr class="tRow ${onLoan ? "onLoan" : ""}" id="${rowNumber}">
|
||||||
|
<td class="title">
|
||||||
|
${title} ${
|
||||||
|
signed ? '<span class="signed" title="Signed by the author" >✒</span>︎' : ""
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="author">${authors}</td>
|
||||||
|
<td class="publisher">${publisher}</td>
|
||||||
|
<td class="year">${year}</td>
|
||||||
|
<td class="isbn">${isbn13}</td>
|
||||||
|
</tr>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableTemplate(books) {
|
||||||
|
return `<table class="bookTable">
|
||||||
|
<tr>
|
||||||
|
<th data-sort-by="sortTitle" class="tHeader title">Title</th>
|
||||||
|
<th data-sort-by="sortAuthor" class="tHeader author">Author</th>
|
||||||
|
<th data-sort-by="publisher" class="tHeader publisher">Publisher</th>
|
||||||
|
<th data-sort-by="year" class="tHeader year">Year</th>
|
||||||
|
<th class="tHeader isbn">ISBN</th>
|
||||||
|
</tr>${books.reduce((acc, book) => {
|
||||||
|
return acc.concat(TableRowTemplate(book));
|
||||||
|
}, "")} </table>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
init();
|
||||||
|
});
|
@ -13,208 +13,7 @@
|
|||||||
as="style"
|
as="style"
|
||||||
rel="stylesheet preload prefetch"
|
rel="stylesheet preload prefetch"
|
||||||
/>
|
/>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript" src="app.js"></script>
|
||||||
var sortState = {
|
|
||||||
sortBy: "sortAuthor",
|
|
||||||
sortOrder: "asc",
|
|
||||||
};
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
fetch("/api")
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((books) => {
|
|
||||||
// prepare response
|
|
||||||
books.forEach((book) => {
|
|
||||||
book.sortTitle = titleCleaner(book.title);
|
|
||||||
if (!book["isbn-10"] && book["isbn-13"]) {
|
|
||||||
book["isbn-10"] = ISBNfromEAN(book["isbn-13"]);
|
|
||||||
}
|
|
||||||
if (!book.coverurl && book["isbn-10"]) {
|
|
||||||
book.coverurl =
|
|
||||||
`https://images-na.ssl-images-amazon.com/images/P/` +
|
|
||||||
book["isbn-10"] +
|
|
||||||
`.01.LZZ.jpg`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return books;
|
|
||||||
})
|
|
||||||
.then((books) => {
|
|
||||||
document.getElementById("search").addEventListener("input", (e) => {
|
|
||||||
search(books, e.target.value);
|
|
||||||
});
|
|
||||||
return books;
|
|
||||||
})
|
|
||||||
.then(renderTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
function search(books, searchBy) {
|
|
||||||
searchBy = searchCleaner(searchBy);
|
|
||||||
if (searchBy !== "") {
|
|
||||||
books = books.filter(
|
|
||||||
({ title, authors, genre, publisher, series, year }) => {
|
|
||||||
return Object.values({
|
|
||||||
title,
|
|
||||||
authors: authors.join(" "),
|
|
||||||
genre,
|
|
||||||
publisher,
|
|
||||||
series,
|
|
||||||
year,
|
|
||||||
}).find((field) => searchCleaner(field).indexOf(searchBy) !== -1);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
renderTable(books);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTable(books, sortField) {
|
|
||||||
if (sortField) {
|
|
||||||
if (sortState.sortBy === sortField && sortState.sortOrder === "asc") {
|
|
||||||
sortState.sortOrder = "desc";
|
|
||||||
} else {
|
|
||||||
sortState.sortOrder = "asc";
|
|
||||||
}
|
|
||||||
sortState.sortBy = sortField;
|
|
||||||
}
|
|
||||||
books.sort((one, two) =>
|
|
||||||
(one[sortState.sortBy] + one["sortTitle"]).localeCompare(
|
|
||||||
two[sortState.sortBy] + two["sortTitle"]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (sortState.sortOrder === "desc") {
|
|
||||||
books.reverse();
|
|
||||||
}
|
|
||||||
books.forEach((e, i) => (e.rowNumber = i)); // re-key
|
|
||||||
|
|
||||||
// rendering
|
|
||||||
var bookElement = document.getElementById("books");
|
|
||||||
bookElement.innerHTML = TableTemplate(books);
|
|
||||||
|
|
||||||
// add listeners for selecting book to view
|
|
||||||
Array.from(bookElement.querySelectorAll("tbody tr"))
|
|
||||||
.slice(1) // remove header from Array
|
|
||||||
.forEach((row) => {
|
|
||||||
row.addEventListener("click", (e) => {
|
|
||||||
// add listener to swap current book
|
|
||||||
document.getElementById("current").innerHTML = BookTemplate(
|
|
||||||
books[e.currentTarget.id]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// add sorting callbacks
|
|
||||||
Array.from(
|
|
||||||
bookElement.querySelectorAll("tbody tr th[data-sort-by]")
|
|
||||||
).forEach((row) => {
|
|
||||||
row.addEventListener("click", function (e) {
|
|
||||||
renderTable(books, e.target.dataset.sortBy); // only add callback when there's a sortBy attribute
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// mark currently active column
|
|
||||||
bookElement
|
|
||||||
.querySelector("tbody tr th[data-sort-by=" + sortState.sortBy + "]")
|
|
||||||
.classList.add(sortState.sortOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
function titleCleaner(title) {
|
|
||||||
return title
|
|
||||||
.replace('"', "")
|
|
||||||
.replace(":", "")
|
|
||||||
.replace(/^(An?|The)\s/i, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchCleaner(str) {
|
|
||||||
return str
|
|
||||||
.toLowerCase()
|
|
||||||
.replaceAll('"', "")
|
|
||||||
.replaceAll(":", "")
|
|
||||||
.replaceAll("'", "")
|
|
||||||
.replaceAll(" ", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function ISBNfromEAN(EAN) {
|
|
||||||
ISBN = EAN.slice(3, 12);
|
|
||||||
var checkdigit =
|
|
||||||
(11 -
|
|
||||||
(ISBN.split("").reduce((s, n, k) => s + n * (10 - k), 0) % 11)) %
|
|
||||||
11;
|
|
||||||
return ISBN + (checkdigit === 10 ? "X" : checkdigit);
|
|
||||||
}
|
|
||||||
|
|
||||||
function BookTemplate({
|
|
||||||
"isbn-13": isbn13,
|
|
||||||
authors,
|
|
||||||
coverurl,
|
|
||||||
description,
|
|
||||||
format,
|
|
||||||
notes,
|
|
||||||
onLoan,
|
|
||||||
publisher,
|
|
||||||
series,
|
|
||||||
signed,
|
|
||||||
title,
|
|
||||||
volume,
|
|
||||||
year,
|
|
||||||
}) {
|
|
||||||
return `${coverurl ? `<img src="${coverurl}"/>` : ""}
|
|
||||||
<h1 ${onLoan ? "class='onLoan' " : ""}>${title}</h1>
|
|
||||||
<h2>${authors}</h2>
|
|
||||||
<span>${isbn13}</span><br/>
|
|
||||||
<span>${publisher}, ${year}</span><br/>
|
|
||||||
${
|
|
||||||
series
|
|
||||||
? `<span>${series}${volume ? `, Volume ${volume}` : ""}</span><br/>`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
${signed ? "<span>Signed by the author ✒</span><br/>" : ""}
|
|
||||||
<span>${format}</span>
|
|
||||||
${onLoan ? `<h2 class="onLoan">On loan to ${onLoan}</h2>` : ""}
|
|
||||||
<div class="description">
|
|
||||||
<p>${description}</p>
|
|
||||||
${notes ? `<span>Notes:</span><p>${notes}</p>` : ""}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TableRowTemplate({
|
|
||||||
"isbn-13": isbn13,
|
|
||||||
authors,
|
|
||||||
onLoan,
|
|
||||||
publisher,
|
|
||||||
rowNumber,
|
|
||||||
signed,
|
|
||||||
title,
|
|
||||||
year,
|
|
||||||
}) {
|
|
||||||
return `<tr class="tRow ${onLoan ? "onLoan" : ""}" id="${rowNumber}">
|
|
||||||
<td class="title">
|
|
||||||
${title} ${
|
|
||||||
signed
|
|
||||||
? '<span class="signed" title="Signed by the author" >✒</span>︎'
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td class="author">${authors}</td>
|
|
||||||
<td class="publisher">${publisher}</td>
|
|
||||||
<td class="year">${year}</td>
|
|
||||||
<td class="isbn">${isbn13}</td>
|
|
||||||
</tr>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TableTemplate(books) {
|
|
||||||
return `<table class="bookTable">
|
|
||||||
<tr>
|
|
||||||
<th data-sort-by="sortTitle" class="tHeader title">Title</th>
|
|
||||||
<th data-sort-by="sortAuthor" class="tHeader author">Author</th>
|
|
||||||
<th data-sort-by="publisher" class="tHeader publisher">Publisher</th>
|
|
||||||
<th data-sort-by="year" class="tHeader year">Year</th>
|
|
||||||
<th class="tHeader isbn">ISBN</th>
|
|
||||||
</tr>${books.reduce((acc, book) => {
|
|
||||||
return acc.concat(TableRowTemplate(book));
|
|
||||||
}, "")} </table>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
|
||||||
init();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
Loading…
Reference in New Issue
Block a user