261 lines
8.9 KiB
JavaScript
261 lines
8.9 KiB
JavaScript
/**
|
|
* Übernimmt Sortierung, Suche (innerhalb der Kategorie) und Pagination der
|
|
* Kategorie-Ansicht vollständig clientseitig.
|
|
*
|
|
* Ablauf:
|
|
* 1. Kompletten Kategorie-Datensatz einmalig per fetch() nachladen
|
|
* 2. Sortieren + nach Suchbegriff filtern (JS-Array).
|
|
* 3. Ergebnisliste + Pagination-UI daraus neu rendern.
|
|
*
|
|
* Ohne JS bleibt die serverseitig gerenderte Seite (echte Links/Formulare)
|
|
* voll funktionsfähig
|
|
*/
|
|
(function () {
|
|
const state = {
|
|
allItems: [],
|
|
sort: 'alphabet',
|
|
query: '',
|
|
itemsPerPage: 10,
|
|
currentPage: 1,
|
|
};
|
|
|
|
let listContainer, sortRadios, searchInput, limitSelect;
|
|
let prevBtn, nextBtn, numbersContainer, jsNav, resultCountEl;
|
|
let loadingElement;
|
|
let searchDebounceTimer;
|
|
|
|
function init() {
|
|
listContainer = document.querySelector('.s-res-list');
|
|
if (!listContainer) return;
|
|
|
|
sortRadios = document.querySelectorAll('.sort-radio');
|
|
searchInput = document.getElementById('cat-search-input');
|
|
limitSelect = document.getElementById('s-res-per-page');
|
|
prevBtn = document.getElementById('prev-page-btn');
|
|
nextBtn = document.getElementById('next-page-btn');
|
|
numbersContainer = document.getElementById('dynamic-page-numbers');
|
|
jsNav = document.getElementById('js-page-navigation');
|
|
resultCountEl = document.getElementById('s-res-result-count');
|
|
loadingElement = document.getElementById('results-loading');
|
|
|
|
const checkedRadio = document.querySelector('.sort-radio:checked');
|
|
state.sort = checkedRadio ? checkedRadio.value : 'alphabet';
|
|
state.query = searchInput ? searchInput.value : '';
|
|
state.itemsPerPage = limitSelect ? (parseInt(limitSelect.value, 10) || 10) : 10;
|
|
|
|
if (loadingElement) {
|
|
loadingElement.hidden = false;
|
|
}
|
|
|
|
fetchFullDataset()
|
|
.then(function (data) {
|
|
state.allItems = data.results || [];
|
|
attachEvents();
|
|
render();
|
|
|
|
if (jsNav) {
|
|
jsNav.style.display = 'flex';
|
|
}
|
|
})
|
|
.catch(function (err) {
|
|
/*
|
|
* Falls das Nachladen nicht funktioniert, bleibt die bereits
|
|
* serverseitig ausgegebene Kategorieansicht sichtbar.
|
|
*/
|
|
console.error(
|
|
'Kategorie-Beiträge konnten nicht nachgeladen werden:',
|
|
err
|
|
);
|
|
})
|
|
.finally(function () {
|
|
if (loadingElement) {
|
|
loadingElement.hidden = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
function fetchFullDataset() {
|
|
return fetch('php/controller/showCategory-data.php', { credentials: 'same-origin' })
|
|
.then(function (res) {
|
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
return res.json();
|
|
});
|
|
}
|
|
|
|
function getFilteredSortedItems() {
|
|
let items = state.allItems;
|
|
|
|
const needle = state.query.trim().toLowerCase();
|
|
if (needle !== '') {
|
|
items = items.filter(function (item) {
|
|
const haystack = ((item.title || '') + ' ' + (item.content || '')).toLowerCase();
|
|
return haystack.indexOf(needle) !== -1;
|
|
});
|
|
}
|
|
|
|
items = items.slice().sort(function (a, b) {
|
|
if (state.sort === 'likes') {
|
|
return (b.likes || 0) - (a.likes || 0);
|
|
}
|
|
if (state.sort === 'newest' || state.sort === 'oldest') {
|
|
return state.sort === 'newest' ? (b.id - a.id) : (a.id - b.id);
|
|
}
|
|
// alphabet (Standard)
|
|
return (a.title || '').toLowerCase().localeCompare((b.title || '').toLowerCase());
|
|
});
|
|
|
|
return items;
|
|
}
|
|
|
|
function render() {
|
|
const items = getFilteredSortedItems();
|
|
|
|
if (resultCountEl) resultCountEl.textContent = items.length;
|
|
|
|
const totalPages = Math.max(1, Math.ceil(items.length / state.itemsPerPage));
|
|
if (state.currentPage > totalPages) state.currentPage = totalPages;
|
|
if (state.currentPage < 1) state.currentPage = 1;
|
|
|
|
const start = (state.currentPage - 1) * state.itemsPerPage;
|
|
const pageItems = items.slice(start, start + state.itemsPerPage);
|
|
|
|
listContainer.innerHTML = '';
|
|
if (pageItems.length === 0) {
|
|
const empty = document.createElement('p');
|
|
empty.textContent = 'Keine Beiträge gefunden.';
|
|
listContainer.appendChild(empty);
|
|
} else {
|
|
pageItems.forEach(function (item) {
|
|
listContainer.appendChild(buildCard(item));
|
|
});
|
|
}
|
|
|
|
updatePaginatorUI(state.currentPage, totalPages);
|
|
}
|
|
|
|
function buildCard(item) {
|
|
const card = document.createElement('div');
|
|
card.className = 's-res-item';
|
|
card.setAttribute('data-likes', item.likes);
|
|
card.setAttribute('data-category', (item.category || '').toLowerCase());
|
|
|
|
const link = document.createElement('a');
|
|
link.className = 's-res-link';
|
|
link.href = 'index.php?pfad=showArticle&id=' + encodeURIComponent(item.id);
|
|
link.textContent = item.title;
|
|
|
|
const title = document.createElement('h2');
|
|
title.className = 's-res-item-title';
|
|
title.appendChild(link);
|
|
|
|
const authorName = document.createElement('span');
|
|
authorName.className = 's-res-author-name';
|
|
authorName.textContent = item.author;
|
|
|
|
const author = document.createElement('p');
|
|
author.className = 's-res-author';
|
|
author.append('Von: ', authorName);
|
|
|
|
const likes = document.createElement('span');
|
|
likes.className = 's-res-likes';
|
|
likes.textContent = '❤️ ' + item.likes;
|
|
|
|
const metaRow = document.createElement('div');
|
|
metaRow.className = 's-res-meta-row';
|
|
metaRow.append(author, likes);
|
|
|
|
const content = document.createElement('div');
|
|
content.className = 's-res-content';
|
|
content.append(title, metaRow);
|
|
|
|
const arrow = document.createElement('div');
|
|
arrow.className = 's-res-arrow';
|
|
arrow.textContent = '→';
|
|
|
|
card.append(content, arrow);
|
|
return card;
|
|
}
|
|
|
|
function updatePaginatorUI(currentPage, totalPages) {
|
|
if (!prevBtn || !nextBtn || !numbersContainer) return;
|
|
|
|
prevBtn.disabled = currentPage <= 1;
|
|
nextBtn.disabled = currentPage >= totalPages;
|
|
|
|
numbersContainer.innerHTML = '';
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
const btn = document.createElement('button');
|
|
btn.type = 'button';
|
|
btn.className = 's-res-page-btn' + (i === currentPage ? ' s-res-page-btn-active' : '');
|
|
btn.textContent = i;
|
|
btn.addEventListener('click', function () {
|
|
state.currentPage = i;
|
|
render();
|
|
});
|
|
numbersContainer.appendChild(btn);
|
|
}
|
|
}
|
|
|
|
function attachEvents() {
|
|
sortRadios.forEach(function (radio) {
|
|
radio.addEventListener('change', function () {
|
|
state.sort = this.value;
|
|
state.currentPage = 1;
|
|
render();
|
|
});
|
|
});
|
|
|
|
if (searchInput) {
|
|
// Live-Filterung während des Tippens (leicht entprellt), da der
|
|
// Datensatz bereits vollständig im Browser liegt.
|
|
searchInput.addEventListener('input', function () {
|
|
clearTimeout(searchDebounceTimer);
|
|
const value = this.value;
|
|
searchDebounceTimer = setTimeout(function () {
|
|
state.query = value;
|
|
state.currentPage = 1;
|
|
render();
|
|
}, 150);
|
|
});
|
|
}
|
|
|
|
if (limitSelect) {
|
|
limitSelect.addEventListener('change', function () {
|
|
state.itemsPerPage = parseInt(this.value, 10) || 10;
|
|
state.currentPage = 1;
|
|
render();
|
|
});
|
|
}
|
|
|
|
if (prevBtn) {
|
|
prevBtn.addEventListener('click', function () {
|
|
if (state.currentPage > 1) {
|
|
state.currentPage -= 1;
|
|
render();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (nextBtn) {
|
|
nextBtn.addEventListener('click', function () {
|
|
state.currentPage += 1;
|
|
render();
|
|
});
|
|
}
|
|
|
|
// Mit aktivem JS übernimmt render() Sortierung/Suche live
|
|
// ein echtes Absenden des Formulars (No-JS-Fallback) ist dann nicht mehr nötig.
|
|
const filterForm = document.getElementById('cat-filter-form');
|
|
if (filterForm) {
|
|
filterForm.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
})(); |