Merge branch '36-refactor-dataset-class' into '22-integrate-api-and-frontend'
Resolve "Refactor Dataset class" See merge request padas/24ss-5430-web-and-data-eng/gruppe-3/datadash!35
This commit is contained in:
commit
baa8349110
@ -1,30 +1,35 @@
|
|||||||
import {searchBarTimeout, searchSection, lastQuery} from "./main.js"
|
|
||||||
import Dataset from "./dataset.js"
|
|
||||||
// TODO consider renaming this to "searchUtility.js"
|
// TODO consider renaming this to "searchUtility.js"
|
||||||
export function fetchQuery(fetchString, clearResults) {
|
|
||||||
|
import { searchBarTimeout, searchSection, lastQuery } from "./main.js"
|
||||||
|
import Dataset from "./dataset.js"
|
||||||
|
|
||||||
|
export async function fetchQuery(fetchString, clearResults) {
|
||||||
clearTimeout(searchBarTimeout);
|
clearTimeout(searchBarTimeout);
|
||||||
fetch(fetchString)
|
const response = await fetch(fetchString);
|
||||||
.then(resp => resp.json())
|
const data = await response.json();
|
||||||
.then((data) => {
|
|
||||||
parseContent(data.content, clearResults);
|
parseContent(data.content, clearResults);
|
||||||
lastQuery.totalPages = data.totalPages;
|
lastQuery.totalPages = data.totalPages;
|
||||||
if (clearResults) {
|
if (clearResults) {
|
||||||
lastQuery.currentPage = 0;
|
lastQuery.currentPage = 0;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseContent(content, clearResults) {
|
function parseContent(content, clearResults) {
|
||||||
|
const nothingFoundElement = searchSection.querySelector("#nothing-found");
|
||||||
|
|
||||||
if (content.length === 0) {
|
if (content.length === 0) {
|
||||||
searchSection.querySelector("#nothing-found ").classList.remove("hidden");
|
nothingFoundElement.classList.remove("hidden");
|
||||||
searchSection.querySelector(".datasets").classList.add("hidden");
|
searchSection.querySelector(".datasets").classList.add("hidden");
|
||||||
} else {
|
} else {
|
||||||
searchSection.querySelector("#nothing-found").classList.add("hidden");
|
nothingFoundElement.classList.add("hidden");
|
||||||
|
|
||||||
|
const datasets = content.map(dataset => Dataset.get(dataset.id) ?? new Dataset(dataset));
|
||||||
searchSection.querySelector(".datasets").classList.remove("hidden");
|
searchSection.querySelector(".datasets").classList.remove("hidden");
|
||||||
const datasets = content.map(dataset => new Dataset(dataset));
|
|
||||||
if (clearResults) {
|
if (clearResults) {
|
||||||
Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove());
|
Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const dataset of datasets) {
|
for (const dataset of datasets) {
|
||||||
searchSection.querySelector(".datasets")
|
searchSection.querySelector(".datasets")
|
||||||
.appendChild(dataset.createDatasetHTMLElement());
|
.appendChild(dataset.createDatasetHTMLElement());
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { vote } from "./main.js";
|
import { DATASET_ENDPOINT, getBaseURL } from "./main.js";
|
||||||
|
|
||||||
export default class Dataset {
|
export default class Dataset {
|
||||||
|
static #datasets = new Map();
|
||||||
|
|
||||||
#abstract;
|
#abstract;
|
||||||
#author;
|
#author;
|
||||||
#categories;
|
#categories;
|
||||||
@ -13,8 +15,13 @@ export default class Dataset {
|
|||||||
#upvotes;
|
#upvotes;
|
||||||
#url;
|
#url;
|
||||||
#votes;
|
#votes;
|
||||||
|
#elements = [];
|
||||||
|
|
||||||
constructor({abst: shortDescription, author, categories, date, description, id, rating, title, type, upvotes, url, votes}) {
|
static get(id) {
|
||||||
|
return this.#datasets.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor({ abst: shortDescription, author, categories, date, description, id, rating, title, type, upvotes, url, votes }) {
|
||||||
this.#abstract = shortDescription;
|
this.#abstract = shortDescription;
|
||||||
this.#author = author;
|
this.#author = author;
|
||||||
this.#categories = categories;
|
this.#categories = categories;
|
||||||
@ -27,34 +34,179 @@ export default class Dataset {
|
|||||||
this.#upvotes = upvotes;
|
this.#upvotes = upvotes;
|
||||||
this.#url = url;
|
this.#url = url;
|
||||||
this.#votes = votes;
|
this.#votes = votes;
|
||||||
|
|
||||||
|
Dataset.#datasets.set(id, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
get abstract() {
|
||||||
|
return this.#abstract;
|
||||||
|
}
|
||||||
|
|
||||||
|
get author() {
|
||||||
|
return this.#author;
|
||||||
|
}
|
||||||
|
|
||||||
|
get categories() {
|
||||||
|
return this.#categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
get date() {
|
||||||
|
return this.#date;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return this.#description;
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this.#id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get rating() {
|
||||||
|
return this.#rating;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return this.#title;
|
||||||
|
}
|
||||||
|
|
||||||
|
get type() {
|
||||||
|
return this.#type;
|
||||||
|
}
|
||||||
|
|
||||||
|
get upvotes() {
|
||||||
|
return this.#upvotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get url() {
|
||||||
|
return this.#url;
|
||||||
|
}
|
||||||
|
|
||||||
|
get votes() {
|
||||||
|
return this.#votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get mainElement() {
|
||||||
|
return this.#elements[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
get elements() {
|
||||||
|
return this.#elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main methods
|
||||||
|
|
||||||
|
// Only on main page
|
||||||
createDatasetHTMLElement() {
|
createDatasetHTMLElement() {
|
||||||
let template = document.querySelector("#dataset-template");
|
const clone = this.#createTemplateInstance("dataset-template");
|
||||||
const clone = template.content.cloneNode(true);
|
if (clone == null) {
|
||||||
clone.querySelector(".dataset").dataset.id = this.#id;
|
return null;
|
||||||
clone.querySelector("h3").innerText = this.#title;
|
|
||||||
clone.querySelector("p").innerText = this.#description;
|
|
||||||
clone.querySelector("span").innerText = this.#upvotes;
|
|
||||||
let votedIDs = window.localStorage;
|
|
||||||
// depending on whether the button has been up/downvoted, its according button gets 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
|
clone.querySelector(".dataset").dataset.id = this.#id;
|
||||||
clone.querySelector(".upvote-btn").addEventListener("click", () => {
|
clone.querySelector(".dataset-title").innerText = this.#title;
|
||||||
vote(this.#id, true);
|
clone.querySelector(".dataset-description").innerText = this.#description;
|
||||||
});
|
clone.querySelector(".upvote-count").innerText = this.#upvotes;
|
||||||
|
|
||||||
clone.querySelector(".downvote-btn").addEventListener("click", () => {
|
this.#elements.push(clone.children[0]);
|
||||||
vote(this.#id, false);
|
this.#createUpvoteButtons(clone);
|
||||||
})
|
|
||||||
|
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only on details page
|
||||||
|
createUpvoteComponent() {
|
||||||
|
let clone = this.#createTemplateInstance("voting-template")
|
||||||
|
|
||||||
|
clone.querySelector(".upvote-count").innerText = this.#upvotes;
|
||||||
|
|
||||||
|
this.#elements.push(clone.children[0]);
|
||||||
|
this.#createUpvoteButtons(clone);
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
#createUpvoteButtons(templateInstance) {
|
||||||
|
if (this.storageGetKey("is-voted", false)) {
|
||||||
|
// The template instance (clone) has to be pushed before this can work
|
||||||
|
this.#disableVoteBtns();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event Listeners
|
||||||
|
templateInstance.querySelector(".upvote-btn").addEventListener("click", () => {
|
||||||
|
this.vote();
|
||||||
|
});
|
||||||
|
|
||||||
|
templateInstance.querySelector(".downvote-btn").addEventListener("click", () => {
|
||||||
|
this.vote(false);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#createTemplateInstance(templateId) {
|
||||||
|
let template = document.getElementById(templateId);
|
||||||
|
if (template == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.content.cloneNode(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
isMainElement(element) {
|
||||||
|
return this.#elements.length > 0 && this.#elements[0].isEqualNode(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
async vote(up = true) {
|
||||||
|
const fetchURL = new URL(
|
||||||
|
`${DATASET_ENDPOINT}/id/${this.#id}/${up ? "up" : "down"}vote`,
|
||||||
|
getBaseURL(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await fetch(fetchURL, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
this.#upvotes = data.upvotes;
|
||||||
|
for (const element of this.#elements) {
|
||||||
|
element.querySelector(".upvote-count").innerText = this.#upvotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.storageSetKey("vote-type", up ? "upvote" : "downvote");
|
||||||
|
this.storageSetKey("is-voted", true);
|
||||||
|
this.#disableVoteBtns();
|
||||||
|
}
|
||||||
|
|
||||||
|
#disableVoteBtns() {
|
||||||
|
if (this.#elements.length > 0) {
|
||||||
|
const voteType = this.storageGetKey("vote-type", null);
|
||||||
|
|
||||||
|
this.#elements.flatMap(e => Array.from(e.querySelectorAll(".upvote .btn")))
|
||||||
|
.forEach(btn => {
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
if (btn.classList.contains(`${voteType}-btn`)) {
|
||||||
|
btn.classList.add("isVoted");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storageGet() {
|
||||||
|
const json = localStorage.getItem(this.#id);
|
||||||
|
return json != null ? JSON.parse(json) : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
storageSetKey(key, value) {
|
||||||
|
let currentStorage = this.storageGet();
|
||||||
|
currentStorage[key] = value;
|
||||||
|
localStorage.setItem(this.#id, JSON.stringify(currentStorage));
|
||||||
|
}
|
||||||
|
|
||||||
|
storageGetKey(key, defaultValue) {
|
||||||
|
return this.storageGet()?.[key] ?? defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
<li class="dataset" data-id="id">
|
<li class="dataset" data-id="id">
|
||||||
<div class="dataset-info">
|
<div class="dataset-info">
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<h3>title</h3>
|
<h3 class="dataset-title">title</h3>
|
||||||
<p>Simply daily accountability phone call, powered by AI</p>
|
<p class="dataset-description">Simply daily accountability phone call, powered by AI</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<aside class="upvote">
|
<aside class="upvote">
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import {fetchQuery} from "./contentUtility.js";
|
import { fetchQuery } from "./contentUtility.js";
|
||||||
import Dataset from "./dataset.js";
|
import Dataset from "./dataset.js";
|
||||||
|
|
||||||
const apiEndpoint = "/api/v1/datasets";
|
export const DATASET_ENDPOINT = "/api/v1/datasets";
|
||||||
const baseURL = location.origin;
|
export const getBaseURL = () => location.origin;
|
||||||
|
|
||||||
const defaultPagingValue = 20;
|
const defaultPagingValue = 20;
|
||||||
export const lastQuery = {
|
export const lastQuery = {
|
||||||
totalPages: 0,
|
totalPages: 0,
|
||||||
currentPage: 0
|
currentPage: 0,
|
||||||
};
|
};
|
||||||
const votedIDs = window.localStorage;
|
|
||||||
const loadedCategories = new Set;
|
const loadedCategories = new Set;
|
||||||
|
|
||||||
// definition of all buttons & sections
|
// definition of all buttons & sections
|
||||||
@ -71,24 +71,6 @@ resetButton.addEventListener("click", () => {
|
|||||||
updateSections();
|
updateSections();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Consider moving this to datasets.js completely // TODO: we dont need them, do we? there in dataset.js
|
|
||||||
const upvoteButtonClickListener = e => {
|
|
||||||
const entryID = e.target.parentElement.parentElement.dataset.id;
|
|
||||||
vote(entryID, true);
|
|
||||||
};
|
|
||||||
for (const upvoteButton of upvoteButtons) {
|
|
||||||
upvoteButton.addEventListener("click", upvoteButtonClickListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consider moving this to datasets.js completely // TODO: we dont need them, do we? there in dataset.js
|
|
||||||
const downvoteButtonClickListener = e => {
|
|
||||||
const entryID = e.target.parentElement.parentElement.dataset.id;
|
|
||||||
vote(entryID, false);
|
|
||||||
};
|
|
||||||
for (const downvoteButton of downvoteButtons) {
|
|
||||||
downvoteButton.addEventListener("click", downvoteButtonClickListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
// functions of the main page
|
// functions of the main page
|
||||||
function navigateToAdd() {
|
function navigateToAdd() {
|
||||||
window.location.href = "/add.html"; //TODO: move to EventListener?
|
window.location.href = "/add.html"; //TODO: move to EventListener?
|
||||||
@ -123,42 +105,18 @@ function getSortQuery() {
|
|||||||
// creates query for the whole toolbar, so that searching, sorting and filtering are always combined
|
// creates query for the whole toolbar, so that searching, sorting and filtering are always combined
|
||||||
function createQuery() {
|
function createQuery() {
|
||||||
updateSections();
|
updateSections();
|
||||||
let queryURL = new URL(apiEndpoint + "/search", baseURL);
|
let queryURL = new URL(DATASET_ENDPOINT + "/search", getBaseURL());
|
||||||
queryURL.searchParams.append("search", getSearchQuery());
|
queryURL.searchParams.append("search", getSearchQuery());
|
||||||
|
|
||||||
let filterQuery = getFilterQuery();
|
let filterQuery = getFilterQuery();
|
||||||
queryURL.searchParams.append(filterQuery[0], filterQuery[1]);
|
queryURL.searchParams.append(filterQuery[0], filterQuery[1]);
|
||||||
|
|
||||||
let sortQuery = getSortQuery();
|
let sortQuery = getSortQuery();
|
||||||
queryURL.searchParams.append("sort", sortQuery[0]);
|
queryURL.searchParams.append("sort", sortQuery[0]);
|
||||||
queryURL.searchParams.append("direction", sortQuery[1]);
|
queryURL.searchParams.append("direction", sortQuery[1]);
|
||||||
queryURL.searchParams.append("size", defaultPagingValue.toString(10));
|
queryURL.searchParams.append("size", defaultPagingValue.toString(10));
|
||||||
return queryURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function vote(entryID, up) {
|
return queryURL;
|
||||||
const fetchURL = new URL(
|
|
||||||
`${apiEndpoint}/id/${entryID}/${up ? "up" : "down"}vote`,
|
|
||||||
baseURL,
|
|
||||||
);
|
|
||||||
fetch(fetchURL, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(resp => resp.json())
|
|
||||||
.then((data) => {
|
|
||||||
console.log(data.upvotes); // TODO: remove, check einbauen: data.id === entryID?
|
|
||||||
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");
|
|
||||||
votedButton.classList.add("isVoted");
|
|
||||||
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.
|
||||||
@ -179,7 +137,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("api/v1/categories", baseURL);
|
const fetchURL = new URL("api/v1/categories", getBaseURL());
|
||||||
fetch(fetchURL)
|
fetch(fetchURL)
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@ -196,32 +154,30 @@ function fetchCategories() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetches entries for the initial page
|
// fetches entries for the initial page
|
||||||
function fetchInitialEntries() {
|
async function fetchInitialEntries() {
|
||||||
let recentsQueryURL = new URL(apiEndpoint + "/search", baseURL);
|
let recentsQueryURL = new URL(DATASET_ENDPOINT + "/search", getBaseURL());
|
||||||
recentsQueryURL.searchParams.append("sort", "date");
|
recentsQueryURL.searchParams.append("sort", "date");
|
||||||
recentsQueryURL.searchParams.append("direction", "desc");
|
recentsQueryURL.searchParams.append("direction", "desc");
|
||||||
recentsQueryURL.searchParams.append("size", "6");
|
recentsQueryURL.searchParams.append("size", "6");
|
||||||
fetch(recentsQueryURL)
|
|
||||||
.then(resp => resp.json())
|
|
||||||
.then((data) => {
|
|
||||||
const datasets = data.content.map(dataset => new Dataset(dataset));
|
|
||||||
for (const dataset of datasets) {
|
|
||||||
document.querySelector("#recents .datasets")
|
|
||||||
.appendChild(dataset.createDatasetHTMLElement());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let topVotedQueryURL = new URL(apiEndpoint + "/search", baseURL);
|
const recentsResponse = await fetch(recentsQueryURL);
|
||||||
|
const recentsData = await recentsResponse.json();
|
||||||
|
|
||||||
|
const recentsDatasets = recentsData.content.map(dataset => new Dataset(dataset));
|
||||||
|
for (const recentDataset of recentsDatasets) {
|
||||||
|
document.querySelector("#recents .datasets").appendChild(recentDataset.createDatasetHTMLElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
let topVotedQueryURL = new URL(DATASET_ENDPOINT + "/search", getBaseURL());
|
||||||
topVotedQueryURL.searchParams.append("sort", "upvotes");
|
topVotedQueryURL.searchParams.append("sort", "upvotes");
|
||||||
topVotedQueryURL.searchParams.append("direction", "desc");
|
topVotedQueryURL.searchParams.append("direction", "desc");
|
||||||
topVotedQueryURL.searchParams.append("size", "1");
|
topVotedQueryURL.searchParams.append("size", "1");
|
||||||
fetch(topVotedQueryURL)
|
|
||||||
.then(resp => resp.json())
|
const topVotedResponse = await fetch(topVotedQueryURL);
|
||||||
.then((data) => {
|
const topVotedData = await topVotedResponse.json();
|
||||||
let dataset = new Dataset(data.content[0]);
|
const topVotedDataset = new Dataset(topVotedData.content[0]);
|
||||||
document.querySelector("#top .datasets")
|
|
||||||
.appendChild(dataset.createDatasetHTMLElement());
|
document.querySelector("#top .datasets").appendChild(topVotedDataset.createDatasetHTMLElement());
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
|
Loading…
Reference in New Issue
Block a user