4. Einen Webserver programmieren

4.6 HTML und Server-Code trennen mit ejs-Templates

Wenn Sie die Übungsaufgaben aus dem letzten Abschnitt gelöst haben, dann werden Sie wohl häufig HTML-Quelltext als String innerhalb der .js-Datei haben, die den Server-Quelltext enthält. Hier ist ein Beispiel aus meinem Code:

         var response = htmlHeader + 
`<body><p>File ` +  filename + ` successfully uploaded.</p>
<p><a href="index.html">back</html></p></body></html>` ;
        return res.send(response);

Vielleicht haben Sie auch den Nachteil gespürt: jedes Mal, wenn die das Format Ihrer Seiten ändern wollen, müssen Sie den Server-Quelltext ändern und somit auch den Server neu starten. Das wäre bei echten Anwendungen natürlich ein No-Go. Besser wäre es doch, den HTML-Code in eine Datei auszulagern, die dann bei Bedarf vom Server gelesen wird. Betrachten Sie nochmal obiges Beispiel, jetzt mit Farbmarkierung:

         var response = htmlHeader + 
`<body><p>File ` +  filename + ` successfully uploaded.</p>
<p><a href="index.html">back</html></p></body></html>` ;
        return res.send(response);

Da sehen Sie auch das Problem mit dem Auslagern in eine externe HTML-Datei: die Variable filename soll dynamisch in den HTML-Text eingesetzt werden. Wir bräuchten also nicht nur eine normale externe HTML-Datei, sondern eine HTML-Datei mit Platzhaltern. Diese Platzhalter müssen irgendwie als solche markiert sein.

Übung Überlegen Sie sich, wie ein Datenformat HTML mit Platzhaltern aussehen könnte und mit welcher Syntax Sie dann den Befehl
Lies bla.html und ersetze Platzhalter mit Werten
im Quelltext des Servers schreiben könnten. Sie müssen so etwas jetzt nicht implementieren; denken Sie aber über mögliche Syntax nach.

Erste Schritte mit ejs-Templates: alles offline

Speichern Sie 01-templates-strings.js. Hier ist der Quelltext:
const ejs = require('ejs');

const template = "Guten Tag, <%=anrede%> <%=name%>.";
let string1 = ejs.render(template, {anrede: "sehr geehrte Frau Professor", name: "Buchbinder"});
let string2 = ejs.render(template, {anrede: "lieber", name: "Alois"});

console.log(string1);
console.log(string2);

Starten Sie das Programm 01-templates-strings.js nun auf der Konsole:

node 01-templates-strings.js                        
Guten Tag, sehr geehrte Frau Professor Buchbinder.
Guten Tag, lieber Alois.

In dem String "Guten Tag, <%=anrede%> <%=name%>." markieren die Symbole <% und %>, dass hier ein Platzhalter drinsteht. Die Funktion ejs.render ersetzt dann den Platzhalter durch die übergebenen Werte. In unserem Beispiel gibt es zwei Platzhalter, anrede und name; in Zeile 5 zum Beispiel wird dann dürch Übergeben des Dictionarys {anrede: "lieber", name: "Alois"} der Platzhalter andere durch den String "lieber" erstzt.

Das Platzhalter-durch-Werte-Ersetzen in ejs ist aber noch komplexer und flexibler: Sie können zwischen den <%...%> im Prinzip beliebigen Javascript-Code schreiben; mit <%= machen Sie deutlich, dass hier der Wert des Platzhalters als String eingefügt werden soll. Wenn das <% nicht von einem = gefolgt ist, können Sie Javscript-Code hinschreiben, wie zum Beispiel

const ejs = require('ejs');
const fs = require('fs');

language = 'English'
months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'];

let templateString = `Die Namen der Monate auf <%=language%> sind 
<% for (i in listOfMonthNames) {%><%=listOfMonthNames[i]%>, 
<% } %>`;

const result = ejs.render(templateString, {language : 'English', listOfMonthNames : months});
console.log(result);

Sie können es noch komplexer machen, z.B. ein if einfügen und den rot markierten String oben ersetzen durch

`Die Namen der Monate auf <%=language%> sind 
<% for (i in listOfMonthNames) {%><%=listOfMonthNames[i]%><% if (i < listOfMonthNames.length-1) {%>, <%} else {%>.<%}%><% } %>`
Übung

Schreiben Sie ein node-Programm (alles offline, kein Web hier), das zwei Dateien einliest, sagen wir textfile.txt und template.ejs und den Inhalt von textfile.txt Zeile für Zeile und pro Zeile Wort für Wort in einen HTML-Table übersetzt. Hier ist ein Beispiel:

Die Datei textfile.txt:
Hello. This is 
a list of
lines. Each line
contains a couple
of words.                                    
                                
Output Ihres node.js-Programmes:
<table>
   <tr><td>Hello.</td><td>This</td><td>is</td><td></td>
   </tr>
   <tr><td>a</td><td>list</td><td>of</td>
   </tr>
   <tr><td>lines.</td><td>Each</td><td>line</td>
   </tr>
   <tr><td>contains</td><td>a</td><td>couple</td>
   </tr>
   <tr><td>of</td><td>words.</td>
   </tr>
</table>                                    
                                

Ihr Programm solll also also die Datei als String einlesen, diesen dann erstmal in separate Zeilen zerlegen, und dann jede Zeile in separate, durch Whitespace separierte Wörter zerlegen. Das Ergebnis könnte dann zum Beispiel ein Array aus Arrays sein, dass Sie der Funktion render übergeben; Ihre Datei template.ejs beinhaltet höchstwahrscheinlich zwei verschachtelte for-Schleifen.

Jetzt könnten Sie im Prinzip Ihre Html-Dateien mit Templates schreiben, sie dann mit fileReadSync oder ähnlichem in Ihrem Server-Quellcode einlesen und per ejs.render die Platzhalter durch Werte ersetzen, um dynamisch Inhalt in Html zu übersetzen. Natürlich geht das noch einfacher: wenn res Ihr Http-Response-Objekt ist, können Sie einfach per res.render("filename.ejs", {var1 : value1, var2 : value2 ...} den in filename.ejs enthaltenen Html-Text mit Platzhaltern rendern, analog zu ejs.render. Das Lesen aus der Datei, Platzhalter durch Werte ersetzen und Ergebnis in den Response schreiben und zurückschicken erledigt alles die Funktion res.render. Als Beispielcode nehmen Sie gerne

Übungsaufgabe Schreiben Sie eine Anwendung mit Server, der den Nutzer durch ein "Anmeldeformular" führt. Das Formular soll mehrere Seiten haben, beispielsweise die folgenden:

  1. Account erstellen, Nutzername und Passwort festlegen.
  2. Login mit Nutzernamen und Passort.
  3. Nach erfolgtem Login: mehrere Seiten mit Eingabefelder für jeweils Name, Geburtsdatum, Adresse, Telefonnummer, Email-Adresse.

Jede Seite soll einen Link auf die nächste und die vorherige Seite haben. Die ganze Anwendung soll persistentes Verhalten zeigen. Wenn Sie also innerhalb der verschiedenen Seiten navigieren, sollen bereits eingetragene Daten nicht verloren