Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Willkommen

Dieser Kurs ermöglicht dir einen Einstieg in die Webentwicklung mit Html, CSS und JavaScript. Anhand einer Blog Page von Galaxus wirst du nach und nach vieles davon nachbauen und sehen, was es teilweise braucht, um ein schönes Layout hinzukriegen oder wie ein Button zu Funktionen verholfen wird.

Viel Spass beim Lernen und ausprobieren.

WasLink
HTML, CSS, Javascript
Nachschlage Werk für html, css, javascriptSELFHTML Wiki
Effektive "Bibel" von der Mozilla Foundation, was bei html, css, javascript grundsätzlich gilt / funktionieren sollte. Inklusive lern BeispieleMDN Web Docs - HTML
Nachschlagewerk zu CSS GridCSS Grid Complete Guide
Nachschlagewerk zu CSS FlexboxCSS Flexbox Guide
CSS VariablenW3Schools CSS Variables
CSS NestingW3Schools CSS Nesting
CSS Methodologies ➡️ welche Architekturen für CSS gibt esCSS Methodologies Guide
Was kann Vs Code. Bspw. HTMLVS Code HTML Features
Tools
Zum nachprüfen, auf welche API's heute ein Browser zugreifen kann bzw. darf; äusserst hilfreich, wenn man anstelle einer App überlegt, eine Webseite zu bauenWhat Web Can Do Today
Zum nachprüfen, was von welchen Browser unterstützt wirdCan I Use
Vs CodeVisual Studio Code
Node JsNode.js Download
Debug mit ChromeChrome DevTools JavaScript
Debug mit VSCodeVS Code Debugging
Kurs relevante Links
GithubGitHub Repository
End-ResultateWeb Engineering Results

Tutorials

Video Aufzeichnungen

Die Links funktionieren nur bei entsprechender Berechtigung.

Datum KurstagLink
19. AugustKurstag Setup
4. SeptemberErster HTML Kurstag
16. SeptemberZweiter HTML Kurstag
30. SeptemberTeil 1 CSS
7. OktoberTeil 2 CSS
14. OktoberTeil 3 CSS
21. OktoberJavaScript Einstieg

Setup

Erforderliche Applikationen

Folgende Applikationen musst du auf deinem Gerät installiert haben:

  • Visual Studio Code - Code-Editor
  • NodeJS - JavaScript Runtime
  • Chrome - Browser für Entwicklung

Für jede Applikation findest du eine detaillierte Beschreibung in diesem Setup-Bereich.

Visual Studio Code

Visual Studio Code wird das wichtigste während dem Kurs sein. Damit erstellst du den Code für die Webseite. Du wirst rasch sehen, wie viel Unterstützung dir VS Code dabei bietet.

Download

  1. Besuche die Seite Vs Code Download
  2. Wähle die Version deines Betriebssystems und klicke auf den großen Button. Dieser wählt in der Regel die beste Option für deine Arbeitsumgebung.

VS Code Download

Settings

Öffne die Einstellungen im VS Code. Passe folgendes an:

SuchbegriffEinstellung
editor.linkedEditingAktiviere "linked editing" Linked Editing
saveÜberprüfe die Einstellungen zu "save". Mindestens "Auto Save" → onFocusChange aktivieren ergibt Sinn. Zudem kannst du auch einstellen, dass z.B. dein File automatisch formatiert wird Save Settings
Liveserver (zuerst Plugin installieren)Custom Browser Klicke dann auf Settings JSON und erweitere das JSON so, dass es den Wert fürs Debug Attach auf true hat! Attach Debug

Plugins

Sobald du Visual Studio Code (VS Code) installiert hast, öffne es. Damit das Leben mit VS Code noch angenehmer wird, kannst du noch ein paar Plugins installieren. Dafür gehst du in deinem VS Code auf den Bereich "Erweiterungen" und gibst den Namen oben ein. Danach klickst du jeweils auf "Installieren". Die Version kann sich unter Umständen erhöht haben, wenn du die Erweiterungen installierst. Die Angabe unten ist die Mindest-Version.

VS Code Extensions

Erforderliche Plugins

PluginVersionBeschreibung
Highlight Matching Tag3.xFärbt Klammern / Code die zusammenpassen ein
Path Intellisense16.xVereinfacht die Angabe von Pfaden
Prettier - Code Formatter58.xKann den Code automatisch formatieren. Hilft, um eine bessere Übersicht zu haben
Live Server64.xWeb Server, damit die Webseite "richtig" ausgeführt werden kann

Optionale Plugins

Für den Kurs nicht zwingend nötig, können aber allenfalls zusätzlich unterstützen:

PluginVersionBeschreibung
German Language Pack for Visual Studio Code4.xDamit kannst du VS Code in Deutsch nutzen

NodeJS

NodeJS verwenden wir nur im Hintergrund. Dieses Tool ist dafür zuständig, dass dein Computer JavaScript ausführen kann. Da wir eine Webseite programmieren, welche auf JavaScript aufbaut, ist dieses Tool essentiell.

Download und Installation

Wähle auf der Seite Node Download LTS und danach die 64-Bit Version von Windows bzw. Mac.

Node Download

Bei den Installationsschritten kannst du überall den Standard belassen und dich bis zum Abschluss durchklicken.

Alternative Installation ohne Admin-Rechte

Wenn du keine Admin-Rechte hast, kannst du diese Anleitung verwenden: Install without Admin Rights

Online-Alternativen

Ansonsten können folgende Online-Tools verwendet werden:

  • Browser (JavaScript Console)
  • StackBlitz - Online-Entwicklungsumgebung
  • Programiz Online Compiler - online compiler
  • Andere beliebige Node.js Online-Tools

Chrome

Falls du den Chrome Browser noch nicht installiert hast, ist es jetzt an der Zeit. Die Entwicklungstools von Chrome bieten einiges, was andere Browser leider nicht mitbringen. Du kannst den Browser nach dem Kurs auch wieder löschen und ihn vor allem zum Fehler suchen einsetzen.

Download und Installation

  1. Öffne download Chrome
  2. Der dort angezeigte Download-Knopf sollte eigentlich immer der korrekte für dein Betriebssystem sein
  3. Die Installationsschritte sind selbsterklärend - befolge sie bis zum Abschluss der Installation

🌐 HTML Einstieg

Grundlagen lernen

Am einfachsten lernst du die HTML-Grundlagen im Tutorial-Kurs von FreeCodeCamp. Mit dem Abschluss des HTML-Teils solltest du die wichtigsten Tags kennen, die wir auch in unserem Beispiel nutzen werden.


Erste HTML-Seite erstellen

Schritt 1: Projekt-Setup

  1. Visual Studio Code öffnen
  2. "Ordner öffnen" wählen

Ordner öffnen

  1. Passenden Ordner anlegen (z.B. "myFirstWebpage")
  2. Öffnen des Ordners bestätigen

Schritt 2: HTML-Datei erstellen

  1. Neues File anlegen: index.html

  2. Emmet-Abkürzung verwenden:

  3. Grundgerüst sollte automatisch erstellt werden:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

Schritt 3: LiveServer starten

  1. Rechtsklick auf index.html
  2. "Open with LiveServer" wählen

Open with LiveServer

💡 Hinweis: Falls der Button nicht verfügbar ist, überprüfe dein Setup und installiere die LiveServer Extension (siehe Abschnitt "Setup")

Schritt 4: Browser-Ansicht

  • Automatisches Öffnen im Browser-Fenster
  • Standard-Port: 5500
  • Live-Reload: Änderungen werden sofort angezeigt

LiveServer konfigurieren

Custom Browser einstellen

  1. Einstellungen öffnen
  2. Nach "liveserver" suchen
  3. Custom Browser einstellen

LiveServer Custom Browser

Erweiterte Konfiguration

Für weitere Einstellungen kannst du alles in der settings.json bearbeiten:

{
  "liveServer.settings.port": 5500,
  "liveServer.settings.CustomBrowser": "chrome",
  "liveServer.settings.donotShowInfoMsg": true
}

Nächste Schritte

Nach dem erfolgreichen Setup kannst du mit dem Blog Page Tutorial fortfahren und deine erste komplette Webseite erstellen!

✅ Was du jetzt kannst:

  • HTML-Grundgerüst erstellen
  • LiveServer verwenden
  • Live-Reload für Entwicklung nutzen
  • VS Code für Web-Entwicklung konfigurieren

📄 Blog Page erstellen

Projekt-Ziel

Wir bauen eine Blog Page nach dem Vorbild von Galaxus Beispiel-Artikel.

Die Page enthält:

  • Video/Bild als Slide
  • Fazit-Bereiche
  • Tabellen und Bilder
  • Produkt-Kacheln
  • Empfohlene Blog-Pages
  • Like- und Kommentar-Funktionen

Entwicklungsansatz

Struktur vor Design: Wir fokussieren uns zunächst auf das HTML-Grundgerüst. Das Design kommt später mit CSS.

⚠️ Hinweis: Ignoriere die linke und rechte Spalte. Wir konzentrieren uns ausschließlich auf die Blog Page selbst.


Mindest-Anforderungen

✅ Kern-Elemente (Must-have)

  1. Video oder Bild als Slide (freie Tag-Wahl)
  2. Rubrik
  3. Titel im H1-Format
  4. Lead-Text
  5. Haupttext
  6. Weitere Überschriften im H2-Format (max. ein H1 pro Page!)
  7. Weitere Bilder
  8. Gefällt mir Button
  9. Empfehlungen mit weiteren Blog Pages

🌟 Erweiterte Elemente (Nice-to-have)

Wenn Zeit vorhanden:

  1. Autor:innen-Bereich
  2. Tabelle
  3. Fazit-Box
  4. Produkt-Kachel
  5. Autor:innen-Box mit Folgen-Button
  6. Thema mit Folgen-Button

🚫 Verzicht: Community-Bereich (zu komplex für Einstieg)


HTML-Grundgerüst erstellen

Schritt 1: Projekt-Setup

  1. VS Code öffnen
  2. Ordner für Übungen anlegen
  3. GitHub Repository für Referenz: webEngineerDgEditors

Schritt 2: Basis-Datei erstellen

  1. index.html anlegen
  2. Grundgerüst mit ! + Tab erstellen
  3. LiveServer starten für sofortige Änderungsanzeige
  4. Titel definieren

Schritt 3: Body-Struktur aufbauen

A. Media-Bereich

<!-- Video/Image als Slide -->
<iframe 
  src="YOUTUBE_EMBED_URL" 
  title="Video Titel">
</iframe>
<!-- ODER -->
<img src="bild.jpg" alt="Beschreibung">

B. Content-Header

<!-- Rubrik -->
<p class="category">Kategorie</p>

<!-- Haupttitel -->
<h1>Artikel-Titel</h1>

<!-- Lead-Text -->
<h2>Lead-Text oder Untertitel</h2>

C. Hauptinhalt

<!-- Text-Abschnitte -->
<p>Erster Textabschnitt...</p>

<!-- Gruppierte Inhalte -->
<section>
  <h3>Zwischenüberschrift</h3>
  <p>Zugehöriger Text...</p>
  <img src="bild.jpg" alt="Beschreibung">
</section>

D. Interaktions-Elemente

<!-- Gefällt mir Button -->
<button>❤️ Gefällt mir</button>

E. Empfehlungen

<!-- Empfohlene Artikel -->
<section>
  <h2>Diese Beiträge könnten dich auch interessieren</h2>
  <!-- Struktur für empfohlene Pages -->
</section>

Content-Strategien

Text-Optionen

MethodeBeschreibungVerwendung
Lorem Ipsumlorem + Tab in VS CodePlatzhalter-Text
Original-ContentVon Beispiel-Seite kopierenRealitätsnaher Content
Eigener ContentSelbst geschriebene TextePersonalisierte Inhalte

Semantische HTML-Struktur

Verwende logische Gruppierungen:

<!-- Beispiel: Gruppierte Inhalte -->
<article>
  <section class="intro">
    <h1>Titel</h1>
    <p>Lead</p>
  </section>
  
  <section class="content">
    <h2>Abschnitt</h2>
    <p>Text</p>
  </section>
  
  <section class="recommendations">
    <h2>Empfehlungen</h2>
    <!-- Empfohlene Artikel -->
  </section>
</article>

Best Practices

HTML-Struktur

  • Einen H1 pro Page
  • Logische Heading-Hierarchie (H1 → H2 → H3)
  • Semantische Tags verwenden (article, section, aside)
  • Alt-Attribute für alle Bilder

Media-Integration

YouTube-Video einbetten:

<iframe 
  width="560" 
  height="315" 
  src="https://www.youtube.com/embed/VIDEO_ID" 
  title="Video Titel"
  frameborder="0" 
  allowfullscreen>
</iframe>

Responsive Bilder:

<img 
  src="bild.jpg" 
  alt="Beschreibende Bildbeschreibung"
  width="100%">

Entwicklungsworkflow

Schritt-für-Schritt-Checklist

  1. HTML-Grundgerüst erstellen
  2. LiveServer starten
  3. Media-Bereich implementieren
  4. Content-Header aufbauen
  5. Hauptinhalt strukturieren
  6. Interaktions-Elemente hinzufügen
  7. Empfehlungen-Bereich erstellen
  8. Erweiterte Elemente (optional)

Iterativer Ansatz

  1. Basis-Version mit Mindest-Elementen
  2. Schritt-für-Schritt-Erweiterung
  3. Kontinuierliche Browser-Tests
  4. Spätere CSS-Integration

🚀 Nächste Schritte

Nach Fertigstellung der HTML-Struktur:

  1. CSS-Styling hinzufügen
  2. Responsive Design implementieren
  3. JavaScript-Interaktivität einbauen
  4. Performance-Optimierung

Ziel erreicht: Solide HTML-Basis für eine professionelle Blog Page! 🎉

🎨 CSS Einstieg

💡 Was ist CSS?

CSS steht für Cascading Style Sheets und bildet die Grundlage des Designs für jede Webseite.

🔍 Wichtig: Sogar wenn du kein eigenes CSS definiert hast, gibt es ein CSS! Jeder Browser definiert dies über das "User Agent Stylesheet".


🔧 CSS im Browser untersuchen

📋 Schritt-für-Schritt-Anleitung

1. Beispiel-Seite öffnen

  • Galaxus Artikel in Chrome öffnen
  • Rechtsklick auf einen Bereich der Seite
  • "Untersuchen" auswählen (oder F12 drücken)

Chrome Entwicklertools öffnen

2. Entwicklertools verstehen

Das Entwickler-Werkzeug wird auf der rechten Seite angezeigt. Die Position lässt sich umschalten.

Platzierung ändern

3. Auswahl-Werkzeug aktivieren

  • Auswahl-Werkzeug in der Toolbar aktivieren
  • Ermöglicht das direkte Anklicken von Elementen

Auswahl-Werkzeug aktivieren

4. Element inspizieren

  • Überschrift oder anderes Element anklicken
  • CSS-Eigenschaften werden rechts unter "Styles" angezeigt

Element auswählen


🏗️ CSS-Hierarchie verstehen

📊 Kaskadierung (Cascading)

Von unten nach oben gelesen:

PrioritätQuelleBeschreibung
NiedrigsteUser Agent StylesheetBrowser-Standard CSS
MittlereWebsite CSSEntwickler-definierte Styles
HöchsteSpezifische SelektorenÜberschreibende Definitionen

🔍 Was du in den DevTools siehst:

  • 📋 User-Agent-Stylesheet: Browser-Standard (unterste Ebene)
  • 🎯 Hierachische Definitionen: Von unten nach oben
  • ✂️ Durchgestrichene Regeln: "Verlierende" CSS-Definitionen
  • 🏆 Gewinnende Definition: Letzte, spezifischste Regel
  • 🤖 Framework-Klassen: Z.B. .yDkbjfY3 (automatisch generiert)

💡 Beispiel-CSS-Eigenschaft

margin-bottom: 16px; /* 16 Pixel Abstand unterhalb des Elements */

📚 CSS lernen

🎯 Empfohlene Lernressourcen

🚀 FreeCodeCamp Tutorial

Link: Full Stack Developer Kurs

Vorgehen:

  1. CSS-Sektion auswählen
  2. Computer Basics überspringen (falls bereits bekannt)
  3. Responsive Web Design für Fortgeschrittene

📖 Lernweg-Struktur

1. CSS Basics
   ├── Selektoren
   ├── Eigenschaften  
   └── Werte

2. Layout-Konzepte
   ├── Box Model
   ├── Flexbox
   └── CSS Grid

3. Responsive Design
   ├── Media Queries
   ├── Breakpoints
   └── Mobile First

🎨 Praktische Anwendung

🎯 Nächster Schritt: Blog-Seite stylen

Nach dem Tutorial-Durchlauf:

  1. ✅ HTML-Grundgerüst (bereits vorhanden)
  2. 🎨 CSS-Design hinzufügen
  3. 📱 Responsive machen
  4. ✨ Interaktivität erweitern

🔥 Ziel: Die Blog-Seite von einem reinen HTML-Gerüst zu einer ansprechend gestylten Webseite verwandeln!


💡 Best Practices

✅ CSS-Grundregeln

  • 📝 Kommentare für komplexe Bereiche
  • 🏗️ Logische Struktur in CSS-Dateien
  • 🎯 Spezifische Selektoren verwenden
  • 🔄 Wiederverwendbare Klassen erstellen

🔧 Browser-DevTools nutzen

  • 🔍 Live-Editing für schnelle Tests
  • 📊 Computed Styles für finale Werte
  • 🎯 Element-Inspektion für Debugging
  • 📱 Device-Simulation für Responsive Tests

🚀 Workflow-Empfehlung

📋 Entwicklungsschritte

  1. 🖼️ Design analysieren (Galaxus-Beispiel)
  2. 🏗️ HTML-Struktur überprüfen
  3. 🎨 CSS schrittweise hinzufügen
  4. 🔄 Browser-Tests kontinuierlich
  5. 📱 Responsive optimieren

Bereit für kreatives CSS-Design! 🎉

📱 Responsive Web Design

🕰️ Die Evolution des Web Designs

📊 2009: Die einfache Zeit

Im Jahr 2009 war noch alles viel einfacher. Die meisten User nutzten eine Bildauflösung von 1024x768 Pixel - dem damaligen Desktop-Standard. Statistiken 2009

Alle Auflösungen waren im Desktop-Segment angesiedelt. Die Welt des Web-Engineers war einfach:

  • Eine Seite für alle
  • <table> Tags für Layout
  • Feste Breiten funktionierten perfekt

SBB Seite Alte Website-Darstellung

Apple Seite Alte Website-Darstellung

📈 2014: Der Mobile Wandel

Statistiken 2014

Deutlich kleinere Auflösungen dominieren das Chart:

  • 📱 Smartphones an der Spitze
  • "Others" deutlich gestiegen
  • 💥 Paradigmenwechsel: Responsive Design wird Pflicht

🚨 Realität: Smartphones haben der Webentwicklung einen Dämpfer verpasst. Plötzlich müssen Seiten auch auf relativ kleinen Auflösungen gut aussehen.


📱 Mobile First & Responsive Design

🍎 Das iPhone-Problem (2008)

Ursprüngliche Lösung: Webseiten wurden "gezoomed" dargestellt

  • Browser verkleinerte Desktop-Darstellung
  • Zoom-Geste ermöglichte Navigation
  • ❌ Problem: Nicht zielführend für positive User Experience

🔧 Die Viewport-Lösung

Emmet-Shortcut ! erstellt automatisch:

<meta name="viewport" content="width=device-width, initial-scale=1" />

Was passiert:

  • Browser zeigt Webseite 1:1 dar (keine Verkleinerung)
  • 400px Button = volle Breite auf 400px-Screen
  • Verantwortung liegt bei der Entwicklerin

📖 Mehr Infos: W3Schools Viewport Guide


🏗️ Mobile First Strategie

📋 Konzept-Übersicht

Mobile First = Design beginnt mit kleinster Auflösung

📱 320px (Mobile)  →  📲 768px (Tablet)  →  🖥️ 1200px (Desktop)

🎯 Breakpoint-System

Breakpoint = Bestimmte Auflösung, wo sich das Design anpassen muss

Beispiel-Szenario:

  • Button bis 400px: width: 100%
  • Button ab 450px: Anpassung nötig ❌
  • Lösung: Breakpoint bei 400px definieren

📊 Bootstrap Breakpoints (Beispiel)

Bootstrap Breakpoints

Mobile First erkennbar:

  • Erster Breakpoint: min-width: 576px
  • Bis dahin: "Kleinster Screen" als Basis
  • Alle weiteren: min-width für schrittweise Erweiterung

🛠️ Praktische Umsetzung

📁 Projekt-Setup

Schritt 1: Ordner-Struktur

  1. Neuen Ordner erstellen
  2. VS Code öffnen
  3. Files erstellen: index.html + main.css

Setup Beispiel

Schritt 2: Grid-System vorbereiten

W3Schools Referenz: CSS RWD Intro

12-Spalten-System (bewährter Standard):

  • Flexible Aufteilung für verschiedene Layouts
  • Prozentuale Breiten für responsive Verhalten

📂 Grid CSS erstellen

Ordner-Struktur:

project/
├── index.html
├── main.css
└── styleguide/
    └── grid.css

Grid-Klassen definieren: (grid.css)

.col-1 {
  width: 8.33%;
}
.col-2 {
  width: 16.66%;
}
.col-3 {
  width: 25%;
}
.col-4 {
  width: 33.33%;
}
.col-5 {
  width: 41.66%;
}
.col-6 {
  width: 50%;
}
.col-7 {
  width: 58.33%;
}
.col-8 {
  width: 66.66%;
}
.col-9 {
  width: 75%;
}
.col-10 {
  width: 83.33%;
}
.col-11 {
  width: 91.66%;
}
.col-12 {
  width: 100%;
}

🔧 HTML-Grundgerüst

index.html Body:

<body>
  <h1>Responsive is cool</h1>
  <div class="col-3">first column</div>
  <div class="col-3">second column</div>
  <div class="col-3">third column</div>
  <div class="col-3">fourth column</div>
</body>

Box-Sizing hinzufügen (main.css):

* {
  box-sizing: border-box;
}

Grid-Styling erweitern (grid.css):

[class*="col-"] {
  float: left;
  padding: 15px;
  border: 1px solid red;
}

🔗 CSS verknüpfen

HTML Head erweitern:

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <link rel="stylesheet" href="main.css" />
</head>

CSS Import (main.css):

@import "styleguide/grid.css";

Jetzt sollte deine Page wie folgt aussehen. Erste Ansicht

📐 Row-System implementieren

Was noch fehlt ist die Möglichkeit, einzelne Zeilen zu definieren. Wir möchten sicherstellen, dass sich die Verteilung nur auf einen bestimmten Bereich bezieht. Dafür benötigen wir noch folgende css Anweisung in unserem grid.css file

Row-CSS hinzufügen (grid.css):

.row::after {
  content: "";
  clear: both;
  display: table;
}

HTML mit Rows strukturieren:

<body>
  <h1>Responsive is cool</h1>
  <div class="row">
    <div class="col-3">first column</div>
    <div class="col-3">second column</div>
    <div class="col-3">third column</div>
    <div class="col-3">fourth column</div>
  </div>
</body>

Spiele nun etwas mit der Breite des Bildschirms. Du wirst nun allenfalls feststellen, dass nicht alle Breiten gut aussehen. Je Breiter der Bildschirm, desto mehr unnötige Abstände produzieren wir. Genau das wollen wir nun verbessern, indem wir im Zusammenspiel mit den MediaQueries unser Grid erst richtig erweitern.

Da wir mobile-first designen, kümmern wir uns um die Namens Zusätze erst ab dem ersten Breakpoint. Also in unserem Beispiel der Breakpoint mit dem Namen sm für Small


📱 Responsive Breakpoints

🎯 Media Queries implementieren

Mobile First Basis-Grid:

/* Mobile (Standard - kein Breakpoint nötig) */
.col-1 {
  width: 8.33%;
}
.col-2 {
  width: 16.66%;
}
/* ... bis col-12 */

Small Devices (576px+):

@media (min-width: 576px) {
  .col-sm-1 {
    width: 8.33%;
  }
  .col-sm-2 {
    width: 16.66%;
  }
  .col-sm-3 {
    width: 25%;
  }
  .col-sm-4 {
    width: 33.33%;
  }
  /* ... bis col-sm-12 */
}

💡 Responsive HTML-Beispiel

Nun hast du die Möglichkeit, das Layout für jeden Breakpoint zu definieren. Mache bspw. mal col-12 im mobile, für sm danach col-sm-3 und siehe wie sich das Layout nun verhält.

<div class="row">
  <div class="col-12 col-sm-3">first column</div>
  <div class="col-12 col-sm-3">second column</div>
  <div class="col-12 col-sm-3">third column</div>
  <div class="col-12 col-sm-3">fourth column</div>
</div>

Verhalten:

  • Mobile: 4 Zeilen (je 100% Breite)
  • ab 576px: 4 Spalten (je 25% Breite)

🤔 Library vs. Custom Solution

Nun hast du gesehen, dass es nicht unbedingt Magie ist, ein Grid selber zu programmieren. Damit alles funktioniert, fehlen trotzdem noch einige CSS Klassen, die noch definiert werden müssen. Daher stellt sich doch gleich die Frage, wieso nicht von Anfang an Bootstrap oder andere CSS Bibliotheken einsetzen? Wenn dein Projekt keine speziellen Anforderungen hat, du mit grundsätzlich 12 Spalten problemlos dein Design umsetzen kannst, dann kannst du gut und gerne eine Bibliothek wie Bootstrap einsetzen. Trotzdem muss man sich immer bewusst sein, dass der Einsatz einer solchen Bibliothek auch erfordert, dass man sie korrekt versteht und entsprechend korrekt einsetzt. Du musst die Klassen verstehen und wissen, welche für was eingesetzt wird. Ein einfaches Beispiel mit Bootstrap sieht wie folgt aus

<div class="container">
  <div class="row">
    <div class="col-sm">One of three columns</div>
    <div class="col-sm">One of three columns</div>
    <div class="col-sm">One of three columns</div>
  </div>
</div>

⚖️ Entscheidungskriterien

AspektBootstrapCustom Grid
⚡ Setup-ZeitSchnellLänger
🎯 FlexibilitätBegrenztVollständig
📚 LernaufwandFramework lernenCSS verstehen
🔧 AnpassungenWorkaroundsDirekte Kontrolle
📦 Bundle-SizeGrößerMinimal

✅ Bootstrap verwenden, wenn:

  • 🚀 Schnelle Prototypen erstellen
  • 12-Spalten-System ausreicht
  • Standard-Designs umsetzen
  • Team bereits Bootstrap-Erfahrung hat

✅ Custom Grid verwenden, wenn:

  • 🎨 Unique Designs entwickeln
  • Volle Kontrolle benötigt
  • Performance kritisch ist
  • Lernziele CSS-Verständnis beinhalten

🚀 The Modern Way: CSS Grid

🌟 CSS Grid Vorteile (seit 2016/2017)

Warum CSS Grid?

  • 🏗️ 2D-Layout (Zeilen + Spalten)
  • 🎯 Intuitive Syntax
  • 📱 Native Browser-Support
  • ⚡ Performance-optimiert

📚 CSS Grid Lernressourcen

ResourceTypBeschreibung
CSS Grid Garden🎮 SpielInteraktives Lernen
Scrimba CSS Grid🎥 VideoHands-on Kurs
MDN Grid Guide📖 DocsUmfassende Referenz

🔧 CSS Grid Implementation

Variablen definieren:

:root {
  --columns-amount: 12;
  --gap-small: 20px;
}

Grid-Container:

.row {
  display: grid;
  grid-template-columns: repeat(var(--columns-amount), 1fr);
  grid-gap: var(--gap-small);
}

Du wirst feststellen, dass wir bereits eine vertikale Verteilung haben. Nun möchten wir noch so was wie zuvor haben und die jeweilige Breite festlegen. Dafür müssen wir unser grid.css einfach wiederum um die breakpoints erweitern. Anstelle von breiten verwenden wir jedoch ein grid-column span.

Responsive Grid-Spans:

/* Mobile */
.col-12 {
  grid-column: span 12;
}

/* Small Devices */
@media (min-width: 576px) {
  .col-sm-4 {
    grid-column: span 4;
  }
}

/* Medium Devices */
@media (min-width: 768px) {
  .col-md-3 {
    grid-column: span 3;
  }
}

🎯 Advanced CSS Grid Features

Du hast nun ein Grid ähnliches Layout mit CSS Grid als Basis gebaut. Überlege dir folgende Anforderung. Nebst dem eigentlichen Inhalt, der auf 12 Spalten verteilt ist, möchtest du nun auf einer virtuellen ersten bzw. letzten Spalte bei Bedarf einen "Seiten-Content" einfügen. Responsive Breiten

Der Rest soll sich weiterhin verteilen wie zuvor. Das Element auf der Seite soll sich an der Höhe orientieren, wo auch der entsprechende Inhalt steht. Überleg dir mal, wie du das ohne CSS Grid machen würdest. Damit das Beispiel funktioniert, kopiere folgendes HTML in dein index.html

<body>
  <h1>Responsive is cool</h1>
  <div class="row">
    <button class="col-12 col-sm-4 col-md-3">button 1</button>
    <button class="col-12 col-sm-4 col-md-3">button 2</button>
    <button class="col-12 col-sm-4 col-md-3">button 3</button>
    <button class="col-12 col-sm-4 col-md-3">button 4</button>
    <button class="col-12 col-sm-4 col-md-3">button 5</button>
    <button class="col-12 col-sm-4 col-md-3">button 6</button>
    <button class="col-12 col-sm-4 col-md-3">button 7</button>
    <button class="col-12 col-sm-4 col-md-3">button 8</button>
    <button class="col-12 col-sm-4 col-md-3">button 9</button>
    <button class="col-12 col-sm-4 col-md-3">button 10</button>
    <button class="col-12 col-sm-4 col-md-3">button 11</button>
    <button class="col-12 col-sm-4 col-md-3">button 12</button>
    <button class="col-12 col-sm-4 col-md-3">button 13</button>
    <button class="col-12 col-sm-4 col-md-3">button 14</button>
    <button class="col-12 col-sm-4 col-md-3">button 15</button>
    <button class="col-12 col-sm-4 col-md-3">button 16</button>
    <button class="col-12 col-sm-4 col-md-3">button 17</button>
    <button class="col-12 col-sm-4 col-md-3">button 18</button>
    <button class="col-12 col-sm-4 col-md-3">button 19</button>
    <button class="col-12 col-sm-4 col-md-3">button 20</button>
    <button class="col-12 col-sm-4 col-md-3">button 21</button>
    <button class="col-12 col-sm-4 col-md-3">button 22</button>
    <button class="col-12 col-sm-4 col-md-3">button 23</button>
    <button class="col-12 col-sm-4 col-md-3">button 24</button>
    <button class="col-12 col-sm-4 col-md-3">button 25</button>
    <button class="col-12 col-sm-4 col-md-3">button 26</button>
    <button class="col-12 col-sm-4 col-md-3">button 27</button>
    <button class="col-12 col-sm-4 col-md-3">button 28</button>
    <button class="col-12 col-sm-4 col-md-3">button 29</button>
    <button class="col-12 col-sm-4 col-md-3">button 30</button>
    <button class="col-12 col-sm-4 col-md-3">button 31</button>
    <button class="col-12 col-sm-4 col-md-3">button 32</button>
    <button class="col-12 col-sm-4 col-md-3">button 33</button>
    <button class="col-12 col-sm-4 col-md-3">button 34</button>
    <button class="col-12 col-sm-4 col-md-3">button 35</button>
    <button class="col-12 col-sm-4 col-md-3">button 36</button>
    <button class="col-12 col-sm-4 col-md-3">button 37</button>
    <button class="col-12 col-sm-4 col-md-3">button 38</button>
    <button class="col-12 col-sm-4 col-md-3">button 39</button>
    <button class="col-12 col-sm-4 col-md-3">button 40</button>
    <button class="col-12 col-sm-4 col-md-3">button 41</button>
    <button class="col-12 col-sm-4 col-md-3">button 42</button>
    <button class="col-12 col-sm-4 col-md-3">button 43</button>
    <button class="col-12 col-sm-4 col-md-3">button 44</button>
    <button class="col-12 col-sm-4 col-md-3">button 45</button>
    <button class="col-12 col-sm-4 col-md-3">button 46</button>
    <button class="col-12 col-sm-4 col-md-3">button 47</button>
    <button class="col-12 col-sm-4 col-md-3">button 48</button>
    <button class="col-12 col-sm-4 col-md-3">button 49</button>
    <button class="col-12 col-sm-4 col-md-3">button 50</button>
  </div>
</body>

Erweiterte Spalten:

Damit wir unser .aside links bzw. rechts hinzufügen können, müssen wir als erstes dem ganzen Inhalt weitere Spalten hinzufügen. Das einfachste ist, unserer "row" Klasse diese Spalten hinzuzufügen. Dies machen wir nun im grid.css und passen einfach unsere grid-template-columns definition der .row klasse an.

.row {
  display: grid;
  grid-template-columns: 150px repeat(var(--columns-amount), 1fr) 150px;
  grid-gap: var(--gap-small);
}

Nun wird die Page etwa so aussehen: Responsive Breiten

Wir haben also erreicht, dass wir unseren Inhalt neu auf 14 Spalten verteilen. Was wir aber nicht möchten, ist, dass die erste Spalte durch Inhalt belegt wird. Wir möchten alles eines nach "rechts" verschieben. Wie erreichen wir das rein nur über CSS? Wir wollen keine Klasse im HTML anpassen müssen.

grid-span:

Damit wir das Ziel erreichen, können wir unser CSS mit folgendem Attribut erweitern:

.col-12 {
  grid-column: 2 / span 12;
}

Teste die entsprechende Auflösung. Sieht es gut aus? Das ganze benötigen wir nun auch für unsere anderen Auflösungen. Benutze Media Queries und erweitere die col-x Klassen entsprechend. Wir verzichten auf alle col-definitionen, da wir nur entweder 12 spalten, 4 oder 3 brauchen (col-12, col-sm-4, col-md-3).

/* Mobile-Spaltenklassen */
col-12 {
  grid-column: 2 / span 12;
}

/* Spaltenklassen für Breakpoint 576px (sm) */
@media (min-width: 576px) {
  .col-sm-4 {
    grid-column: 2 / span 4;
  }
}

/* Spaltenklassen für Breakpoint 768px (md) */
@media (min-width: 768px) {
  .col-md-3 {
    grid-column: 2 / span 3;
  }
}

Teste ein wenig das neue Layout. Folgende Dinge sollten dir auffallen:

  • Je nach Grösse des Bildschirms haben wir einen etwas zu grossen gap
  • Je nach Grösse des Bildschirms sind die zusätzlichen Spalten am Anfang und Ende zu breit
  • Die Höhe der Elemente ist teilweise unterschiedlich
  • Die Elemente sind nun immer ab der zweiten Spalte gereiht und nicht mehr verteilt Alle dies Dinge wollen wir nun beheben. Die einfachste ist auf jeden Fall das Gap und die Spaltenbreite.

Passe daher dein css entsprechend an. Wir setzen Variablen ein, die wiederum mit Media-Queries definiert werden. So können wir die Angaben automatisch setzen, basierend auf der Viewport Grösse. Genius 🤓

:root {
  --columns-amount: 12;
  --column-width: 50px;
  --gap-width: 20px;

  @media (min-width: 768px) {
    --column-width: 150px;
    --gap-width: 40px;
  }
}

.row {
  display: grid;
  grid-template-columns: var(--column-width) repeat(
      var(--columns-amount),
      1fr
    ) var(--column-width);
  grid-gap: var(--gap-width);
}

Gleiche Höhen:

Für das Problem mit der Höhe der Elemente bietet uns CSS Grid eine sehr einfach Eigenschaft an. Mit "grid-auto-rows" und dem Wert "1fr" weisen wir das Grid an, jeweils die selbe Höhe einzusetzen. Basierend auf der Mindest-Höhe, welches ein Element einnimmt. Du kannst beispielsweise bei einem Button den Text so lange machen, dass er noch höher wird. Achte dann darauf, was geschieht. Dafür erweitere einfach das css ganz am Anfang um den entsprechenden Wert.

.row {
  display: grid;
  grid-auto-rows: 1fr; /* Alle Elemente gleiche Höhe */
  grid-template-columns: var(--column-width) repeat(
      var(--columns-amount),
      1fr
    ) var(--column-width);
  grid-gap: var(--gap-small);
}

Das etwas mühsamere Problem ist, dass wir nun unsere Elemente verteilen möchten, sobald die Breite der Elemente weniger als 12 Spalten einnimmt. Daher für unsere col-sm-4 sowie col-md-3 Klassen benötigen wir eine bessere Lösung.

Wenn wir weiterhin von unserem 12er Grid ausgehen, müssen wir das CSS Grid immer nur für das 4te bzw. 3te Element anweisen, auf die erste Stelle zu springen. Dafür hat CSS den sehr schönen nth-child Selector. Mit diesem können wir genau unser CSS entsprechend erweitern. Passe nun also unsere bisherigen .col-sm-4 bzw. .col-md-3 Klassen so an.

nth-child selector:

  .col-sm-4:nth-child(3n+1) {
    grid-column: 2 / span 4;
  }

  .col-md-3:nth-child(4n+1) {
    grid-column: 2 / span 3;
  }

Wenn du es testet, sieht es irgendwie noch nicht so ganz toll aus. Weisst du wieso? Das Problem liegt leider genau wieder bei unserem neuen Selektor. Das nth-child von bspw. col-sm-4 wird auch bei einer Auflösung von mehr als unserem md-breakpoint appliziert. Wir haben sie also dreifach (col-12, col-sm-4, col-m-3). Wie beheben wir nun das Problem?

Es gibt zwei Varianten. Wir erweitern unsere Media-Queries, dass sie nicht nur ein Min-Width sondern auch ein Max-Width berücksichtigen. Dies hat den Nachteil, dass wir bei Anpassungen nach "oben" zusätzliche Schritte implementieren müssen. Der andere Ansatz ist, dass wir ganz den Mobile First Ansatz verfolgen und von klein nach gross dafür sorgen müssen, dass die CSS Angaben wichtiger sind als die vorherigen. Wir definieren also nicht nur unsere nth-child Elemente die rutschen sollen, sondern auch noch alle die bleiben sollen. Zusätzlich müssen wir auch dafür ein nth-child Selektor einsetzen, damit er als wichtiger betrachtet wird und die anderen Anweisungen überschreibt. Unser css sieht dann wie folgt aus:

/* ... von col-sm-1 */
  .col-sm-4:nth-child(1n + 1) {
    grid-column: span 4;
  }
  .col-sm-4:nth-child(3n + 1) {
    grid-column: 2 / span 4;
  }
  …
  .col-md-3:nth-child(1n + 1) {
    grid-column: span 3;
  }
  .col-md-3:nth-child(4n + 1) {
    grid-column: 2 / span 3;
  }
/* ... bis col-md-12 */

Erweitere dein HTML innerhalb der .row. Wo genau, spielt keine Rolle. Vergib die Klasse .aside

<div class="aside">I am an edge case</div>

Wie können wir nun das Element auf die Seite stellen? Einfacher als das. Mit unserem grid-column aus dem css grid haben wir nun die freie Möglichkeit, mit column und row zu definieren, wohin das Element soll. Füge im "grid.css" einfach folgende Klasse hinzu.

.aside {
  grid-column: 1
}

Sieht toll aus, oder? Du kannst etwas mit der Position spielen, span hinzufügen und/oder auch die Position definieren. Ein Beispiel dafür könnte wie folgt aussehen.

.aside {
  grid-column: 1;
  grid-row: span 13;
  place-self: center;
}

🔄 CSS Flexbox

Die bisherigen Layout Möglichkeiten beziehen sich sowohl auf die horizontale wie auch auf die vertikale Verteilung von Elementen. Man kann es sich auch so vorstellen, dass man damit sehr gut fährt, wenn man das eigentlich Design-Layout umsetzen möchte. Also wo gibt es auf der Seite Inhalt? Gibt es eine Navbar? Gibt es einen Side-Content? Gibt es einen Footer? Mit CSS Grid, Bootstrap oder Spalten Design lassen sich diese Art von Design / Layouts sehr gut strukturieren und umsetzen. Man könnte auch von 2 Dimensionalen Layouts sprechen. Nebst diesen Varianten hält seit einigen Jahren auch Flexbox Einzug im ganzen CSS Universum. Analog CSS Grid kennt der Browser CSS Flexbox direkt und wird unterdessen breit von den Browser unterstützt. Flexbox ist sogar älter als CSS Grid. Bei Flexbox sprechen wir nun eher von einem eindimensionalen Design. Wir können damit sehr gut Elemente auf der selben Ebene layouten. Sei dies auf der vertikalen oder horizontalen Ausrichtung.

🎯 Flexbox vs. Grid

FeatureCSS GridCSS Flexbox
📐 Dimensionen2D (Zeilen + Spalten)1D (Eine Richtung)
🎯 Use CasePage LayoutComponent Layout
📱 ResponsiveBreakpoint-basiertContent-basiert
🔧 KomplexitätHöherEinfacher

📚 Flexbox Lernressourcen


🎯 Technologie-Entscheidung

🤔 Die richtige Wahl treffen

Fragen zur Orientierung:

  1. 🆕 Neues Projekt oder bestehende Libraries?
  2. 👥 Team-Erfahrung mit welcher Technologie?
  3. 🎨 Layout-Flexibilität: Immer 12 Spalten oder variabel?
  4. 📱 Device-Support: Welche Breakpoints nötig?

💡 Empfehlungen:

SzenarioEmpfehlungBegründung
🚀 Neue WebsiteCSS Grid + FlexboxModern, flexibel, performant
🔧 Bestehende Bootstrap-SiteBootstrapKonsistenz wahren
🎯 Einfache LayoutsFlexboxWeniger Overhead
🏗️ Komplexe LayoutsCSS Grid2D-Kontrolle

⚠️ Anti-Patterns vermeiden

  • ❌ Bootstrap + CSS Grid Mix (Konflikte)
  • ❌ Float-basierte Layouts (veraltet)
  • ❌ Table-Layout für Design (nicht semantisch)

🎉 Zusammenfassung

✅ Was du gelernt hast:

  1. 📱 Mobile First Prinzip
  2. 🔧 Custom Grid System
  3. 📊 Breakpoint-Strategien
  4. 🚀 CSS Grid Modernisierung
  5. ⚖️ Technologie-Entscheidungen

🎯 Nächste Schritte:

  1. 💻 Praktisches Üben mit eigenem Projekt
  2. 🎮 CSS Grid Garden durchspielen
  3. 🐸 Flexbox Froggy meistern
  4. 🏗️ Blog-Seite responsive machen

Du bist bereit für modernes, responsives Webdesign! 🌟

🎨 Blog Page mit CSS

🎯 Was wir designen

Analog zum HTML-Teil werden wir uns nicht um alle Elemente kümmern bzw. nur wenn die Zeit reicht.

✅ Diese Elemente setzen wir sicher um:

  • Textgrössen inklusive Überschriften etc.
  • Video oder Bild als Slide (du kannst aktuell wählen welches Tag du hier verwendest)
  • Rubrik
  • Weitere Bilder
  • Buttons
  • Gefällt mir Bereich
  • Blog Page Preview inklusive Ansicht für "weitere spannende Artikel"

🌟 Folgende Elemente wenn Zeit vorhanden:

  • Autor*innen Bereich
  • Tabellen
  • Fazit Box
  • Produkt Kachel
  • Autor*innen Box mit Folgen-Button
  • Thema mit Folgen-Button

📐 Zuerst mal das Grobe

Bei CSS macht es Sinn, zunächst das Grobe zu definieren. Du kannst dir das vorstellen wie auch bei einem Schreibprogramm wie Word. Zuerst versuchst du mal Überschriften, Seitenränder etc. zu optimieren und erst später z.B. auch noch Tabellen, Aufzählungen und weiter ganz nach deinem Geschmack.

🗺️ Basis

Setup-Prozess

Damit wir überhaupt CSS ergänzen können, brauchen wir unser CSS:

Schritt 1: Neuen Ordner erstellen

  • Neuen Ordner erstellen: html_blog_page_with_css

Schritt 2: HTML kopieren

  • HTML aus der HTML-Übung in den neuen Ordner kopieren
  • Alternative: Von GitHub herunterladen

Schritt 3: CSS-Datei erstellen

  • main.css im neuen Ordner erstellen

Schritt 4: CSS verknüpfen

Im index.html den CSS-Import ergänzen:

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>blog page 37832</title>
    <link rel="stylesheet" href="main.css">
</head>

✅ Jetzt kannst du in diesem CSS Styles für deine Webseite einsetzen!


Das Ding mit den User Agent Stylesheets

Wir wollen als erstes den äusseren Rand der Seite definieren. Nun kriegen wir etwas das Problem, dass es User Agent Stylesheets gibt, die uns allenfalls in die Quere kommen. Wir wollen also nicht einfach einen neuen Rand hinzufügen, sondern den bestehenden anpassen.

Margin-Problem analysieren

Schritt 1: LiveServer starten

  • Rechtsklick auf index.html
  • "LiveServer" auswählen

Schritt 2: Chrome DevTools öffnen

  • Seite in Chrome öffnen
  • Entwickler-Werkzeug aktivieren

Schritt 3: Element inspizieren

  • Auswahl-Werkzeug aktivieren
  • Mit Maus über verschiedene Elemente fahren
  • Bei "äusseren" Elementen: Orange Farbe um die ganze Page

Margin-Analyse

Schritt 4: Margin verstehen

  • Orange Farben = Margins
  • Grüne Farben = Paddings
  • Body-Element hat 8px margin als Standard

Margin mit Farben

💡 Erkenntniss: Wir müssen den body-Selektor übersteuern, um genau diesen Wert zu vergrössern!

CSS-Lösung implementieren

Body-Margin anpassen:

body {
  margin: 20px;
}

Teste verschiedene Werte und überprüfe das Ergebnis!


Weitere Anpassungen

Damit die Page immer mehr der Galaxus-Vorlage entspricht, passen wir weitere Elemente an.

iFrame responsive machen

Das eingebundene iFrame ganz am Anfang stört gewaltig durch die definierte Breite und Höhe. Wir wollen dies per CSS definieren.

HTML bereinigen:

  • Höhe und Breite aus dem HTML entfernen

CSS-Lösung:

iframe {
  width: 100%;
  aspect-ratio: 16 / 9;
  height: auto;
  display: block;
}

⚠️ Wichtig: Min-height müssen wir bei einem iFrame definieren. Ansonsten wird es "zerquetscht"!

Bilder responsive machen

Die Bilder haben auch noch eine falsche Grösse. Definiere den Selektor auf alle imgs:

img {
  max-width: 100%;
  height: auto;
}

✅ Resultat: Die maximale Breite wird auf 100% der ganzen Seite gesetzt und so "sprengen" die Bilder das Layout in der Breite nicht mehr.


🎯 Nächste Schritte

Nach diesen Grundlagen-Anpassungen:

  1. Typography verfeinern
  2. Farben hinzufügen
  3. Layout optimieren
  4. Responsive Design implementieren

Die Basis steht - jetzt wird es kreativ! 🚀

📚 Font und Schriftgrösse anpassen

Diese wenigen Anpassungen haben schon einen starken Effekt auf das Optische. Basierend auf der Vorlage müssen wir uns auch noch Schrift und Schriftgrösse anschauen.

Font-Family analysieren

Wenn du z.B. das Titel-Element H2 untersuchst, findest du folgende Definition:

font-family: Galactica, Arial, sans-serif;

Body-Font definieren

Wir ergänzen die Schrift auf unsere ganze Page im CSS mittels dem Body-Selektor:

body {
  margin: 20px;
  font-family: Galactica, Arial, sans-serif;
}

Computed Styles überprüfen

Wenn du deine Seite nochmals untersuchst, wirst du eine Änderung feststellen. Aber irgendwie stimmt die Schrift noch nicht. Du kannst über "Computed" genau sehen, welche Schrift dein Browser ausgewählt hat.

Font Computed

Fallback-Mechanismus:

  1. Galactica versuchen → nicht gefunden
  2. Arial versuchen → ✅ gefunden und verwendet
  3. sans-serif als letzte Option

⚠️ Problem: Es rendert die Schrift "Arial", da Galactica nicht verfügbar ist.


Custom Font laden

Wir wollen aber die selbe Schrift wie in der Vorlage. Dazu müssen wir die Schrift zuerst laden.

Font-Loading Grundlagen

Referenz: W3Schools CSS3 Fonts

Font-URL finden

Die URL auf den Font ist auf der Galaxus-Seite zu finden:

Original-Pfad: /static/fonts/Galactica/galactica-2024-12-19.woff2

Vollständige URL: https://static.digitecgalaxus.ch/static/fonts/Galactica/galactica-2024-12-19.woff2

@font-face implementieren

@font-face {
  font-family: 'Galactica';
  src: url("https://static.digitecgalaxus.ch/static/fonts/Galactica/galactica-2024-12-19.woff2") format('woff2');
}

Galactica Font geladen

✅ Lade die Page neu und du wirst sehen, dass nun tatsächlich die Galactica-Schrift verwendet wird!


Schriftgrössen und Gewichte definieren

Damit auch die Grössen und font-weight aller Texte stimmen, gehe durch die Elemente aus der Vorlage durch und definiere es entsprechend in deinem CSS.

H1-Element Beispiel

Aus der Vorlage extrahierte Werte:

  • font-weight: 650
  • font-stretch: normal
  • line-height: 36px
  • font-size: 30px
h1 {
  font-weight: 650;
  font-stretch: normal;
  line-height: 36px;
  font-size: 30px;
}

Systematisches Vorgehen

  1. H1-H4 Elemente durchgehen
  2. P-Tags analysieren
  3. Werte aus Vorlage übernehmen

⚠️ Beachte: Falls du den Lead als H2 und weitere als H3 definiert hast, musst du das beim Kopieren entsprechend berücksichtigen!


Globale Text-Optimierungen

Vermutlich gibt es jetzt immer noch klare optische Unterschiede. Ein paar zusätzliche Definitionen kannst du mit einem * für alle Elemente hinzufügen:

Universal-Selector

* {
  text-wrap-mode: wrap;
  text-wrap-style: pretty;
  overflow-wrap: break-word;
  color-scheme: light;
  -webkit-font-smoothing: antialiased;
}

P-Tag Letter-Spacing

p {
  font-weight: 400;
  font-stretch: normal;
  line-height: 28px;
  font-size: 18px;
  letter-spacing: 0.005em;
}

Browser-spezifische Optimierungen

-webkit-font-smoothing

Das -webkit-font-smoothing ist etwas speziell und wird hier nicht unbedingt empfohlen, weil es nicht alle Browser unterstützen.

Aber: Es macht im Chrome einen wesentlichen optischen Unterschied!

A/B Test

Teste es mit und ohne:

  • Ohne: Text wirkt beinahe "zu fett" oder "blurry"
  • Mit: Schärfere, glattere Darstellung

Weitere Verbesserungen

Die weiteren Definitionen machen die Umbrüche etc. noch etwas besser:

  • text-wrap-mode: Intelligentere Textumbrüche
  • text-wrap-style: Schönere Zeilenumbrüche
  • overflow-wrap: Besseres Verhalten bei langen Wörtern

Typography-Grundlage ist gelegt! 🎉

📐 Breite der Seite

Problem: Zu breite Darstellung

Im Vergleich wirst du feststellen, dass unsere Page zu breit werden kann. Die Vorlage hat beim Text eine eingeschränkte Breite.

Analyse mit DevTools

Versuche mittels dem Dev-Tools in Chrome herauszufinden, welche Werte und wo du sie hinzufügen musst.


HTML-Struktur optimieren

Container-Ansatz

Arbeite hier mit dem div-Element um den eigentlichen Artikel zu umschliessen und vergib dann eine Klasse, auf welcher du das max-width abkopierst.

⚠️ Beachte: Das Slide Video/Bild hat eine andere Breite definiert. Füge also allenfalls zwei verschiedene Klassen dazu oder passe iframe an.

Hero-Bereich Spacing

Wenn du schon beim iframe bist: Du findest auch eine margin-Angabe dazu. Wir wollen zwischen Hero-Bild/Video noch etwas Abstand gewinnen.


CSS-Implementation

Content-Breite definieren

.content-max-width {
  max-width: calc(640px + (80px * 2));
  margin: 0 auto;
}

iframe optimieren

iframe {
  min-width: 100%;
  max-width: 1839px;
  aspect-ratio: 16 / 9;
  height: auto;
  display: block;
  margin: 0 auto 80px auto;
}

Zentrierung verstehen

Mit margin: 0 auto; kannst du die Elemente zentrieren:

  • 0: Kein vertikaler Margin
  • auto: Automatische horizontale Zentrierung

Implementierungs-Checklist

  1. 📦 Container-div um Artikel-Content
  2. 🎯 Klasse vergeben für max-width
  3. 🎬 iframe-Spacing anpassen
  4. 📱 Responsive testen

Layout-Breite ist professionell optimiert! 🎉

🏷️ Rubrik

Rubrik-Styling optimieren

Die Rubrik "Produkttest" ist noch nicht ganz korrekt. Die Farbe stimmt nicht. Ausserdem können wir dafür sorgen, dass sicher immer alles mit Grossbuchstaben dargestellt wird.

Korrekte Farbe finden

Die korrekte Farbe kannst du entweder dem Galaxus Design System entnehmen oder ab der Vorlage suchen/kopieren.

💡 Hinweis: Wir setzen hier und in allen folgenden Styles immer nur das Galaxus Light Theme um. Ansonsten müssten wir sehr viel mehr CSS produzieren.

CSS-Klasse definieren

Definiere eine Klasse, auf welcher du die Werte vergibst und füge sie überall hinzu, wo du eine Rubrik hast im HTML.

CSS-Implementierung:

.content-category {
  color: rgb(147, 83, 185);
  text-transform: uppercase;
}

HTML-Anwendung:

<p class="content-category">Produkttest</p>

Eigenschaften erklärt

EigenschaftWertBeschreibung
colorrgb(147, 83, 185)Galaxus-Purple für Kategorien
text-transformuppercaseAutomatische Grossschreibung

✅ Resultat

  • Korrekte Purple-Farbe wie im Design System
  • Automatische Grossbuchstaben für Konsistenz
  • Wiederverwendbare Klasse für alle Rubriken

Rubrik-Styling ist professionell umgesetzt! 🎉

🔘 Buttons

Button Design System

Die Buttons sehen noch nicht ganz so aus wie sie sollten. Häufig gibt es in einem Design System einen "primary" und "standard" oder "secondary" Button. Das ist bei uns nicht anders.

Button-Hierarchie

Button-TypVerwendungBeschreibung
PrimaryPrimäre AktionDie gewünschte/erwartete Aktion
SecondaryAlternative AktionenWeniger prominente Buttons

⚠️ Realität: Teilweise wird das auch bei uns nicht ganz eingehalten und es gibt mehr als einen Primary Button.

Design-Referenzen

Design System: Galaxus Button Design

Tipp: Da es nicht so einfach ist die nötigen CSS-Werte zu finden, kann es einfacher sein, wenn du das Beispiel im Dev-Tool des Browsers anschaust. Immerhin die Farben findest du einigermassen übersichtlich.

Responsive Considerations

Damit sich die Buttons in jedem Layout korrekt verhalten, werden wir auch mit Responsive Design in Berührung kommen. Mache allenfalls noch das Responsive Web Design Beispiel durch, damit du das besser verstehst.


CSS-Struktur mit Verschachtelung

Um die Varianten zu stylen, können wir uns sowohl die CSS-Verschachtelung sowie den Umstand zu Nutze machen, dass es ein button-Element ist. Damit können wir den "standard" auf dem Button und mittels einer Klasse "primary" als Variante definieren.


Button.css erstellen

Setup-Prozess

Buttons sind gut geeignet, dass wir das ganze CSS aus dem "main" loslösen und ein eigenes dafür erstellen. So hast du den CSS-Code separat und ist einfacher zu verstehen.

Schritt 1: Datei erstellen

  • buttons.css File erstellen

Schritt 2: Import hinzufügen

Im main.css am Anfang des Files:

@import url("buttons.css");

CSS-Implementierung

Danach fügst du nach und nach alle CSS-Definitionen ein, die für Primary und Standard-Button nötig sind. Sinnvoller nutzt du die Macht des Cascadings: Indem du z.B. den Standard-Button als ersten definierst, kannst du danach die Abweichungen für primary und die states hinzufügen (hover, focus, disabled).

Vollständiges Button-CSS:

button {
  background-color: #eee;
  color: #000;
  border: 1px solid #0003;
  border-radius: 3px;
  padding: 7px 15px;
  width: 100%;
  cursor: pointer;
  text-align: center;
  font-family: inherit;
  font-size: inherit;
  
  &:hover {
    background-color: #ddd;
  }
  
  &:focus-visible {
    background-color: #ddd;
  }
  
  &.primary {
    background-color: #444;
    color: #fff;
    
    &:hover {
      background-color: #000;
    }
    
    &:focus-visible {
      background-color: #000;
    }
  }
  
  &.disabled,
  &.primary.disabled {
    color: #0006;
    border: 1px solid #0001;
  }
}

CSS-Eigenschaften erklärt

EigenschaftStandardPrimaryBeschreibung
background-color#eee#444Hintergrundfarbe
color#000#fffTextfarbe
border1px solid #0003GleichRahmen
border-radius3pxGleichRunde Ecken
padding7px 15pxGleichInnenabstand
width100%GleichVolle Breite
cursorpointerGleichHand-Cursor

States und Interaktionen

Hover-States:

  • Standard: Helleres Grau (#ddd)
  • Primary: Schwarz (#000)

Focus-States:

  • Accessibility: Gleiche Farben wie Hover
  • Keyboard-Navigation: Bessere Sichtbarkeit

Disabled-States:

  • Transparente Textfarbe: #0006
  • Schwacher Rahmen: #0001

HTML-Implementation

Am Schluss musst du im HTML überall noch "primary" hinzufügen, wo es ein solcher ist.

HTML-Beispiele

Standard Button:

<button>Standard Button</button>

Primary Button:

<button class="primary">Primary Button</button>

Disabled Button:

<button class="primary disabled">Disabled Primary</button>

Vorlage-Konformität

Hältst du dich an die Vorlage, sind das tatsächlich aktuell alle Buttons als Primary. Eventuell können wir diesen Umstand später noch verbessern.

Button-System ist professionell implementiert! 🎉

❤️ Like Bereich

Responsive Layout-Unterschiede

Der "Like"-Bereich enthält keine neuen Elemente (ausser noch der "# Personen gefällt dieser Artikel"). Vor allem aber definiert der Bereich verschiedene Layouts für Mobile und Desktop, damit alles etwas besser passt.

Layout-Vergleich

MobileDesktop
Mobile LayoutMobile Layout

CSS-Methodik: Utility-First

Wir können für diesen Bereich ein eigenes CSS anlegen oder gezielte CSS-Klassen schreiben, die etwas allgemeiner gehalten sind. Dadurch kann die Wiederverwendung erhöht werden.

Utility-First Ansatz

Anstelle: "blog-page-like-button"
Verwende: Mehrere Klassen die den jeweiligen Anwendungszweck beschreiben
Beispiel: "margin-bottom-small" oder ähnlich

CSS-Methodologien

Diese Art von CSS nennt sich "Utility-First" und wird etwa von der CSS-Bibliothek Tailwind und teilweise von Bootstrap verwendet.

Unser Ansatz: Kombination von:

  • Utility-Klassen (Utility-First CSS)
  • Komponenten-basierten/semantischen Klassen (OOCSS)

📖 Mehr Infos: CSS Methodologies Guide


Utilities.css erstellen

Setup

Erstelle ein neues CSS-File utilities.css und importiere es im main.css:

@import url("utilities.css");

Margin-Werte analysieren

Wenn du die Vorlage auf Desktop-Grösse untersuchst und nach verschiedenen Margins Ausschau hältst, wirst du verschiedene Angaben finden:

WertBezeichnungAbkürzung
0pxZero0
16pxSmalls
24pxMediumm
32pxLargel
40pxX-Largexl
64pxXX-Largexxl

Koordinatensystem verstehen

Koordinatensystem

Für oben/unten bzw. rechts/links wird gerne y bzw. x benutzt, welches aus dem klassischen Koordinatensystem stammt.

Naming Convention

Für "margin top small" machen wir mt-s draus:

  • m = margin
  • t = top
  • s = small

Utility-Klassen implementieren

Margin-Top Utilities

/* margin-top utilities */
.mt-0 {
    margin-top: 0;
}
.mt-s {
    margin-top: 16px;
}
.mt-m {
  margin-top: 24px;
}
.mt-l {
  margin-top: 32px;
}
.mt-xl {
  margin-top: 40px;
}
.mt-xxl {
  margin-top: 64px;
}

Margin-Bottom Utilities

/* margin-bottom utilities */
.mb-0 {
  margin-bottom: 0;
}
.mb-s {
  margin-bottom: 16px;
}
.mb-m {
  margin-bottom: 24px;
}
.mb-l {
  margin-bottom: 32px;
}
.mb-xl {
  margin-bottom: 40px;
}
.mb-xxl {
  margin-bottom: 64px;
}

Margin Vertical Utilities

/* margin-top and bottom utilities */
.my-0 {
  margin-top: 0;
  margin-bottom: 0;
}
.my-s {
  margin-top: 16px;
  margin-bottom: 16px;
}
.my-m {
  margin-top: 24px;
  margin-bottom: 24px;
}
.my-l {
  margin-top: 32px;
  margin-bottom: 32px;
}
.my-xl {
  margin-top: 40px;
  margin-bottom: 40px;
}
.my-xxl {
  margin-top: 64px;
  margin-bottom: 64px;
}

HTML-Anwendung

Nachdem nun die CSS-Klassen erstellt sind, fülle im HTML nun dort die Klassen ein, wo sie entsprechend für ein Margin sorgen.

Neue Margins


✅ Progress Tracking

📋 Stand Like-Bereich:

  • Abstände oben und unten
  • Text unter dem Button zentriert mit richtigen Abständen
  • Breite des Like-Buttons

Text unter dem Button

Text-Alignment

Du hast bereits die nötigen margins für die Abstände definiert und musst sie nur noch korrekt einsetzen im HTML. Allenfalls musst du etwas mit "0" aushelfen, wenn das User Agent Stylesheet dazwischen funkt.

Für's Zentrieren gibt es grundsätzlich verschiedene Varianten. Die einfachste für den Fall ist aber text-align: center.

Text Utilities

/* text utilities */
.text-center {
  text-align: center;
}
.text-left {
  text-align: left;
}
.text-right {
  text-align: right;
}
.text-justify {
  text-align: justify;
}

✅ Updated Progress:

  • Abstände oben und unten
  • Text zentriert mit richtigen Abständen
  • Breite des Like-Buttons

Breite des Bereichs

Desktop-Analyse

Der Bereich ist in den verschiedenen Elementen in der Breite schmaler. Im Prinzip könnte man einfach der ganzen Section eine Breite verpassen. Die Vorlage nutzt aber tatsächlich Einschränkungen auf dem Button und dem Text darunter selbst.

Untersuche mit dem Browser die Elemente. Dann wirst du sehen, dass beim Desktop folgendes steht:

  • width: fit-content
  • min-width: 200px

Button-CSS erweitern

Da dies sehr spezifisch ist, macht es Sinn, beim button.css eine Spezialbreite einzuführen:

&.width-200-fit {
    min-width: 200px;
    width: fit-content;
}

HTML erweitern

<button class="primary width-200-fit">❤️ Gefällt mir</button>

Falsche Button-Position

Zentrierung korrigieren

Erst jetzt fällt auf, dass der Button eigentlich linksseitig orientiert ist. Auch das sollten wir ändern.

Der Trick ist relativ simpel: Anstelle das "text-center" auf dem Text selbst zu vergeben, setzt du die CSS-Klasse auf dem section container, welcher Button und Text vereint. Somit kriegen alle Elemente zentriert.

Text Center

🎉 Falls du das von Beginn an so gemacht hast, umso besser!

✅ Final Progress:

  • Abstände oben und unten
  • Text zentriert mit richtigen Abständen
  • Breite des Like-Buttons

Ein ❤️ für Details

Falls du willst und magst, kannst du im Like-Bereich noch 2-3 Details beheben:

Font-Size Anpassung

Wenn du genau hinschaust, ist die Font-Size hier kleiner als üblich. Versuche herauszufinden wie gross und finde einen Weg mit CSS, dies anzupassen.

SVG-Herz im Button

Das Herz im Button ist ein SVG und sollte weiss sein. Zudem gibt es dort noch einen Abstand und alles sollte auf der selben Höhe liegen. All das ist eher schwierig. Wenn du dich fit dafür fühlst, versuche es. Es muss aber auch nicht sein.

Endlösung

Du findest den Endstand der Lösung auf GitHub

Like-Bereich ist responsive und professionell umgesetzt! 🎉

🎯 CSS Variablen

Warum CSS Variablen?

Beim Like-Bereich haben wir sehr viele "statische" Werte für Abstände benutzt und teilweise auch x-fach wiederholt. Dies lässt sich mit Variablen deutlich besser gestalten. Es erlaubt, falls nötig, auch das einfachere Umsetzen von Responsive Design.


Variablen einführen

Setup-Prozess

Als erstes wollen wir all die margin-Angaben durch Variablen ersetzen. Dafür führen wir ein separates CSS-File ein und importieren es wie üblich im main.css.

@import url("variables.css");

Design System Ansatz

Wir haben in den utilities Angaben für margins von xs bis xxl eingesetzt. Diese wollen wir in den Variablen definieren.

In einem top Design System gäbe es einige Pixel-Angaben, die sich für alles wiederholen:

  • Set von Variablen für Margins
  • Konsistente Spacing-Skala
  • Wiederverwendbare Werte

💡 Pragmatischer Ansatz: Wir definieren die Variablen explizit für die Margins. Später können wir diese möglicherweise mit anderen zusammenführen.

CSS Variablen Grundlagen

Referenz: W3Schools CSS Variables

Variablen in CSS sind relativ umfassend und daher auch komplex. Wir konzentrieren uns hier auf die Basics.


Margin-Variablen definieren

Variable Definition

Definiere mit der Angabe :root {} die Angaben für unsere verschiedenen Margins:

/* margin variables */
:root {
  --margin-xs: 12px;
  --margin-s: 16px;
  --margin-m: 24px;
  --margin-l: 32px;
  --margin-xl: 40px;
  --margin-xxl: 64px;
}

Utilities aktualisieren

Danach gehe zum utilities.css und ersetze die fixen Zahlen durch unsere Variablen:

Margin-Top Utilities:

.mt-0 {
    margin-top: 0;
}
.mt-s {
    margin-top: var(--margin-s);
}
.mt-m {
  margin-top: var(--margin-m);
}
.mt-l {
  margin-top: var(--margin-l);
}
.mt-xl {
  margin-top: var(--margin-xl);
}
.mt-xxl {
  margin-top: var(--margin-xxl);
}

Margin-Bottom Utilities:

.mb-0 {
  margin-bottom: 0;
}
.mb-s {
  margin-bottom: var(--margin-s);
}
.mb-m {
  margin-bottom: var(--margin-m);
}
.mb-l {
  margin-bottom: var(--margin-l);
}
.mb-xl {
  margin-bottom: var(--margin-xl);
}
.mb-xxl {
  margin-bottom: var(--margin-xxl);
}

Margin-Right Utilities:

.mr-0 {
  margin-right: 0;
}
.mr-xs {
  margin-right: var(--margin-xs);
}
.mr-s {
  margin-right: var(--margin-s);
}
.mr-m {
  margin-right: var(--margin-m);
}
.mr-l {
  margin-right: var(--margin-l);
}
.mr-xl {
  margin-right: var(--margin-xl);
}
.mr-xxl {
  margin-right: var(--margin-xxl);
}

Margin Vertical (Top + Bottom) Utilities:

.my-0 {
  margin-top: 0;
  margin-bottom: 0;
}
.my-s {
  margin-top: var(--margin-s);
  margin-bottom: var(--margin-s);
}
.my-m {
  margin-top: var(--margin-m);
  margin-bottom: var(--margin-m);
}
.my-l {
  margin-top: var(--margin-l);
  margin-bottom: var(--margin-l);
}
.my-xl {
  margin-top: var(--margin-xl);
  margin-bottom: var(--margin-xl);
}
.my-xxl {
  margin-top: var(--margin-xxl);
  margin-bottom: var(--margin-xxl);
}

✅ Funktionalität prüfen

Like-Bereich testen: Prüfe kurz deinen "Like"-Bereich, ob die ganzen Abstände noch passen oder ob doch etwas kaputt ging.


Weitere Optimierungen

Im Prinzip könntest du jetzt das restliche CSS nach weiteren Möglichkeiten für Variablen absuchen:

Erweiterungsmöglichkeiten

BereichVariablen-Optionen
ButtonsBorder-radius, Padding, Colors
TypographyFont-sizes, Line-heights, Font-weights
ColorsBrand-colors, Background-colors
LayoutContainer-widths, Grid-gaps

Nächste Schritte

Wir lassen weitere Variablen vorerst sein und kümmern uns dafür um ein responsives Layout.

Variable-System etabliert - bereit für responsive Design! 🎉

💃🏼 Responsive is key

Warum Responsive Design?

Wenn wir eine Page für möglichst viele Endgeräte nutzen wollen, sollten wir sie responsive gestalten.

📖 Siehe auch: Responsive Web Design Guide

💡 Falls du nicht klarkommst, nutze den Stand aus GitHub


Mobile First?

Desktop First vs. Mobile First

Wenn wir eine Seite responsive gestalten, sollten wir uns festlegen:

AnsatzBeschreibungHeute empfohlen?
Desktop FirstZuerst Desktop, dann kleiner❌ Veraltet
Mobile FirstZuerst Mobile, dann grösser✅ Standard

Was bedeutet Mobile First?

Grundsatz: Wir gehen immer davon aus, dass es ein Mobile-Gerät mit schmalem Screen ist.

Praktische Beispiele:

ElementMobileDesktopGrund
Margins0px24pxMehr Platz verfügbar
Button-Breite100%fit-contentSchmaler Screen nutzen
Font-Size16px18pxBessere Lesbarkeit

Fazit: CSS für Mobile definieren, dann für breitere Bildschirme erweitern!


Breakpoints definieren

Galaxus Design System

Damit wir überhaupt verschiedene Bildschirmbreiten unterstützen, müssen wir Breakpoints definieren.

Breakpoints = Stellen im Screen, wo wir sagen "hey, jetzt sollte ich etwas am Design anpassen"

Galaxus Breakpoints

Referenz: Galaxus Screen Ranges

RangeBreiteGeräte
zero → xs0 - 699pxMobile
xs → s700px - 999pxTablet
s → m1000px+Desktop

Media Queries - Button responsive machen

CSS-Magie: Media Queries

Media Queries ermöglichen, auf den aktuellen Screen dynamisch zu reagieren.

Regel: Bis zum ersten Breakpoint gilt Mobile → da müssen wir nichts Spezielles tun.

Button-Problem analysieren

Der Like-Button ist auf Mobile zu schmal und sollte die ganze Breite einnehmen, damit er optisch besser wirkt.

Browser-Analyse mit Dev Tool:

  • bis 700px: Button volle Breite
  • ab 700px: Button schmal

Responsive-Verhalten

zero → xs (699px)xs (700px) → ∞
Mobile ButtonResponsive von xs

Für unseren Button bedeutet dies, wir müssen ihn beim xs-breakpoint auf schmal definieren. Gehe dazu erneut zum button.css file und suche die Stelle raus, die wir mit einem zusätzlichen CSS definiert haben.


CSS-Implementation

Wir können mit einem Media Query den Button so direkt übersteuern, dass er ab dem xs-breakpoint schmal bleibt. Dafür entfernen wir unsere spezialklasse wieder, die wir zuvor angelegt haben.

/* ENTFERNEN: */
&.width-200-fit {
    min-width: 200px;
    width: fit-content;
}

Media Query hinzufügen

Neues Button.css mit Media Query:

button {
  background-color: #eee;
  color: #000;
  border: 1px solid #0003;
  border-radius: 3px;
  padding: 7px 15px;
  width: 100%; /* Mobile First: Volle Breite */
  cursor: pointer;
  text-align: center;
  font-family: inherit;
  font-size: inherit;
  
  &:hover {
    background-color: #ddd;
  }
  
  &:focus-visible {
    background-color: #ddd;
  }
  
  &.primary {
    background-color: #444;
    color: #fff;
    
    &:hover {
      background-color: #000;
    }
    
    &:focus-visible {
      background-color: #000;
    }
  }
  
  &.disabled,
  &.primary.disabled {
    color: #0006;
    border: 1px solid #0001;
  }

  /* Responsive Styles */
  @media screen and (min-width: 700px) {
    width: fit-content;
    min-width: 200px;
  }
}

Design-Überlegung

Wenn wir das so machen, applizieren wir das Verhalten auf alle Buttons. In der Vorlage ist das eigentlich nicht so. Jedoch ist das tatsächlich nicht mal so schön:

Hässliche Button-Platzierungen

Problem: Die Buttons wirken etwas unsauber und es gibt quasi eine Zick-Zack-Linie.

Insider-Info: Der Bereich wird aktuell bei Clippy tatsächlich neu gestaltet!

HTML bereinigen

Entferne zu guter Letzt die CSS-Klasse im HTML:

<!-- ENTFERNEN: class="width-200-fit" -->
<button class="primary">❤️ Gefällt mir</button>

Resultat

✅ Was haben wir erreicht?

  • Mobile First Ansatz implementiert
  • Responsive Breakpoints definiert
  • Button-Verhalten optimiert
  • Sauberer Code ohne Spezialklassen

🔗 Fertige Version

Die komplette Lösung findest du unter: GitHub - Responsive Version

Responsive Design erfolgreich implementiert! 🎉

🔭 Blog Page Preview

Verschiedene Ansichten

Die Blog Page Preview gibt es an verschiedenen Orten und Formaten:

Layout-Vergleich

AnsichtMobileDesktop
Einzelne PageMobile One PageDesktop One Page
ListenansichtMobile ListDesktop List

💡 Glück: Die Änderungen sind minimal zwischen den Versionen. Wir können uns auf die Mobile konzentrieren und haben ev. die restlichen Varianten bereits mit abgebildet.


Flexbox implementieren

Lernressourcen

Damit wir das Design umsetzen können, nutzen wir Flexbox:

Element-Auswahl

Starte auf dem Element, welches diese Blog Page umschliesst.

Flexbox Article


Utilities erweitern

Flexbox-Utilities hinzufügen

Damit wir Flexbox verwenden können, brauchen wir im utilities.css die folgenden Klassen:

/* Flexbox utilities */
.d-flex {
  display: flex;
}

.flex-row {
  flex-direction: row;
}

.flex-column {
  flex-direction: column;
}

HTML-Implementation

Setze die beiden Klassen d-flex und flex-row auf dem HTML-Element article und prüfe, was passiert.

Flex angewendet

✅ Resultat: Die Text-Elemente sind schon mal rechts vom Bild. Eigentlich genau was wir möchten!


Bild-Spezialbehandlung

Problem

Das Bild benötigt relativ viele spezielle Behandlungen, damit es passt. Damit du nicht zu viele Klassen schreiben musst, erstelle eine flex-image-aside Klasse.

CSS-Implementation

.flex-image-aside {
  flex: 0 1 30%;  /* Kurzschreibweise für grow, shrink, basis */
  max-width: 144px;
}

HTML-Anwendung

Vergib diese Klasse dem Bild:

<img src="bild.jpg" alt="Beschreibung" class="flex-image-aside">

Flex angewendet

Flex Direction anpassen

Von Row zu Column

Jetzt müssen wir im "rechten" Bereich wiederum statt nebeneinander das Ganze untereinander haben. Also von "row" zu "column".

Ahnst du schon, wie du das mit Flexbox machen kannst?

Container hinzufügen

Falls dir ein Container fehlt, um diese Elemente zu umschliessen, kannst du ein div HTML-Element nutzen. Diese eignen sich super, um vor allem CSS-Klassen zu setzen.

<article class="d-flex flex-row">
  <img src="bild.jpg" alt="Beschreibung" class="flex-image-aside">
  <div class="d-flex flex-column">
    <h3>Titel</h3>
    <p>Beschreibung</p>
  </div>
</article>

Flex aber hässliches Bild

Bildverzerrung beheben

Typisch Entwicklung: Man löst ein Problem und schafft sich neue... Wunderbar oder?

Problem: Das Bild ist jetzt komplett verzogen.

Lösung: object-fit: cover

.flex-image-aside {
  flex: 0 1 30%;  /* Kurzschreibweise für grow, shrink, basis */
  max-width: 144px;
  object-fit: cover; /* Verhindert Verzerrung */
}

Spacing-Optimierungen

Jetzt stören noch die Abstände oben / unten zwischen den Texten sowie sind Bild und Text zu nah aneinander.

Nutze deine Utility-Klassen um die Abstände anzupassen:

  • mr-s für rechten Abstand am Bild
  • my-0 um vertikale Margins zu entfernen

Danach müssen wir uns noch um die Schriftgrössen kümmern. Die sind jeweils 13 und 20. Zwischen dem Bild und dem Text gibt es einen Abstand von 16px. Flex Margins Overview Als Margin-Top bei allen Texten sind 4px definiert. Wir brauchen also doch noch so was wie ein xxs.

Anpassungen vornehmen

Damit alles klappt musst du noch die Fonts im fonts.css erweitern. Ausserdem brauchen wir neue Werte für die Font-Weights. Die fonts.css könnte nun so aussehen (die Werte wie 13, 20 kannst du auch durch small, large oder ähnlich ersetzen).

.font-13 {
  font-size: 13px;
  line-height: 20px;
}
.font-20 {
  font-size: 20px;
  line-height: 28px;
}
.font-weight-regular {
  font-weight: 400;
}
.font-weight-medium {
  font-weight: 500;
}
.font-weight-bold {
  font-weight: 700;
}

Setzte dann die Klassen im Html alle. Achte darauf dass du die my-0 durch mt-s und mb-0 ersetzen musst, dort wo es ein mt-s gibt.

Fixed Margins

Nur noch ein paar Details

Jetzt fehlen uns nur noch diese hübschen Linien oben und unten. Das sind einfache Border die wir ebenfalls im utilities.css definieren können. Auch hier könnten die Farben und Dicken jeweils über Variablen ausgelagert werden.

.border-top {
  border-top: 1px solid #ddd;
}
.border-bottom {
  border-bottom: 1px solid #ddd;
}

Wrong paddings

Jetzt sind die Linien noch zu nah. Das können wir mit Padding korrigieren, da eine Border zwischen padding und Margin liegt.

Wrong paddings

Gemäss Vorlag gibt es ein Padding oben und unten von 16px. Erweitere deine utilitites.css mit Paddings und setze danach die Klassen ein.

Da wir für margin und padding die selben Werte nutzen, legen wir sie gleich zusammen in den Variablen zu spaces und nutzen im utilties.css dann dieselbe Variable für Margin und Padding Angaben.

Variables.css

/* general spaces variables */
:root {
  --space-xxs: 4px;
  --space-xs: 12px;
  --space-s: 16px;
  --space-m: 24px;
  --space-l: 32px;
  --space-xl: 40px;
  --space-xxl: 64px;
}

Utilities.css

/* margin-top utilities */
.mt-0{
    margin-top: 0;
}
.mt-xxs {
  margin-top: var(--space-xxs);
}
.mt-xs {
…
…

/* padding-top utilities */
.pt-0 {
  padding-top: 0;
}
.pt-xxs {
  padding-top: var(--space-xxs);
}
…
…

Setze dann py-s auf das Article Element

Final mit festen Paddings und Margins

Fleissarbeit…

Jetzt hast du alle Grundlagen im CSS, um all die Blog Page Previews zu gestalten. Gehe durch jede Stelle in der Index.html Seite durch, ergänze die Klassen und es sollte soweit passen. Für Desktop könnte man das Bild noch etwas breiter machen. Aber das ist deine Wahl ob du das noch machen möchtest 😊 Viel Erfolg!

Ps. Du findest die fertige Version auf Github https://github.com/rekoch/webEngineerDgEditors/tree/main/public/02_html_css/08_blog_page_preview

🎁 Wrap it up

Erfolgreiche Umsetzung!

Du hast jetzt all die Elemente umgesetzt:

✅ Erfolgreich implementiert:

  • Textgrössen inklusive Überschriften etc.
  • Video oder Bild als Slide
  • Rubrik
  • Weitere Bilder
  • Buttons
  • Gefällt mir Bereich
  • Blog Page Preview inklusive Ansicht für "weitere spannende Artikel"

Dabei sehr vieles gelernt:

TechnologieWas du gelernt hast
CSS VariablenWartbare, skalierbare Styles
FlexboxModerne Layout-Techniken
Responsive DesignMobile First Entwicklung
Font-ManagementCustom Fonts korrekt einbinden
Utility-First CSSWiederverwendbare Klassen

🎊 Gratulation - du bist jetzt ein CSS-Profi!


Erweiterte Challenges

Wenn du jetzt ungebremst noch weiter machen möchtest, dann kannst du folgende offene Elemente versuchen schön zu gestalten:

Noch zu implementieren:

  • Autor*innen Bereich
  • Tabellen
  • Fazit Box
  • Produkt Kachel
  • Autor*innen Box mit Folgen-Button
  • Thema mit Folgen-Button

Schwierigkeits-Einschätzung

Von "einfach bis komplizierter" vorgehen:

ElementAufwand (0-10)Begründung
Beschreibung Bilder2Optional: Beschreibungstext unter den Bildern. Benötigt noch etwas HTML (figure, figurecaption) sowie CSS für die Darstellung
Produkt Kachel3Sehr ähnlich wie Blog Page Preview (verzichte besser auf die Verfügbarkeits-Anzeige)
Fazit Box4-6Tabelle besser gestalten (pro/kontra). Herausforderung: CSS-Umsetzung. Eventuell CSS Grid lernen → macht Aufwand grösser
Thema mit Folgen5Elemente wie bei Blog Page Preview wiederverwendbar. Neue Herausforderungen: Einrücken und Button rechts vom Text. CSS Grid könnte helfen
Autor*innen Box6Analog "Thema mit Folgen", plus korrekte Darstellung/Grösse des Autor*innen-Bilds. Grid zum Angleichen mit "Thema folgen"
Tabellen8-10Je nachdem wie "schön" die Tabelle aussehen soll, kann dies enorm aufwändig werden

Empfohlene Reihenfolge:

  1. ** Beschreibung Bilder** (Quick Win)
  2. Produkt Kachel (Flexbox-Wiederholung)
  3. Fazit Box (CSS Grid Lerneffekt)
  4. Thema mit Folgen (Layout-Komplexität)
  5. Autor*innen Box (Bild-Optimierung)
  6. Tabellen (Master-Challenge)

Fertige Lösung

Die "fertige" Blog Page mit dem ganzen CSS findest du unter: GitHub - Completed Blog Page

💡 Hinweis: Du findest unter dem Link allenfalls auch CSS, HTML-Elemente/Lösungen, die nicht Teil des Tutorials direkt sind. Falls du konkret Fragen hast oder es ähnlich machen willst, frage am besten einfach im Kurs nach.

Du hast eine solide CSS-Basis geschaffen - jetzt wird experimentiert! 🚀

Geschichte von JavaScript

JavaScript hat eine recht interessante Geschichte hinter sich. Diese zeigt auch auf, wie früh JavaScript entstanden ist und eigentlich durch den Fortschritt des WWW geprägt wurde. Siehe auch Wiki JavaScript Geschichte

Wie viel hat JavaScript denn nun mit Java zu tun? Etwa so viel wie ein HotDog mit einem Hund → nichts! Dies ist zwar nicht ganz korrekt. Wenn du die Geschichte auf Wikipedia nachliest, findest du heraus wieso. Die damals genannte Skriptsprache "LiveScript" sollte Java ansprechen können. Weil diese Einwicklung von Netscape (=Browser) und Sun (=Java) zusammen kreiert wurde, nannte man sie später JavaScript. Also eine Web-Skript-Sprache, welche Java zudienen soll. Vermutlich weisst du unterdessen, dass dies heute etwas anders funktioniert.

Einführung JavaScript

ECMA und JavaScript

Heute gibt es nebst JavaScript den Namen "EcmaScript". Vielleicht hast du davon gehört. ECMA Skript vor, was eine Skriptsprache für den Browser unterstützen soll. Es ist also eine Art Vorgabe. JavaScript ist eine Programmiersprache, die sich an diese Vorgabe möglichst hält. Die beiden Namen sind also nicht das gleiche, werden aber häufig gleich verwendet.

Have Fun with JavaScript

Wie und wo kann Javascript genutzt werden?

  • Direkt im Browser. Öffne dazu beispielsweise die Entwicklertools auf Chrome. Danach wechselst du dort auf „console". Direkt in diesem Fenster kannst du JavaScript schreiben und ausführen.
  • Mit einer Lauftzeitumgebung wie dies bei Node der Fall ist. Auf dem Computer findest du „node.exe". Starte dies und code direkt drauf los.

== und ===

Time to code. Juhuu. Starte auf deinem Windows oder Mac "node". Gib auf der Konsole nun folgendes ein:

0 == 0;

Überlege dir nun, was das Resultat ist. Mache nun jeweils das selbe, mit allen nachfolgenden Beispielen. Überlege dir immer VOR der Eingabe, was wohl das Resultat sein könnte:

0 == new String("0");

0 == "0";

new String("0") == "0";

null == undefined;

"" == false;

1 == true;

"1" == true;

new String("66") == true;

Jetzt wiederhole die Schritte und benutze anstelle von == jeweils ===

Objekte und Funktionen

Verwende weiterhin jeweils node, um die Beispiele zu programmieren.

var person = {};
//eingeben;
this.person;
//eingeben;

Was lernst du daraus? Mit {} konntest du einfach so ein Objekt erstellen.

JavaScript erstellt aus allem direkt ein Objekt. Damit lässt sich wunderbar ein Objekt auch erweitern. Entgegen bspw. Java, muss dieses Objekt nicht auf einer Klasse definieren.

Gib folgendes ein:

this.person.name = "urs";
this.person;

Genau. Es hat funktioniert. Jetzt hast du einfach so deinem leeren Objekt kurzerhand ein neues Property angefügt und abgefüllt. Auch hier. Keine Klasse sagt, wie dein Objekt auszusehen hat. Ausserdem ist jedes Objekt immer komplett unabhängig von einem anderen. Du hast eine Variable deklariert, jedoch keinen Wert vergeben. Solche Variablen sind in JavaScript zwar vorhanden, aber sind "undefined". Du darfst also nicht versuchen, auf ein weiteres Property zuzugreifen. Du wirst wohl noch oft den Fehler antreffen, dass du auf eine undefined Variable zugreifen willst.

Funktionen

In JavaScript kannst du dir jedes Objekt wie eine Art HashMap in Java vorstellen. Du hast also eine Art Katalog, welcher definiert, welche Werte existieren und was dort abgespeichert ist. Die Werte können dabei andere Objekte oder auch Funktionen sein. Versuche folgendes:

var person = {};
person.adress = { strasse: "ume egge", nummer: 2, ort: "hie" };
this.person;
person.sayHello = function () {
  return "hallo du";
};
this.person;
this.person.sayHello();

Funktionen können auf verschiedene Arten erstellt werden.

function doIt() {
  return "no";
}
var doItMaybe = function () {
  return "still no";
};
var doItDefiniv = function stillBlocking() {
  return "I give up";
};

Erste Variante wird gehoistet und kann vorher schon benutzt werden. Andere Varianten nicht. Named hat Vorteile beim Debuggen. Führe alle Varianten mit () aus ->

  • Kein grosser Unterschied
  • Führe Variante 2 und 3 ohne () aus
  • Der Name wird bei Variante 3 entsprechend angezeigt

In JavaScript ist wirklich ALLES ein Objekt. Sogar Funktionen. Lass es uns beweisen und gib folgendes ein:

doItMaybe instanceOf Object
    doItMaybe.candidate = 'nid ig'

das erste wird true zurückgeben.

Was denkst, du was nun in doItMaybe drin ist wenn du es ausführst? Auch ein Array verhält sich eigentlich genau gleich wie ein Objekt:

var a = [];
    a[0] = 1;
    a; //-> Ausgabe prüfen
    a[100] = 100;
    //-> denkst du das geht?
    a; //-> Ausgabe prüfen

Hier musst du immer sehr darauf achten, wenn du bspw. einen Loop hast mit einer Schleifenvariable. Ein Fehler und deine Schleife läuft endlos.

Higher Order Function

Eine Funktion kann eine Funktion enthalten. Ist dies der Fall, nennt man dieses Konstrukt eine "higher Order function". In Frameworks wie Angular oder React wirst du sehr häufig solche Konstrukte vorfinden.

var higherOrderFunction = function (justANumber, func) {
    justANumber = justANumber * 1000;
    func(justANumber);
    }
    this.higherOrderFunction(10, function(answer){console.log(answer)};

Das ganze kann auch mit einer Arrow functions ausgeführt werden:

this.higherOrderFunction(10, (answer) => console.log(answer));

Let and const

In einem modernen JavasScript Umfeld soll immer let und const verwendet werden. Man kann Variablen auch mit dem Keyword var erstellen. Dies verursacht jedoch ungewünschte Effekte und soll nicht mehr gemacht werden);

Basics

Damit du den Einstieg in die JavaScript findest, solltest du die Grundlagen verstehen. Wie in jeder Programmiersprache sind dies die üblich verdächtigen wie Variablen, Funktionen, Schleifen etc. JavaScript ist da auch nicht viel anders.

Einerseits findest du auf W3Schools verschiedene Tutorials dazu W3Schools JavaScript und anderseits im GitHub verschiedene Übungen, die du auf deinen Gerät direkt programmieren und ausprobieren kannst.

GitHub Übungen

  • gehe auf Github Repo und lade den Code per ZIP File herunter Download als ZIP
  • entpacke das ZIP lokal
  • öffne den folder /public/03_javascript/01_basics direkt im Vs Code VS Code folder öffnen
  • nun kannst du jedes File Schritt für Schritt in deinem Tempo durchgehen. Es gibt bei den meisten ein Excercise dazu, wo das zuvor gelernte üben kannst
  • Starte die "Erklär" Files am Besten lokal auf deinem Gerät mit der Vs Code Debug Funktion. So kannst du durchgehen und siehst auch immer die Ausgabe in der Konsole Vs Code Debug der Erklär Files
  • wenn du danach die übungen machst, überprüfe sie indem du mit dem Debugger durchgehst oder mittels dem Befehl node gefolgt vom Dateinamen die Datei ausführst.
node 02_variables_excercises.js

🐛 Debuggen von JavaScript

Natürlich ist der ganze JavaScript Code denn du schreibst und schreiben wirst immer direkt korrekt und funktioniert wie du es dir vorgestellt hast. Wenn es in der Ausnahme doch mal nicht so sein sollte, ist es hilfreich zu wissen, wie du Code prüfen und im Besten Fall auch korrigieren kannst.

Früher hättest du dazu mehrheitlich alles mittels "console.log()" machen müssen. Die Methode kann grundsätzlich immer noch passend sein. Gerade wenn es in einem komplexen Code Stück geschieht, wo nicht so klar ist, wann, wie oft etc. es aufgerufen wird.

Debug JS mit Chrome

Die Methode welche den einfachsten Zugang gewährt, ist der Browser selbst. Denn der Browser führt direkt JavaScript aus und unterstützt dich mit vielen sinnvollen Tools.

  1. Öffne die Seite 02_buttonReactive mit Chrome
  2. Öffne die Dev Tools (F12, rechtsklick im HTML -> untersuchen)
  3. Scrolle zu den Buttons am Ende der Seite Blog Page Buttons
  4. Drücke bspw. auf den "Dieser Artikel gefällt mir!" Button. Du siehst, dass sich die Anzeige ändert und bspw. von "59 auf 60" bzw. zurück auf "59" wechselt
  5. Damit wir den dahinter liegenden JavaScript Code debuggen können, müsstest du schon fast wissen wie der heisst. Du kannst auch auf gut Glück die Dateien betrachten.
  6. Wähle im Dev-Tool das Tab "sources" dev Tool Sources dev Tool Sources
  7. In diesem Fall haben wir "Glück" und es gibt den Folder "javascript" und die Datei "buttons.js" umd die es wohl geht. Klicke die Datei an. Der Source-Code wird auf der rechten Seite angezeigt dev Tool Sources
  8. Wenn du weiter schaust findest du die Funktion toggleLikeButtonState was wohl unser gesuchtes Stück Code sein dürfte. Klicke danach links beim Zeilenmarker mit der Maus. Du solltest eine Markierung kriegen.
  9. Drücke jetzt nochmals auf den like button. Wenn es geklappt hat, siehst du im Dev Tool nun die Stelle umrandet. Zudem etwas weiter rechts noch die ganzen debug tools. Auf der Seite selbst steht ebenfalls eine Info. dev Tool Break dev Tool Break
  10. Mit F10 oder dem button kannst du nun einzeln weiter gehen. Im Code kannst du bei fast allen Elementen mit der Maus darüber fahren und siehst die Details dazu
  11. Wir haben aktuell am Anfang gestoppt. Daher ist currentState noch nicht gefüllt dev Tool Break dev Tool Break
  12. Drücke jetzt auf den Button oder F10 einmal dev Tool Break
  13. Jetzt siehst du das der Zeiger eins weiter gewandert ist und die Variable currentState den aktuellen Wert anzeigt. In diesem Fall inactive
  14. Jetzt kannst du beliebig weiter steppen. Beispielsweise ist es dann spannend, ob beim ersten if im code das richtige passiert?
  15. Mit jedem Step siehst du auch, wie sich das HTML anpasst, vorausgesetzt, der Schritt ändert auch tatsächlich was. Bei Zeile 50+ werden die Texte, Farben etc. angepasst.
  16. Gehe mal ganz durch, bis du wieder "normal" bist und drücke denn Like-Button nochmals. Du solltest dann beim if in die andere Variante reinlaufen

Was kann ich tun, wenn ich nicht weiss in welchem File die Änderung gemacht wird?

  1. Wir hatten vorhin "glück" dass wir das File einfach so gefunden haben.
  2. Lösche den aktuellen Breakpoint im buttons.js file wieder. Dies kannst du auf verschiedene Wege. Das einfachste ist wohl einfach nochmals an der Stelle zu klicken und die Markierung sollte verschwinden
  3. Gehe dann im Dev-Tool zurück ins Tab "Elements" wo das html angezeigt
  4. Suche den Button (oder mit dem Fadenkreuz anwählen) damit du an der richtigen Stell im HTML bist
  5. Mache dann einen Rechtsklick und wähle "Break on" -> "subtree modifications" dev Tool Break Subtree
  6. Klicke dann nochmals den Button
  7. Du solltest an genau der Stelle "breaken" -> pausieren, wo durch das Javascript der Text geändert wird dev Tool Break
  8. Dies ist super hilfreich, wenn du ein Verhalten im HTML prüfen willst und durch welches Javascript das genau ausgelöst wird.
  9. Im Debug Tool siehst du alle aktiven Breakpoints. Die dom-Breakpoints kannst du so prüfen und auch wieder löschen dev Tool Breakpoints

Was wenn ich einfach "egal wo im HTML, wenn es geklickt wird will ich das Javascript finden"?

Auch da hilft dir Chrome…

  1. Lösche allfällig vorhandene Breakpoints (inklusive den vorherigen DOM)
  2. Gehe dort wo du die DOM Breakpoints siehst weiter nach unten und klappe "Event Listener Breakpoints" auf
  3. Wähle "mouse" und dann "click" dev Tool Break on Listener
  4. Klicke nochmals auf den Button oder auch bewusst dieses Mal auf einen anderen
  5. Der Debugger sollte dich automatisch dorthin führen, wo tatsächlich der "Start" Code liegt dev Tool Break

Und wenn ich aus meinen Code heraus genau sagen will, wo es stoppen soll?

Es kann sein, dass du Zugang zum Code hast, weil du gerade entwickelst. Dann weisst du ev. genau welchen Teil des Skripts du genauer untersuchen möchtest. Alternativ kannst du auch mit VS Code debuggen

Öffne bei dir im VS Code einen Stand mit JavaScript. Entweder das gleiche Beispiel wie oben oder sonst irgendwas anderes. Öffne das JavaScript im VS Code. Gehe beispielsweise zum Buttons.js

Schreibe an der Stelle, an der du den Debugger nutzen willst "debugger;" rein

debugger statement

Öffne die Page mit dem LiveServer und dann das Dev Tool wie vorhin. Drücke einen Button. Der Debugger stoppt an genau der Stelle.

Das kann helfen, wenn du auch verstehen willst, wo dein File/Code genau liegt. Vor allem wenn du mit React, Angular oder ähnlichen Bibliotheken arbeitest, kann es sein, dass dein Code nochmals umgewandelt wird.

Aber Achtung! Du darfst das "debugger" statement natürlich nicht produkiv ausliefern. Sonst stoppt die Page bei allen, die gleichzeitig das Dev Tool öffnen (wenigstens nur dann :D )

Weitere Infos zum Chrome Debug Tool

Noch viel umfassender findest du die Debug Tools Erklärungen hier Chrome Debug Tool

Debug JavaScript mit VS Code

Wenn dein Code nicht wie erwartet funktioniert, hast du zwei Debugging-Optionen: Browser (Chrome) oder VS Code. Der Vorteil beim VS Code Debugging ist, dass du Code korrigieren und sofort die Auswirkungen sehen kannst.

Zusätzliche Ressourcen

Eine grundlegende Anleitung findest du in der offiziellen VS Code Dokumentation. Falls das nicht reibungslos funktioniert, verwendet diese Anleitung zusätzlich den LiveServer und hat sich in Tests bewährt.


Debug Session starten mit VS Code

Vorbereitung

  1. Projekt öffnen: Öffne den Ordner public/03_javascript/02_buttonReactive in VS Code
    (alternativ ein beliebiges Projekt mit JavaScript und HTML)
  2. LiveServer starten: Starte die index.html mit deinem LiveServer
  3. URL überprüfen: Du solltest die Seite über folgende URL sehen:
    http://127.0.0.1:5500/public/03_javascript/03_buttonReactive/index.html
    

Debug-Verbindung einrichten

  1. Debug-Panel öffnen: Gehe zum "Debug"-Bereich in VS Code
  2. Befehlspalette öffnen:
    • Mac: Cmd + Shift + P
    • Windows/Linux: Ctrl + Shift + P
  3. Befehl eingeben: Tippe Debug: link in die Befehlszeile
  4. Debug-Link öffnen: Klicke auf das Ergebnis "Debug: Open Link" Debug Link
  5. URL eingeben: Kopiere die LiveServer-URL und füge sie ein Link bestätigen
  6. Verbindung bestätigen: Der Browser sollte die Seite öffnen und in VS Code solltest du die "Aufrufliste" sehen Aufrufliste

Breakpoints setzen

  1. JavaScript-Datei öffnen: Gehe zu einer JavaScript-Datei (z.B. buttons.js)
  2. Breakpoint setzen: Klicke am linken Rand der gewünschten Zeile
    → Ein roter Punkt wird angezeigt Breakpoint
  3. Code ausführen: Gehe zurück zur Webseite und führe eine Aktion aus (z.B. Button klicken)
  4. Debugging startet: Der Debugger stoppt an der markierten Stelle Breakpoint paused

Debug-Steuerung

  1. Debug-Toolbar: Du siehst eine verschiebbare Button-Leiste zur Debugging-Steuerung Steuerung
  2. Step Over (F8):
    • Drücke den ersten Button rechts vom Play-Symbol
    • Führt die aktuelle Zeile aus und geht zur nächsten
  3. Step Into (F7):
    • Wenn du wissen möchtest, was sich hinter einer Funktion wie toggleLikeButtonState(button); verbirgt
    • Springt in die Methode hinein für detaillierte Analyse
  4. Schritt-für-Schritt:
    • F7 = Mehr ins Detail (Step Into)
    • F8 = Überspringen (Step Over)

Variable inspizieren

  1. Werte anzeigen:
    • Fahre mit der Maus über eine Variable und bleibe stehen
    • Der Wert wird direkt angezeigt
    • Zusätzlich gibt es oben links eine Übersicht aller Variablen Values
  2. Werte ändern:
    • Doppelklick auf eine Variable in der Übersicht
    • Ändere den Wert und bestätige mit Enter Change values
  3. Simulation:
    • Hilfreich zum Testen: "Wie würde sich der Code verhalten, wenn...?"
    • Ändere Werte temporär zur Simulation verschiedener Szenarien

Weiterführende Informationen

Detaillierte Informationen findest du in der offiziellen VS Code Debugging-Dokumentation.


Tipps

  • LiveServer erleichtert das Debugging erheblich
  • Breakpoints können jederzeit gesetzt/entfernt werden
  • Variablen-Werte können zur Laufzeit geändert werden
  • Step Into vs. Step Over je nach gewünschtem Detailgrad verwenden

JavaScript und Aynchronität

Weil ein Browser bereits auf dem Betriebssystem als Applikation läuft, steht den Teilen in JavaScript nur ein Thread zur Verfügung. Ein Thread ist eine Ressource, welche das Betriebssystem einer Applikation für die Ausführung von Rechenoperationen bereit stellt. Hat man nur 1 solchen Thread, können keine Rechenoperationen gleichzeitig behandelt werden. Der Browser kümmert sich aber nebst dem Ausführen von JavaScript auch noch um die Darstellung der Seite mit HTML oder CSS. Wenn du jetzt also bspw. die ersten 100 Zahlen der Fibonacci Folge berechnen willst, würde die Webseite während dieser Zeit "einfrieren" und keine Interaktionen vom Benutzer mehr entgegen nehmen. Dieses "Problem"wird so gelöst, dass der Browser angewiesen wird (mit JavaScript), dass er aufwändigere Operationen nachlagert ausführt. Du weisst dann nicht genau, wann dein Stück Code effektiv ausgeführt wird. Dieses "asynchrone" Verhalten führt im Umgang mit JavaScript zu einer höheren Komplexität.

Du kannst dir so vorstellen, wie wenn du jemanden bittest, dir einen Kaffee zu besorgen. Während der Kaffee "hoffentlich" organisiert wird, arbeitest du weiter. Wenn er dann da ist, wirst du den Kaffee trinken wollen. Die Aktion "Kaffee trinken" definierst du zwar schon bei der Bestellung des Kaffees (das ist deine Absicht), führst sie aber erst aus, wenn der Kaffee da ist.

In JavaScript wird dies so gelöst, indem eine Funktion eine andere Funktion entgegen nimmt. Die Funktion "Kaffee trinken" wird der Funktion "Kaffee bestellen" mitgegeben. Wenn der Kaffee dann da ist, wird er getrunken. Zeitpunkt und Detail der Ausführung wird in der Methode "Kaffee bestellen" definiert, der konkrete Ablauf von "Kaffee trinken" in der Funktion, die übergeben wird.

Unter w3schools Callbacks findest du ebenfalls ein Code Beispiel dazu. Sicher findest du auch auf YouTube Erklär-Videos zu Callbacks.

Die Syntax sieht wie folgt aus:

function functionWithCallback(callback) {
  console.log("Funktion mit Callback wird ausgeführt");
  callback();
}

function callbackFunction() {
  console.log("Callback-Funktion wird ausgeführt");
}

functionWithCallback(callbackFunction);

Die Funktion functionWithCallback nimmt als Parameter eine Funktion entgegen, welche dann ausgeführt wird. Mittels () siehst du, dass es eine Funktion ist. Inhaltlich weiss die Methode nicht, was sie ausführt, bestimmt aber den Zeitpunkt.

Die Funktion callbackFunction ist die Funktion selbst. Ein Beispiel mit dem Kaffee trinken können wir erweitern, dass die Funktion "Kaffee bestellen" der Funktion "Kaffee trinken" auch noch mitgibt, welchen Kaffee genau eigentlich bestellt wurde.

function orderCoffee(callback) {
  console.log("Kaffee wird bestellt...");
  console.log("Kaffee ist da!");
  callback("Milchkaffee");
}

function drinkCoffee(coffeeType) {
  console.log(`Kaffee wird getrunken: ${coffeeType} ☕️`);
}

orderCoffee(drinkCoffee);

Die Funktion "drinkCoffee" verlangt also ebenfalls einen Parameter, den sie nutzen will. Sie weiss nicht "was" dort mitkommt. Die "orderCoffee" Funktion muss sich nun also darum kümmern und übergibt den Typ beim Aufruf des Callbacks.

Wieso macht das so nicht viel Sinn? Der Aufruf von "callback" ist aktuell relativ synchron, da Zeile für Zeile abgearbeitet wird. Stell dir vor, es braucht lange bis die Aktion durch ist. In einem technischen Sinne beispielsweise, weil die Aufgabe über einen entfernten Server erfolgt oder eine lang dauernde Datebankabfrage erfolgt. Du kannst so was mit einem "setTimeout()" simulieren.

function orderCoffee(callback) {
  console.log("Kaffee wird bestellt...");
  setTimeout(() => {
    console.log("Kaffee ist da!");
    callback("Milchkaffee");
  }, 2000);
}

Wir warten nun 2 Sekunden, bis es weiter geht.

Die Syntax wie oben kann zu Problemen mit this führen und ist etwas mühsam, wenn wir das oft brauchen. Darum kennt JavaScript die "Arrow" Funktion.

Arrow functions

Anstelle von

orderCoffee(drinkCoffee) übergeben wir den Inhalt von "drinkCoffee" direkt mit der Arrow Syntax

function orderCoffee(callback) {
  console.log("Kaffee wird bestellt...");
  setTimeout(() => {
    console.log("Kaffee ist da!");
    callback("Milchkaffee");
  }, 2000);
}

orderCoffee((coffeeType) => {
  console.log(`Kaffee wird getrunken: ${coffeeType} ☕️`);
});

die ganze drinkCoffee Funktion ist nicht mehr nötig und wird direkt geschrieben. Du musst das nicht und kannst immer noch innerhalb des Arrow Bodys {} eine andere Funktion aufrufen.

🧠 Blog Page Intelligence

Intro

Von statisch zu dynamisch

Bisher ist unsere Blog Page statisch - sie kann aufgerufen werden, aber nichts passiert bei Interaktionen. Buttons haben keine Wirkung. Hier kommt JavaScript ins Spiel!

Mittels JavaScript geben wir der Blog Page mehr Intelligenz:

  • Design-Anforderungen eleganter lösen
  • Buttons mit Aktionen ausstatten
  • Datenladung und -speicherung optimieren

🎯 Hauptziele

Kern-Features (Must-have)

  1. Diagramm/Tabelle dynamischer gestalten

    • Interaktive Elemente
    • Responsive Verhalten
    • Bessere Benutzererfahrung
  2. Alle Buttons mit Wirkung ausstatten

    • Like-Funktionalität
    • Follow-Buttons
    • Feedback für Benutzeraktionen
  3. Backend-Kommunikation

    • Daten remote laden
    • Daten persistent speichern
    • API-Integration

Erweiterte Features (Nice-to-have)

Wenn die Zeit gut reicht:

  1. Simples Login-System

    • Benutzer-Differenzierung
    • Personalisierte Like/Follow-Zustände
    • Session-Management
  2. Einfaches CMS über Backend

    • Content direkt editieren
    • WYSIWYG-Editor
    • Content-Management-Interface

Lernziele

Nach diesem Kapitel wirst du:

  • JavaScript für UI-Interaktionen einsetzen können
  • API-Kommunikation verstehen und implementieren
  • State Management in Frontend-Anwendungen beherrschen
  • Event-driven Programming anwenden können
  • Den kompletten Entwicklungszyklus von Frontend zu Backend verstehen

🤖 Table Auto Design

Das Problem

Wenn du den letzten Stand aus dem GitHub Repository nimmst, findest du eine Tabelle für die Benchmark-Übersicht.

Die Tabelle startet hier: index.html#L202

Design-Herausforderung

Obwohl die Tabelle funktioniert, hat sie ein fundamentales Problem: Die Breite der Diagramm-Säulen ist fest im HTML hinterlegt.

<p
  class="table-background-brown text-right mt-xxs mb-xxs pr-s py-xxs"
  style="width: 24%"
></p>

CSS hat hier seine Grenzen - Breiten und Höhen basierend auf "anderen" Elementen zu definieren, kann CSS nicht.

🎯 Ziele der dynamischen Tabelle

Wir möchten eine Tabelle, die folgendes kann:

  1. Automatische Berechnung: 100% Breite = höchster Wert, alle anderen prozentual abgeleitet
  2. Dynamische Anpassung: Ändern sich Werte, passt sich das Design automatisch an
  3. Wiederverwendbarkeit: Tabelle kann kopiert und mit neuen Werten verwendet werden

JavaScript Setup

Grundlagen JavaScript einbinden

Informationen zum Einbinden findest du auf SelfHTML.

Unser Ansatz:

  • Script am Ende der Seite laden (bessere Performance - First Contentful Paint)
  • Module-System für strukturierte Code-Organisation

Ordnerstruktur erstellen

Erstelle einen javascript Ordner mit folgenden Dateien:

javascript/
├── main.js
└── tables.js

JavaScript verknüpfen

1. In main.js das tables.js importieren:

import "./tables.js";

2. Am Ende von index.html (vor </body>):

<script src="javascript/main.js" type="module"></script>
</body>

💡 Wichtig: type="module" ermöglicht das Importieren anderer Scripts

✅ Test der Einbindung

In tables.js einfügen:

console.log("tables.js loaded");

Testen:

  1. index.html über LiveServer öffnen
  2. Chrome DevTools → Console
  3. Seite neu laden

Du solltest den Text sehen! 🥳 Klick rechts auf das Script-Link - es führt direkt zur Datei. Console log


DOM-Manipulation

Das Document Object Model (DOM)

Der Browser stellt JavaScript das DOM zur Verfügung - eine JavaScript-Repräsentation des gesamten HTML. Über diese Schnittstelle lassen sich Inhalte auslesen und manipulieren.

Elemente finden mit querySelectorAll

document.querySelectorAll("[selector]");

Findet alle Nodes mit dem angegebenen Selektor. Mehr dazu: MDN querySelectorAll


Data-Attribute for the Win

Das Problem lösen

Problem: Suche nach div findet zu viele unnötige Elemente.
Lösung: Data-Attribute

Vorteile von Data-Attributen:

  • Werden von Screenreadern ignoriert
  • Stören HTML-Struktur minimal
  • Geben JavaScript mehr "Wissen"

Tabelle markieren

Tabellen-Container markieren: Dies ist ein neues div Element welches um die bestehende Tabelle erweitert werden muss. Achte darauf, dass du das </div> nach der Tabelle schliesst.

<div data-table-name="benchmark">
  <h4 class="font-20 mb-s">XP-Pen Magic Notepad - CPU Benchmark</h4>
  <!-- ... restliches HTML ... -->
  <p class="font-13 font-color-light">Score (higher is better)</p>
</div>

Jede Column markieren:

<p
  class="table-background-brown text-right mt-xxs mb-xxs pr-s py-xxs"
  data-table-column
>
  720
</p>

💡 Alternative: data-table-type statt data-table-name für verschiedene Tabellentypen


JavaScript-Implementierung

1️⃣ Tabellen finden

document.querySelectorAll("[data-table-name]").forEach((table) => {
  // Code für jede Tabelle
});

Foreach siehe foreach Erklärung

2️⃣ Spalten finden und höchsten Wert ermitteln

Jetzt haben wir das Tabellen Element und können innerhalb wiederum all unsere Spalten finden. Wieso brauchen wir das? Damit die Breite der Spalten stimmt, müssen wir zuerst den höchsten Wert finden. Denn dieser bestimmt 100%. Davon abgeleitet können wir dann berechnen, wie viel % Breit die Spalte sein sollte.

Zuerst suchen wir uns alle columns. Danach gehen wir durch alle Spalten durch und merken uns den höchsten Wert. Dass kannst du mit einer lokalen Variable machen. Diese muss mit let definiert sein, wenn du den Werte anpassen willst. Ist der Wert Schreibgeschützt kannst du const nutzen. In unserem Fall brauchen wir let da wir den Wert neu schreiben, wenn er grösser als der vorherige ist.

document.querySelectorAll("[data-table-name]").forEach((table) => {
  
  // neuer Code
  const columns = table.querySelectorAll("[data-table-column]");

  let columnWidest = 0;
  columns.forEach((col) => {
    const colWidth = Number(col.innerText);
    if (colWidth > columnWidest) {
      columnWidest = colWidth;
    }
  });
  // neuer Code
  
});

Erklärung:

  • col.innerText: Nur Text, keine HTML-Tags oder Leerzeichen
  • Number(): Konvertiert Text zu Zahl für Vergleiche
  • Optional: trim() für Leerzeichen entfernen

3️⃣ Breiten berechnen und setzen

document.querySelectorAll("[data-table-name]").forEach((table) => {
  const columns = table.querySelectorAll("[data-table-column]");

  let columnWidest = 0;
  columns.forEach((col) => {
    const colWidth = Number(col.innerText);
    if (colWidth > columnWidest) {
      columnWidest = colWidth;
    }
  });
  // neuer Code
  columns.forEach((col) => {
    const colWidth = col.innerText;
    const width = (100 / columnWidest) * colWidth;
    col.style.width = `${width}%`;
  });
  // neuer Code
});

4️⃣ HTML-Style entfernen

Da JavaScript die Breite berechnet, kannst du das style="width: 24%" im HTML entfernen:

<p
  class="table-background-brown text-right mt-xxs mb-xxs pr-s py-xxs"
  data-table-column
></p>

Im Prinzip kannst du es als Rückfall drin lassen, sollte das JavaScript nicht geladen werden.


Erweiterte Version (Kürzer aber komplexer)

Kompakte Lösung

const maxWidth = Math.max(
  ...Array.from(columns, (col) => Number(col.innerText))
);
columns.forEach((col) => {
  col.style.width = `${(100 * Number(col.innerText)) / maxWidth}%`;
});

Erklärung der neuen Konzepte

Math.max()

MDN Math.max - Gibt den höchsten Wert zurück.

Array.from()

MDN Array.from - Erstellt Array aus Columns und deren innerText-Werten: [720, 789, 1023]

Spread Operator ...

MDN Spread Syntax - "Spreaded" Array-Elemente als einzelne Parameter:

  • Math.max([1,2,3]) ❌ funktioniert nicht
  • Math.max(...[1,2,3]) ✅ funktioniert → Math.max(1,2,3)

Template Literals

`${(100 * Number(col.innerText)) / maxWidth}%`;

Mit ${} können Variablen und Berechnungen direkt im String verwendet werden.


Fazit

AnsatzVorteileNachteile
AusführlichGut verständlich, lernfreundlichMehr Code
KompaktWeniger Code, eleganterErfordert mehr JS-Wissen

Lernempfehlung

  1. Beginne mit der ausführlichen Version
  2. Verstehe jeden Schritt
  3. Erweitere Wissen schrittweise zu kompakteren Lösungen

💡 AI & Google: Du wirst oft auf kompakte Varianten stoßen - je mehr du lernst, desto verständlicher werden sie!

🛎️ Push the Button

🎯 Ziele

Unsere Buttons sehen zwar aus wie Buttons, aber sie machen noch nichts. Wir wollen folgende Funktionen erreichen:

  1. Artikel Like Button - mit Unlike und Zähler
  2. Autor:in Folgen Button - mit Unfollow
  3. Thema Folgen Button - mit Unfollow

Frontend-Focus: In dieser Umsetzung konzentrieren wir uns auf Frontend-Logik. Daten werden nur im Frontend gespeichert - bei einem Page-Reload werden alle Status zurückgesetzt.


Skript Setup

Button-Script erstellen

  1. Neue Datei erstellen: buttons.js im javascript Ordner
  2. Import in main.js hinzufügen
  3. Test mit Console-Output (siehe Kapitel "Table Auto Design")

🐛 Debugging-Optionen

Für lokales Debugging siehe:


Event Listener verstehen

Events Grundlagen

Ein Event ist eine Nachricht wenn eine Aktion stattfindet - wie "Button XY wurde gedrückt". Der Browser kennt Standard-Reaktionen auf Events (z.B. Checkbox an/aus).

Mehr dazu: MDN Event.preventDefault

Events finden

Öffne für mehr Infos

Herausforderung: Nicht jedes Element bietet die selben Events an:

  • Button kann geklickt werden
  • Textfeld kann beschrieben werden
  • Maus kann bewegt werden

Aber wie finde ich heraus, welche Events verfügbar sind?

MDN-Dokumentation durchsuchen

  1. Button-spezifische API: HTMLButtonElement
  2. Instance Methods: HTMLButtonElement#instance_methods
  3. Problem: Der "click" Event ist hier nicht direkt zu finden!

Vererbungskette verstehen

Wichtiger Hinweis: Button erbt Methoden vom Eltern-Element:

"inherits methods from its parent, HTMLElement"

Lösungsweg: Durch die Vererbungskette navigieren:

  • HTMLButtonElementHTMLElementElement
  • Ziel: Element API - hier finden sich die meisten Events!

Praktische Tipps

MethodeBeschreibungEffektivität
DokumentationMDN systematisch durchsuchenVollständig aber zeitaufwändig
Trial & ErrorEvents ausprobieren und testenSchnell für bekannte Events
IDE-SupportAutocomplete und IntelliSense nutzenSehr praktisch während Entwicklung

Praxis-Tipp: Die Devise "trial and error" ist oft effizienter als stundenlanges Dokumentation-Studium!

Event Listener erstellen

Schritt 1: Alle Buttons finden und Event Listener registrieren

document.querySelectorAll("button").forEach((button) => {
  // Für jeden Button einen Click-Listener erstellen
});

Event Listener hinzufügen: MDN addEventListener

button.addEventListener("click", () => {
  // Handle button click
});

Der erste Parameter ist der Event Type, der zweite (nach dem ,) nimmt eine Funktion entgegen, welche ausgeführt werden soll, wenn der Listener greift. Nun wird es etwas kompliziert, wenn du dies im Detail verstehen möchtest… Lies dazu JavaScript und Callbacks oder übernimmt einfach mal nachfolgende Code Snippets.

Erster Test

document.querySelectorAll("button").forEach((button) => {
  button.addEventListener("click", () => {
    // Handle button click
    alert(`Button "${button.textContent.trim()}" wurde geklickt!`);
  });
});

Teste im Browser - bei jedem Button-Klick sollte ein Alert erscheinen!


HTML mit Data-Attributen erweitern

Button-Unterscheidung

Wir haben 3 verschiedene Button-Typen. Für bessere JavaScript-Kontrolle erweitern wir das HTML:

Data-Attribute hinzufügen:

  • data-button-state: "active" oder "inactive"
  • data-button: Button-Name zur Identifikation

HTML-Updates

Like Button

<button
  data-button-state="inactive"
  data-button="like_article"
  class="primary mb-s font-small align-items-center text-center">

Autor:in folgen

<button
  data-button-state="inactive"
  data-button="follow_author"
  class="primary">

Thema folgen

<button
  data-button-state="inactive"
  data-button="follow_topic"
  class="primary">

Spezifische Button-Auswahl

document.querySelectorAll("button[data-button]").forEach((button) => {
  // Nur Buttons mit data-button Attribut
});

Button States Management

⚡ Switch-Statement für Button-Typen

Siehe auch JavaScript Switch Statement

document.querySelectorAll("button[data-button]").forEach((button) => {
  button.addEventListener("click", () => {
    switch (button.dataset.button) {
      case "like_article":
        break;
      case "follow_author":
        break;
      case "follow_topic":
        break;
      default:
        console.warn(`Unbekannter Button-Typ: ${button.dataset.button}`);
    }
  });
});

Toggle-Funktionalität

Im nächsten Schritt wollen wir ein einfaches "toggle" einbauen. Das ist der Begriff, wenn zwischen Zuständen gewechselt wird. Meistens zwischen 2 wie bei uns. Von inaktiv auf aktiv und zurück.

Toggle-Funktion erstellen:

Damit der Button tatsächlich "toggled" wollen wir die Funktion toggleButtonState so erweitern, dass sie den nun aktiven State ausliest und ihn "umdreht". Zusätzlich setzen bzw. entfernen wir den primary-button als CSS Klasse. So wird bereits optisch was passieren.

Lies also als erstes den state aus dem Button und speichere ihn als lokale Variable in der Funktion ab.

function toggleButtonState(button) {
  const currentState = button.dataset.buttonState;

Nun kannst du mit einem "if" unterscheiden was basierend auf dem aktuellen State passieren soll. Nebst der CSS Klasse setzen wir auch den State zurück.

function toggleButtonState(button) {
  const currentState = button.dataset.buttonState;
  
  if (currentState == "active") {
    button.classList.add("primary");
    button.dataset.buttonState = "inactive";
  } else {
    button.classList.remove("primary");
    button.dataset.buttonState = "active";
  }
}

Funktionsaufruf in Switch-Statement:

Nun muss die Funktion noch aufgerufen werden.

switch (button.dataset.button) {
  case "like_article":
    toggleButtonState(button);
    break;
  case "follow_author":
    toggleButtonState(button);
    break;
  case "follow_topic":
    toggleButtonState(button);
    break;
}

CSS-Klassen automatisch togglen

Mit classList.toggle wird das setzen/entfernen der CSS Klasse noch einfacher:

function toggleButtonState(button) {
  const currentState = button.dataset.buttonState;
  button.classList.toggle("primary");
  
  if (currentState == "active") {
    button.dataset.buttonState = "inactive";
  } else {
    button.dataset.buttonState = "active";
  }
}

Button-Texte dynamisch ändern

Text-Parameter hinzufügen

function toggleButtonState(button, activationText, inactivatingText) {
  const currentState = button.dataset.buttonState;
  button.classList.toggle("primary");

  if (currentState == "active") {
    button.textContent = activationText;
    button.dataset.buttonState = "inactive";
  } else {
    button.textContent = inactivatingText;
    button.dataset.buttonState = "active";
  }
}

Erklärungen

  • mit .textContent erhalten wir direkt den Text ohne inneres HTML
  • activationText und inactivatingText sind zwei neue Funktions Parameter
  • Die Texte müssen übergeben werden, damit sie was tun

Text-Konstanten definieren

Diese werden am Anfang des Files definiert und erlauben einfaches lesen der Texte.

const likePageText = "Dieser Artikel gefällt mir!";
const unlikePageText = "Dieser Artikel gefällt mir nicht mehr";
const followAuthorText = "Autor:in folgen";
const unfollowAuthorText = "Autor:in nicht mehr folgen";
const followTopicText = "Thema folgen";
const unfollowTopicText = "Thema entfolgen";

Switch-Statement mit Texten

Damit die Texte stimmen, übergibst du sie aus der Switch Funktion an die Toggle Funktion.

switch (button.dataset.button) {
  case "like_article":
    toggleButtonState(button, likePageText, unlikePageText);
    break;
  case "follow_author":
    toggleButtonState(button, followAuthorText, unfollowAuthorText);
    break;
  case "follow_topic":
    toggleButtonState(button, followTopicText, unfollowTopicText);
    break;
}

Like Button erweitern

Like Counter implementieren

Der Like Button braucht zusätzliche Features:

  • Zähler für Likes
  • Icon mit Herz/gebrochenem Herz (Kosmetik)

HTML für Counter markieren

Zahl mit span und ID markieren:

Den counter legen wir lokal im JavaScript an, mit dem Bewusstsein, dass die Daten später von einem Server kommen sollten. Dazu müssen wir zudem im HTML die Stelle markieren, wo der Counter steht.

Gehe zur Stelle im HTML wo steht "59 Personen gefällt dieser Artikel". Wir wollen nur die Zahl steuern können. Dafür ist ein <span> Element perfekt geeignet, da es keine Zeile erstellt. Füge ein span Element um die Zahl ein und vergib die id "data-like-counter".

<span id="data-like-counter">59</span> Personen gefällt dieser Artikel

💡 Wichtig: IDs dürfen nur einmal pro HTML existieren!

Like-spezifische Toggle-Funktion

Der Like Button wird nun eine Sonderbehandlung brauchen. Lege eine neue Function an im JavaScript und nenne sie toggleLikeButtonState. Als Parameter brauchen wir den Button.

function toggleLikeButtonState(button) {
  const currentState = button.dataset.buttonState;
  const likeCounter = document.querySelector("#data-like-counter");
  // Alternative: document.getElementById("data-like-counter");
  
  let currentCount = parseInt(likeCounter.textContent);
  
  if (currentState == "inactive") {
    currentCount++;
  } else {
    currentCount--;
  }
  
  likeCounter.textContent = currentCount;
}

Erklärungen

  • currentCountdient als Zwischenspeicher des aktuellen Count
  • parseIntkonvertiert den Text zu einer Zahl, damit das Rechnen sauber klappt
  • button.dataset.buttonStateholt den Wert des Data Attributes mit Namen button-state
  • der currentState dient für die Unterscheidung, ob plus oder minus gezählt wird
  • mit likeCounter.textContent = wird der neue Wert gesetzt

Integration mit bestehender Toggle-Logik

Handelt es sich um den Like Button, soll die neue Logik aufgerufen werden.

case "like_article":
  toggleLikeButtonState(button, likePageText, unlikePageText);
  break;

Text und Aussehen korrigieren

Wenn du jetzt ausprobierst, was passiert, wirst du bemerken, dass zwar der Zähler funktioniert, dafür aber der Text und Aussehen nicht mehr stimmen. Das gute ist, die Logik haben wir ja bereits im "toggleButtonState" geschrieben und können sie einfach nochmals aufrufen, bevor wir den Counter setzen.

function toggleLikeButtonState(button) {
  const currentState = button.dataset.buttonState;
  const likeCounter = document.querySelector("#data-like-counter");
  // Alternative: document.getElementById("data-like-counter");
  
  let currentCount = parseInt(likeCounter.textContent);
  
  // bestehende Logik wiederverwenden
  toggleButtonState(button, likePageText, unlikePageText);

  if (currentState == "inactive") {
    currentCount++;
  } else {
    currentCount--;
  }
  
  likeCounter.textContent = currentCount;
}

Refactoring

An der Lösung ist noch etwas unsauber, dass sich die Methode toggleLikeButtonState ums direkte setzten der Texte kümmert. Wir möchten auch hier die Texte als Parameter entgegen nehmen, auch wenn wir hier grundsätzlich "wissen" um welche Texte es sich handelt. Lagerst du die Verantwortung jedoch an den Aufrufer aus, dann ist die Methode "unabhängig" und könnte einfacher umgezogen werden. Erweitere also die Methode genau gleich wie toggleButtonState um die zwei Parameter, übergib dieses an die toggleButtonState Methode und übergib die Werte wieder bei der Switch Methode.

case "like_article":
        toggleLikeButtonState(button, likePageText, unlikePageText);
        break;

function toggleLikeButtonState(button, activationText, inactivatingText) {
  const currentState = button.dataset.buttonState;
  const likeCounter = document.getElementById("data-like-counter");
  let currentCount = parseInt(likeCounter.textContent);
  toggleButtonState(button, activationText, inactivatingText);

Icon-Integration

Das Icon wird aktuell gelöscht, sobald du den Button drückst. Eigentlich möchten wir auch das Icon korrekt setzen. Dafür erstellen wir zuerst einmal das nötige HTML, dass wir dann einsetzen können. Erstelle am Anfang des Scripts je eine Variable für das "filledHeart" und das "brokenHeart".

SVG-Icons vorbereiten

const filledHeart = document.createElement("span");
const brokenHeart = document.createElement("span");

filledHeart.innerHTML = `<svg
  fill="none" viewBox="0 0 16 16" width="16" height="16"
  class="svg-icon">
  <path fill="#fff" fill-rule="evenodd" 
    d="M13.971 3.029a3.53 3.53 0 0 0-4.983 0L8 4.018l-.988-.989a3.53 3.53 0 0 0-4.983 0 3.54 3.54 0 0 0 0 4.991L8 14l5.972-5.98a3.54 3.54 0 0 0 0-4.991" 
    clip-rule="evenodd"></path>
</svg>`;

brokenHeart.innerHTML = `<svg
  fill="none" viewBox="0 0 16 16" width="16" height="16"
  class="svg-icon">
  <path fill="#000" fill-rule="evenodd" 
    d="M13.971 3.029a3.53 3.53 0 0 0-4.983 0L8 4.018l-.988-.989a3.53 3.53 0 0 0-4.983 0 3.54 3.54 0 0 0 0 4.991L8 14l5.972-5.98a3.54 3.54 0 0 0 0-4.991" 
    clip-rule="evenodd"></path>
  <polyline points="6,4 8,8 7,10 9,12" stroke="#fff" stroke-width="1" fill="none"/>
</svg>`;

// Abstand zwischen Icon und Text
filledHeart.classList.add("mr-s");
brokenHeart.classList.add("mr-s");

Erklärungen

  • createElementerstellt ein HTML Element vom Typ, den du mitgibst
  • mit innerHtmlkann HTML angefügt werden
  • der SVG Code selbst ist eher kommpliziert. Im Prinzip gibt es die Linien vor, die gezeichnet werden

Icons in Toggle-Funktion integrieren

if (currentState == "inactive") {
  button.prepend(filledHeart);
  currentCount++;
} else {
  button.prepend(brokenHeart);
  currentCount--;
}

🎉 Ergebnis

Alle Buttons sind jetzt voll funktionsfähig:

Like Button: Toggle mit Counter und Icon
Follow Author: Toggle mit Text-Änderung
Follow Topic: Toggle mit Text-Änderung

Nächste Schritte

Im nächsten Kapitel verbinden wir die Buttons mit einem Backend für persistente Datenspeicherung!

⛓️‍💥 Backend verbinden

Frontend trifft Backend

Situation verstehen

Frontend-Only: Alles ist einfach - nur ein Datenzustand im Browser:

  • Button gedrückt? ✅ HTML/Cookie
  • Seite geliked? ✅ Lokaler Speicher

Mit Backend: Zwei Datenzustände synchronisieren:

  • 🖥️ Frontend-State (Browser)
  • 🗄️ Backend-State (Datenbank)

🎯 Erste Implementierung-Ziele

  1. Like Count vom Backend laden und korrekt anzeigen
  2. Blog-Page-ID-Wechsel mit Backend-Daten synchronisieren

Code-Setup

Projekt-Code holen

VS Code Settings anpassen

LiveServer-Konfiguration erweitern:

  1. Settings öffnen: Button unten links → Settings

Settings öffnen

  1. LiveServer suchen: "liveServer" eingeben → "In settings.json öffnen"

Settings öffnen 3. JSON erweitern:

"liveServer.settings.ignoreFiles": [
  ".vscode/**",
  "public/00_backend/**"
]

Achte darauf, dass bei der Zeile davor ein Komma am Schluss ist. JSON edit

  1. Speichern und schließen

Optional: SQLite-Editor

SQLite3 Editor Extension installieren für direkten Datenbank-Zugriff.


Projekt-Struktur verstehen

Backend-Struktur (00_backend/)

00_backend/
├── 📄 app.js                    # Express-Hauptanwendung
├── 📄 package.json              # Dependencies & Scripts
├── 📄 .env                      # Umgebungsvariablen
├── 📄 editorialContent.db       # SQLite-Datenbank
├── 📄 readme.md                 # Setup-Anweisungen
├── 📁 routes/                   # API-Endpunkte
│   ├── likes.js                 # Like-Funktionalität
│   ├── authorFollow.js          # Autor-Follow
│   └── topicFollow.js           # Topic-Follow
├── 📁 db/                       # Datenbank-Layer
│   ├── blogPageLikesRepo.js     # Like-Operationen
│   ├── authorFollowRepo.js      # Autor-Follow-Ops
│   └── topicFollowRepo.js       # Topic-Follow-Ops
└── 📁 utils/                    # Middleware & Tools
    ├── corsMiddleware.js        # CORS-Konfiguration
    └── swagger.js               # API-Dokumentation

Backend starten

# Terminal öffnen
cd public/00_backend

# Dependencies installieren  
npm install

# Development-Server starten
npm run dev

# ✅ Backend läuft auf Port 3000
# 📚 API-Docs: http://localhost:3000/api-docs/

Frontend-Struktur (04_01_backendBaseConnection/)

04_01_backendBaseConnection/
├── 📄 index.html                # Haupt-HTML
├── 📁 css/                      # Styling
├── 📁 javascript/
│   ├── 📄 main.js               # Entry Point
│   ├── 📁 services/             # Backend-Services
│   │   ├── 📄 observer.js       # Event-System (Singleton)
│   │   ├── 📄 httpClient.js     # HTTP-Wrapper
│   │   ├── 📄 blogPageLikes.js  # Like-API-Calls
│   │   ├── 📄 authorFollow.js   # Autor-Follow-APIs
│   │   └── 📄 topicFollow.js    # Topic-Follow-APIs
│   └── 📁 pages/blogPage/       # Blog-spezifische Logik
│       ├── 📄 blogPageMain.js   # Koordination
│       ├── 📄 buttons.js        # Button-Events
│       ├── 📄 likeStateHandler.js    # Like-State
│       ├── 📄 followStateHandler.js  # Follow-State
│       └── 📄 tables.js         # Tabellen-Management

Architektur-Highlights

Services Layer

  • Backend-Integration: Alle API-Calls (erfordert laufenden Backend-Server)
  • Observer Pattern: Event-System für applikationsweite Zustandssynchronisation
  • Vorteil: Keine Page-Reloads bei Datenänderungen

Event-Driven Architecture

  • Observer.js: Singleton-Pattern für typsafe Events
  • Auto UI-Updates: Änderungen reflektiert überall
  • Performance: Minimale API-Calls durch intelligente Zustandsverwaltung

Moderne JavaScript-Patterns

  • ES6-Module: Import/Export-System
  • Async/Await: Statt Promise-Chains
  • Event Prevention: Verhindert Page-Reloads

Frontend ↔ Backend Workflow

  1. Frontend → API-Calls über Services
  2. Backend → JSON-Response
  3. Observer → Änderungen an UI-Komponenten verteilen
  4. UI → Automatische Updates ohne Page-Reload

Like Count Implementation

Schritt 1: setLikeCounter-Funktion erstellen

In likeStateHandler.js - am Ende der Datei:

function setLikeCounter() {
  // Logik kommt hier rein
}

Schritt 2: BlogPageId-Validierung

function setLikeCounter() {
  if (blogPageId) {
    // Nur weiter wenn gültige BlogPageId vorhanden
  }
}

Schritt 3: HTML-Element finden und Test-Wert setzen

function setLikeCounter() {
  if (blogPageId) {
    document.getElementById("data-like-counter").textContent = 99;
  }
}

Schritt 4: Funktion integrieren

blogPageMain.js erweitern:

// Importiere die notwendigen Module
import "./buttons.js";
import "./tables.js";
import "./likeStateHandler.js";

likeStateHandler.js - am Ende, damit die Funktion aufgerufen wird:

observeUserIdChange();
observeBlogPageIdChange();
observeLikeEvents();
setLikeCounter();

Starte die Page mit dem LiveServer und prüfe, ob du was siehst beim Button unterhalb des "Dieser Artikel gefällt mir!".

Aus 2 Gründen wirst du jetzt nichts sehen:

  1. Die "blogPageId" ist noch 0 gesetzt, was unser if abfängt
  2. Später wollen wir sicherstellen, dass wir erst was anzeigen, wenn die Daten stimmen. Darum gibt es ein "invisible" auf dem ganzen Element. Dieses müssen wir entfernen

Schritt 5: BlogPageId setzen & Sichtbarkeit

Test-BlogPageId setzen:

let blogPageId = 37832;  // Statt 0

Invisible-Klasse entfernen:

function setLikeCounter() {
  if (blogPageId) {
    document.getElementById("data-like-counter").textContent = 99;
    document.getElementById("like-counter").classList.remove("invisible");
  }
}

✅ Test: Du solltest jetzt "99" und den Text sehen!


Backend-Integration

Schritt 1: Service importieren

import { appObserver, ObserverEvents } from "../../services/observer.js";
import { getLikesPerBlogPage } from "../../services/blogPageLikes.js";

Damit kannst du die Daten vom Backend laden.

Schritt 2: Backend-Call implementieren

function setLikeCounter() {
  if (blogPageId) {
    try {
      const response = getLikesPerBlogPage(blogPageId);
      document.getElementById("data-like-counter").textContent = response.likeCount;
      document.getElementById("like-counter").classList.remove("invisible");
    } catch (error) {
      console.error("Error loading likes:", error);
    }
  }
}

** Problem**: Keine Zahl wird angezeigt? Async-Problem! Siehe auch callbacks

Schritt 3: Async/Await Pattern

Problem: Code wartet nicht auf Backend-Response.
Lösung: await für asynchrone Operationen. Zudem musst du die function mit async auszeichnen, damit ein awaiterlaubt ist.

async function setLikeCounter() {
  if (blogPageId) {
    try {
      const response = await getLikesPerBlogPage(blogPageId);
      document.getElementById("data-like-counter").textContent = response.likeCount;
      document.getElementById("like-counter").classList.remove("invisible");
    } catch (error) {
      console.error("Error loading likes:", error);
    }
  }
}

✅ Test: Echte Backend-Daten werden geladen!


Event-Driven Counter Updates

Im Prinzip könnte sich die BlogPageId ändern. Dafür gibt es bereits einen abonnierten Event im likeStateHandler. Anstelle dass wir den Counter beim Laden setzen, nutzen wir den Event. Immer wenn sich die Id ändert laden wir den korrekten Wert aus dem Backend.

BlogPageId-Change-Event nutzen

Direkten Funktionsaufruf entfernen:

observeUserIdChange();
observeBlogPageIdChange();
observeLikeEvents();
// setLikeCounter(); ← Diese Zeile entfernen

Observer-Integration

In observeBlogPageIdChange():

function observeBlogPageIdChange() {
  appObserver.subscribe(
    ObserverEvents.BLOG_PAGE_ID_CHANGED,
    async (data) => {
      blogPageId = data.blogPageId;
      console.log("Current Blog Page ID in likeStateHandler:", blogPageId);
      
      setLikeCounter(); // ← Hier aufrufen
    },
    true  // Replay-Funktion für Initial-State
  );
}

Event-Flow verstehen

  1. main.js → BlogPageId = 2 setzen
  2. main.jsappObserver.emit() Event versenden
  3. Observer → Alle Subscriber informieren
  4. likeStateHandlersubscribe() mit true = Replay-Funktion
  5. Replay → Auch nachträglich registrierte Subscriber erhalten letzten Wert

BlogPageId zurücksetzen

let blogPageId = 0;  // Wieder auf 0, da Wert von Observer kommt

✅ Test: Likes werden weiterhin geladen, aber jetzt event-driven!

Du kannst dies auch im Console.log überprüfen. LikeStateHandler Ausgabe


Testing & Experimentation

BlogPageId dynamisch ändern

In main.js experimentieren:

// Verschiedene Blog-Page-IDs testen
appObserver.emit(ObserverEvents.BLOG_PAGE_ID_CHANGED, { blogPageId: 37832 });

Development Workflow

  1. Backend starten: cd 00_backend && npm run dev
  2. Frontend öffnen: index.html über LiveServer
  3. API testen: http://localhost:3000/api-docs/
  4. Console-Logs beobachten für Event-Flow-Debugging

🎉 Meilenstein erreicht!

✅ Was funktioniert jetzt:

  1. Backend-Integration - Like-Count aus Datenbank
  2. Event-Driven Updates - Observer-Pattern implementiert
  3. Async/Await - Moderne JavaScript-Patterns
  4. Dynamic BlogPageId - Reaktion auf ID-Änderungen

🚀 Nächste Schritte:

Im nächsten Kapitel machen wir die Like-Buttons funktional und synchronisieren Backend-State mit Frontend-Interaktionen!

❤️‍🩹 Like-Funktion wiederherstellen

🎯 Überblick

Mit connect the backend hast du dafür gesorgt, dass Like-Counter-Daten aus dem Backend stammen. Der Button selbst funktioniert aber noch nicht korrekt. Wir implementieren die Like-Verwaltung in zwei Schritten:

Ziele:

  1. Blog-Page liken - mit korrekter UI und Backend-Verbindung
  2. Blog-Page entliken - mit korrekter UI und Backend-Verbindung
  3. User-ID-Wechsel - korrekte Reaktion auf Benutzer-Änderungen

Like & Dislike Implementation

Toggle-Logik verstehen

Like und Dislike sind sehr ähnlich - wir togglen zwischen den Zuständen:

  1. Aktuellen Zustand abfragen (geliked oder nicht?)
  2. Zustand umkehren (like → unlike, unlike → like)
  3. UI entsprechend aktualisieren

Architektur-Entscheidung

Das File likeStateHandler.js soll erneut die Haupt Verantwortung dafür übernehmen. Wir haben auch ein File button.js. Dies soll einzig und alleine feststellen, dass der Button gedrückt wurde und danach alles weitere dem likeStateHandler.js überlassen.

File-Aufgabenverteilung:

  • buttons.js - Nur Button-Klick erkennen und Event versenden
  • likeStateHandler.js - Komplette Like-Logik verwalten

Um so eine Aufgabe zu delegieren, gibt es grundsätzlich verschiedene Möglichkeiten.

  1. Wir stellen eine Funktion bereit über "export" die danach von anderen direkt genutzt werden kann
  2. Wir versenden einen Event und kümmern uns nicht darum, welches File sich der Aufgabe animmt.

Da wir bereits ein Event System zur Verfügung haben, setzen wir Variante 2 um. Diese lässt mehr Spielraum zu.

Event-basierte Kommunikation:

  • ❌ Option 1: Direkte Funktionsaufrufe (enger gekoppelt)
  • Option 2: Event versenden (flexibler, entkoppelter)

Like Event versenden

Im button.js File überwachen wir bereits, ob ein Button geklickt wird. Was uns dort noch fehlt ist der Versand unseres Events, damit wir danach im likeStateHandler.js darauf reagieren können.

Button.js erweitern

Observer und Events importieren:

import { appObserver, ObserverEvents } from "../../services/observer.js";

Event bei Button-Klick versenden:

document.querySelectorAll("button[data-button]").forEach((button) => {
  button.addEventListener("click", () => {
    switch (button.dataset.button) {
      case "like_article":
        appObserver.emit(ObserverEvents.LIKE_BUTTON_CLICKED);
        break;
      // ... andere Cases
    }
  });
});

✅ Test der Event-Übertragung

  1. Seite mit LiveServer starten
  2. Like Button klicken
  3. Console beobachten - Zahlen-Output sollte sich erhöhen

🎉 Erfolg: LikeStateHandler empfängt und verarbeitet dein Event!


Like Event verarbeiten

Du siehst im Console.log wo der Event bereits empfangen wird. Im likeStateHandler.js um Zeile 70 rum. Jetzt müssen wir noch die Logik erweitern, damit es auch das macht, was wir uns erhoffen. Nämlich von dislike auf like und zurück ändern, je nach aktuellem Zustand.

Gehe zur function observeLikeEvents() und ersetze die zuvor implementiere Code Stelle durch den Aufruf einer neuen Funktion die wir toggleLikeState() nennen und im Observer aufrufen.

Toggle-State-Funktion erstellen

Observer-Funktion erweitern:

function observeLikeEvents() {
  appObserver.subscribe(ObserverEvents.LIKE_BUTTON_CLICKED, async () => {
    toggleLikeState();
  });
}

async function toggleLikeState() {
  // Logik kommt hier rein
}

Aktuellen Like-Status abfragen

Damit wir nun wissen, was wir tun müssen, brauchen wir den aktuellen Zustand des Likes (hat die Userin die Page geliked oder nicht)? Wir machen so was ähnliches schon im observeBlogPageIdChange bzw. im setLikeCounter. Als erstes holen wir uns den Zustand aus dem Backend. Denke daran, dass wir wieder alles asynchron haben. Erweitere die Funktion wo nötig mit async

Import der benötigten Services:

import {
  getLikesPerBlogPage,
  getIsLikingBlogPage,
  likeBlogPage,
  unlikeBlogPage,
} from "../../services/blogPageLikes.js";

Like-Status für User abfragen:

async function toggleLikeState() {
  const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
    blogPageId,
    currentUserId
  );

  if (doesUserLikeBlogPageResponse.liked) {
    // User hat bereits geliked → Unlike
  } else {
    // User hat noch nicht geliked → Like
  }
}

Backend-Status ändern

Jetzt können wir direkt dem Backend mitteilen, dass sich der Zustand geändert hat.

async function toggleLikeState() {
  const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
    blogPageId,
    currentUserId
  );

  if (doesUserLikeBlogPageResponse.liked) {
    await unlikeBlogPage(blogPageId, currentUserId);
  } else {
    await likeBlogPage(blogPageId, currentUserId);
  }
}

UI-Updates implementieren

Das UI braucht noch etwas mehr, damit es jeweils korrekt ist.

Button-UI-Update-Funktion

function updateLikeButtonUi(isLiked) {
  const likeButton = document.querySelector(
    "button[data-button='like_article']"
  );

  if (isLiked) {
    likeButton.classList.remove("primary");
    likeButton.innerHTML = unlikePageText;
    likeButton.prepend(brokenHeart.cloneNode(true));
  } else {
    likeButton.classList.add("primary");
    likeButton.innerHTML = likePageText;
    likeButton.prepend(filledHeart.cloneNode(true));
  }
}

Erklärungen

  • damit die Funktion weiss, was zu tun ist, nimmt sie ein Boolean Argument isLikedentgegen
  • basierend auf dem Wert, wird entweder der like oder der dislike Zustand angeschrieben
  • die Texte sind bereits als constim File vorhanden
  • die CSS Klasse primary muss entfernt oder hinzugefügt werden

Toggle-Funktion komplettieren

Zuletzt passe den Aufruf im toggleLikeState()an. Am Schluss ergänze den setLikeCounter() Aufruf, der unabhängig des Zustands genutzt werden kann.

async function toggleLikeState() {
  const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
    blogPageId,
    currentUserId
  );

  if (doesUserLikeBlogPageResponse.liked) {
    await unlikeBlogPage(blogPageId, currentUserId);
    updateLikeButtonUi(false);
  } else {
    await likeBlogPage(blogPageId, currentUserId);
    updateLikeButtonUi(true);
  }

  setLikeCounter(); // Counter aktualisieren
}

✅ Funktionstest

Backend und Frontend starten und Like-Button testen!


Error Handling implementieren

Obwohl der Code funktioniert, könnten Fehler im Backend dazu führen, dass wir nicht sauber darauf reagieren. Ein try-catch um die potentiell problematischen Stellen hilft uns hier. Zudem wollen wir sicher gehen, dass wir mit .liked nicht auf ein allfällig nicht vorhandenes Objekt zugreifen.

Dafür umschliessen wir den ganzen Code oben mit einem try und fangen Errors im Catch und geben sie in der Console aus.

Try-Catch für Robustheit

async function toggleLikeState() {
  try {
    const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
      blogPageId,
      currentUserId
    );

    if (doesUserLikeBlogPageResponse.liked) {
      await unlikeBlogPage(blogPageId, currentUserId);
      updateLikeButtonUi(false);
    } else {
      await likeBlogPage(blogPageId, currentUserId);
      updateLikeButtonUi(true);
    }

    setLikeCounter();
  } catch (error) {
    console.error("Error toggling like state:", error);
  }
}

prüfen auf undefined

Für den Fall, dass die response komplett leer / undefined ist, machen wir nichts. Nutzen wir den ? Operator bspw. if (doesUserLikeBlogPageResponse.?liked) würde das technisch funktionieren und keinen Fehler mehr geben. In Kombination mit einem if hat es aber zur Folge, dass sowohl bei (nicht vorhanden) wie auch (not liked) ein false ergibt und unsere Logik damit unsauber wäre. Wir prüfen also separat ob es vorhanden ist und stoppen alle weiteren Arbeiten, falls nicht. Das kannst du direkt nach dem abwarten der response machen.

async function toggleLikeState() {
  try {
    const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
      blogPageId,
      currentUserId
    );

    if (!doesUserLikeBlogPageResponse) {
      return; // Keine Response erhalten
    }

    if (doesUserLikeBlogPageResponse.liked) {
      await unlikeBlogPage(blogPageId, currentUserId);
      updateLikeButtonUi(false);
    } else {
      await likeBlogPage(blogPageId, currentUserId);
      updateLikeButtonUi(true);
    }

    setLikeCounter();
  } catch (error) {
    console.error("Error toggling like state:", error);
  }
}

User-ID-Wechsel handhaben

Aktuell berücksichtigt die Logik nur eine statische UserId aus dem likeStateHandler.js, die mit 0 gesetzt ist. Ein Wechsel der UserId wird nicht berücksichtigt. Zudem ist der initiale Stand beim Laden der Page potentiell falsch, da dies noch nicht korrekt geladen wird.

User-Change Observer erweitern

Bestehende Observer-Funktion finden:

function observeUserIdChange() {
  appObserver.subscribe(
    ObserverEvents.USER_ID_CHANGED,
    async (data) => {
      currentUserId = data.userId;
      console.log("Current User ID in likeStateHandler:", currentUserId);
      // Hier Like-Status für neuen User laden
    },
    true // Replay-Funktion für Initial-State
  );
}

Like-Status-Check-Funktion erstellen

Auch hier ist es ähnlich wie mit der Blog Page Id. Die UserId wird initial schon im main.js gesetzt. Weil das Abo mit "true" am schluss auch Daten erhält, wenn es erst nachträglich das Abo bezieht, kriegen wir ganz sicher auch einen Initial-State und können darauf reagieren.

Jetzt müssen wir im Prinzip nur herausfinden, welches der aktuelle State ist und das UI entsprechend korrekt darstellen.

Diesen Teil aus der vorherigen Methode macht genau das was wir erneut brauchen:

const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
  blogPageId,
  currentUserId
);
if (!doesUserLikeBlogPageResponse) {
  return;
}

Wie so oft… wenn es nach "ich kann das nochmals brauchen" klingt, dann schreit es danach, die Funktionalität in eine separate Funktion auszulagern.

Wiederverwendbare Status-Check-Funktion:

Damit wir "von aussen" wissen, ob es einen Fehler gab oder ob die Antwort "true / false" ist, müssen wir unsere neue Funktion etwas smarter gestalten. Da wir im JavaScript ohne weiteres und jederzeit ein Objekt erstellen können, machen wir genau das. Wir erweitern die Antwort der Funktion um: {success: boolean, liked: boolean, error: string}

Als erstes nutzen wir das, wenn die "response" undefined / null ist. Dann hat irgendwie was nicht geklappt und wir können nicht sagen, ob eine Userin die Page liked oder nicht.

async function checkUserLikeStatus() {
  try {
    const response = await getIsLikingBlogPage(blogPageId, currentUserId);

    if (response === undefined || response === null) {
      return { success: false, liked: false, error: "No response received" };
    }
  } catch (error) {
    return { success: false, liked: false, error: error.message };
  }
}

Mit return {} erstellen wir ein Objekt, füllen es mit den Properties ab und geben dies zurück. Das ist das coole an JavaScript. Kein "boilerplate" nötig wo wir zuerst mal den definieren müssen, wie das Objekt aussieht. Auf der anderen Seite haben wir auf der "aufrufenden" Seite keine Ahnung, was wir eigentlich als Antwort kriegen…

Das selbe gilt wenn wir in den "catch" laufen. Wir wissen es hat was nicht geklappt und können die Antwort aus dem error direkt weitergeben.

async function checkUserLikeStatus() {
  try {
    const response = await getIsLikingBlogPage(blogPageId, currentUserId);

    if (response === undefined || response === null) {
      return { success: false, liked: false, error: "No response received" };
    }

  } catch (error) {
    return { success: false, liked: false, error: error.message };
  }
}

Wenn es weder "catch" noch undefined / null gewesen ist, hat es wohl funktioniert. Also geben wir ein sucess:true, den like state aus der response sowie keine Fehlermeldung == null zurück.

async function checkUserLikeStatus() {
  try {
    const response = await getIsLikingBlogPage(blogPageId, currentUserId);

    if (response === undefined || response === null) {
      return { success: false, liked: false, error: "No response received" };
    }

    return { success: true, liked: response.liked, error: null };

  } catch (error) {
    return { success: false, liked: false, error: error.message };
  }
}

Toggle-Funktion refactoren

Status-Check-Funktion verwenden:

async function toggleLikeState() {
  try {
    const likeStatus = await checkUserLikeStatus();

    if (!likeStatus.success) {
      console.error("Could not determine like status:", likeStatus.error);
      return;
    }

    if (likeStatus.liked) {
      await unlikeBlogPage(blogPageId, currentUserId);
      updateLikeButtonUi(false);
    } else {
      await likeBlogPage(blogPageId, currentUserId);
      updateLikeButtonUi(true);
    }

    setLikeCounter();
  } catch (error) {
    console.error("Error toggling like state:", error);
  }
}

Erklärungen

  • wir nutzen neu den likeStatusum zu wissen, ob wir abbrechen oder nicht
  • mit dem .kannst du auf die Properties eines Objekts zugreifen. Hier für den status likeStatus.success, den like zustand likeStatus.liked und fehlermeldung likeStatus.error

Like State updaten und initialiseren

Jetzt haben wir die Basis erarbeitet, um die Logik in unserer Changed User ID Logik wieder zu verwenden. Gehe zur Funktion observeUserIdChange() wo wir nun als erstes unsere neue Funktion checkUserLikeStatus aufrufen und analog zu vorher weiter verarbeiten.

const likeStatus = await checkUserLikeStatus();

Wenn der Status success ist, können wir direkt das UI updaten und wenn nicht, machen wir nichts bzw. können noch eine Meldung in der Console ausgeben. Zudem rufe noch die setLikeCounter auf. Damit haben wir bereits alles gemacht, um bei einem change der userID alles korrekt zu machen. Starte alles inklusive Backend und teste etwas. Du kannst nun oben rechts die userID anpassen und schauen was passiert.

Dein Code im observeUserIdChange() sollte ungefähr so aussehen.

function observeUserIdChange() {
  appObserver.subscribe(
    ObserverEvents.USER_ID_CHANGED,
    async (data) => {
      currentUserId = data.userId;
      const likeStatus = await checkUserLikeStatus();
      if (likeStatus.success) {
        updateLikeButtonUi(likeStatus.liked);
        setLikeCounter();
      } else {
        console.error("Could not fetch like status:", likeStatus.error);
      }
    },
    true
  );
}

Was noch fehlt ist das selbe, wenn die blog page id ändert. Dies ist in unserem Fall eigentlich nicht möglich, aber wir wollen den Init über das Main.js nutzen, wo die Blog Page ID gesetzt wird. So ist beim Laden sichergestellt, dass der Like State stimmt.

Ergänze daher auch die Funktion observeBlogPageIdChange() mit dem Setzen des likeStatus und des counters.

💡 Dir fällt sicher auf, dass wir wiederum Code in zwei Methoden haben, der deckungsgleich ist. Das Laden des States und das Setzen.

DRY-Prinzip - Wiederverwendbare Init-Funktion:

Don't repeat yourself. Legen wir dies so zusammen, dass wir es wieder verwenden können. Mittels einer neuen Funktion initLikeState() fassen wir die gemeinsame Logik zusammen und nutzen danach diese.

async function initLikeState() {
  const likeStatus = await checkUserLikeStatus();

  if (likeStatus.success) {
    updateLikeButtonUi(likeStatus.liked);
    setLikeCounter();
  } else {
    console.error("Could not fetch like status:", likeStatus.error);
  }
}

Observer-Funktionen finalisieren

User-ID-Change Observer:

function observeUserIdChange() {
  appObserver.subscribe(
    ObserverEvents.USER_ID_CHANGED,
    async (data) => {
      currentUserId = data.userId;
      await initLikeState();
    },
    true
  );
}

Blog-Page-ID-Change Observer:

function observeBlogPageIdChange() {
  appObserver.subscribe(
    ObserverEvents.BLOG_PAGE_ID_CHANGED,
    async (data) => {
      blogPageId = data.blogPageId;
      await initLikeState();
    },
    true
  );
}

🎉 Endergebnis

✅ Implementierte Features

  1. Like/Unlike Toggle - Backend-synchronisiert
  2. UI-Updates - Button-Text und Icon-Änderungen
  3. Counter-Updates - Automatische Zähler-Aktualisierung
  4. User-Switching - Korrekte Like-Status-Anzeige pro User
  5. Error Handling - Robuste Fehlerbehandlung
  6. DRY Code - Wiederverwendbare Funktionen

Vollständiger Test

Test-Szenarios:

  1. ✅ Like/Unlike mit verschiedenen Users
  2. ✅ User-ID-Wechsel oben rechts
  3. ✅ Backend-Verbindung aktiv
  4. ✅ Counter-Updates in Echtzeit

Die Like-Funktionalität ist jetzt vollständig implementiert! 🚀

🚓 Follow-Funktion wiederherstellen

Aufgabe

Du hast gelernt, wie du das Backend für State-Management nutzt - mit Services wie Observer und blogPageLikes. Jetzt fehlt die Follow-Funktionalität für Author und Topic. Nutze dein erworbenes Wissen!

💪 Challenge: Wissen ist Macht, und mit Macht kommt... noch mehr Code! 😄


Author Follow implementieren

Vorbereitung: followStateHandler.js

Das File followStateHandler.js ist bereits vorbereitet:

const followAuthorText = "Autor:in folgen";
const unfollowAuthorText = "Autor:in nicht mehr folgen";
const followTopicText = "Thema folgen";
const unfollowTopicText = "Thema entfolgen";

import { appObserver, ObserverEvents } from "../../services/observer.js";

let currentUserId = 0;
let blogPageId = 0;

function observeUserIdChange() {
  appObserver.subscribe(
    ObserverEvents.USER_ID_CHANGED,
    async (data) => {
      currentUserId = data.userId;
      console.log("Current User ID in followStateHandler:", currentUserId);
      // Weitere Aktionen bei User-ID-Änderung
    },
    true
  );
}

function observeBlogPageIdChange() {
  appObserver.subscribe(
    ObserverEvents.BLOG_PAGE_ID_CHANGED,
    async (data) => {
      blogPageId = data.blogPageId;
      console.log("Current Blog Page ID in followStateHandler:", blogPageId);
      // Weitere Aktionen bei Blog-Page-ID-Änderung
    },
    true
  );
}

observeUserIdChange();
observeBlogPageIdChange();

Integration aktivieren

Das File wird noch nicht geladen! Import in blogPageMain.js hinzufügen:

import "./followStateHandler.js";

Implementation-Aufgaben

1️⃣ Button Event-System erweitern

In buttons.js: Follow-Button-Klicks via Observer weiterleiten

Beispiel für Author-Follow:

case "follow_author":
  appObserver.emit(ObserverEvents.AUTHOR_FOLLOW_BUTTON_CLICKED, {
    button: button
  });
  break;

2️⃣ Follow State Handler implementieren

Event empfangen und verarbeiten:

appObserver.subscribe(ObserverEvents.AUTHOR_FOLLOW_BUTTON_CLICKED, async (data) => {
  const button = data.button;
  // Toggle-Logik implementieren
});

3️⃣ UI korrekt aktualisieren

  • Text zwischen follow/unfollow wechseln
  • CSS-Klasse primary richtig setzen/entfernen
  • Backend-State synchronisieren

4️⃣ User/BlogPage-ID-Changes handhaben

Bei Änderungen der User-ID oder Blog-Page-ID korrekten Follow-Status laden und UI aktualisieren.


Hilfreiche Code-Snippets

Author Email aus Button extrahieren

const authorEmail = button.dataset.authorEmail;

Event mit Button-Daten versenden

appObserver.emit(ObserverEvents.AUTHOR_FOLLOW_BUTTON_CLICKED, {
  button: button
});

Event mit Button-Daten empfangen

appObserver.subscribe(ObserverEvents.AUTHOR_FOLLOW_BUTTON_CLICKED, async (data) => {
  const button = data.button;
  const authorEmail = button.dataset.authorEmail;
  // Toggle-Logik hier
});

UI-Update-Pattern

function updateAuthorFollowButtonUi(isFollowing, button) {
  if (isFollowing) {
    button.classList.remove("primary");
    button.textContent = unfollowAuthorText;
  } else {
    button.classList.add("primary");
    button.textContent = followAuthorText;
  }
}

Topic Follow implementieren

Ähnlich, aber mit Unterschieden

Topic Follow ist fast identisch zu Author Follow, mit diesen wichtigen Unterschieden:

1️⃣ Neuer Event-Typ benötigt

ObserverEvents.TOPIC_FOLLOW_BUTTON_CLICKED

2️⃣ Multiple Button Support

Topics können mehrfach pro Blog-Page vorkommen. Alle Topic-Buttons berücksichtigen:

const buttons = document.querySelectorAll("button[data-button='follow_topic']");

for (const btn of buttons) {
  // UI für jeden Button aktualisieren
}

3️⃣ Topic-Name extrahieren

const topicName = button.dataset.topicName;

Implementation-Checkliste

✅ Author Follow

  • Observer Events für Author-Follow erstellt
  • Button-Click-Events in buttons.js implementiert
  • Toggle-Logik in followStateHandler.js
  • Backend-Integration (follow/unfollow API-Calls)
  • UI-Updates (Text + CSS-Klassen)
  • User-ID-Change Handling
  • Blog-Page-ID-Change Handling

✅ Topic Follow

  • Observer Events für Topic-Follow erstellt
  • Multiple Button Support implementiert
  • Topic-Name-Extraktion aus Button-Data
  • Alle Topic-Buttons bei Status-Änderung aktualisieren
  • Backend-Integration vervollständigt

Testing-Szenarios

Ausgiebige Tests durchführen

  1. User-ID wechseln - Follow-Status sollte sich entsprechend ändern
  2. Button-Klicks - Follow/Unfollow funktioniert
  3. Blog-Page-ID ändern - Neue Follow-Status werden geladen
  4. Multiple Topic-Buttons - Alle werden synchron aktualisiert
  5. Backend-Persistenz - Status bleibt nach Page-Reload erhalten

🎉 That's it folks!

Wenn alle Tests erfolgreich sind: Gratulation! 🥂

🐛 Debugging-Hilfe

Falls etwas nicht funktioniert:

  1. Console-Logs überprüfen
  2. Network-Tab für API-Calls kontrollieren
  3. Event-Flow durch Observer verfolgen

Endlösung verfügbar

Bei völliger Verzweiflung kannst du die komplette Lösung auf GitHub einsehen:

GitHub: Follow Buttons mit Backend Integration


🏆 Lernziele erreicht

Nach erfolgreicher Implementation beherrschst du:

Event-driven Architecture mit Observer Pattern
Multi-Button State Management
Backend-Frontend-Synchronisation
Dynamic UI Updates
Error Handling & Robustness

Du bist bereit für komplexere Frontend-Herausforderungen! 🚀

💝 Wrap it up

So long and thanks for all the fish

Du hast in diesem JavaScript-Teil grundlegende Funktionen umgesetzt wie:

  • Styling einer Tabelle
  • Reagieren auf Button-Klicks
  • Backend-Anbindung
  • Komplette Neugestaltung für eine flüssigere Benutzererfahrung

Refactoring - Der Engineering-Alltag

Und ja, ganz viel Refactoring! Das ist tägliche Arbeit im Engineering-Leben:

  1. Etwas implementieren
  2. Weiterentwickeln
  3. Feststellen: "Oh, das geht ja jetzt einfacher!"

Komplexe Konzepte gemeistert

Du hast fortgeschrittene Konzepte erfolgreich umgesetzt:

  • Observer Pattern
  • State Management
  • Event-driven Architecture
  • API-Integration

Wie weiter?

Du hast jetzt ein solides Verständnis für JavaScript und kannst darauf aufbauen.

Option 1: Bestehende Anwendung erweitern

Erweitere das aktuelle Beispiel um weitere Funktionen:

  • Komplette Blog-Page aus dem Backend laden
  • Zusätzliche Features implementieren

💡 Backend ist bereit: Datenbank, statische Routen etc. sind bereits eingerichtet!

Option 2: Framework verwenden

Nimm dir ein modernes Framework zur Hand und baue die Page damit nach:

FrameworkBeschreibungTutorial-Link
AngularVollständiges Framework von GoogleAngular Tutorial
ReactKomponenten-basierte Library von MetaReact Tutorial

Framework-Vorteile

Vieles, was wir aufwändig manuell gebaut haben, nehmen dir Angular/React ab:

  • Automatisches State Management
  • Komponenten-System
  • Routing
  • Form-Handling

Trade-off: Du musst mehr Zeit investieren, um zu verstehen, wie diese Frameworks funktionieren.


🏆 Erreichte Meilensteine

JavaScript-Grundlagen gemeistert
DOM-Manipulation verstanden
API-Integration erfolgreich
Moderne Patterns angewendet
Full-Stack Development erlebt

Congratulations! 🎊 Du bist bereit für den nächsten Schritt in deiner Development-Journey!