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:
		@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user