257 lines
8.7 KiB
JavaScript
257 lines
8.7 KiB
JavaScript
/**
|
|
* Übernimmt Sortierung, Kategorie-Filter und Pagination der Suchergebnisse
|
|
* vollständig clientseitig
|
|
*
|
|
* Ablauf:
|
|
* 1. Kompletten Ergebnis-Datensatz einmalig per fetch() nachladen
|
|
* (php/controller/search-results-data.php, liest nur die Session-Daten,
|
|
* keine erneute Datenbank-Suche).
|
|
* 2. Sortieren + filtern (JS-Array).
|
|
* 3. Ergebnisliste + Pagination-UI daraus neu rendern.
|
|
*
|
|
* Ersetzt sorter.js, filter.js und paginator.js, deren Klick-Handler sich
|
|
* gegenseitig ins Gehege kamen.
|
|
* Ohne JS bleibt die serverseitig gerenderte Seite (echte Links/Formulare)
|
|
* voll funktionsfähig.
|
|
*/
|
|
(function () {
|
|
const state = {
|
|
allItems: [],
|
|
sort: 'alphabet',
|
|
category: 'all',
|
|
itemsPerPage: 10,
|
|
currentPage: 1,
|
|
};
|
|
|
|
let listContainer, sortRadios, categorySelect, limitSelect;
|
|
let prevBtn, nextBtn, numbersContainer, jsNav, resultCountEl;
|
|
let loadingElement;
|
|
|
|
function init() {
|
|
listContainer = document.querySelector('.s-res-list');
|
|
if (!listContainer) return;
|
|
|
|
sortRadios = document.querySelectorAll('.sort-radio');
|
|
categorySelect = document.getElementById('category-filter');
|
|
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.category = categorySelect ? categorySelect.value : 'all';
|
|
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 Ergebnisliste sichtbar.
|
|
*/
|
|
console.error(
|
|
'Suchergebnisse konnten nicht nachgeladen werden:',
|
|
err
|
|
);
|
|
})
|
|
.finally(function () {
|
|
if (loadingElement) {
|
|
loadingElement.hidden = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
function fetchFullDataset() {
|
|
return fetch('php/controller/search-results-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;
|
|
|
|
if (state.category !== 'all') {
|
|
const wanted = state.category.toLowerCase();
|
|
items = items.filter(function (item) {
|
|
return (item.category || '').toLowerCase() === wanted;
|
|
});
|
|
}
|
|
|
|
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 (categorySelect) {
|
|
categorySelect.addEventListener('change', function () {
|
|
state.category = this.value;
|
|
state.currentPage = 1;
|
|
render();
|
|
});
|
|
}
|
|
|
|
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/Filter live
|
|
// ein echtes Absenden des Formulars (No-JS-Fallback) ist dann nicht mehr nötig.
|
|
const filterForm = document.getElementById('s-res-filter-form');
|
|
if (filterForm) {
|
|
filterForm.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
})(); |