Zurechtgezwirbelt: Mit Mustache zur dynamischen Single-Page-App

Dynamisches Erzeugen von HTML ist wesentlich für Webanwendungen. Ein gezielter Einsatz des passenden Werkzeugs erspart das Verwenden umfangreicher Frameworks.

In Pocket speichern vorlesen Druckansicht 23 Kommentare lesen

(Bild: jakkaje879/Shutterstock.com)

Lesezeit: 6 Min.
Von
  • Ulrich Hilger
Inhaltsverzeichnis

Single-Page-Webanwendungen (SPA) bedeuten einen fortwährenden Neuaufbau von Teilen der Bedienoberfläche. Jede Änderung entspricht dabei dem dynamischen Erzeugen von HTML-Code im Hintergrund und dessen Einbau in die im Browser dargestellte HTML-Seite zur Laufzeit. Für diesen Mechanismus gibt es schlanke und einfach zu verwendende Werkzeuge. Dieser Beitrag zeigt das beispielhaft anhand des Web-Template-Systems Mustache.

In einer HTML-Seite sind Inhalte mit der Auszeichnung von Struktur- und Gestaltungsinformationen vermischt. Ein Vorlagenmechanismus führt zur Trennung von Inhalten und Auszeichnungen, die erst zur Laufzeit einer App zusammengeführt werden. Dafür wirken verschiedene Elemente zusammen:

  • Inhalte, die die App anzeigen soll und die üblicherweise im JSON-Format vorliegen, ruft sie zur Laufzeit vom Server ab.
  • Eine Vorlage enthält den HTML-Code mit der Beschreibung, wie Inhalte erscheinen sollen. An den Stellen, an denen die in HTML beschriebenen Inhalte aus den JSON-Daten vom Server vorgesehen sind, befinden sich Platzhalter.
  • Das Vorlagenwerkzeug erkennt sich wiederholende Strukturen im Inhalt. Es sorgt automatisch für das Erstellen wiederkehrender HTML-Muster wie der stets gleichen HTML-Ausdrücke für die zahlreichen gleichartigen Elemente einer Liste.
  • JavaScript-Funktionen zum Verwenden des Vorlagenwerkzeugs erstellen sowohl Inhaltseinträge an den von den Platzhaltern bezeichneten Stellen in der Vorlage als auch HTML-Code. Letzterer enthält Auszeichnungen und darstellungsbereite Inhalte.

Viele Schritte, die mit dem Anwendungsmuster einhergehen, lassen sich als kleine Skriptbibliothek vorwegnehmen, so dass fertige Hilfsfunktionen die Prozedur fast vollständig übernehmen können. Was für eine eigene SPA dann noch zu tun ist, erfordert nur wenige Zeilen an zusätzlichem Code.

Die folgenden Abschnitte beschreiben die Implementierung des Anwendungsmusters. Darauf folgt eine Übersicht der Funktionen, die sich unverändert übernehmen und wiederverwenden lassen.

Das Einsetzen des Anwendungsmusters erfordert eine HTML-Seite mit folgendem Inhalt:

<!DOCTYPE html>
<html>
  <head>
    <!-- Kopfdaten nach Bedarf -->
    <link rel="stylesheet" type="text/css" href="app.css">
  </head>
  <body>
    <!-- weiterer HTML-Inhalt nach Bedarf -->
    <div class="zentraler-inhalt">
      <!-- hier wird zur Laufzeit Inhalt dynamisch hinzugefügt -->
    </div>
    <!-- weiterer HTML-Inhalt nach Bedarf -->

    <!-- Skripte -->
    <script src="js/mustache/mustache.min.js"></script>
    <script src="js/app.js"></script>
    <script>
      document.addEventListener('DOMContentLoaded', function () {
        meine_spa_initialisieren(); // irgendwo in app.js
      });
    </script>
  </body>
</html>

Zusätzlich zum Einbinden des Skripts von Mustache bindet app.js das Skript der eigenen App ein und ergänzt darin die nachfolgend beschriebenen Funktionen. Im Beispiel ist der Bereich .zentraler-inhalt veränderlich gemeint. Dem tragen verschiedene Vorlagen Rechnung, die mit Inhalt gefüllt zur Laufzeit einsetzbar sind.

Zum Abrufen von Inhalten dient gewöhnlich ein einfaches HTTP GET, etwa zur Abfrage des Inhalts eines Ordners mit Audiodateien:

HTTP GET \
  http://mein-server:9191/tango/media/Musik/A/ACDC/Highway-To-Hell/

Aus einer App heraus sieht das Ausführen der Ausgabe wie folgt aus:

function inhalt_abrufen() {
  var serverUrl = 'http://mein-server:9191/tango';
  var serviceUrl = '/media/Musik/A/ACDC/Highway-To-Hell/';
  var getUrl = serverUrl + serviceUrl;
  http_call('GET', getUrl, '', function(antwort) {
    // hier die Antwort verarbeiten
  });
}

Dazu nutzt der Code die Hilfsfunktion http_call, die in der wiederverwendbaren Bibliothek weiter unten enthalten ist. Der Server antwortet darauf mit dem Inhalt des Ordners als JSON-Struktur:

{
  "Medialiste": [{
      "name": "01-Highway-to-hell.mp3",
      "typ": "audio",
      "interpret": "ACDC",
      "titelAnzName": "Highway to hell",
      "album": "Highway To Hell"
    }, {
      "name": "02-Girls-got-rhythm.mp3",
      "typ": "audio",
      "interpret": "ACDC",
      "titelAnzName": "Girls got rhythm",
      "album": "Highway To Hell"
    },

    // weitere Angaben hier

    {
      "name": "10-Night-prowler.mp3",
      "typ": "audio",
      "interpret": "ACDC",
      "titelAnzName": "Night prowler",
      "album": "Highway To Hell"
    }]
}

Der nächste Schritt verwandelt diesen Inhalt mithilfe von Mustache in HTML:

var vorlage = 'vorlagen/titel-liste.txt';
html_erzeugen(vorlage, JSON.parse(antwort), function(html) {
  // hier das HTML in die HTML-Seite schreiben
  // wie weiter unten beschrieben
});

Hierbei kommt erneut Code aus der wiederverwendbaren Bibliothek zum Einsatz. Die Funktion html_erzeugen liest die Vorlage vorlagen/titel-liste.txt aus dem Vorlagen-Cache und weist Mustache an, den JSON-Inhalt vom Server als HTML zu erzeugen. Wenn sich die gewünschte Vorlage noch nicht im Cache befindet, ist zunächst das Laden vom Server nötig.

Das asynchrone Laden der Vorlage bedingt, dass eine Callback-Funktion den fertigen HTML-Code verarbeitet. Darauf folgt die Übergabe an die Funktion html_erzeugen und die Ausführung, sobald die Vorlage im Cache ist.

Die Vorlage vorlagen/titel-liste.txt enthält dabei folgenden, mit Platzhaltern gemischten HTML-Code:

<div class='titel-liste-behaelter'>
  <ul class='titel-liste'>
    {{#Medialiste}}
    <li class='entity-eintrag entity-typ-{{typ}} noselect'
        typ='{{typ}}'
        interpret='{{interpret}}'
        album='{{album}}'
        titelAnzName='{{titelAnzName}}'
        dateiName='{{name}}'>{{titelAnzName}}</li>
    {{/Medialiste}}
  </ul>
</div>

Die Namen der Platzhalter entsprechen den Feldnamen der JSON-Struktur vom Server. Auf diese Weise kann Mustache erkennen, welche Teile des JSON-Inhalts an welchen Stellen im HTML-Code erscheinen müssen. Aus der Vorlage und dem JSON-Inhalt macht Mustache schließlich folgenden HTML-Block:

<div class='titel-liste-behaelter'>
  <ul class='titel-liste'>
    <li class='entity-eintrag entity-typ-audio noselect'
        typ='audio' interpret='ACDC' album='Highway To Hell'
        titelAnzName='Highway to hell' 
        dateiName='01-Highway-to-hell.mp3'>
          Highway to hell
    </li>
    <li class='entity-eintrag entity-typ-audio noselect'
        typ='audio' interpret='ACDC' album='Highway To Hell'
        titelAnzName='Girls got rhythm' 
        dateiName='02-Girls-got-rhythm.mp3'>
          Girls got rhythm
    </li>

    <!-- weitere Titel -->

    <li class='entity-eintrag entity-typ-audio noselect'
        typ='audio' interpret='ACDC' album='Highway To Hell'
        titelAnzName='Night prowler' 
        dateiName='10-Night-prowler.mp3'>
          Night prowler
    </li>
  </ul>
</div>

Am HTML-Ergebnis ist ersichtlich, wie viel Text sich je Datenelement wiederholt. Ein typischer Nutzen von Vorlagen ist, dass eine solche Struktur in der Vorlage nur einmal stellvertretend für den jeweiligen Typ von Inhalt zu formulieren ist. Den restlichen HTML-Code erstellt das Vorlagenwerkzeug dynamisch am jeweiligen Inhalt orientiert.

Danach erfolgt das Hinzufügen des resultierenden HTML-Codes zur HTML-Seite:

document.querySelector(".zentraler-inhalt").innerHTML = html;

Somit hat Mustache Schritt für Schritt einen beliebigen Teilinhalt vom Server abgerufen, dynamisch in HTML umgewandelt und der HTML-Seite einer Single-Page-Webanwendung hinzugefügt. Das erweckt den Anschein einer umfänglichen Prozedur, was aber nur an der recht eingehenden Betrachtung liegt. Tatsächlich führen der Einsatz von Mustache sowie der beiden Hilfsfunktionen http_call und html_erzeugen zu einer überschaubaren Vorgehensweise. Zum Beleg folgt der Code nochmals in der Zusammenfassung.

function audio_ordner_klick() {
  // Code, z. B. zur Ermittlung des gewuenschten Inhalts
  // von 'service' koennte hier stehen. Nachfolgend
  // stattdessen hart codiert.
  var service = '/media/Musik/A/ACDC/Highway-To-Hell/';
  var url = 'http://mein-server:9191/audio-app' + service;
  inhalt_abrufen_und_zeigen(url, 'vorlagen/titel-liste.txt');
}

function inhalt_abrufen_und_zeigen(url, vorlage) {
  http_call('GET', url, '', function(antwort) {
    html_erzeugen(vorlage, JSON.parse(antwort), function(html) {
      document.querySelector(".zentraler-inhalt").innerHTML = html;
    });
  });
}

Dieser Codeblock in einer Single-Page-Webanwendung genügt zum dynamischen Erzeugen von HTML. Um den Rest kümmern sich Mustache und die erwähnten Hilfsfunktionen.

In diesem Abschnitt ist der Code für die gewünschten Funktionen enthalten. Sie sind als beispielhaft zu verstehen und können je nach Anwendungsfall auch anders gelagert sein.

Eine SPA kann die Funktion http_call einsetzen, um Funktionen auf dem Server aufzurufen und deren Antwort zu verarbeiten. Beispiele finden sich unter „Inhalte abrufen“ und unter „Vorlagen verwalten“.

function http_call(method, url, data, callback) {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function () {
    if (this.readyState === 4 && this.status === 200) {
      callback(this.responseText);
    }
  };
  xhr.open(method, url);
  if (method === 'GET') {
    xhr.send();
  } 
  else if (method === 'POST' || method === 'PUT' || 
    method === 'DELETE') {
    xhr.setRequestHeader('Content-type', 
                         'application/x-www-form-urlencoded');
    xhr.send(data);
  }
}

Diese wenigen Zeilen Code können HTML-Vorlagen einer App in einem Cache verwalten und rufen Mustache zum Erzeugen von HTML aus JSON-Inhalten auf:

var cache = new Array(); // vorlagen-cache

function html_erzeugen(vurl, inhalt, cb) {
  var vorlage = cache[vurl];
  if (vorlage === undefined) {
    self.vorlage_laden_und_fuellen(vurl, inhalt, cb);
  } else {
    self.vorlage_fuellen(vurl, inhalt, cb);
  }
}

function vorlage_fuellen(vurl, inhalt, cb) {
  cb(Mustache.render(cache[vurl], inhalt));
}

function vorlage_laden_und_fuellen(vurl, inhalt, cb) {
  http_call('GET', vurl, '', function(antwort) {
    cache[vurl] = antwort;
    vorlage_fuellen(vurl, inhalt, cb);
  });
}

Im Parameter vurl steht die URL einer Vorlage. Der Parameter inhalt dient dazu, den Inhalt vom Server in „geparstem“ JSON zu übermitteln, wie in JSON.parse(serverantwort) vorgesehen. Im Parameter cb erfolgt die Übergabe einer Callback-Funktion, die das von Mustache erstellte HTML zurückerhält.

Werkzeuge wie Mustache erzeugen HTML dynamisch aus Vorlagen und sorgen in Webanwendungen für eine saubere Trennung von Inhalt, Anwendungslogik, Struktur und Gestaltung. Sofern das verwendete Werkzeug eine Trennung von Anwendungslogik und Gestaltung einhält, bleiben Code und Vorlagen auch in umfangreicheren Projekten wartbar. Das verhindert Boilerplate-Code, etwa zum Erzeugen sich wiederholender Inhalte oder Strukturen.

Mustache umfasst lediglich 9,5 KByte. Gemeinsam mit den Codezeilen dieses Beitrags lässt sich mit überschaubarem Aufwand eine Anwendung zum dynamischen Erstellen von HTML in jede App einbauen. Ein wesentlicher Bestandteil von Single-Page-Webanwendungen kommt auf diese Weise ohne großen Aufwand mit ein wenig JavaScript-Code zustande.

Ulrich Hilger
arbeitet als Lead IT Consultant bei der msg. Mit mehr als 20 Jahren Projekterfahrung im internationalen IT-Umfeld sind Requirements Engineering sowie die Konzeption und das Design von IT-Systemen Schwerpunkte seiner Tätigkeit.

(mai)