169 lines
5.9 KiB
JavaScript
169 lines
5.9 KiB
JavaScript
/**
|
||
* api-demo.js
|
||
* Fetch & API Demo – jsonplaceholder.typicode.com/users
|
||
* Lädt Benutzer per fetch(), rendert sie im DOM, mit Fehlerbehandlung & Filterung.
|
||
*/
|
||
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
// ── DOM-Referenzen ────────────────────────────────────────────────────────
|
||
const userList = document.getElementById('user-list');
|
||
const loadingEl = document.getElementById('api-loading');
|
||
const errorEl = document.getElementById('api-error');
|
||
const errorMsgEl = document.getElementById('api-error-msg');
|
||
const countEl = document.getElementById('user-count');
|
||
const searchInput = document.getElementById('api-search');
|
||
const btnReload = document.getElementById('btn-reload');
|
||
const btnRetry = document.getElementById('btn-retry');
|
||
|
||
// Nur auf der API-Demo-Seite ausführen
|
||
if (!userList) return;
|
||
|
||
// Alle geladenen User merken (für Client-seitigen Filter)
|
||
let allUsers = [];
|
||
|
||
// ── Hilfsfunktionen ───────────────────────────────────────────────────────
|
||
|
||
/** Zeigt den Lade-Spinner */
|
||
function showLoading () {
|
||
loadingEl.removeAttribute('hidden');
|
||
errorEl.setAttribute('hidden', '');
|
||
userList.innerHTML = '';
|
||
countEl.textContent = '';
|
||
if (btnReload) btnReload.disabled = true;
|
||
}
|
||
|
||
/** Versteckt den Lade-Spinner */
|
||
function hideLoading () {
|
||
loadingEl.setAttribute('hidden', '');
|
||
if (btnReload) btnReload.disabled = false;
|
||
}
|
||
|
||
/** Zeigt eine Fehlermeldung an */
|
||
function showApiError (message) {
|
||
hideLoading();
|
||
errorMsgEl.textContent = message;
|
||
errorEl.removeAttribute('hidden');
|
||
countEl.textContent = '';
|
||
}
|
||
|
||
/**
|
||
* Erstellt eine User-Karte als <li>-Element
|
||
* @param {Object} user – JSON-Objekt aus der API
|
||
* @returns {HTMLLIElement}
|
||
*/
|
||
function createUserCard (user) {
|
||
const li = document.createElement('li');
|
||
li.className = 'api-user-card';
|
||
li.innerHTML = `
|
||
<div class="api-user-avatar" aria-hidden="true">
|
||
${user.name.charAt(0).toUpperCase()}
|
||
</div>
|
||
<div class="api-user-info">
|
||
<h3 class="api-user-name">${user.name}</h3>
|
||
<p class="api-user-username">@${user.username}</p>
|
||
<ul class="api-user-details">
|
||
<li>📧 <a href="mailto:${user.email}">${user.email}</a></li>
|
||
<li>🏙️ ${user.address.city}</li>
|
||
<li>💼 ${user.company.name}</li>
|
||
<li>🌐 <a href="https://${user.website}" target="_blank" rel="noopener noreferrer">${user.website}</a></li>
|
||
</ul>
|
||
</div>
|
||
`;
|
||
return li;
|
||
}
|
||
|
||
/**
|
||
* Rendert eine Liste von Usern in den DOM
|
||
* @param {Array} users
|
||
*/
|
||
function renderUsers (users) {
|
||
userList.innerHTML = '';
|
||
|
||
if (users.length === 0) {
|
||
userList.innerHTML = '<li class="api-no-results">Keine Benutzer gefunden. 🔍</li>';
|
||
countEl.textContent = '';
|
||
return;
|
||
}
|
||
|
||
// DocumentFragment für performantes Einfügen (ein Reflow statt n)
|
||
const fragment = document.createDocumentFragment();
|
||
users.forEach(function (user) {
|
||
fragment.appendChild(createUserCard(user));
|
||
});
|
||
userList.appendChild(fragment);
|
||
|
||
countEl.textContent = users.length + ' Benutzer geladen ✅';
|
||
}
|
||
|
||
// ── Haupt-Fetch-Funktion ──────────────────────────────────────────────────
|
||
|
||
function loadUsers () {
|
||
showLoading();
|
||
|
||
// ① fetch() aufrufen
|
||
fetch('https://jsonplaceholder.typicode.com/users')
|
||
|
||
// ② Antwort prüfen & als JSON parsen
|
||
.then(function (response) {
|
||
if (!response.ok) {
|
||
throw new Error('HTTP-Fehler: ' + response.status + ' ' + response.statusText);
|
||
}
|
||
return response.json();
|
||
})
|
||
|
||
// ③ Daten verarbeiten & im DOM rendern
|
||
.then(function (users) {
|
||
console.log('✅ API-Antwort erhalten:', users);
|
||
console.table(users.map(function (u) {
|
||
return { Name: u.name, 'E-Mail': u.email, Stadt: u.address.city, Firma: u.company.name };
|
||
}));
|
||
|
||
allUsers = users;
|
||
hideLoading();
|
||
renderUsers(allUsers);
|
||
|
||
// Suchfeld zurücksetzen
|
||
if (searchInput) searchInput.value = '';
|
||
})
|
||
|
||
// ④ Fehlerbehandlung
|
||
.catch(function (err) {
|
||
console.error('❌ Fetch-Fehler:', err);
|
||
showApiError(err.message || 'Unbekannter Fehler. Bitte Internetverbindung prüfen.');
|
||
});
|
||
}
|
||
|
||
// ── Client-seitiger Filter ────────────────────────────────────────────────
|
||
|
||
function filterUsers (query) {
|
||
if (!query.trim()) {
|
||
renderUsers(allUsers);
|
||
return;
|
||
}
|
||
const q = query.toLowerCase();
|
||
const filtered = allUsers.filter(function (user) {
|
||
return (
|
||
user.name.toLowerCase().includes(q) ||
|
||
user.username.toLowerCase().includes(q) ||
|
||
user.address.city.toLowerCase().includes(q) ||
|
||
user.company.name.toLowerCase().includes(q) ||
|
||
user.email.toLowerCase().includes(q)
|
||
);
|
||
});
|
||
renderUsers(filtered);
|
||
}
|
||
|
||
// ── Event Listener ────────────────────────────────────────────────────────
|
||
|
||
if (btnReload) btnReload.addEventListener('click', loadUsers);
|
||
if (btnRetry) btnRetry.addEventListener('click', loadUsers);
|
||
|
||
if (searchInput) {
|
||
searchInput.addEventListener('input', function () {
|
||
filterUsers(this.value);
|
||
});
|
||
}
|
||
|
||
// ── Beim Seitenaufruf automatisch laden ───────────────────────────────────
|
||
loadUsers();
|
||
});
|