HTML-Templatingsprachen

Historisch
Aufwändige Pflege der Webseiten “von Hand”
Erste Automatisierung
Erste Entwurfsmuster für serverseitigen Code
  • Frameworks wie Ruby on Rails prägen das “Model-View-Controller”-Muster
  • Templatingsprachen erlauben eine strikte Trennung von Daten und ihrer Darstellung
Zunehmende Verlagerung der Logik in den Client machen neue Muster erforderlich
“Single Page Applications” - Thema der fortgeschrittenen Web-Anwendungen
Déjà-vu: Generatoren für statische Webseiten werden wieder modern
Erlauben “auf Knopfdruck” das Erzeugen von Webseiten, die auf komplexen Datenbeständen basieren

Historisch: Vermischung von Ausgabe und Berechnung

  • Zeile 7
    Beginn eines Bereichs
    Zeile 8
    Herstellen einer Datenbankverbindung
    Zeile 9
    Erfragen von Datensätzen mit den Spalten name und org
    Zeile 10 - 13
    Schleife über alle gefundenen Datensätze
    Zeile 11 & 12
    Ausgabe der Daten in der jeweiligen Zeile
  • Kein guter Stil!
    • HTML-Ausgabe geht im PHP-Quelltext unter
    • Fehlerbehandlung schwierig
  • templating/negative.php
    <!DOCTYPE html>
    <html>
      <head>
        <title>PHP</title>
      </head>
      <body>
         <?php
           $mysqli = new mysqli("dbsrv", "user", "pass", "db");
           $res = $mysqli->query("SELECT name, org FROM party");
           while ($row = $res->fetch_assoc()) {
             echo "<h1> " . $row['name'] . "</h1>";
             echo "<p> " . $row['org'] . "</p>";
           }
         ?>
      </body>
    </html>
    

Vorteil von Templatingsprachen

Trennung von Daten und ihrer Repräsentation
  • Erlaubt unterschiedliche Repräsentationen für die gleichen Daten
  • Entkoppelt die Arbeit von Designern und Programmierern
Grundsätzlich: Fast identisches Vorgehen für Umgebungen auf dem Server oder im Browser
Unterschiede primär in der Parameterverarbeitung und den Speicherorten der Quelldateien
In dieser Veranstaltung: Serverseitige Erzeugung von Webseiten.
html_templating cluster_output data data render Templating-Engine data->render template Template template->render html html render->html edwin_name Edwin Odesseiron edwin_org Red Wizards of Thay head head html->head body body html->body title title head->title style style head->style h1 h1 body->h1 p p body->p h1->edwin_name p->edwin_org

Die Templatingsprache Handlebars

Quelloffene Templatingsprache
In HTML eingebettete Sprache
  • Arbeitet ausschließlich mit vorab aufbereiteten Daten
  • Folglich: Erzeugt oder verändert unter keinen Umständen neue Datensätze
Kennt aus Programmiersprachen bekannte Kontrollstrukturen
Schleifen, Verzweigungen
Trivial erweiterbar durch Entwickler
Für komplexe Darstellungsaufgaben

Angabe von Daten im “Frontmatter”

Erweiterung: HTML-Dokumente können auch Daten enthalten (“Template”)
  • Daten-Bereich wird durch drei Bindestriche (---) vom HTML- und Handlebars-Quelltext getrennt
  • Erfordert Interpretation durch ein anderes Programm als den Browser
  • Streng genommen ein eigenes Dateiformat, daher Endung .hbs
Angabe von JSON-Daten, welche zu einem bestimmten Dokument gehören
Werte lassen sich dann im Template einsetzen
Konvention: Aufteilung des Datenbestandes in Namensräume
  • page für alle Daten der konkreten Seite
  • global für global verfügbare Daten (Name des Webauftritts, …)

Einsetzen von Daten (Interpolation / “Handlebars Expression”)

  • framework_projects/vorlesung/pages/interpolation-basic-1.hbs
    {
      "vorname": "James",
      "nachname": "Bond"
    }
    ---
    <p>
      Der Name ist {{ page.nachname }},
      <em>{{ page.vorname }}</em> {{ page.nachname }}.
    </p>
    
Geschweifte Klammern {} im Frontmatter bilden ein “Objekt”
  • Daten im Objekt werden in "name": "wert"-Paaren angegeben
  • Der name ist immer eine Zeichenkette und muss immer in Anführungszeichen (") notiert werden
  • Der wert folgt komplexeren Regeln, kann aber auch eine Zeichenkette sein
  • Mehrere "name": "wert"-Paare werden durch Kommata getrennt
Doppelt geschweifte Klammern {{ }} im HTML setzen die Werte ein
Angabe eines “Pfades” zu den Werten im Frontmatter
  • framework_projects/vorlesung/pages/interpolation-basic-2.hbs
    {
      "vorname": "Harry",
      "nachname": "Potter"
    }
    ---
    <p>
      Der Name ist {{ page.nachname }},
      <em>{{ page.vorname }}</em> {{ page.nachname }}.
    </p>
    

Primitive Typen: Wahrheitswerte, Zahlen und Zeichenketten

Grundsätzliche Frage: Welche Typen dürfen auf der rechten Seite angegeben werden?
Exakter: Was ist die Menge der erlaubten Werte auf der rechten Seite?
Wahrheitswerte: Sind exakt true oder false
Keine Anführungszeichen, immer komplett klein geschrieben
Zahlen: Alle Ziffern von 0 bis 9, maximal 1 . für Nachkommastellen und Vorzeichen mit + oder -
Keine Anführungszeichen
Zeichenketten: Alle Buchstaben außer dem Zeilenumbruch für neue Zeilen
Werden immer in Anführungszeichen notiert
Fehlende Werte: Spezialwerte undefined und null
Keine Anführungszeichen, stehen für die Abwesenheit von Werten
  • framework_projects/vorlesung/pages/interpolation-types.hbs
    {
        "vorname": "Hermine",
        "nachname": "Granger",
        "weiblich": true,
        "geburtsjahr": 1988
    }
    ---
    <ul>
      <li>Vorname: {{ page.vorname }}</li>
      <li>Nachname: {{ page.nachname }}</li>
      <li>Weiblich: {{ page.weiblich }}</li>
      <li>Männlich: {{ page.männlich }}</li>
      <li>Geburtsjahr: {{ page.geburtsjahr }}</li>
    </li>

Verzweigungen mit dem if-Block

Allgemeine Syntax: Blöcke werden mit {{#name}} eingeleitet und mit {{/name}} geschlossen
  • #-Zeichen kennzeichnet einen öffnenden Block
  • name des Blocks muss beim Öffnen und Schließen identisch sein
  • Inhalt des Blocks unterliegt dann speziellen Regeln (je nach Typ des Blocks), typischerweise in Abhängigkeit von bereitgestellten Daten
Spezieller if-Block: Angabe eines Wahrheitswertes
  • Inhalt des Blocks wird nur ausgegeben, wenn die Bedingung erfüllt ist
  • Wahrheitswert darf (sollte?) aus den Daten kommen
Folgende Werte erfüllen eine Bedingung nicht (“falsy”):
  • false
  • 0
  • "" (leere Zeichenkette)
  • null und undefined
  • [] (leere Liste, Erklärung folgt später)
Alle anderen Werten gelten als wahr (“truthy”):
true, Strings mit Inhalt, Zahlen ≠ 0, …

Beispiel zum if-Block

  • framework_projects/vorlesung/pages/if-basics.hbs
    {
      "vorname": "Hermine",
      "nachname": "Granger",
      "weiblich": true,
      "geburtsjahr": 1979
    }
    ---
    <ul>
      <li>Vorname: {{ page.vorname }}</li>
      <li>Nachname: {{ page.nachname }}</li>
      {{#if page.weiblich}}
        <li>Geschlecht: Weiblich</li>
      {{/if }}
      {{#if page.männlich}}
        <li>Geschlecht: Männlich</li>
      {{/if}}
      <li>Geburtsjahr: {{ page.geburtsjahr }}</li>
    </ul>

Beispiel zum if-Block: Redundanz vermeiden

  • framework_projects/vorlesung/pages/if-basics-compact.hbs
    {
      "vorname": "Ronald Bilius",
      "nachname": "Weasley",
      "männlich": true,
      "geburtsjahr": 1980
    }
    ---
    <ul>
      <li>Vorname: {{ page.vorname }}</li>
      <li>Nachname: {{ page.nachname }}</li>
      <li>
        Geschlecht:
        {{#if page.weiblich}}
          Weiblich
        {{/if }}
        {{#if page.männlich}}
          Männlich
        {{/if}}
      </li>
      <li>Geburtsjahr: {{ page.geburtsjahr }}</li>
    </ul>

Beispiel zum if-Block: Doppelte Geschlechter

  • framework_projects/vorlesung/pages/if-basics-double.hbs
    {
      "vorname": "Kim",
      "nachname": "Sheringham",
      "weiblich": true,
      "männlich": true,   
      "geburtsjahr": 1979
    }
    ---
    <ul>
      <li>Vorname: {{ page.vorname }}</li>
      <li>Nachname: {{ page.nachname }}</li>
      <li>
        Geschlecht:
        {{#if page.weiblich}}
          Weiblich
        {{/if }}
        {{#if page.männlich}}
          Männlich
        {{/if}}
      </li>
      <li>Geburtsjahr: {{ page.geburtsjahr }}</li>
    </ul>

Alternativen mit dem else-Block

Zu jedem if-Block kann optional ein else-Block definiert werden
  • Wird ausgeführt wenn der if-Block eben nicht ausgeführt wurde
  • Es werden niemals beide Blöcke ausgeführt
  • framework_projects/vorlesung/pages/if-else.hbs
    {
      "vorname": "Firenze",
      "nachname": "(Zentaur)",
      "geburtsjahr": "unbekannt"
    }
    ---
    <ul>
      <li>Vorname: {{ page.vorname }}</li>
      <li>Nachname: {{ page.nachname }}</li>
      <li>
        Geschlecht: 
        {{#if page.weiblich}}
          Weiblich
        {{else}}
          Nicht weiblich (Männlich?)
        {{/if}}
      </li>
      <li>Geburtsjahr: {{ page.geburtsjahr }}</li>
    </ul>

Listen & Schleifen

Daten: Listen ermöglichen die Angaben von vielen Werten
  • Werte werden in eckigen Klammern [] notiert und durch Kommata getrennt
  • Reihenfolge der Elemente ist relevant
  • Doppelte Elemente sind erlaubt
Template: Schleifen ermöglichen die Ausgaben von vielen Werten
  • #each ermöglicht die Iteration über Listen
  • Inhalt des #each-Blocks wird für jedes Element der Liste einmal ausgeführt
Template: Daten stehen im #each-Bereich ohne Präfix zur Verfügung
  • Keine gesonderte Iterationsvariable
  • Zugriff auf primitive Werte mit this

Beispiel zu Listen & Schleifen

  • framework_projects/vorlesung/pages/loop-basics.hbs
    {
      "dinge": ["Hase", "Katze", "🐈", true, "Hase", 13, "💩"]
    }
    ---
    <h1>Auflistung von Dingen</h1>
    <ol>
      {{#each page.dinge }}
        <li>Ding: {{ this }}</li>
      {{/each}}
    </ol>

Schleifen und der aktuelle Kontext

Technisches Detail: Daten sind in Handlebars in verschiedenen Kontexten organisiert
Gleiches Prinzip wie Blöcke in Programmiersprachen
Mit this kann auf den aktuellen Kontext zugegriffen werden
In jedem der bisherhigen Beispiele hätte man auch this.page.wert schreiben können
Allerdings: Daten aus dem übergeordneten Kontext stehen nicht automatisch zur Verfügung
Abweichung zu Sprachen wie Visual Basic, Pascal, Java, …
Mit ../ kann auf den Kontext der übergeordneten Ebene zugegriffen werden
Lässt sich auch mehrfach angeben
Schleifen (each) eröffnen einen neuen Kontext
Verzweigungen (if) hingegen nicht

Beispiel zum Zugriff auf übergeordneten Kontext

  • framework_projects/vorlesung/pages/loop-context.hbs
    {
      "name": "Hagrid",
      "tiere": [
        "Spinne", "Flubberwürmer", "Hippogreif",
        "Niffler", "Knallrümpfige Kröter"
      ]
    }
    ---
    <ul>
      {{#each page.tiere}}
        <li>{{../page.name}} stellte vor: {{ this }}</li>
      {{/each}}
    </ul>
    

Hobby-Beispiel zu Listen

  • framework_projects/vorlesung/pages/loop-hobbies.hbs
    {
      "hobbies": ["lesen", "schwimmen", "reiten"],
      "name": "Thomas"
    }
    ---
    <p>
      Mein Name ist {{ page.name }} und meine Hobbies sind
      <ul>
        {{#each page.hobbies }}
          <li>{{ this }}</li>
        {{/each}}
      </ul>
    </p>

Listen mit Objekten

Listen dürfen beliebige Werte enthalten, auch Objekte oder weitere Listen
Konvention: Werte einer Liste sind gleichartig
  • framework_projects/vorlesung/pages/loop-objects.hbs
    {
      "personen": [
        {
          "hobbies": ["lesen", "schwimmen", "reiten"],
          "name": "Thomas"
        },
        {
          "name": "Marianne",
          "hobbies": ["Xbox", "PS4", "PC"]
        }
      ]
    }
    ---
    {{#each page.personen}}
      <p>
        Mein Name ist {{ name }} und meine Hobbies sind
        <ul>
          {{#each hobbies }}
            <li>{{ this }}</li>
          {{/each}}
        </ul>
      </p>
    {{/each}}

Listen mit Listen

Möglich, aber sehr gewöhnungsbedürftige Syntax
Schlüsselwort this taucht zweimal mit verschiedenen Bedeutungen auf
  • framework_projects/vorlesung/pages/loop-nested-list.hbs
    {
      "numbers": [
        [1,2,3,4,5],
        [2,4,6,8,10],
        [3,6,9,12,15],
        [4,8,12,16,20],
        [5,10,15,20,25]
      ]
    }
    ---
    <table>
      {{#each page.numbers}}
        <tr>
          {{#each this}}
            <td>
              {{ this }}
            </td>
          {{/each}}
        </tr>
      {{/each}}
    </table>

Geschachtelte Objekte

Daten: Der Wert hinter einem Namen darf ein neues Objekt sein
  • Wird begrenzt durch geschweifte Klammern { }
  • Hilfreich, wenn gleiche Bezeichner verschiedene Bedeutung haben können
Template: . (Punkt) zum navigieren in eine tiefere Ebene
Überall möglich, wo Werte eingesetzt werden sollen
  • framework_projects/vorlesung/pages/interpolation-object.hbs
    {
      "person": {
        "name": "Ronald Bilius Weasley",
        "spitzname": "Won-Won"
      },
      "schule": {
        "name": "Hogwarts",
        "ort": "England"
      }
    }
    ---
    <p>
      {{ page.person.name }} (&quot;{{ page.person.spitzname}}&quot;)
      studiert in {{ page.schule.name }} ({{ page.schule.ort }}).
    </p>
    

Datums-Beispiel zu geschachtelteten Objekten

Grundidee: Logische zusammengehörige Daten gruppieren
Zum Beispiel Datumsangaben, Adressen, …
  • framework_projects/vorlesung/pages/interpolation-object-date.hbs
    {
      "date": {
        "year": 2018, "month": 10, "day": 13
      }
    }
    ---
    <p>
      Für Europäer:
      {{ page.date.day }}.{{ page.date.month }}.{{ page.date.year }}
    </p>
    <p>
      Für Amerikaner:
      {{ page.date.month }}/{{ page.date.day }}/{{ page.date.year }}
    </p>
    <p>
      Für Informatiker:
      {{ page.date.year }}{{ page.date.month }}{{ page.date.day }}
    </p>
    

Mehrstufige Templating-Logik

Problem: Bestimmte Konstrukte müssen auf jeder Seite wiederholt werden
Gut ersichtlich am ersten Beispiel: Der HTML-Code für zwei verschiedene Dokumente ist identisch
  • framework_projects/vorlesung/pages/interpolation-basic-1.hbs
    {
      "vorname": "James",
      "nachname": "Bond"
    }
    ---
    <p>
      Der Name ist {{ page.nachname }},
      <em>{{ page.vorname }}</em> {{ page.nachname }}.
    </p>
    
  • framework_projects/vorlesung/pages/interpolation-basic-2.hbs
    {
      "vorname": "Harry",
      "nachname": "Potter"
    }
    ---
    <p>
      Der Name ist {{ page.nachname }},
      <em>{{ page.vorname }}</em> {{ page.nachname }}.
    </p>
    

Einbindung mit include

Grundidee: Spezielle Fragmente immer wieder in den Quelltext einsetzen
In den Fragmenten stehen die gleichen Daten zur Verfügung wie dem einbindenden Dokument
Nicht jedes Dokument eignet sich zur Einbindung
Daher werden verfügbare Fragmente / Dokumente im Ordner templates hinterlegt
  • framework_projects/vorlesung/templates/person-greeting.hbs
    <!-- templates/person-greeting.hbs -->
    <p>
      Der Name ist {{ page.nachname }},
      <em>{{ page.vorname }}</em> {{ page.nachname }}.
    </p>
    
  • framework_projects/vorlesung/pages/include-person.hbs
    {
      "vorname": "James",
      "nachname": "Bond"
    }
    ---
    {{include "person-greeting"}}

Fragmente für vollständige HTML-Dokumente

Vorsicht: Alle in diesem Kapitel bisher gezeigten HTML-Dokumente waren ungültig!
Kein DOCTYPE, keine html-, head- oder body-Elemente
Mögliche Abhilfe: In jedem Dokument zwei bestimmte HTML-Fragmente einbinden
  • Einmal alles “vor” dem body, einmal alles “nach” dem body
  • framework_projects/vorlesung/templates/include-html-head.hbs
    <!-- templates/include-html-head.hbs -->
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>{{ page.title }}</title>
      </head>
      <body>
  • framework_projects/vorlesung/templates/include-html-foot.hbs
    <!-- templates/include-html-foot.hbs -->
      </body>
    </html>
  • framework_projects/vorlesung/pages/include-valid-document.hbs
    { "title": "Include Beispiel I" }
    ---
    {{include "include-html-head"}}
    <p>Ich bin ein valides Dokument</p>
    {{include "include-html-foot"}}

Fragmente für häufig benötigte Funktionalität

Bestimmte Bereiche einer Seite sollen auf allen Seiten identisch sein
Navigation, Kopfbereich, Fußbereich, …
  • framework_projects/vorlesung/templates/include-navigation.hbs
    <!-- templates/include-navigation.hbs -->
    <nav>
      <ul>
        <li><a href="#">Hauptseite</a></li>
        <li><a href="#">Keine Hauptseite</a></li>
      </ul>
    </nav>
  • framework_projects/vorlesung/pages/include-navigation.hbs
    { "title": "Include Beispiel II" }
    ---
    {{include "include-html-head"}}
    {{include "include-navigation" }}
    <p>Ich bin ein valides Dokument (mit Navigation)</p>
    {{include "include-html-foot"}}
    

Alternative Einbindung der Navigation

  • framework_projects/vorlesung/templates/alt-include-html-head.hbs
    <!-- templates/alt-include-html-head.hbs -->
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>{{ page.title }}</title>
      </head>
      <body>
        {{include "include-navigation" }}
  • framework_projects/vorlesung/templates/alt-include-html-foot.hbs
    <!-- templates/alt-include-html-foot.hbs -->
      </body>
    </html>
  • framework_projects/vorlesung/pages/alt-include-navigation.hbs
    { "title": "Include Beispiel III" }
    ---
    {{include "alt-include-html-head"}}
    <p>Ich bin ein valides Dokument (mit Navigation)</p>
    {{include "alt-include-html-foot"}}
    

Einbindung mit template und content

Problem bei include: Inhaltlich zusammengehörige Fragmente werden zerrissen
  • Unpraktisch für Quelltexteditoren:
    • Fehlermeldungen aufgrund von unvollständigen Dokumenten
    • Erfordert das öffnen vieler sehr kleiner Dateien
    • Automatische Einrückung nicht möglich
  • Erfordert Namenskonventionen um Zusammenhänge zu verdeutlichen (z.B. head & foot)
Eine mögliche Lösung: Angabe eines template im Frontmatter
  • Keine Standard-Funktion von Handlebars
  • “Magischer” Wert der von der Laufzeitumgebung für die Übung interpretiert wird
Referenzierte Templates sollten eine {{content}} Anweisung beinhalten
An dieser Stelle wird das aufrufende Dokument eingesetzt

Valides HTML-Dokument mit template und content

  • framework_projects/vorlesung/templates/template-html.hbs
    <!-- templates/template-html.hbs -->
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>{{ page.title }}</title>
      </head>
      <body>
        {{ content }}
      </body>
    </html>
  • framework_projects/vorlesung/pages/template-valid-document.hbs
    {
      "title": "Template Beispiel I",
      "template": "template-html"
    }
    ---
    <p>Ich bin ein valides Dokument</p>

Unterschiedliche Templates für unterschiedliche Anforderungen

Typisches Vorgehen: Ein Template für das HTML-Gerüst
Enthält nur das Minimum an technisch notwendigen Elementen
Weitere Templates für bestimmte Seitenarten
  • Bauen auf dem “Basis”-HTML-Template auf
  • Zum Beispiel für Neuigkeiten, Shopartikel, Videoseiten, Bildergallerien, …
Spezielle Templates verlassen sich oftmals auf bestimmte Daten im Frontmatter
Korrekte Dokumentation daher essentiell

Einbindung der Navigation mit template

  • framework_projects/vorlesung/templates/template-nav.hbs
    { "template": "template-html" }
    ---
    <!-- templates/template-nav.hbs -->
    <nav>
      <ul>
        <li><a href="#">Hauptseite</a></li>
        <li><a href="#">Keine Hauptseite</a></li>
      </ul>
    </nav>
    {{ content }}
  • framework_projects/vorlesung/pages/template-nav.hbs
    {
      "title": "Template Beispiel II",
      "template": "template-nav"
    }
    ---
    <p>Ich bin ein valides Dokument (mit Navigation)</p>

Organisation von Daten

Daten im Frontmatter sind in allen Templates verfügbar
Eignen sich also auch zur Parametrisierung
Reaktion auf bestimmte Daten ist eine vom Programmierer wählbare Konvention
Außer dem Frontmatter-Wert für template hat kein Bezeichner eine feste Bedeutung

Globale Konfiguration mit global.json

Problem: Manchmal werden Daten auf allen Seiten benötigt
  • Name des gesamten Internetauftritts
  • Gesamten Auftritt in Wartungsmodus versetzen
Lösung: Datei global.json im gleichen Ordner wie die package.json anlegen
Inhalt dieser Datei steht auf allen Seiten im Objekt global zur Verfügung

Debugging mit der debugJson-Anweisung

Gibt den aktuellen Kontext einigermaßen ansprechend formatiert aus
Vorsicht: Keine Standard-Funktion von Handlebars, kommt aus dem Übungs-Framework
  • framework_projects/vorlesung/pages/data-debug-output.hbs
    {
      "hallo": "welt",
      "optionen": [1,2,3],
      "wahr": false
    }
    ---
    {{ debugJson }}
    
    Antwort: {{ global.answer }}

Zusammenführen von Frontmatter-Daten

Grundregel: Spezifischere Daten haben Vorrang
  • Im gleichen Dokument definierte Werte sind immer sichtbar
  • Nicht überschriebene Werte werden vom Aufrufer übernommen
  • framework_projects/vorlesung/pages/data-hierarchy.hbs
    {
      "a" : 1,
      "b" : 2,
      "c" : [3,4],
      "d" : { "hallo": "welt" }
    }
    ---
    <p>Daten aus der Seite</p>
    {{ debugJson }}
    
    {{ include "data-hierarchy-1" }}
    
    <p>Daten aus der Seite</p>
    {{ debugJson }}
    
  • framework_projects/vorlesung/templates/data-hierarchy-1.hbs
    {
      "a" : "eins",
      "c" : [5,6],
      "d" : { "hello": "world" }
    }
    ---
    <!-- templates/data-hierarchy-1.hbs -->
    <p>Daten aus <code>data-hierarchy-1</code></p>
    {{ debugJson }}
    

Handlebars und Escaping

Daten könnten von Benutzern kommen und Benutzer sind grundsätzlich nicht vertrauenswürdig
Handlebars lässt daher standardmäßig in den Daten keinen HTML-Code zu
Vorgehensweise: HTML-Code aus den Frontmatter-Daten wird durch Entitäten ersetzt
< wird zu &lt;, > zu &gt;, …
Möglicher Ausweg: Daten einsetzen mit {{{ }}}
“Triple stash” nimmt keine Ersetzungen vor
  • framework_projects/vorlesung/pages/data-escaping.hbs
    {
      "headline": "<h1>Ich bin eine Überschrift</h1>",
      "rede": "Er schrie <aaaaah>",
      "smiley": "<O_o>"
    }
    ---
    {{ page.headline }}
    <hr>
    {{{ page.headline }}}
    <hr>
    {{ page.rede }}
    <hr>
    {{{ page.rede }}}
    <hr>
    {{ page.smiley }}
    <hr>
    {{{ page.smiley }}}

Bekanntes Beispiel: title-Angabe zur Steuerung des <title>-Elements

  • framework_projects/vorlesung/templates/template-html.hbs
    <!-- templates/template-html.hbs -->
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>{{ page.title }}</title>
      </head>
      <body>
        {{ content }}
      </body>
    </html>

Erweitertes Beispiel: Globaler Name und Seitenname

Grundidee: Name des gesamten Webauftritts und Name des aktuellen Dokuments anzeigen
Weit verbreitete Struktur, theoretisch auch mehr Ebenen denkbar
Defensiv programmierte Umsetzung: Verlässt sich nicht auf page.title
Alternativ möglich: Ausgabe einer Fehlermeldung
  • framework_projects/vorlesung/templates/page-and-site-name.hbs
    {{#if page.title }}
      {{ page.title }} - {{ global.name }}
    {{else}}
      {{ global.name }}
    {{/if}}

Beispiel: Dokumente im Template als unfertig kennzeichnen

Grundidee: Dokumente zwar veröffentlichen, aber explizit als unfertig kennzeichnen
Zum Beispiel um Sie von Kunden oder Bekannten prüfen zu lassen
Umständlich: In jedem unfertigen Dokument separat einen Hinweis anbringen
Viel Aufwand wenn der Hinweis geändert werden müsste
Besser: Unfertige Dokumente als Konzept mit in ein Template aufnehmen
Anzeige über spezielle Frontmatter-Werte steuern
  • framework_projects/vorlesung/templates/template-draft.hbs
    { "template": "template-html" }
    ---
    <!-- templates/template-draft.hbs -->
    {{#if page.unfinished}}
      <p>Achtung: Diese Seite ist noch unfertig!</p>
    {{/if}}
    {{ content }}
  • framework_projects/vorlesung/pages/data-draft-1.hbs
    {
      "unfinished": true,
      "template": "template-draft"
    }
    ---
    <h1>Herzlich willkommen auf meiner Webseite</h1>
  • framework_projects/vorlesung/pages/data-draft-2.hbs
    {
      "template": "template-draft"
    }
    ---
    <h1>Herzlich willkommen auf meinem Lebenslauf</h1>
    <ul>
      <li>Geboren</li>
      <li>Grundschule</li>
    </ul>

Beispiel: Bildergallerie (Konzept)

Grundidee: Anzeige vieler unterschiedlicher Bildergallerien
Viele identische Strukturen, jede Gallerie hat …
  • Einen Titel
  • Eine kurze Beschreibung
  • Viele Bilder, wobei jedes Bild folgende Daten hat:
    • URL zum Bild
    • Titel

Beispiel: Bildergallerie (Umsetzung 1)

  • framework_projects/vorlesung/templates/gallery.hbs
    { "template": "template-html" }
    ---
    
    <h1>{{ page.title }}</h1>
    <p>{{ page.description }}</p>
    
    {{#each page.images}}
      <figure>
        <figcaption>{{ title }}</figcaption>
        <img src="{{ src }}" width="400">
      </figure>
    {{/each}}
    
  • framework_projects/vorlesung/pages/gallery-1.hbs
    {
      "template": "gallery",
      "title": "Meine erste Gallerie",
      "description": "Bilder von tollen Spielen",
      "images": [
        {
          "src": "/assets/screenshot/dwarf-fortress.png",
          "title": "Adventures of Boatmurdered"
        },
        {
          "src": "/assets/screenshot/evil-genius.png",
          "title": "Sei ein fieses Genie!"
        }
      ]
    }
    ---

Beispiel: Bildergallerie (Umsetzung 2)

  • framework_projects/vorlesung/templates/gallery-content.hbs
    { "template": "template-html" }
    ---
    
    <h1>{{ page.title }}</h1>
    
    {{ content }}
    
    {{#each page.images}}
      <figure>
        <figcaption>{{ title }}</figcaption>
        <img src="{{ src }}" width="400">
      </figure>
    {{/each}}
    
  • framework_projects/vorlesung/pages/gallery-2.hbs
    {
      "template": "gallery-content",
      "title": "Meine erste Gallerie",
      "images": [
        {
          "src": "/assets/screenshot/dwarf-fortress.png",
          "title": "Crossover of Dungeon Keeper & The Sims"
        },
        {
          "src": "/assets/screenshot/evil-genius.png",
          "title": "Evil will always win because Good is very, very, very dumb"
        }
      ]
    }
    ---
    <p>
      Auf dieser Seite beschreibe ich einige tolle Spiele.
      Alle hier beschriebenen Spiele gibt es
      <abbr title="Digital Rights Management">DRM</abbr>-
      frei auf <a href="https://www.gog.com">gog.com</a>.
    </p>
    

Allgemeine Problematik: Was gehört wohin?

Mindestens drei Ebenen um Daten zu speichern:
  • Im Frontmatter eines Dokuments im pages-Ordner
  • Im Frontmatter eines Dokuments im templates-Ordner
  • In der global.json
Template-Hierarchie kann beliebig viele Stufen enthalten
Jedes Template kann wiederum auf andere Templates verweisen

Was fehlt Handlebars?

Handlebars-Templates dürfen keine “komplexe Logik” nutzen (“semantic templates without frustration”)
Was “komplex” ist definieren die Handlebars-Autoren unter anderem wie folgt:
  • Vergleiche wie “ist gleich” oder “größer als” sind komplexe Logikausdrücke
  • Mehrfachauswahl (case / of, select / case, switch / case) ist komplexe Logik
Vorgeschlagene Lösung: Arbeiten mit selbst definierten JavaScript “Helpern”
Wird in den kommenden Veranstaltungen vertieft

Formal: Javascript Object Notation (JSON)

Notation zur Speicherung beliebiger Datenstrukturen
Beliebige Inhalte bei festgelegter Syntax
Universelles Austauschformat
Trotz Javascript-Ursprung: Parser für beliebige Sprachen verfügbar
[Lesbare Spezifikation][json-spec]
Relativ einfaches Syntaxdiagramm, Implementierung vergleichsweise trivial
Keine Struktorvorgabe
Gleichartig von Daten eine Konvention, keine Vorgabe

Datentypen

Als Wurzel des Dokuments darf jeder dieser Werte verwendet werden
Typischerweise wird die Wurzel eine Liste oder ein Objekt sein

number & string

Zahlen in der JSON-Notation
Strings in der JSON-Notation

array & object

Arrays in der JSON-Notation
Objekte in der JSON-Notation
Reihenfolge der Elemente im Quelltext ist nur für Arrays signifikant
Keine Garantien bezüglich der Reihenfolge der Schlüssel von Objekten

Typische Probleme mit JSON-Daten

Toll: JSON ist ein kompaktes Format mit dem sich beliebige Datensätze gut notieren lassen
Große Auswahl an Parsern für faktisch jede Programmiersprache
Doof: JSON ist ein kompaktes Format mit dem sich beliebige Datensätze gut notieren lassen
Hoher Freiheitsgrad erfordert Disziplin

Typisches Problem: Wohin mit identifizierenden Merkmalen?

  • intro_javascript/people-keys.json
    {
      "Harry" : {
        "Jahrgang": 1980,
        "Freunde": ["Hermine", "Ron"],
        "Muggel": false,
        "Haus": "Gryffindor"
      },
      "Tom": {
        "Jahrgang": 1926,
        "Freunde": [],
        "Muggel": false,
        "Haus": "Slytherin"
      },
      "Dudley": {
        "Jahrgang": 1980,
        "Freunde": ["Piers", "Malcolm"],
        "Muggel": true
      },
    }
    
  • intro_javascript/people-list.json
    [
      {
        "Name": "Harry",
        "Jahrgang": 1980,
        "Freunde": ["Hermine", "Ron"],
        "Muggel": false,
        "Haus": "Gryffindor"
      },
      {
        "Name": "Tom",
        "Jahrgang": 1926,
        "Freunde": [],
        "Muggel": false,
        "Haus": "Slytherin"
      },
      {
        "Name": "Dudley",
        "Jahrgang": 1980,
        "Freunde": ["Piers", "Malcolm"],
        "Muggel": true
      }
    ]
    
Beide Varianten haben Vor- und Nachteile
  • Iteration mit #each nur über Listen möglich
  • Zugriff auf namentliche IDs mit Objekten sehr einfach

Typisches Problem: Exakte Syntax

  • intro_javascript/invalid-doc.json
    ["Lawful", "Neutral", "Chaotic"],
    ["Good", "Neutral", "Evil"],
    {
      /* Alignments used as described above  */
      goodness: 0, // "Lawful"
      niceness: 2, // "Evil"
    },
    {
      law: "implies honor, trustworthiness, obedience
            to authority, and reliability.",
      chaos: "implies freedom, adaptability, and flexibility"
    }
    ]
    
  • Zu viele Wurzelelemente
    Ein JSON-Objekt darf nur einen Wert an der Wurzel haben
    Schlüssel ohne Anführungszeichen
    Im Gegensatz zu Javascript müssen auch Strings für Schlüssel in Anführungszeichen notiert werden
    Kommentare
    Die JSON-Spezifikation sieht keinerlei Kommentare vor
    “Hängendes” Komma
    Im Gegensatz zu Javascript sind Kommata am Ende der Liste von Name-Wert-Paaren in Objekten unzulässig
    Zeilenumbruch innerhalb eines Strings
    Muss mit \n maskiert werden, darf nicht ohne Maskierung verwendet werden
    Unbalancierte Klammern
    Am Ende des Dokuments steht ein ] zuviel

Typisches Problem: Subtile Unterschiede

  • intro_javascript/people-inconsistent.json
    {
      "Morte": {
        "Alignment": "Chaotic Good",
        "Special Abilities": [
          "Litany of Curses",
          "Skull Mob"
        ],
        "Class": "Fighter",
        "Attributes": [12, 16, 16, 13, 9, 6]
      },
      "Annah": {
        "Alignment": "Chaotic Neutral",
        "Class": [
          "Fighter",
          "Thief"
        ]
        "Attributes": [14, 18, 16, 10, 13]
      }
    }
    
  • Unterschied: Spezialfähigkeiten
    Dem Charakter Annah fehlt die Liste an Spezialfähigkeiten
    Unterschied: Klassen
    Morte verwendet für Klassen einen String, Annah eine Liste von Strings
    Unterschied: Attribute
    Annah hat nur 5 Attribute
Abhilfe: Formale Schema-Spezifikation mit z.B. JSON Schema
XML erlaubt wesentlich erprobtere Formen der Schema-Angabe