Merge branch '32-add-paging-functionality' into '22-integrate-api-and-frontend'
upvoting suppression by local storage now works, there is a bug however with... See merge request padas/24ss-5430-web-and-data-eng/gruppe-3/datadash!33
This commit is contained in:
commit
f60ce5babf
@ -1,25 +1,31 @@
|
|||||||
import {searchBarTimeout, searchSection} from "./main.js"
|
import {searchBarTimeout, searchSection, lastQuery} from "./main.js"
|
||||||
import Dataset from "./dataset.js"
|
import Dataset from "./dataset.js"
|
||||||
|
// TODO consider renaming this to "searchUtility.js"
|
||||||
export function fetchQuery(fetchString) {
|
export function fetchQuery(fetchString, clearResults) {
|
||||||
clearTimeout(searchBarTimeout);
|
clearTimeout(searchBarTimeout);
|
||||||
fetch(fetchString)
|
fetch(fetchString)
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
parseContent(data.content);
|
parseContent(data.content, clearResults);
|
||||||
|
lastQuery.totalPages = data.totalPages;
|
||||||
|
if (clearResults) {
|
||||||
|
lastQuery.currentPage = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseContent(content) {
|
function parseContent(content, clearResults) {
|
||||||
if (content.length === 0) {
|
if (content.length === 0) {
|
||||||
searchSection.querySelector("#nothing-found ").classList.remove("hidden");
|
searchSection.querySelector("#nothing-found ").classList.remove("hidden");
|
||||||
} else {
|
} else {
|
||||||
searchSection.querySelector("#nothing-found").classList.add("hidden");
|
searchSection.querySelector("#nothing-found").classList.add("hidden");
|
||||||
const datasets = content.map(dataset => new Dataset(dataset));
|
const datasets = content.map(dataset => new Dataset(dataset));
|
||||||
Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove());
|
if (clearResults) {
|
||||||
|
Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove());
|
||||||
|
}
|
||||||
for (const dataset of datasets) {
|
for (const dataset of datasets) {
|
||||||
searchSection.querySelector(".datasets").appendChild(dataset.createDatasetHTMLElement());
|
searchSection.querySelector(".datasets")
|
||||||
|
.appendChild(dataset.createDatasetHTMLElement());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,15 @@ export default class Dataset {
|
|||||||
clone.querySelector("h3").innerText = this.#title;
|
clone.querySelector("h3").innerText = this.#title;
|
||||||
clone.querySelector("p").innerText = this.#description;
|
clone.querySelector("p").innerText = this.#description;
|
||||||
clone.querySelector("span").innerText = this.#upvotes;
|
clone.querySelector("span").innerText = this.#upvotes;
|
||||||
|
let votedIDs = window.localStorage;
|
||||||
|
// depending on whether the button has been up/downvoted, its according button get disabled and hidden
|
||||||
|
if (votedIDs.getItem(this.#id)) {
|
||||||
|
let votedButton = clone.querySelector(votedIDs.getItem(this.#id)? ".upvote-btn":".downvote-btn");
|
||||||
|
votedButton.classList.add("isVoted");
|
||||||
|
votedButton.disabled = true;
|
||||||
|
let notVotedButton = clone.querySelector(votedIDs.getItem(this.#id)? ".downvote-btn":".upvote-btn");
|
||||||
|
notVotedButton.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
// Event Listeners
|
// Event Listeners
|
||||||
clone.querySelector(".upvote-btn").addEventListener("click", () => {
|
clone.querySelector(".upvote-btn").addEventListener("click", () => {
|
||||||
@ -48,4 +57,8 @@ export default class Dataset {
|
|||||||
|
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getID() {
|
||||||
|
return this.#id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ header {
|
|||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search-entry:focus-visible {
|
#search-entry:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
@ -117,6 +118,7 @@ header {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: .5em;
|
gap: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
.upvote-btn, .downvote-btn, #search-btn, #filter-btn, #sort-btn, #reset-tools-btn {
|
.upvote-btn, .downvote-btn, #search-btn, #filter-btn, #sort-btn, #reset-tools-btn {
|
||||||
background: var(--icon-url) no-repeat;
|
background: var(--icon-url) no-repeat;
|
||||||
@ -143,6 +145,7 @@ header {
|
|||||||
--icon-url: url(looking-glass.svg);
|
--icon-url: url(looking-glass.svg);
|
||||||
--icon-size: 1rem;
|
--icon-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#filter-btn {
|
#filter-btn {
|
||||||
--icon-url: url(filter.svg);
|
--icon-url: url(filter.svg);
|
||||||
--icon-size: 1rem;
|
--icon-size: 1rem;
|
||||||
@ -158,6 +161,10 @@ header {
|
|||||||
--icon-size: 1rem;
|
--icon-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.isVoted {
|
||||||
|
filter: brightness(1.75);
|
||||||
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
width: .05rem;
|
width: .05rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
|
@ -4,11 +4,11 @@ import Dataset from "./dataset.js";
|
|||||||
const apiEndpoint = "/api/v1/datasets";
|
const apiEndpoint = "/api/v1/datasets";
|
||||||
const baseURL = location.origin;
|
const baseURL = location.origin;
|
||||||
const defaultPagingValue = 20;
|
const defaultPagingValue = 20;
|
||||||
const lastQuery = {
|
export const lastQuery = {
|
||||||
url: "",
|
|
||||||
totalPages: 0,
|
totalPages: 0,
|
||||||
currentPage: 0
|
currentPage: 0
|
||||||
};
|
};
|
||||||
|
const votedIDs = window.localStorage;
|
||||||
|
|
||||||
// definition of all buttons & sections
|
// definition of all buttons & sections
|
||||||
const addButton = document.getElementById("add-btn");
|
const addButton = document.getElementById("add-btn");
|
||||||
@ -35,12 +35,12 @@ addButton.addEventListener("click", () => {
|
|||||||
filterButton.addEventListener("change", () => {
|
filterButton.addEventListener("change", () => {
|
||||||
const filterString = filterButton.value;
|
const filterString = filterButton.value;
|
||||||
if (filterString !== filterButton.querySelector("#default-filter").value) {
|
if (filterString !== filterButton.querySelector("#default-filter").value) {
|
||||||
fetchQuery(createQuery());
|
fetchQuery(createQuery(), true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
searchButton.addEventListener("click", () => {
|
searchButton.addEventListener("click", () => {
|
||||||
fetchQuery(createQuery());
|
fetchQuery(createQuery(), true);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,18 +48,18 @@ searchBar.addEventListener("input", () => {
|
|||||||
updateSections();
|
updateSections();
|
||||||
clearTimeout(searchBarTimeout);
|
clearTimeout(searchBarTimeout);
|
||||||
searchBarTimeout = setTimeout(() => {
|
searchBarTimeout = setTimeout(() => {
|
||||||
fetchQuery(createQuery());
|
fetchQuery(createQuery(), true);
|
||||||
}, searchDelay);
|
}, searchDelay);
|
||||||
});
|
});
|
||||||
|
|
||||||
searchBar.addEventListener('keypress', function (e) {
|
searchBar.addEventListener('keypress', function (e) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
fetchQuery(createQuery());
|
fetchQuery(createQuery(), true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
sortButton.addEventListener("change", () => {
|
sortButton.addEventListener("change", () => {
|
||||||
fetchQuery(createQuery());
|
fetchQuery(createQuery(), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
resetButton.addEventListener("click", () => {
|
resetButton.addEventListener("click", () => {
|
||||||
@ -69,7 +69,7 @@ resetButton.addEventListener("click", () => {
|
|||||||
updateSections();
|
updateSections();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Consider moving this to datasets.js completely
|
// Consider moving this to datasets.js completely // TODO: we dont need them, do we? there in dataset.js
|
||||||
const upvoteButtonClickListener = e => {
|
const upvoteButtonClickListener = e => {
|
||||||
const entryID = e.target.parentElement.parentElement.dataset.id;
|
const entryID = e.target.parentElement.parentElement.dataset.id;
|
||||||
vote(entryID, true);
|
vote(entryID, true);
|
||||||
@ -78,8 +78,8 @@ for (const upvoteButton of upvoteButtons) {
|
|||||||
upvoteButton.addEventListener("click", upvoteButtonClickListener);
|
upvoteButton.addEventListener("click", upvoteButtonClickListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consider moving this to datasets.js completely
|
// Consider moving this to datasets.js completely // TODO: we dont need them, do we? there in dataset.js
|
||||||
const downvoteButtonClickListener = e => {
|
const downvoteButtonClickListener = e => {
|
||||||
const entryID = e.target.parentElement.parentElement.dataset.id;
|
const entryID = e.target.parentElement.parentElement.dataset.id;
|
||||||
vote(entryID, false);
|
vote(entryID, false);
|
||||||
};
|
};
|
||||||
@ -89,11 +89,11 @@ for (const downvoteButton of downvoteButtons) {
|
|||||||
|
|
||||||
// functions of the main page
|
// functions of the main page
|
||||||
function navigateToAdd() {
|
function navigateToAdd() {
|
||||||
window.location.href = "/add";
|
window.location.href = "/add"; //TODO: move to EventListner?
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilterQuery() {
|
function getFilterQuery() {
|
||||||
let filterString= filterButton.value.toUpperCase();
|
let filterString = filterButton.value.toUpperCase();
|
||||||
if (filterString === "NONE") {
|
if (filterString === "NONE") {
|
||||||
return ["type", "%"]
|
return ["type", "%"]
|
||||||
} else if (document.querySelector('#filter-btn option:checked').parentElement.label === "Standard categories") {
|
} else if (document.querySelector('#filter-btn option:checked').parentElement.label === "Standard categories") {
|
||||||
@ -141,17 +141,22 @@ export function vote(entryID, up) {
|
|||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}})
|
}
|
||||||
.then(resp => resp.json())
|
})
|
||||||
.then((data) => {
|
.then(resp => resp.json())
|
||||||
console.log(data.upvotes); // TODO: remove, check einbauen: data.id === entryID?
|
.then((data) => {
|
||||||
let dataset = document.querySelector('[data-id= ' + CSS.escape(entryID) + ']')
|
console.log(data.upvotes); // TODO: remove, check einbauen: data.id === entryID?
|
||||||
dataset.querySelector("span").innerText = data.upvotes;
|
let dataSets = document.querySelectorAll('[data-id= ' + CSS.escape(entryID) + ']');
|
||||||
});
|
for (const dataSetElement of dataSets) {
|
||||||
}
|
dataSetElement.querySelector("span").innerText = data.upvotes;
|
||||||
|
let votedButton = dataSetElement.querySelector(up? ".upvote-btn":".downvote-btn");
|
||||||
function incrementPageCount() {
|
votedButton.classList.add("isVoted");
|
||||||
lastQuery.currentPage++;
|
votedButton.disabled = true;
|
||||||
|
let notVotedButton = dataSetElement.querySelector(up? ".downvote-btn":".upvote-btn");
|
||||||
|
notVotedButton.style.visibility = "hidden";
|
||||||
|
votedIDs.setItem(dataSetElement.getAttribute("data-id"), up);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// updates the page display. If no query is present, the initial page is shown, otherwise the search results.
|
// updates the page display. If no query is present, the initial page is shown, otherwise the search results.
|
||||||
@ -173,7 +178,7 @@ function updateSections() {
|
|||||||
// fetches the further categories used in the filter function
|
// fetches the further categories used in the filter function
|
||||||
function fetchCategories() {
|
function fetchCategories() {
|
||||||
const fetchURL = new URL(
|
const fetchURL = new URL(
|
||||||
"api/v1/categories" , baseURL);
|
"api/v1/categories", baseURL);
|
||||||
fetch(fetchURL)
|
fetch(fetchURL)
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@ -196,7 +201,8 @@ function fetchInitialEntries() {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
const datasets = data.content.map(dataset => new Dataset(dataset));
|
const datasets = data.content.map(dataset => new Dataset(dataset));
|
||||||
for (const dataset of datasets) {
|
for (const dataset of datasets) {
|
||||||
document.querySelector("#recents .datasets").appendChild(dataset.createDatasetHTMLElement());
|
document.querySelector("#recents .datasets")
|
||||||
|
.appendChild(dataset.createDatasetHTMLElement());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -207,8 +213,9 @@ function fetchInitialEntries() {
|
|||||||
fetch(topVotedQueryURL)
|
fetch(topVotedQueryURL)
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
let dataset = new Dataset(data.content[0]);
|
||||||
document.querySelector("#top .datasets")
|
document.querySelector("#top .datasets")
|
||||||
.appendChild(new Dataset(data.content[0]).createDatasetHTMLElement());
|
.appendChild(dataset.createDatasetHTMLElement());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +224,21 @@ window.onload = function () {
|
|||||||
fetchInitialEntries();
|
fetchInitialEntries();
|
||||||
updateSections();
|
updateSections();
|
||||||
if (searchBar.value !== "") {
|
if (searchBar.value !== "") {
|
||||||
fetchQuery(createQuery());
|
fetchQuery(createQuery(), true);
|
||||||
|
}
|
||||||
|
let observer = new IntersectionObserver(() => {
|
||||||
|
if (!searchSection.classList.contains("hidden")) {
|
||||||
|
fetchPagingResults();
|
||||||
|
}
|
||||||
|
}, {root: null, rootMargin: "0px", threshold: .9});
|
||||||
|
observer.observe(document.getElementById("observable"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchPagingResults() {
|
||||||
|
lastQuery.currentPage++
|
||||||
|
if (lastQuery.currentPage < lastQuery.totalPages) {
|
||||||
|
let pagingQuery = new URL(createQuery());
|
||||||
|
pagingQuery.searchParams.append("page", lastQuery.currentPage.toString(10));
|
||||||
|
fetchQuery(pagingQuery, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>DataDash</title>
|
<title>DataDash</title>
|
||||||
<link rel="stylesheet" href="main.css">
|
<link rel="stylesheet" href="main.css">
|
||||||
<script type="module" src="main.js" defer></script>
|
<script type="module" src="main.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="add-btn" title="Add a new API entry"></div>
|
<div id="add-btn" title="Add a new API entry"></div>
|
||||||
@ -78,6 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul class="datasets">
|
<ul class="datasets">
|
||||||
</ul>
|
</ul>
|
||||||
|
<div id="observable" style="height: 2rem"></div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
Reference in New Issue
Block a user