Bonus: fetch & API – JSONPlaceholder Users, DOM-Rendering, Fehlerbehandlung, Filter
This commit is contained in:
parent
21d6e4e451
commit
d2b2132605
12 changed files with 593 additions and 6 deletions
143
api.html
Normal file
143
api.html
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="API Demo – fetch & JSON – David & Karo EIS Projekt">
|
||||||
|
<title>API Demo – David & Karo</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<link rel="icon" href="img/logo.png" type="image/png">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="header-container">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="img/logo.png" alt="Logo">
|
||||||
|
<div class="logo-text">
|
||||||
|
<span class="institution">David & Karo</span>
|
||||||
|
<span class="project">EIS Projekt</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Dark Mode Toggle Button -->
|
||||||
|
<button id="dark-mode-toggle" type="button" class="dark-mode-toggle" title="Dark Mode aktivieren">
|
||||||
|
<span class="dark-mode-icon">🌙</span>
|
||||||
|
</button>
|
||||||
|
<!-- Hamburger Menu Button (nur Mobile) -->
|
||||||
|
<button id="nav-toggle" type="button" class="nav-toggle" aria-label="Menü öffnen" aria-expanded="false">
|
||||||
|
<span></span><span></span><span></span>
|
||||||
|
</button>
|
||||||
|
<nav id="main-nav">
|
||||||
|
<a href="index.html">Start</a>
|
||||||
|
<a href="ueber_uns.html">Über uns</a>
|
||||||
|
<a href="eis_projekt.html">Projekt</a>
|
||||||
|
<a href="team.html">Team</a>
|
||||||
|
<a href="galerie.html">Galerie</a>
|
||||||
|
<a href="notenrechner.html">Notenrechner</a>
|
||||||
|
<a href="textanalyse.html">Textanalyse</a>
|
||||||
|
<a href="api.html" class="active">API Demo</a>
|
||||||
|
<a href="kontakt.html">Kontakt</a>
|
||||||
|
<a href="impressum.html">Impressum</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="hero">
|
||||||
|
<h1>🌐 fetch & API Demo</h1>
|
||||||
|
<p>Live-Daten von <code>jsonplaceholder.typicode.com/users</code> – geladen per JavaScript fetch(). ✨</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>💡 Wie funktioniert's?</h2>
|
||||||
|
<div class="info-cards">
|
||||||
|
<div class="info-card">
|
||||||
|
<h4>1️⃣ fetch(URL)</h4>
|
||||||
|
<p>JavaScript sendet einen HTTP-GET Request an die API-URL und erhält eine Promise zurück.</p>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<h4>2️⃣ .then(r => r.json())</h4>
|
||||||
|
<p>Die Antwort wird als JSON geparst – aus Text werden echte JavaScript-Objekte.</p>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<h4>3️⃣ DOM rendern</h4>
|
||||||
|
<p>Die Daten werden mit <code>createElement</code> in den DOM eingefügt – kein Reload nötig!</p>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<h4>4️⃣ .catch(err)</h4>
|
||||||
|
<p>Fehlerbehandlung: Falls die API nicht erreichbar ist, wird eine Fehlermeldung angezeigt.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>👥 Benutzer von der API</h2>
|
||||||
|
<p>Die Liste wird live beim Laden der Seite von <strong>jsonplaceholder.typicode.com</strong> abgerufen:</p>
|
||||||
|
|
||||||
|
<div class="api-controls">
|
||||||
|
<button type="button" id="btn-reload" class="btn">🔄 Neu laden</button>
|
||||||
|
<input type="text" id="api-search" placeholder="🔍 Name oder Stadt filtern…" aria-label="Benutzer filtern">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lade-Indikator -->
|
||||||
|
<div id="api-loading" class="api-loading">
|
||||||
|
<div class="api-spinner"></div>
|
||||||
|
<p>Daten werden geladen…</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fehlermeldung -->
|
||||||
|
<div id="api-error" class="api-error" hidden>
|
||||||
|
<span>❌</span>
|
||||||
|
<div>
|
||||||
|
<strong>Fehler beim Laden der Daten.</strong><br>
|
||||||
|
<span id="api-error-msg"></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="btn-retry" class="btn">Erneut versuchen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Benutzer-Liste -->
|
||||||
|
<ul id="user-list" class="user-list" aria-live="polite"></ul>
|
||||||
|
|
||||||
|
<!-- Anzahl -->
|
||||||
|
<p id="user-count" class="api-count"></p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>📄 Quellcode-Erklärung</h2>
|
||||||
|
<pre class="code-block"><code>// 1. fetch aufrufen
|
||||||
|
fetch('https://jsonplaceholder.typicode.com/users')
|
||||||
|
|
||||||
|
// 2. Antwort als JSON parsen
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('HTTP ' + response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. Daten in console.log prüfen & im DOM rendern
|
||||||
|
.then(users => {
|
||||||
|
console.log('API-Daten:', users);
|
||||||
|
renderUsers(users);
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. Fehlerbehandlung
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fetch-Fehler:', error);
|
||||||
|
showApiError(error.message);
|
||||||
|
});</code></pre>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
||||||
|
<div class="footer-links">
|
||||||
|
<a href="impressum.html">Impressum</a>
|
||||||
|
<a href="kontakt.html">Kontakt</a>
|
||||||
|
<a href="#">Datenschutz</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
<script src="js/api-demo.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
261
css/style.css
261
css/style.css
|
|
@ -2926,3 +2926,264 @@ body.dark .nav-toggle span {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
API Demo – api.html
|
||||||
|
═══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
/* Info-Cards (Erklärung wie fetch funktioniert) */
|
||||||
|
.info-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1.25rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
background: linear-gradient(135deg, #fff0f5, #fce7f3);
|
||||||
|
border: 1px solid #f9a8d4;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card h4 {
|
||||||
|
color: #be185d;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #555;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card code {
|
||||||
|
background: #fce7f3;
|
||||||
|
padding: 0.1em 0.35em;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #be185d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* API Controls (Button + Suchfeld) */
|
||||||
|
.api-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 1.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#api-search {
|
||||||
|
flex: 1 1 220px;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
border: 2px solid #f9a8d4;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#api-search:focus {
|
||||||
|
border-color: #ec4899;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lade-Indikator */
|
||||||
|
.api-loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
color: #be185d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-loading[hidden] { display: none; }
|
||||||
|
|
||||||
|
.api-spinner {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 4px solid #fce7f3;
|
||||||
|
border-top-color: #ec4899;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fehlermeldung */
|
||||||
|
.api-error {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
background: #fff0f0;
|
||||||
|
border: 1px solid #fca5a5;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
color: #b91c1c;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-error[hidden] { display: none; }
|
||||||
|
|
||||||
|
.api-error span:first-child {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Benutzer-Liste */
|
||||||
|
.user-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User-Karte */
|
||||||
|
.api-user-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #f9a8d4;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
box-shadow: 0 2px 8px rgba(236,72,153,0.08);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-user-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 6px 18px rgba(236,72,153,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-user-avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: linear-gradient(135deg, #ec4899, #be185d);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-user-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #be185d;
|
||||||
|
margin: 0 0 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-user-username {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #888;
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-user-details {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #444;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-user-details a {
|
||||||
|
color: #ec4899;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-user-details a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ergebnis-Zähler */
|
||||||
|
.api-count {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #888;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keine Ergebnisse */
|
||||||
|
.api-no-results {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code-Block */
|
||||||
|
.code-block {
|
||||||
|
background: #1e1e2e;
|
||||||
|
color: #cdd6f4;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
line-height: 1.65;
|
||||||
|
border: 1px solid #313244;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block code {
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Dark Mode ── */
|
||||||
|
body.dark .info-card {
|
||||||
|
background: linear-gradient(135deg, #1e1e2e, #2d2d3f);
|
||||||
|
border-color: #7c3aed44;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .info-card h4 { color: #f9a8d4; }
|
||||||
|
body.dark .info-card p { color: #aaa; }
|
||||||
|
body.dark .info-card code {
|
||||||
|
background: #2d2d3f;
|
||||||
|
color: #f9a8d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark #api-search {
|
||||||
|
background: #1e1e2e;
|
||||||
|
color: #f0f0f0;
|
||||||
|
border-color: #7c3aed55;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .api-loading { color: #f9a8d4; }
|
||||||
|
body.dark .api-spinner { border-color: #333; border-top-color: #f9a8d4; }
|
||||||
|
|
||||||
|
body.dark .api-error {
|
||||||
|
background: #2d1515;
|
||||||
|
border-color: #7f1d1d;
|
||||||
|
color: #fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .api-user-card {
|
||||||
|
background: #1e1e2e;
|
||||||
|
border-color: #7c3aed44;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .api-user-name { color: #f9a8d4; }
|
||||||
|
body.dark .api-user-username { color: #777; }
|
||||||
|
body.dark .api-user-details { color: #ccc; }
|
||||||
|
body.dark .api-user-details a { color: #f9a8d4; }
|
||||||
|
|
||||||
|
/* ── Responsive ── */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.api-controls { flex-direction: column; align-items: stretch; }
|
||||||
|
#api-search { flex: unset; width: 100%; }
|
||||||
|
.user-list { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
<a href="galerie.html">Galerie</a>
|
<a href="galerie.html">Galerie</a>
|
||||||
<a href="notenrechner.html">Notenrechner</a>
|
<a href="notenrechner.html">Notenrechner</a>
|
||||||
<a href="textanalyse.html">Textanalyse</a>
|
<a href="textanalyse.html">Textanalyse</a>
|
||||||
|
<a href="api.html">API Demo</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -175,7 +176,8 @@
|
||||||
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="api.html">API Demo</a>
|
||||||
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="#">Datenschutz</a>
|
<a href="#">Datenschutz</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
<a href="galerie.html" class="active">Galerie</a>
|
<a href="galerie.html" class="active">Galerie</a>
|
||||||
<a href="notenrechner.html">Notenrechner</a>
|
<a href="notenrechner.html">Notenrechner</a>
|
||||||
<a href="textanalyse.html">Textanalyse</a>
|
<a href="textanalyse.html">Textanalyse</a>
|
||||||
|
<a href="api.html">API Demo</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -87,7 +88,8 @@
|
||||||
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="api.html">API Demo</a>
|
||||||
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="#">Datenschutz</a>
|
<a href="#">Datenschutz</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
<a href="galerie.html">Galerie</a>
|
<a href="galerie.html">Galerie</a>
|
||||||
<a href="notenrechner.html">Notenrechner</a>
|
<a href="notenrechner.html">Notenrechner</a>
|
||||||
<a href="textanalyse.html">Textanalyse</a>
|
<a href="textanalyse.html">Textanalyse</a>
|
||||||
|
<a href="api.html">API Demo</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="impressum.html" class="active">Impressum</a>
|
<a href="impressum.html" class="active">Impressum</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -117,7 +118,8 @@
|
||||||
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="api.html">API Demo</a>
|
||||||
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="#">Datenschutz</a>
|
<a href="#">Datenschutz</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
<a href="galerie.html">Galerie</a>
|
<a href="galerie.html">Galerie</a>
|
||||||
<a href="notenrechner.html">Notenrechner</a>
|
<a href="notenrechner.html">Notenrechner</a>
|
||||||
<a href="textanalyse.html">Textanalyse</a>
|
<a href="textanalyse.html">Textanalyse</a>
|
||||||
|
<a href="api.html">API Demo</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -87,7 +88,8 @@
|
||||||
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="api.html">API Demo</a>
|
||||||
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="#">Datenschutz</a>
|
<a href="#">Datenschutz</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
169
js/api-demo.js
Normal file
169
js/api-demo.js
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
|
@ -127,7 +127,8 @@
|
||||||
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="api.html">API Demo</a>
|
||||||
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="#">Datenschutz</a>
|
<a href="#">Datenschutz</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
<a href="galerie.html">Galerie</a>
|
<a href="galerie.html">Galerie</a>
|
||||||
<a href="notenrechner.html" class="active">Notenrechner</a>
|
<a href="notenrechner.html" class="active">Notenrechner</a>
|
||||||
<a href="textanalyse.html">Textanalyse</a>
|
<a href="textanalyse.html">Textanalyse</a>
|
||||||
|
<a href="api.html">API Demo</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
<a href="galerie.html">Galerie</a>
|
<a href="galerie.html">Galerie</a>
|
||||||
<a href="notenrechner.html">Notenrechner</a>
|
<a href="notenrechner.html">Notenrechner</a>
|
||||||
<a href="textanalyse.html">Textanalyse</a>
|
<a href="textanalyse.html">Textanalyse</a>
|
||||||
|
<a href="api.html">API Demo</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
<a href="galerie.html">Galerie</a>
|
<a href="galerie.html">Galerie</a>
|
||||||
<a href="notenrechner.html">Notenrechner</a>
|
<a href="notenrechner.html">Notenrechner</a>
|
||||||
<a href="textanalyse.html" class="active">Textanalyse</a>
|
<a href="textanalyse.html" class="active">Textanalyse</a>
|
||||||
|
<a href="api.html">API Demo</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
<a href="galerie.html">Galerie</a>
|
<a href="galerie.html">Galerie</a>
|
||||||
<a href="notenrechner.html">Notenrechner</a>
|
<a href="notenrechner.html">Notenrechner</a>
|
||||||
<a href="textanalyse.html">Textanalyse</a>
|
<a href="textanalyse.html">Textanalyse</a>
|
||||||
|
<a href="api.html">API Demo</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -108,7 +109,8 @@
|
||||||
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
<p>© 2026 – Made with 💕 & ✨ – David & Karo</p>
|
||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a href="impressum.html">Impressum</a>
|
<a href="impressum.html">Impressum</a>
|
||||||
<a href="kontakt.html">Kontakt</a>
|
<a href="api.html">API Demo</a>
|
||||||
|
<a href="kontakt.html">Kontakt</a>
|
||||||
<a href="#">Datenschutz</a>
|
<a href="#">Datenschutz</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue