implement dynamic project loading from api

This commit is contained in:
Michi 2025-03-18 21:20:24 +01:00
parent b2eeb3b7ae
commit f5745b9e81
5 changed files with 162 additions and 42 deletions

View file

@ -9,4 +9,5 @@ logo = 'Logo'
gotop = 'Nach oben!' gotop = 'Nach oben!'
godown = 'Nach unten!' godown = 'Nach unten!'
language = 'Sprache' language = 'Sprache'
select-language = 'Sprache auswählen' select-language = 'Sprache auswählen'
load-more = 'Mehr laden'

View file

@ -9,4 +9,5 @@ logo = 'Logo'
gotop = 'Go to top' gotop = 'Go to top'
godown = 'Scroll down!' godown = 'Scroll down!'
language = 'Language' language = 'Language'
select-language = 'Select language' select-language = 'Select language'
load-more = 'Load more'

View file

@ -1,5 +1,5 @@
<div id="projects" class="projects"> <div id="projects" class="projects">
<h1>Projekte</h1> <h1>{{ T "projects" }}</h1>
<div class="tag-filter"> <div class="tag-filter">
{{ range .Site.Data.tags }} {{ range .Site.Data.tags }}
<div class="tag"> <div class="tag">
@ -10,37 +10,8 @@
</div> </div>
<div class="project-list"> <div class="project-list">
<div class="project-card"> </div>
<div class="card-first"> <div>
<p></p> <button class="project-load-btn" alt='{{ T "load-more" }}' title='{{ T "load-more" }}' name='{{ T "load-more" }}'>{{ T "load-more" }}</button>
</div>
<div class="card-second">
<i class="ai-newspaper"></i>
<h2>Mein Fazit: 1 Jahr mit dem Europa-Park ResortPass</h2>
<p>14.01.2025</p>
</div>
</div>
<div class="project-card">
<div class="card-first">
<p></p>
</div>
<div class="card-second">
<i class="ai-newspaper"></i>
<h2>Mein Fazit: 1 Jahr mit dem Europa-Park ResortPass</h2>
<p>14.01.2025</p>
</div>
</div>
<div class="project-card">
<div class="card-first">
<p></p>
</div>
<div class="card-second">
<i class="ai-newspaper"></i>
<h2>Mein Fazit: 1 Jahr mit dem Europa-Park ResortPass</h2>
<p>14.01.2025</p>
</div>
</div>
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
// Add event listeners // Add event listeners
document.addEventListener('DOMContentLoaded', function(){ document.addEventListener('DOMContentLoaded', async function(){
scrollTopVisibilityUpdate(); scrollTopVisibilityUpdate();
updateNavStyle(); updateNavStyle();
calculateAge(".age"); calculateAge(".age");
@ -9,6 +9,9 @@ document.addEventListener('DOMContentLoaded', function(){
languageSelector.addEventListener('change', function(){ languageSelector.addEventListener('change', function(){
window.location.href = languageSelector.value + window.location.hash; window.location.href = languageSelector.value + window.location.hash;
}); });
// load projects
loadMoreContent('.project-list', 6);
}); });
window.addEventListener('scroll', function(){ window.addEventListener('scroll', function(){
@ -26,6 +29,10 @@ document.querySelectorAll(".nav-links a").forEach(element => {
}); });
}); });
document.querySelector(".project-load-btn").addEventListener('click', function(){
loadMoreContent('.project-list', 3);
});
// Generic functions // Generic functions
function toggleDisplayByClass(className, displayType){ function toggleDisplayByClass(className, displayType){
let items = document.getElementsByClassName(className); let items = document.getElementsByClassName(className);
@ -94,4 +101,95 @@ function calculateAge(selector){
} }
obj.textContent = age; obj.textContent = age;
}
// load projects from api
// content api: https://api.michivonah.ch/?limit=8&page=1
async function loadContent(endpoint = "https://api.michivonah.ch", limit = 6, page = 1){
try{
constRequestUrl = `${endpoint}/?limit=${limit}&page=${page}`;
const response = await fetch(constRequestUrl);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data;
}
catch(error){
return console.error({"message":error});
}
}
// add projects to page
async function showProjects(wrapperSelector, data){
const wrapper = document.querySelector(wrapperSelector);
for (const project of data){
wrapper.append(await getProjectCard(project));
}
}
// create project card
async function getProjectCard(data){
const title = data.title;
const url = data.url;
const image = data.image;
const date = data.date;
const icon = data.icon;
const category = data.category;
const dateFormatted = new Date(date).toLocaleDateString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric"
});
const container = document.createElement("div");
container.classList = "project-card";
container.classList.add(category);
//container.style.backgroundImage = `url(${image})`;
const cardFirst = document.createElement("div");
cardFirst.classList = "card-first";
const emptyText = document.createElement("p");
cardFirst.appendChild(emptyText);
const cardImage = document.createElement("img");
cardImage.src = image;
cardFirst.appendChild(cardImage);
const cardSecond = document.createElement("div");
cardSecond.classList = "card-second";
const cardIcon = document.createElement("i");
cardIcon.classList = icon;
const cardTitle = document.createElement("h2");
cardTitle.textContent = title;
const cardDate = document.createElement("p");
cardDate.textContent = dateFormatted;
cardSecond.appendChild(cardIcon);
cardSecond.appendChild(cardTitle);
cardSecond.appendChild(cardDate);
container.appendChild(cardFirst);
container.appendChild(cardSecond);
return container;
}
async function loadMoreContent(wrapperSelector, amount, endpoint = "https://api.michivonah.ch", btnSelector = ".project-load-btn"){
const wrapper = document.querySelector(wrapperSelector);
const page = ((wrapper.childElementCount <= 0) ? 1 : (wrapper.childElementCount / amount) + 1);
// just for debugging purposes
//console.log(`children: ${wrapper.childElementCount} limit: ${amount} page: ${page}`);
// error validation -> only load more content when page num is valid
if (page % 1 == 0){
await showProjects('.project-list', await loadContent(endpoint, amount, page));
}
else{
// hide button if no more content is available
//if (event.target.tagName == "BUTTON") event.target.style.display = "none";
document.querySelector(btnSelector).style.opacity = 0;
}
} }

View file

@ -19,7 +19,8 @@ body{
--color: #f8f8f8; --color: #f8f8f8;
--font: "Source Sans 3", sans-serif; --font: "Source Sans 3", sans-serif;
--baseRadius: 12px; --baseRadius: 12px;
--baseTransition: all 250ms; --baseTransition: all var(--baseDuration);
--baseDuration: 250ms;
--navSmallHeight: 66px; --navSmallHeight: 66px;
} }
@ -367,9 +368,10 @@ nav.small .nav-links a:last-child:focus{
color: var(--primary); color: var(--primary);
margin: 10px 5px; margin: 10px 5px;
padding: 5px 16px; padding: 5px 16px;
font-size: 1rem;
border-radius: var(--baseRadius); border-radius: var(--baseRadius);
transition: var(--baseTransition); transition: var(--baseTransition);
border: 1px solid var(--primary); border: 2px solid var(--primary);
} }
.tag input{ .tag input{
@ -381,6 +383,13 @@ nav.small .nav-links a:last-child:focus{
color: var(--color); color: var(--color);
} }
.tag:hover{
padding: 5px 26px;
background: var(--secondary);
color: var(--color);
border: 2px solid var(--color);
}
.project-list{ .project-list{
padding: 20px 0; padding: 20px 0;
display: flex; display: flex;
@ -391,6 +400,24 @@ nav.small .nav-links a:last-child:focus{
align-items: stretch; align-items: stretch;
} }
.project-load-btn{
padding: 10px 20px;
background: var(--primary);
color: var(--color);
font-size: 1rem;
border: 2px solid transparent;
border-radius: var(--baseRadius);
transition: var(--baseTransition);
opacity: 1;
}
.project-load-btn:hover{
padding: 10px 40px;
background: var(--secondary);
border: 2px solid var(--color);
cursor: pointer;
}
/* Card animation: https://uiverse.io/suleymanlaarabidev/perfect-husky-88 */ /* Card animation: https://uiverse.io/suleymanlaarabidev/perfect-husky-88 */
.project-card{ .project-card{
width: 30%; width: 30%;
@ -398,7 +425,7 @@ nav.small .nav-links a:last-child:focus{
min-height: 250px; min-height: 250px;
margin: 20px 10px; margin: 20px 10px;
padding: 0 10px; padding: 0 10px;
background-image: url(https://blog.michivonah.ch/content/images/size/w1200/2024/12/ResortPass-Review--1-.jpg); background: var(--primary);
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
@ -406,6 +433,27 @@ nav.small .nav-links a:last-child:focus{
transition: var(--baseTransition); transition: var(--baseTransition);
box-shadow: 0px 0px 10px 5px rgba(0, 0, 0, 0.705); box-shadow: 0px 0px 10px 5px rgba(0, 0, 0, 0.705);
overflow: hidden; overflow: hidden;
position: relative;
animation: fadeIn 400ms linear;
}
@keyframes fadeIn{
0%{
opacity: 0;
}
100%{
opacity: 1;
}
}
.card-first img{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: auto;
height: 100%;
} }
@media (pointer: fine){ @media (pointer: fine){
@ -430,7 +478,7 @@ nav.small .nav-links a:last-child:focus{
transition: var(--baseTransition); transition: var(--baseTransition);
opacity: 1; opacity: 1;
} }
.project-card:hover .card-first{ .project-card:hover .card-first{
height: 0; height: 0;
opacity: 0; opacity: 0;
@ -480,8 +528,9 @@ nav.small .nav-links a:last-child:focus{
box-shadow: 0px 0px 10px 5px rgba(0, 0, 0, 0.705); box-shadow: 0px 0px 10px 5px rgba(0, 0, 0, 0.705);
} }
.card-first{ .card-first img{
display: none; height: calc(100% + 60px);
filter: blur(6px);
} }
.card-second{ .card-second{