HTML5-Tutorium: JavaScript: Hello World 04: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Kowa (Diskussion | Beiträge)
Kowa (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
 
(333 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
{{In Bearbeitung}}
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}
'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/index1.html <code>index1.html</code>],
 
[https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/index2.html <code>index2.html</code>],
'''Musterlösung'''<br/>
[https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/index3.html <code>index3.html</code>],
v01_1: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_1/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html Google PageSpeed])<br/>
[https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/index3b.html <code>index3b.html</code>],
v01_2: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_2/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html Google PageSpeed])<br/>
[https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/index4.html <code>index4.html</code>],
v01_3: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_3/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_3%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_3%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_3%2Findex.html Google PageSpeed])<br/>
[https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/index5.html <code>index5.html</code>]
v01_4: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_4/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_4%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_4%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_4%2Findex.html Google PageSpeed])<br/>
([https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/ SVN-Repository])
v01_5: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_5/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_5%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_5%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_5%2Findex.html Google PageSpeed])
 
[{{Git-Server}}/kowa/wk_hello_world_04.git Git-Repository]
 
==Anwendungsfälle (Use Cases)==
==Anwendungsfälle (Use Cases)==
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_03|dritten Teil des Tutoriums]]
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_03|dritten Teil des Tutoriums]]
ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.
ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.


In diesem Teil des Tutoriums geht es darum, die Anwendung besser zu strukturieren, {{dh}} zu [[Modul|modularisieren]].
In diesem Teil des Tutoriums geht es darum, die Anwendung besser zu strukturieren, {{dh}} zu modularisieren (vgl. [[Programmierprinzipien#Modularit.C3.A4t.2C_Modularity.2C_Teile_und_herrsche.2C_Divide_et_impera|Programmierprinzipien]]).


<strong>Bitte beachten Sie, dass ich einige HTML-Elemente und Funktionen des dritten Tutoriums umbenannt habe.
==Modularisierung==
Das heißt, Ihre Lösungen des [[HTML5-Tutorium: JavaScript: Hello World 03|dritten Teil des Tutoriums]] sind vermutlich nicht mehr ganz konsistent mit
der [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld03/Mussterlösung]. Daher ist es evtl. sinnvoll,
wenn Sie den vierten Teil aus Basis der Musterlösung des dritten Teils bearbeiten.
</strong>


===Modularisierung===
Eine Anwendung, wie {{zB}} ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgaben.
Bei einem Web-Frontend müssen beispielsweise verschieden Komponenten (Buttons, Scrolbars, Textfelder, Datumsfelder etc.) erstellt werden. Bei einem HTML5-Spiel müssen die Spielfiguren, die Spielszenen, die Spiellogik, die Benutzereingaben, die Darstellung das Spiels im Browser etc. erstellt und verwaltet werden, wobei das Erstellen und Verwalten teilweise während der Spielentwicklung und teilweise
während der Spielausführung (Runtime) erfolgt.


Eine Anwendung, wie {{zB}} ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgabe.
An der Entwicklung eines Web-Systems oder eines Spiels sind üblicherweise mehrere oder gar viele
Bei einem HTML5-Spiel müssen die Spielfiguren, die Spielszenen, die Spiellogik, die Benutzereingaben, die Darstellung das Spiels im Browser
etc. erstellt und verwaltet werden, wobei das Erstellen und Verwalten teilweise während der Spiel-Entwicklung und teilweise
während der Spiel-Ausführung (Runtime) erfolgt. An der Entwicklung eines Spiels sind üblicherweise mehrere oder gar viele
Entwickler gleichzeitig beteiligt. Oft müssen einzelnen Komponenten an neue Endgeräte oder Betriebssysteme angepasst werden.
Entwickler gleichzeitig beteiligt. Oft müssen einzelnen Komponenten an neue Endgeräte oder Betriebssysteme angepasst werden.
Beispielsweise muss eine Tastatursteuerung für eine Mobilversion des Spiels durch eine Touch- oder Gestensteuerung ersetzt werden.
Beispielsweise muss eine Tastatursteuerung durch eine Touch- oder Gestensteuerung ersetzt werden,
wenn eine Anwendung so erweitert werden soll, dass es auch auf einem Smartphone oder Tablet läuft.


Das alles ist nur machbar, wenn man die Anwendung modularisiert, {{dh}} in kleine, möglichst unabhängige
Das alles ist nur machbar, wenn man die Anwendung modularisiert, {{dh}} in kleine, möglichst unabhängige
Komponenten unterteilt. Ein großer Feind der Modularisierung sind globale Variablen und Funktionen.
Komponenten unterteilt. Ein großer Feind der Modularisierung sind globale Variablen.
Je mehr Dateien von unterschiedlichen Autoren erstellt werden, desto größer ist die Gefahr, dass es zu Namenskollisionen kommt.
 
Je mehr Dateien von unterschiedlichen Autoren erstellt werden, desto größer ist die Gefahr, dass es beim Zugriff auf globale Variablen zu Namenskollisionen kommt.
Außerdem weiß niemand, welche globale Größe noch benötigt wird und welche nicht. Sicherheitshalber löscht man daher keine dieser Variablen, auch wenn sie vollkommen veraltet sind.
 
Daher gilt der [[Multimedia-Programmierung: Best Practices#Modularisierung|Grundsatz]]: Verwende so wenig globale Größen wie möglich.
Daher gilt der [[Multimedia-Programmierung: Best Practices#Modularisierung|Grundsatz]]: Verwende so wenig globale Größen wie möglich.


==Erstellen eines neuen Projektes==
== Erstellen eines neuen Projekts ==


Erstellen Sie ein neues Projekt <code>HelloWorld04</code>“ und legen Sie dieses in Ihrem Repository ab.
Erstellen Sie ein neues Projekt <code>hello_world_04</code> als Fork von <code>hello_world_03</code> (siehe [[HTML5-Tutorium: JavaScript: Hello World 02|Hello-World-02-Tutorium]]).
Kopieren Sie anschließend alle Dateien aus dem dritten Teil des Tutoriums, passen Sie den Titel in der HTML-Datei an und committen Sie abermals.


==Erstellen einer Ordnerstruktur==
== Die App ==
 
=== Erstellen einer Ordnerstruktur ===


Grundsätzlich gilt auch bei der Programmierung: Ordnung ist das halbe Leben.
Grundsätzlich gilt auch bei der Programmierung: Ordnung ist das halbe Leben.
Zeile 48: Zeile 50:
legt man im entsprechenden Ordner geeignete Unterordner an.
legt man im entsprechenden Ordner geeignete Unterordner an.


Legen Sie im Root-Verzeichnis Ihres Projektes folgende Ordner an:
Legen Sie im <code>web</code>-Verzeichnis Ihres Projekts folgende Ordner an:
* <code>web</code>: Der Inhalt dieses Ordners kommt später auf einen echten Web-Server.  
* <code>web/css</code>: Hier werden die eigentlichen CSS-Dateien der Web-Anwendung gespeichert, die von der App dynamisch nachgeladen werden sollen. In diesem Ordner könnten zur weiteren Strukturierung Unterordner angelegt werden. Allerdings ist das hier nicht notwendig. Es wird nur die Dateien „<code>head.css</code>“ und „<code>body.css</code>“ geben. '''Verschieben Sie die Datei <code>web/main.css</code> in diesen Ordner.'''
* <code>web/css</code>: Hier speichern Sie die CSS-Dateien Ihrer Web-Anwendung.
* <code>web/js</code>: Hierher kommen die JavaScript-Dateien, die von der Anwendung benötigt werden. '''Verschieben Sie die Datei <code>web/main.js</code> in diesen Ordner.'''
* <code>web/js</code>
* <code>web/js/app</code>: Hier speichern Sie die von Ihnen geschriebenen JavaScript-Dateien Ihrer Web-Anwendung.
* <code>web/js/lib</code>: Hier speichern Sie die JavaScript-Bibliotheken, die andere JavaScript-Entwickler erstellt haben und die Sie in Ihrer Anwendung einsetzen..


Da in diesem Teil des Tutoriums fünf Web-Anwendungen mit jeweils unterschiedlichem Modularisierungsgrad
Nehmen Sie in der Datei <code>web/index.html</code> folgende beiden Ersetzungen vor:
erstellt werden, werden hier fünf App-Ordner angelegt:
* <code>main.css</code> <code>css/main.css</code>
* <code>web/js/app1</code>: Aufteilung der App in mehrere Objekte und Dateien.
* <code>main.js</code> <code>js/main.js</code>
* <code>web/js/app2</code>: Definition einer Klasse <code>Greet</code>.
* <code>web/js/app3</code>: Reduktion der Konstanten in <code>Greet</code>.
* <code>web/js/app4</code>: Weitere Modularisierung der Web-App mit JavaScript-Bordmitteln.
* <code>web/js/app4</code>: Alternative Modularisierung der Web-App mit der JavaScript-Bibliothek <code>RequireJS</code>.


Öffnen Sie im Editorbereich die Datei <code>index.html</code>“ und verschieben sie in den Ordner „<code>web</code>“, wobei Sie darauf achten sollten,
Wenn Sie jetzt die Datei <code>index.html</code> mit dem Live Server ausführen, sollte Ihre Anwendung wieder funktionieren.  
dass im Verschiebe-Dialogfenster die Option  „<code>Search for references</code>“ aktiviert ist.
.
Verschieben sie anschließend die Dateien „<code>main.css</code>“ und „<code>main.js</code>“
in die passenden Ordner „<code>web/css</code>“ bzw. „<code>web/js</code>“. Sie werden feststellen, dass die Verweise in den Link- und Script-Elementen der
Datei „<code>index.html</code>“ automatisch angepasst werden. Verschieben Sie nun die Datei „<code>index.html</code>“ in den Ordner „<code>web</code>“.


Das Ergebnis dieser Aktion ist, dass Ihre Web-Anwendung nicht mehr im Root-Verzeichnis des Projektes liegt, sondern (sauber strukturiert) in unterschiedlichen Unterordnern.
Sollte dies der Fall sein, so sollten Sie die Änderungen committen und evtl. auch gleich
Daher ist es möglich, im Root-Verzeichnis des Projektes weitere projektspezifische Ordner und Dateien anzulegen wie „<code>conf</code>“ und „<code>doc</code>“,
per <code>git push</code> auf den Git-Server hochladen. '''Es schadet übrigens nie, vor einen Commit oder gar einen Push noch einmal zu überprüfen, ob die Anwendung noch läuft.'''
die später nicht auf den Web-Server, auf dem die Anwendung schließlich laufen soll, kopiert werden.


==App1==
=== Modularisierung der Datei <code>main.js</code> ===


Benennen Sie die Dateien „<code>index.html</code>“ und „<code>main.js</code>in
Dateien wie die ECMAScript-Datei <code>main.js</code> aus dem dritten Teil des Tutoriums werden sehr schnell groß, wenn man alle Funktionalität in diese eine Datei einfügt. Dies reduziert die Lesbarkeit und die Wartbbarkeit des Codes.
„<code>index1.html</code>“ und „<code>main1.js</code>“ um. (Im Laufe des Tutoriums kommen weitere Versionen dieser beiden Dateien dazu.)
[[Bertrand Meyer]], einer der wesentlichen Begründer der modularen und objektorientierten Programmierung, fordert,
dass ein Programmcode in sogenannte Module zerlegt werden sollen. Ein Modul stellt eine gewisse Funktionalität zur Verfügung. Die zugehörigen Funktionen werden exportiert und können von anderen Modulen importiert werden, sofern sie diese Funktionalität zur Erfüllung ihrer Aufgabe benötigen. Meyer hält es für sinnvoll, dass ein Modul jeweils nur eine oder sehr wenige Aufgaben erfüllt. Damit können Module von vielen anderen Modulen wiederverwendet werden.


===Laden der CSS- und der JavaScript-Dateien===
Die Datei <code>main.js</code> wird im Sinne von Meyer in zwei Teile aufgespalten:
<code>greet.js</code> und <code>main.js</code>. Die erste Datei enthält die eigentliche Begrüßungsfunktionalität,
die zweite initialisiert die App, {{dh}} aktiviert die Begrüßungsfunktiontionalität.


Eine Web-Anwendung funktioniert nur – wie Sie bereits erfahren haben –, wenn nicht nur die HTML-Datei, sondern auch die zugehörigen
Erstellen Sie die JavaScript-Datei <code>web/js/greet.js</code> und verschieben Sie die
CSS- und JavaScript-Dateien geladen werden. Allerdings gibt es dabei ein Problem: Die CSS- und JavaScript-Dateien werden im Laufe der
beiden Funktionen <code>sayHello</code> und <code>sayHelloOnEnter</code>
Zeit immer zahlreicher und/oder größer. Diese Dateien zu laden, dauert seine Zeit.  
(jeweils samt Funktionsrumpf :-) ) in diese Datei.


In der aktuellen Version der Web-Anwendung stehen diese Verweise auf diese Dateien im <code>head</code>-Bereich des Dokuments.
Schreiben Sie vor jede der beiden Funktionsdefinitionen das Schlüsselwort <code>export</code>.
Dieser wird vollständig geladen, bevor der <code>body</code>-Bereich eingelesen wird. Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments
Mit dieser ES-6-Anweisung legen Sie fest, dass die beiden Funktionen von anderen
darstellen kann, solange er die JavaScript- und CSS-Dateien lädt. Wenn dies zu lange dauert, verliert der Besucher die Geduld und verlässt die Seite
ES-6-Dateien mittels eines Import-Befehls importiert werden können. (Funktionen ohne
vorzeitig.
Export-Anweisung könnten von anderen ES-6-Dateien nicht importiert werden.)


Besser wäre es daher andersherum vorzugehen: Es wird zuerst der <code>body</code>-Bereich geladen und dann die JavaScript- und die CSS-Dateien.
In der Datei <code>web/js/main.js</code> werden jetzt diese beiden Funktionen importiert.
Im Falle von JavaScript ist das durchaus sinnvoll, aber im Falle von CSS hat das den Effekt, dass der Browser keine Layout-Vorgaben erhalten hat,
Fügen Sie ganz am Anfang der Datei ({{dh}} vor der verbliebenen Init-Funktion)
wenn er mit dem [[Rendern]] der Seite beginnt. Also verwendet er die browserspezifischen Defaultwerte. Das heißt, die Seite sieht zunächst
die folgende Import-Anweisung ein:
ganz anders aus, als vom Designer geplant. Wenn dann die CSS-Dateien geladen wurden, wird die Seite erneut gerendert und verändert ihr Aussehen.
Auch das ist verwirrend und wirkt unprofessionell.


Was also machen?
<source lang="javascript">
import * as greet from './greet.js';
</source>


===CSS-Dateien===
Damit wird erreicht, dass alle (<code>*</code>) Elemente, die in der Datei <code>greet.js</code>
Für CSS-Dateien empfiehlt Google ernsthaft, die Link-Element ganz ans Ende der HTML-Datei zu stellen, also '''nach''' dem
exportiert werden, unter ihrem jeweiligen Namen im Objekt <code>greet</code> gespeichert werden.  
schließenden <code>html</code>-Tag.
Das heißt, innerhalb der Datei <code>main.js</code> gibt es nun die beiden Funktionen
Damit das Problem mit dem falschen Layout nicht auftritt, sollen wichtige CSS-Befehle direkt – {{dh}} als CSS-Befehle innerhalb eines
<code>greet.sayHello</code> und <code>greet.sayHelloOnEnter</code>.
<code>style</code>-Elements – in die HTML-Datei eingefügt
werden.<ref>{{Quelle|Google (Web)}}, Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery</ref>


Von beiden Vorschlägen rate ich dringend ab. Der erste Vorschlag ist nicht [[HTML]]-konform, ja noch nicht einmal [[SGML]]-konform. Es mag sein,
Fügen Sie jetzt noch das Attribut <code>type="module"</code> in das Script-Element in der
dass diverse Browser diese Syntax verstehen, korrekt ist sie trotzdem nicht. Der zweite Vorschlag hat zur Folge, dass Struktur (HTML) und Layout (CSS)
Datei <code>index.html</code> ein. Damit teilen Sie dem Browser mit, dass Sie die ECMAScript-6-Befehle <code>import</code> und <code>export</code> verwenden möchten.  
vermischt werden. Dies war einer der Kardinalfehler von frühen HTML-Versionen aus der Zeit, bevor es das CSS-Format gab. '''Tun Sie das nicht.'''


Gehen Sie so vor, wie es unter [[Multimedia-Programmierung: Best Practices#CSS|Multimedia-Programmierung: Best Practices]] beschrieben ist.
Anderenfalls hätten sie Zugriff auf alle Objekte, Konstanten, Variablen und Funktionen einer geladenen Datei, so wie die bis ECMAScript 5 üblich war. Das heißt, das Laden einer Datei konnte zu massiven Problemen führen, wenn in zwei verschiedenen Dateien unterschiedliche Objekte etc. zufälligerweise gleich benannt wurden. Um diese Probleme zu umgehen gab es zahlreiche ziemlich aufwändige Hacks. Mit dem Modul-Konzept von ECMAScript gehören diese Probleme der Vergangenheit an.  


In unserer Anwendung haben wir bislang alles richtig gemacht, bis auf die fehlende automatische Komprimierung.
Damit sollte Ihre Anwendung wieder funktionieren.
Diese wird im [[HTML5-Tutorium: JavaScript: Hello World 05|fünften Teil des Tutoriums]] beschrieben.


===JavaScript-Dateien===
Vergessen Sie nicht, Ihr aktuelles Projekt zu committen und auf dem Git-Server zu speichern, sobald alles funktioniert.´


Für JavaScript-Dateien empfiehlt Google ebenfalls, diese nicht schon zu Beginn zu laden.<ref>{{Quelle|Google (Web)}}, Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/BlockingJS</ref> Dieser Vorschlag ist sehr sinnvoll.
==== Musterlösung v04_1 ====
Verison v04_1 [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_1/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html Google PageSpeed])


Entfernen Sie das <code>script</code>-Element aus dem HTML-Header-Bereich und fügen Sie es vor dem schließenden <code>body</code>-Tag  ein:
===Laden der CSS- und der JavaScript-Dateien===
 
<source lang="html5">
  ...
  <script type="text/javascript" src="js/main1.js"></script>
</body>
</source>


===Globale Funktionen als Methoden===
Führen Sie [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html Google PageSpeed] für die
Musterlösung der vorherigen Teilaufgabe aus und sehen Sie sich an, welche Verbesserungsvorschläge Google macht.


Erstellen sie die  Datei „<code>js/app1/greet.js</code>“ und fügen Sie ein weiteres Script-Element in die Datei „<code>index1.html</code>
Folgendes findet Google PageSpeed verbesserungswürdig:
ein (dem dem Skript „<code>main1.js</code>“):
# [https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources?hl=de ''Ressourcen entfernen, die das Rendering blockieren.]''<br />''Mögliche Einsparung von 300 ms.''
# [https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains?hl=de ''Verkettung kritischer Anfragen vermeiden'']<br />''2 Ketten gefunden''
# [''Largest Contentful Paint-Element 1.350 ms'' https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint?hl=de]
# ''Dokument enthält keine Meta-Beschreibung''


<source lang="html5">
''Anmerkung'': Google Pagespped ist auch über die Chroeme-Entwickler-Konsole zugänglich. Damit können auch lokale Server (<ocde>localhost</code> {{oÄ}}) getestet werden. Echte Prformanzprobleme erkennt man aber eher, wenn man seinen Live-Server testet.
  ...
  <script type = "text/javascript" src = "js/app1/greet.js"></script>
  <script type="text/javascript" src="js/main1.js"></script>
</body>
</source>


Fügen Sie in die Datei „<code>greet.js</code>“ ein Objekt ein, dass die beiden Methoden
Eine Meta-Beschreibung hinzuzufügen, ist kein Problem. Fügen Sie folgende Zeile in den Head-Bereich der HTML-Datei ein.
„<code>sayHello</code>“ und „<code<sayHelloOnEnter</code>“ enthält.


<source lang="javascript">
<source language="HTML">
/**
<meta name="description"
* @namespace greet
       content="A Hello World application that demonstrates best practices for HTML, CSS, and JavaScript files."
* Contains methods to say hello to the user of the web app.
>
*/
var
  greet =
  {
    /**
    * Welcomes the user of the web app1 by displaying a welcome message
    * that includes his name. The name is fetched from a text input field.
    */
    sayHello:
       function()
      {
        document.getElementById('heading_hello').innerHTML =
          'Hello, ' + document.getElementById('input_name').value + '!';
        document.getElementById('section_form').classList.add('hidden');
        document.getElementById('section_hello').classList.remove('hidden');
      },
 
    /**
    * An keyboard event observer. It tests whether the enter key has been pressed.
    * If so, the greet method is activated. Default reactions of the browser are
    * disabled.
    * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
    *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
    */
    sayHelloOnEnter:
      function(p_event)
      {
        if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
            document.activeElement === document.getElementById('input_name')
          )
        {
          p_event.preventDefault();
          p_event.stopPropagation();
          greet.sayHello();
        }
      }
  };
</source>
</source>


In der Datei  „<code>main1.js</code>“ werden dagegen alle globalen Funktionen entfernt. Die Funktion „<code>init</code>“
Das effiziente Laden der JavaScript- und CSS-Dateien ist viel komplexer, aber auch viel wichtiger, da die CSS- und JavaScript-Dateien werden im Laufe der
wird durch eine anonyme Funktion ersetzt, die sofort nach ihrer Definition auch ausgeführt wird.
Zeit immer zahlreicher und/oder größer werden. Diese Dateien zu laden, dauert seine Zeit. Auch das Laden von vielen kleinen Dateien dauert deutlich länger, als das Laden einer einzigen großen Datei. Das aber widerspricht der Modularisierung, die wir vorher vorgenommen haben. Modularisierung und Performance widersprechen sich zunächst einmal.
Da die Datei „<code>main1.js</code>“ als Letztes geladen wird, sind alle Objekte, auf die
während der Initialisierung zugegriffen werden muss, bereits vorhanden.


<source lang="javascript">
In Web-Anwendung aus dem dritten Teil des Tutoriums stehen die Verweise auf die CSS- und JavaScript-Dateien im <code>head</code>-Bereich des Dokuments.
// Initialize the app.
Die CSS-Datei wird vollständig geladen, bevor der <code>body</code>-Bereich eingelesen wird. Die JavaScript-Datei <code>main.js</code> wird dagegen parallel zum Body-Bereich geladen. Dies wird durch die Angabe von <code>async</code> im
(function()
Script-Tag wird erreicht.  
{
  document.getElementById('button_submit')
          .addEventListener('click', greet.sayHello);


  window.addEventListener('keydown', greet.sayHelloOnEnter);
Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments
}());
darstellen kann, solange er die CSS-Datei (und evtl. weitere Dateien wie Fonts, Hintergrundbilder etc.) lädt. Wenn dies zu lange dauert,
</source>
verliert der Besucher die Geduld und verlässt die Seite vorzeitig.


Testen Sie Ihre Anwendung und speichern Sie sie zu guter Letzt im Repository.
Besser wäre es daher andersherum vorzugehen: Es wird zuerst der Body-Bereich geladen und dann die JavaScript- und die CSS-Dateien.
Im Falle von JavaScript ist das durchaus sinnvoll (und durch die Angabe von <code>async</code> auch machbar), aber im Falle von CSS hat das den Effekt, dass der Browser noch keine Layout-Vorgaben erhalten hat,
wenn er mit dem [[Rendern]] der Seite beginnt. Also verwendet er die browserspezifischen Defaultwerte. Das heißt, die Seite sieht zunächst
ganz anders aus, als vom Designer geplant. Wenn dann die CSS-Dateien geladen wurden, wird die Seite erneut gerendert und verändert ihr Aussehen.


==App2==
Auch das ist verwirrend und wirkt unprofessionell: https://de.wikipedia.org/wiki/Ampel


Ein wesentliches [[Programmierprinzip]] ist das Prinzip der „Wiederverwendbarkeit“.
Was also machen?
Man solle Code so schreiben, dass man ihn möglichst häufig wiederverwenden kann.
Jede Wiederverwendung bereits bestehende Codes bedeutet Arbeitsersparnis.
Man kann mit einem einfachen Test überprüfen, ob man Code erstellt hat, der
sogar innerhalb desselben Projektes wiederverwendbar ist. Man baut die neu
erstellte Anwendung einfach zweimal in ein HTML-Dokument ein. Wenn beide
„Instanzen“ der Anwendung unabhängig voneinander funktionieren, hat man
alles richtig gemacht.


Zurzeit geht das bei unserer Anwendung noch nicht. Es gibt nämlich nur
===CSS-Dateien===
ein Objekt  „<code>greet</code>“, das nicht für mehrere Anwendungen parallel eingesetzt werden kann.
Damit das Problem mit dem falschen Layout nicht auftritt, empfiehlt Google ([https://developers.google.com/speed/pagespeed/insights/ PageSpeed Insights]),  
Wir müssen also bei Bedarf neue <code>greet</code>-Objekte erzeugen können.
ganz wenige, wichtige CSS-Befehle direkt – {{dh}} als CSS-Befehle innerhalb eines <code>style</code>-Elements – in dem HTML-Head-Bereich einzufügen. Alle anderen CSS-Anweisungen sollen erst am Ende der HTML-Datei dynamisch mittels JavaScript
Das geschieht in JavaScript mit Hilfe von sogenannten [[Konstruktor]]funktionen.
(oder mittels des PageSpeed-Optimization-Modul von Google) gelesen werden.


Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihre Anwendungsdateien:
[https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html PageSpeed Insights (für Musterlösung v04_1)]:
<blockquote cite="https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html">
[https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources?hl=de Ressourcen blockieren den First Paint deiner Seite. Versuche, wichtiges JS und wichtige CSS inline anzugeben und alle nicht kritischen JS und Stile zurückzustellen.]
</blockquote>


* <code>index1.html</code> →<code>index2.html</code>
Gehen Sie so vor, wie es Google empfohlen hat. Genauer gesagt, gehen Sie so vor,
* <code>js/main1.js</code> →<code>js/main2.js</code>
wie es unter [[Multimedia-Programmierung: Best Practices#CSS|Multimedia-Programmierung: Best Practices]] beschrieben ist. In diesem und den folgenden Tutorien werden
* <code>js/app1/greet.js</code> →<code>js/app2/greet.js</code>
die dort beschriebenen Prinzipien berücksichtigt.


In der Datei <code>index2.html</code>“ müssen Sie dafür sorgen, dass
Legen Sie die Datei <code>src/css/head.css</code> an und fügen Sie folgenden (sehr kurzen!) Code ein:
die neuen JavaScript-Dateien anstelle der alten geladen werden.
Und passen Sie den Titel im HTML-Header an.


Das Objekt „<code>greet</code>“  der Datei „<code>js/app2/greet.js</code>“
<source lang="css">
wird durch der folgende [[Konstruktor]]funktion  „<code>Greet</code>“ ersetzt:
body
{ background-color: #C5EFFC; }


<source lang="javascript">
.hidden
/**
{ display: none; }
* @class
</source>
* @classdesc Contains methods to say hello to the user of the web app.
*/
function Greet()
{
  var l_this = this; // this-hack: l_this contains the new object


  /**
In der Datei <code>src/index.html</code> müssen Sie das Link-Element in Header folgendermaßen
  * Welcomes the user of the web app1 by displaying a welcome message
<source lang="html">
  * that includes his name. The name is fetched from a text input field.
<link rel="stylesheet" href="css/head.css"/>
  */
  l_this.sayHello = // this.sayHello = ...
  function()
  {
    document.getElementById('heading_hello').innerHTML =
      'Hello, ' + document.getElementById("input_name").value + '!';
    document.getElementById('section_form').classList.add('hidden');
    document.getElementById('section_hello').classList.remove('hidden');
  };
 
  /**
  * An keyboard event observer. It tests whether the enter key has been pressed.
  * If so, the greet method is activated. Default reactions of the browser are
  * disabled.
  * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
  *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
  */
  l_this.sayHelloOnEnter = // this.sayHelloOnEnter = ...
  function(p_event)
  {
    if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
        document.activeElement === document.getElementById('input_name')
      )
    {
      p_event.preventDefault();
      p_event.stopPropagation();
      l_this.sayHello();  // NOT this.sayHello();
    }
  }
}
</source>
</source>
anpassen. Das heißt, Sie laden im HTML-Head-Bereich nur noch die kleine CSS-Datei.
Damit funktioniert Ihre Web-Anwendung derzeit nur noch rudimentär: Es gibt kaum noch CSS-Anweisungen, die berücksichtigt werden. Noch besser ist es gemäß den Google-Empfehlungen,
wenn man die CSS-Regeln direkt in die HTML-Datei einfügt. Ersetzen Sie also das
Link-Element durch folgendes Style-Element:


In JavaScript kann jede Funktion auch als [[Konstruktor]], {{dh}}
<source lang="html">
zum Erzeugen von neuen Objekte verwendet werden, indem man das Schlüsselwort
<style>
<code>new</code>“ voranstellt:
body
{ background-color: #C5EFFC; }


<source lang="javascript">
.hidden
var l_greet = new Greet();
{ display: none; }
</style>
</source>
</source>


In einer Konstruktorfunktion wird üblicherweise kein <code>return</code>-Befehl eingefügt.
In der Datei <code>src/css/main.css</code> können Sie nun das
Ein Konstruktor verhält sich wie eine [[Prozedur (Informatik)|Prozedur]] und nicht wie
<code>.hidden</code>-Element und
eine [[Funktion (Informatik)|Funktion]]. Sie erstellt  – als [[Seiteneffekt]] – ein neues Objekt und initialisiert es.
das Farbattribut <code>background-color: #C5EFFC;</code> entfernen.
Allerdings wird diese Objekt nirgendwo gespeichert, sondern – automatisch – als Ergebnis 
des Befehls <code>new Greet()</code> zurückgeliefert. Anschließend wird es üblicherweise irgendwo gespeichert,
in einer [[Variable]]n, in einem [[Array]], in einem [[Objekt]] oder in einem [[Funktionsparameter]].


Im Funktionsrumpf selbst kann über die Spezialvariable „<code>this</code>“ auf das neu erstellte Objekt
Es ist sinnvoll, die Datei <code>main.css</code> in <code>body.css</code> umzubenennen:
zugegriffen werden. Dem Objekt können mittels „<code>this.sayHello = ...</code>“,  „<code>this.sayHelloOnEnter = ...</code>
<code>head.css</code> wird im HTML-Head-Bereich vor dem Body-Bereich geladen,  
etc. [[Attribut (Objekt)|Attribute]] zugewiesen werden. Sie speichern in dem neu erstellten Objekt wieder dieselben Methoden,  
<code>body.css</code> soll parallel zum Body-Bereich geladen werden und dann zur Verfügung stehen, wenn der Body vollständig geladen wurde.
wie im Objekt „<code>greet</code>der Anwendung „app1“.  


Beachten Sie, dass in der Definition der so genannte <code>this</code>-Hack zum Einsatz kommt.
Leider ist CSS-Link-Element nicht möglich, <code>async</code> anzugeben, wie wir dies beim Skript-Element gemacht haben, um JavaScript-Code parallel zum Body-Element zu laden. Hier hilft derzeit nur ein kleiner Hack:
Das Objekt <code>this</code> wird in einer lokalen Variablen <code>l_this</code> gespeichert.
Und anschließend wird diese lokale Variable anstelle von <code>this</code> benutzt.
Für die Definition der Attribute „<code>sayHello</code>“ und  „<code>sayHelloOnEnter</code>“ macht es keinen Unterschied,
ob man „<code>this.sayHello...</code>“ oder „<code>l_this.sayHello</code>“ schreibt.


Allerdings wird auch innerhalb des Methodenrumpfes von „<code>sayHelloOnEnter</code>“ auf das neu erstellt
<source lang="html">
Objekt zugegriffen: <code>l_this.sayHello();</code>. Und hier könnte nun nicht <code>this.sayHello();</code>
<!-- Load body.css asynchronously, i.e. parallel to the body content. -->
verwendet werden. Der Grund ist, dass der Methodenrumpf erst ausgeführt wird, wen die Methode „<code>sayHelloOnEnter</code>“
<link rel="preload" href="css/body.css" as="style" onload="this.rel='stylesheet'">
zu irgendeinem anderen Zeitpunkt von irgendeinem anderen Objekt aufgerufen wird. Und  <code>this</code> bezeichnet
</source>
'''immer''' den Aufrufer eine Methode, und nicht das Objekt, das die Methode beinhaltet. Aber im Aufrufer der
Methode ist  „<code>sayHello</code>“ {{iAllg}} gar nicht definiert oder falls doch, dann meist mit einer vollkommen anderen 
Funktionalität. Die Spezialvariable  „<code>this</code>“ verhält sich hier vollkommen anders als sämtliche andere Variablen und Parameter.
(Fachsprachlich: In JavaScript ist die Variable  „<code>this</code>“ [[Variable#Bindung|dynamisch gebunden]], alle übrigen Variablen sind
[[Variable#Bindung|statisch gebunden]].) Daher kopiert man den Wert von „<code>this</code>“ in eine lokale Variable,
auf die die Methode  „<code>sayHelloOnEnter</code>“ auch dann Zugriff hat, wenn Sie von einem anderen Objekt aufgerufen wird.
 
'''Tipp''': Definieren Sie innerhalb einer Konstruktorfunktion immer eine lokale Variable „<code>var l_this = this</code>“ und verwenden Sie innerhalb
des Konstruktors nur „<code>l_this</code>“ und nie „<code>this</code>“. (Sie können natürlich auch einen anderen Alternativnamen für
„<code>this</code>“ verwenden. Übliche Bezeichnungen sind  „<code>that</code>“,  „<code>self</code>“ oder  „<code>me</code>“.)


'''Anmerkung''': Eigentlich ist der  <code>this</code>-Hack unsauber. Besser wäre es, die Methode „<code>bind</code>“ zu verwenden
Die Datei <code>body.css</code> wird für den Medientyp <code>style</code> parallel zum HTML-Dokument geladen, aber nicht aktiviert.
(siehe  [https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Function/bind MDN] oder [http://stackoverflow.com/questions/2236747/use-of-the-javascript-bind-method Stackoverflow]).
Um aus dem Inhalt der vorab geladenen Datei für den Browser ein Stylesheet zu machen, muss man den Wert des Attributs <code>rel</code>  
in <code>'stylesheet'</code> abändern. Das sollte erst erfolgen, wenn das Dokument vollständig geladen wurde, wenn also das
das Fensterobjekt des Browsers, in dem das HTML-Dokument gerendert werden soll, das Ereignis <code>load</code> feuert. Für dieses Ereignis kann man entweder im <code>link</code>-Tag den Händler <code>onload</code> einfügen oder man schreibt den Aktivierungsbefehl in die Init-Funktion in der Datei <code>main.js</code>.


<source lang="javascript">
Für die zweite Variante sollte man dem <code>link</code>-Element eine eindeutigen Identifikator zuweisen:
this.sayHelloOnEnter =
  function(p_event)
<source lang="html">
  {
<link id="body_css" rel="preload" href="css/body.css" as="style">
    ...
      this.sayHello();
    ...
  }.bind(this)
</source>
</source>


Aber für JavaScript-Anfänger ist die Verwendung des <code>this</code>-Hacks deutlich einfacher.
Danach kann man den Aktivierungsbefehl in die Funktion <code>init</code> in der Datei <code>main.js</code> einfügen.
 
Fügen Sie die zuvor erwähnte Zeile


<source lang="javascript">
<source lang="javascript">
var l_greet = new Greet();
document.getElementById('body_css').rel = 'stylesheet';
</source>
</source>
als erste Zeile '''in''' Ihre Initialisierungsfunktion (Datei „<code>main2.js</code>“)
ein und ersetzen Sie in derselben Funktion die beiden Zugriffe auf das globale Objekt
„<code>greet</code>“ durch Zugriffe auf das lokale Objekt „<code>l_greet</code>“.


Danach sollte Ihre Anwendung (Datei <code>index2.html</code>“) wieder wie gewohnt laufen.
Zusammenfassung:
* In <code>head.css</code> stehen möglichst wenig CSS-Anweisungen. Sie sollten ausreichen, um die aktuelle Seite “above the fold” („über dem Zeitungsknick“) fehlerfrei zu rendern. Das heißt, es sollte alles korrekt gerendert werden, was nach dem Laden im größtmöglichen Browserfenster sichtbar ist.
* Der Inhalt der <code>head.css</code> sollte direkt in den Head-Bereich der HTML-Seite eingefügt werden.
* Die restlichen CSS-Anweisungen werden in die Datei <code>body.css</code> eingetragen.
* Diese Datei wird mittels eines kleinen Hacks asynchron, {{dh}} parallel zum Body-Element geladen.


==App3==
===<code>index.html</code>===


Leider bringt uns hinsichtlich der Wiederverwendbarkeit die „Verbesserung“, die wir in App2 gegenüber App1
Da die Datei <code>head.css</code> nur die Hintergrundfarbe sowie die <code>.hidden</code>-Regel enthält, sollten alle HTML-Elemente, die gerendert werden sollen, zunächst unsichtbar gemacht werden. Bei einer Web-Anwendung, wie einem Spiel, die zum Laden länger braucht, könnte man eine Sanduhr oder einen Ladebalken einfügen, um anzuzeigen, dass man noch etwas warten muss. Üblicherweise, sollte man dafür sorgen,
vorgenommen haben, gar nichts. Wir können jetzt zwar beliebig viele <code>greet</code>-Objekte
dass das Laden einer Seite so schnell geht, dass der Benutzer gar nicht erst ungeduldig wird.
mit Hilfe von „<code>new Greet()</code>“ erstellen.
Aber wir haben nichts davon, da alle diese Objekte dieselben HTML-Elemente
im HTML-Dokument lesen und ändern. Das heißt, wir müssen die Konstanten und
am Besten auch gleich noch die Zugriffe auf globale Variablen aus de rKonstruktorfunktion
„<code>greet</code>“ verbannen.


Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihre Anwendungsdateien:
Fügen Sie die CSS-Klasse <code>hidden</code> in beide HTML-Sektions ein:


* <code>index2.html</code> →<code>index3.html</code>
<source lang="html">
* <code>js/main2.js</code> →<code>js/main3.js</code>
<section id="section_form" class="hidden">
* <code>js/app2/greet.js</code> →<code>js/app3/greet.js</code>
...
 
</section>
In der Datei „<code>index3.html</code>“ müssen Sie wiederum dafür sorgen, dass
<section id="section_hello" class="hidden">
die neuen JavaScript-Dateien anstelle der alten geladen werden.
...
Und passen Sie auch wieder den Titel im HTML-Header an.
</section>
 
Ersetzen Sie nun die Konstruktorfunktion der Datei „<code>js/app3/greet.js</code>
durch folgenden Konstruktor:
 
<source lang="javascript">
/**
* @class
* @classdesc Contains methods to say hello to the user of the web app.
*
* @param p_document {object} A DOM object (usually the global object <code>document</code>)
* @param p_section_form {string} The ID of the HTML element that contains the form section.
* @param p_input_name string} The ID of the text field via which the user passes her/his name.
* @param p_section_hello {string} The ID of the HTML element that contains the welcome section.
* @param p_output_hello {string} The ID of the HTML element into which the greeting is to be put.
* @param p_hello {string} The hello test to be displayed.
*/
function Greet(p_document,
              p_section_form, p_input_name,
              p_section_hello, p_output_hello,
              p_hello
              )
{
  var l_this = this; // this-hack
 
  /**
  * Welcomes the user of the web app1 by displaying a welcome message
  * that includes his name. The name is fetched from a text input field.
  */
  l_this.sayHello =
  function()
  {
    p_document.getElementById(p_output_hello).innerHTML =
      p_hello + ', ' + p_document.getElementById(p_input_name).value + '!';
    p_document.getElementById(p_section_form).classList.add('hidden');
    p_document.getElementById(p_section_hello).classList.remove('hidden');
  };
 
  /**
  * An keyboard event observer. It tests whether the enter key has been pressed.
  * If so, the greet method is activated. Default reactions of the browser are
  * disabled.
  * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
  *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
  */
  l_this.sayHelloOnEnter =
  function(p_event)
  {
    if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
        p_document.activeElement === p_document.getElementById(p_input_name)
      )
    {
      p_event.preventDefault();
      p_event.stopPropagation();
      l_this.sayHello();  // NOT this.sayHello();
    }
  }
}
</source>
</source>


Dieser Konstruktor enthält diverse Konstanten nicht mehr, wie {{zB}} <code>'input_name'</code>. Anstelle dessen gibt es diverse Parameter,
Wenn man jetzt das Dokument im Browser öffnet, sieht man nichts, außer einem hellblauen Hintergrund.  
in denen diese Werte dem Konstruktor übergeben werden können: <code>p_input_name</code>. Der Zugriff auf das globale Objekt
„<code>document</code>“ wurde auch gleich noch durch eine Zugriff auf einen lokalen Parameter „<code>p_document</code>“ ersetzt.


Damit ist es jetzt möglich, <code>greet</code>-Objekte für ganz unterschiedliche HTML-Elemente zu definieren.
Abhilfe schafft hier ein kleiner JavaScript-Befehl, der <code>hidden</code> von der Eingabe-Section <code>hidden-form</code> entfernt, sobald das Dokument vollständig geladen wurde (samt allen CSS- und JavaScript-Dateien</code>):
 
Ersetzen Sie zunächst die Initialisierungsmethode in der Datei  „<code>main3.js</code>
durch folgenden Code:


<source lang="javascript">
<source lang="javascript">
(function(p_window, Greet)
document.getElementById('section_form').classList.remove('hidden');
{
  var l_document = p_window.document, // The window object contains the document object.
      l_greet    = new Greet(l_document,
                            'section_form', 'input_name',
                            'section_hello', 'heading_hello',
                            'Hello'
                            );
 
  l_document.getElementById('button_submit')
            .addEventListener('click', l_greet.sayHello);
 
  p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
}(window, Greet));
</source>
</source>


In dieser Datei werden noch gibt es noch zwei globale Variablen, die Browser-Variable  „<code>window</code>“
An welche Stelle dieser Befehl eingefügt wird, erfahren Sie im nachfolgenden Abschnitt.
sowie die von uns definierte Konstruktorfunktion „<code>Greet</code>“. Diese beiden globalen Größen werden der
anonymen Funktion als Argumente übergeben. Innerhalb des Rumpfes der Initialisierungsfunktion wird dann nicht
mehr auf globale Größen zugegriffen, sondern nur noch auch Funktionsparameter. Dies ist ein übliches
Vorgehen, um explizit klar zu machen, auf welche globalen Größen zugegriffen wird.


Dem Konstruktor  „<code>Greet</code>“ werden jetzt alle Information, die er benötigt,
===JavaScript-Dateien===
explizit übergeben. Dies ist zum einen das HTML-Dokument, das mit Hilfe der von ihm erstellten
Methoden manipuliert werden soll, dann eine Reihe von ID-Attribut-Namen, die diejenigen
HTML-Elemente bezeichnen, die die Greet-Methoden lesen oder ändern sollen, sowie
der String „<code>'Hello'</code>“ der zur Begrüßung des Benutzers verwendet werden soll.
Alternativ könnte er auch mit „<code>Hallo</code>“ oder „<code>Howdy</code>“ begrüßt werden.
 
Nun müsste Ihre Anwendung „<code>index3.html</code>“ wieder laufen. Wenn Sie es denn tut, vergessen Sie nicht, sie
im Repository zu speichern.
 
Anschließend sollten Sie in Ihrem WebStorm-Projekt folgende Kopien erstellen:
 
* <code>index3.html</code> →<code>index3b.html</code>
* <code>js/main3.js</code> →<code>js/main3b.js</code>
 
Die Datei  „<code>index3.html</code>“ soll abermals die JavaScript-Datei „<code>js/app3/greet.js</code>“
verwenden, dafür aber „<code>js/main3b.js</code>“ an Stelle von  „<code>js/main3.js</code>“ laden.
 
Fügen Sie in der Datei „<code>index3.html</code>“ in alle ID-Attribut-Bezeichner eine <code>1</code> ein:
<code>section_form</code> → <code>section1_form</code> etc.
 
Schließen Sie dann die beiden Sections „<code>section1_form</code>“ und „<code>section1_hello</code>“
in ein <code>div</code>-Element ein und duplizieren Sie diese <code>div</code>-Element (natürlich samt Inhalt) anschließend.
Sie werden feststellen, dass WebStorm eine Reihe von Fehlern der Art  „<code>Duplicate id reference</code>“ meldet.
Das ist nicht weiter verwunderlich, da Sie ja insbesondere alle ID-Attribut-Bezeichner dupliziert haben.
Sie müssen daher im zweiten  <code>div</code>-Element alle Einser in den  ID-Attribut-Bezeichnern durch Zweier ersetzen.
 
Nun können Sie noch versuchen, die beiden <code>div</code>-Elemente mittels CSS nebeneinander zu legen. Für den
angestrebten Test, ob Sie die Hello-World-App in einem HTML-Dokument zweimal starten können, ist das allerdings irrelevant.
 
Passen Sie nun Ihre Datei „<code>main3b.js</code>“ an die neuen Gegebenheiten an:
Erzeugen Sie zwei Greet-Objekte „<code>greet1</code>“ und „<code>greet2</code>“ (für jedes der beiden neuen <code>div</code>-Elemente eines),
fügen Sie passende <code>click</code>-Event-Listener für die beiden Buttons „<code>button1_submit</code>“ sowie
„<code>button2_submit</code>“ hinzu und sorgen Sie zu guter Letzt dafür, dass beide Methoden  „<code>l_greet1.sayHelloOnEnter</code>“
und  „<code>l_greet2.sayHelloOnEnter</code>“ bei jedem Tastendruck aufgerufen werden.
 
Wenn Sie alles richtig gemacht haben, sollten jetzt in Ihrer Web-Anwendung „<code>index3b.html</code>“
zwei Hello-World-Anwendungen parallel laufen: [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/index3b.html <code>index3b.html</code>]
 
==App4==
Hinsichtlich der Wiederverwendbarkeit der Module ist die Version 3 der Web-Anwendung schon ganz gut.
Allerdings gibt es noch ein paar Unsauberkeiten, die beseitigt werden sollten:
 
# In den Initialisierungsdateien  „<code>main3.js</code>“ und  „<code>main4.js</code>“ sind immer noch konstante Werte enthalten.
# Die Anzahl der Parameter der Konstruktorfunktion ist zu groß.
# In den Initialisierungsdateien ist immer noch zu viel Anwendungscode enthalten.
# Die potentielle Zahl der globalen Größen ist noch zu hoch.
 
Diese Probleme werden folgendermaßen gelöst:
 
# Die Konstanten werden in eine [[JSON]]-Datei ausgelagert, die im ersten Schritt von der jeweiligen Initialisierungsdatei eingelesen wird.
# Der Konstruktorfunktion wird das gesamte JSON-Objekt mit allen Initialisierungswerten in einem einzigen Parameter übergeben.
# Es wird eine anwendungsspezifische Initialisierungsdatei „<code>hello.js</code>“ erstellt. Die Initialisierungsfunktion in der Datei „<code>main4.js</code>“ hat nur noch die Aufgabe, die JSON-Datei zu lesen und die darin enthalten Informationen anschließend an die Methode  „<code>hello.init</code>“ weiterzuleiten.
# Es wird '''ein''' globales Objekt namens  „<code>hello</code>“ erstellt, das die gesamte Web-Anwendung enthält.
 
Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihrer Anwendungsdateien:
 
* <code>index3b.html</code> →<code>index4.html</code>
* <code>js/main3.js</code> →<code>js/main4.js</code>
* <code>js/app3/greet.js</code> →<code>js/app4/greet.js</code>
 
Im Folgenden wird die HTML-Datei „<code>index4.html</code>“
als HTML-[[Template]] verwendet, {{dh}} als Strukturrahmen. '''Sämtliche''' Texte,
die in diesem Dokument enthalten sind, werden dynamisch mittels JavaScript eingefügt.


In der Datei „<code>index4.html</code>“ müssen Sie also nicht nur dafür sorgen, dass
Für JavaScript-Dateien empfiehlt Google ebenfalls, diese nicht schon zu Beginn zu laden, sondern das Laden „aufzuschieben“. Das machen wir bereits mit Hilfe des Async-Attributs.
die neuen JavaScript-Dateien anstelle der alten geladen werden und dass der Titel angepasst wird.
Sie müssen auch sämtliche Text-Elemente mit eindeutigen ID-Attributen versehen.
 
Die HTML-Datei der Musterlösung sieht folgendermaßen aus. Sie können auch Ihre
eigene Datei verwenden, müssen dann aber die JSON-Datei, die anschließend erstellt wird,
entsprechend anpassen.


Fügen Sie nun den zuvor erwähnten Befehl
<source lang="javascript">
<source lang="javascript">
<!DOCTYPE html>
document.getElementById('section_form').classList.remove('hidden');
<html lang="en">
 
<head>
  <meta charset = "UTF-8">
  <meta name    = "viewport"
        content = "width=device-width, initial-scale=1.0, user-scalable=yes"
  >
 
  <title>WK_HelloWorld04 (4)</title>
 
  <link rel = "stylesheet" type = "text/css" href = "css/main.css"/>
</head>
 
<body>
  <div class="left">
    <section id="section1_form">
      <h1 id="heading1_form"></h1>
      <form>
        <div>
          <label id="label1_name" for="input1_name"></label>
          <input id="input1_name" type="text" autofocus="autofocus"/>
        </div>
        <div>
          <button id="button1_reset"  type="reset"></button>
          <button id="button1_submit" type="button"></button>
        </div>
      </form>
    </section>
    <section id="section1_hello" class="hidden">
      <h1 id="heading1_hello"></h1>
      <p id="paragraph1_hello"></p>
    </section>
  </div>
 
  <div class="right">
    <section id="section2_form">
      <h1 id="heading2_form"></h1>
      <form>
        <div>
          <label id="label2_name" for="input2_name"></label>
          <input id="input2_name" type="text" autofocus="autofocus"/>
        </div>
        <div>
          <button id="button2_reset"  type="reset"></button>
          <button id="button2_submit" type="button"></button>
        </div>
      </form>
    </section>
    <section id="section2_hello" class="hidden">
      <h1 id="heading2_hello"></h1>
      <p id="paragraph2_hello"></p>
    </section>
  </div>
 
  <script type = "text/javascript" src = "js/lib/wk/wk_ajax.js"></script>
  <script type = "text/javascript" src = "js/app4/greet.js"></script>
  <script type = "text/javascript" src = "js/app4/hello.js"></script>
  <script type = "text/javascript" src = "js/main4.js"></script>
</body>
 
</html>
</source>
</source>
als letzen Befehl in den Rumpf der Funktion <code>init</code> in der Datei
<code>web/js/main.js</code> ein.


===Modularisierung===
Wenn Sie jetzt die Web-App starten, sollte sie wieder funktionieren.


Um möglichst wenige globale Größen definieren zu müssen, legt man für eine
Was haben Sie erreicht? Solange die Anwendung geladen wird, sehen Sie nur einen blauen Hintergrund ohne Inhalt, bzw. {{iAllg}} einen so genannten Splash Screen. Wenn dann alles, {{dh}} alle CSS- und JavaScript-Dateien geladen wurden, wird die eigentliche Anwendung sichtbar.  
Anwendung oder eine Bibliothek typischerweise eine einzige globale Variable an, die ein Objekt enthält.
In diesem Objekt wird die gesamte Anwendung (Daten, Klassen, Funktionen etc.) gespeichert.


Für die Hello-World-Anwendung wird eine globale „Namensraum-Variable“ namens „<code>hello</code>“ verwendet.
Vergessen Sie nicht: Commit!
In jeder Datei, in der ein Teilaspekt der Anwendung definiert wird, kommt folgender Rahmencode zum Einsatz:


<source lang="javascript">
== Verbesserung der Init-Funktion ==
/** @namespace hello */
var hello =
(function(p_hello)
{ "use strict";


  // anwendungsspezifischer Code
Bislang sieht die Init-Funktion folgendermaßen aus.
 
  return p_hello;
}(hello ? hello : {}));
</source>
 
In der Variablen „<code>hello</code>“ wird das Ergebnis eines Aufrufs einer anonymen Funktion gespeichert.
Dieser Funktion wird als Argument der Inhalt der Variablen „<code>hello</code>“ oder, falls dieses noch nicht
existiert, ein leeres Objekt als Argument übergeben (<code>hello ? hello : {}</code>). Die anonyme Funktion
fügt irgendwelche Daten, Klassen oder Funktionen in dieses Objekt ein gibt es dann als Ergebnis zurück.
 
Das hat den Effekt, dass beim ersten Aufruf einer derartigen Funktion eine neues Objekt erstellt und
in „<code>hello</code>“ gespeichert wird, bei jedem weiteren Aufruf einer derartigen Funktion wird
das bestehende <code>hello</code>-Objekt einfach um weitere Inhalte ergänzt. Wenn die Definition
eines Namensraums über mehrere Dateien verteilt ist – wie dies üblicherweise der Fall ist –,
ist es egal in welcher Reihenfolge diese Dateien geladen werden. Sie können daher auch [[asynchron]]
geladen werden. Wichtig ist nur, dass eine Datei nicht die Inhalte einer anderen Datei überschreiben darf.
 
Die Datei  „<code>js/app4/greet.js</code>“ sieht nun folgendermaßen aus:


<source lang="javascript">
<source lang="javascript">
/** @namespace hello */
function init()
var hello =
{ document.getElementById('button_submit')
(function(p_hello)
          .addEventListener('click', greet.sayHello);
{ "use strict";
  window.addEventListener('keydown', greet.sayHelloOnEnter);


   /**
   document.getElementById('body_css').rel = 'stylesheet';
  * @class    hello.Greet
  * @classdesc Contains methods to say hello to the user of the web app.
  *
  * @param p_document A DOM object (usually the global object <code>document</code>)
  * @param p_init A JSON object containing all init data.
  */
  p_hello.Greet =
  function(p_document, p_init)
  {
    var l_this = this; // this-hack


    /**
   document.getElementById('section_form').classList.remove('hidden');
    * Welcomes the user of the web app1 by displaying a welcome message
}
    * that includes his name. The name is fetched from a text input field.
    */
    l_this.sayHello =
    function()
    {
      var l_elements = p_init.HTMLElements,
          l_text    = p_init.text,
          l_hidden   = p_init.css.hidden,
          l_name    = p_document.getElementById(l_elements.inputName).value;
 
      p_document.getElementById(l_elements.welcome)    .innerHTML = l_text.welcome;
      p_document.getElementById(l_elements.hello)      .innerHTML = l_text.hello.replace('$1', l_name);
      p_document.getElementById(l_elements.sectionHello).classList.remove(l_hidden);
      p_document.getElementById(l_elements.sectionForm) .classList.add(l_hidden);
    };
 
    /**
    * An keyboard event observer. It tests whether the enter key has been pressed.
    * If so, the greet method is activated. Default reactions of the browser are
    * disabled.
    *
    * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
    *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
    */
    l_this.sayHelloOnEnter =
    function(p_event)
    {
      if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
          p_document.activeElement ===
          p_document.getElementById(p_init.HTMLElements.inputName)
        )
      {
        p_event.preventDefault();
        p_event.stopPropagation();
        l_this.sayHello();  // NOT this.sayHello();
      }
    };
  };
 
  return p_hello;
}(hello ? hello : {}));
</source>
</source>


Gegenüber der Version 3 dieser Datei hat sich folgendes geändert:
Man kann auch einen anderen Import-Befehl verwenden, um auf das Objekt <code>greet</code> verzichten zu können.
 
* Es wird keine globale Konstruktorfunktion „<code>Greet</code>“ mehr definiert. Diese Konstruktorfunktion wird vielmehr in der Namensraum-Variablen „<code>hello</code>“ gespeichert:  „<code>hello.Greet</code>“ 
* Die Konstruktorfunktion hat nur noch zwei Parameter: „<code>p_window</code>“ und „<code>p_init</code>“. Der erste Parameter enthält wie in Version 3 das globale Window-Objekt, der zweite ein JSON-Objekt, in dem alle Initialisierungsinformationen enthalten sind.
* Im Rumpf der Konstruktorfunktion werden alle notwendigen Informationen aus  <code>p_init</code> extrahiert: Der Name der Formular-Section, der Name des Text-Eingabefelds, der Text der Hello-Nachricht etc.
 
Legen Sie gleich noch die Datei „<code>js/app4/hello.js</code>“ für die Web-App an:
<source lang="javascript">
<source lang="javascript">
/** @namespace hello */
import { sayHello, sayHelloOnEnter } from './greet.js'
var hello =
(function(p_hello)
function init()
{ "use strict";
{ document.getElementById('button_submit')
          .addEventListener('click', sayHello);
  window.addEventListener('keydown', sayHelloOnEnter);


   /**
   document.getElementById('body_css').rel = 'stylesheet';
  * Initializes the HTML sections of the app that are
  * stated in <code>p_init</code>.
  *
  * @param p_window  The browser window that contains the HTML document to be initialized.
  * @param p_init The initialization info for the form section and welcome section.
  */
  p_hello.init =
  function(p_window, p_init)
  {
    var l_document = p_window.document,
        l_elements = p_init.HTMLElements,
        l_text    = p_init.text,
        l_greet    = new p_hello.Greet(l_document, p_init);


    l_document.getElementById(l_elements.headingForm)  .innerHTML = l_text.title;
   document.getElementById('section_form').classList.remove('hidden');
    l_document.getElementById(l_elements.inputNameLabel).innerHTML = l_text.query;
}
    l_document.getElementById(l_elements.buttonReset)   .innerHTML = l_text.reset;
    l_document.getElementById(l_elements.buttonSubmit)  .innerHTML = l_text.submit;
    l_document.getElementById(l_elements.buttonSubmit)
              .addEventListener('click', l_greet.sayHello);
    p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
  };
 
  return p_hello;
}(hello ? hello : {}));
</source>
</source>


Sie ist wieder aufgebaut, wie zuvor beschrieben: Sie definiert, falls notwendig, das globale Objekt „<code>hello</code>“
Darüber hinaus kann man die Autofokus-Funktionalität verbessern. Das Autofokus-Attribut reagiert laut HTML-Standard nur in manchen Situation.
und fügt anschließend eine Funktion „<code>init</code>“ in dieses Objekt ein. Die Methode „<code>hello.init</code>“
Wenn man die Situationen  selbst definiert werden, in den Autofokus aktiv wird, muss man den Fokus per JavaSript setzen, sobald eine dieser Situationen eintritt.
hat dieselben Parameter, wie die zuvor definierte Konstruktorfunktion und erwartet denselben Input.  
Ihre Aufgabe ist es, die derzeit noch leeren Text-Elemente des HTML-Templates mit Inhalt zu füllen
soweit der Inhalt schon bekannt ist (der Name des Benutzers ist noch unbekannt). Außerdem erstellt Sie
ein passendes Greet-Objekt mit Hilfe der Konstruktorfunktion „<code>hello.Greet</code>“ und weist dem
Submit-Button die darin enthaltene Methode „<code>sayHello</code>“ zu. Außerdem definiert sie
eine Eventlistener, der im Falle eines Key-Down-Ereignisses die Methode „<code>sayHelloOnEnter</code>“
ausführt.
 
===Die JSON-Datei===
Zur Initialisierung der Web-Anwendung werden JSON-Dateien benötigt.
 
Eine JSON-Datei enthält genau ein JavaScript-Objekt in textueller Darstellung.
Allerdings werden nur folgende Datentypen unterstützt<ref>[http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf ECMA International, The JSON Data Interchange Format, 2013]</ref>:
* Nullwert: <code>null</code>
* Boolesche Werte: <code>true</code>, <code>false</code>
* Zahlen
* Strings
* Arrays
* Objekte
 
Insbesondere Funktionen können in einer JSON-Datei nicht enthalten sein. Außerdem müssen
Strings immer in doppelte Anführungszeichen eingeschlossen sein, einfache Anführungszeichen sind nicht erlaubt.
Attributnamen werden ebenfalls durch Strings dargestellt. Auch diese müssen gemäß ECMA-Standard in doppelte
Anführungszeichen eingeschlossen werden. Außerdem können in JSON-Dateien (leider) keine Kommentare
enthalten sein.
 
Folgendes Objekt ist zwar ein korrektes Javascript-Objekt, aber kein gültiges JSON-Objekt:
 
<source lang="javascript">
<source lang="javascript">
{ a: 55, 'b': 'Wolfgang' }
function autofocus()
</source>
{ document.getElementById('input_name').focus(); 
}


In JSON muss dieses Objekt folgendermaßen geschrieben werden:
function reset()
 
{ document.getElementById('input_name').value = '';
<source lang="javascript">
  autofocus(); 
{ "a": 55, "b": "Wolfgang" }
}
</source>


Diese Objekt ist auch ein korrektes JavaScript-Objekt. Da die Syntaxregeln von JSON strenger sind
function init()
als von JavaScript, gilt folgende Regel: Jedes JSON-Objekt ist auch ein JavaScript-Objekt, aber nicht
{ // Say hello, if the submit button is clicked or the buttoen 'Enter' is pressed.
jedes JavaScript-Objekt ist auch ein JSON-Objekt.
  document.getElementById('button_submit').addEventListener('click', sayHello);
  window.addEventListener('keydown', sayHelloOnEnter);


Ein Problem bereitet die Verwendung von JSON. Da ein JSON-Objekt beliebig aufgebaut sein kann,
  // Set the focus it the window is reentered.
kann eine IDE nicht überprüfen, ob ein bestimmtes Attribut oder Element in einem JSON-Objekt
  window.addEventListener('visibilitychange', autofocus);
mit Sicherheit enthalten ist oder nicht. Das hat {{zB}} zur Folge, das WebStorm bei einem Zugriff
 
auf eine Attribut in einem JSON-Objekt den Attributnamen unterringelt und mit der Warnung
  // If the reset button is clicked, not only the textares are reset but also the focus is replaced.
„<code>Unresolved variable ...</code>“ versieht.
  document.getElementById('button_reset').addEventListener('click', reset);


Ein Lösung für dieses Problem ist die Verwendung von [http://json-schema.org/ JSON Schema].
  // Activate the body css file that had been preloaded asynchronously.
Aber darauf wird zunächst verzichtet.
  document.getElementById('body_css').rel = 'stylesheet';


Legen Sie für die zuvor definierte HTML-Datei „<code>index4.html</code>“ folgende JSON-Initialisierungsdatei
  // Now that all files have been loaded, the app can be started.
unter dem Namen „<code>web/init/hello_de.json</code>“ an:
   // So the form is made visible!
 
   document.getElementById('section_form').classList.remove('hidden');
<source lang="javascript">
   autofocus();
{
   "HTMLElements":
  {
    "sectionForm":    "section1_form",
    "headingForm":    "heading1_form",
    "inputNameLabel": "label1_name",
    "inputName":      "input1_name",
    "buttonReset":    "button1_reset",
    "buttonSubmit":  "button1_submit",
    "sectionHello":  "section1_hello",
    "hello":          "heading1_hello",
    "welcome":        "paragraph1_hello"
  },
 
  "text":
  {
    "title":  "Hallo, Fremder!",
    "query":   "Wie heißen Sie?",
    "reset":  "Reset",
    "submit":  "Begrüßung",
    "hello":  "Hallo, $1!",
    "welcome": "Willkommen bei Multimedia-Programmierung!"
  },
 
  "css":
  {
    "hidden": "hidden"
   }
}
}
</source>
</source>


Diese Datei besteht aus einem anonymen Objekt. Es enthält seinerseits drei Objekte  „<code>HTMLElements</code>“,
==== Musterlösung v04_2 ====
„<code>text</code>“ und „<code>css</code>“, die für die Initialisierung der Web-Anwendung gedacht sind.
Verison v04_2 [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_2/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html Google PageSpeed])
Das Objekt „<code>HTMLElements</code>“ enthält sämtliche ID-Attribut-Bezeichner der Web-Anwendung.
Wenn das JSON-Objekt im beispielsweise im Parameter „<code>p_init</code>“ der Web-Anwendung übergeben wird,
kann diese den Namen des Formularabschnitts mittels „<code>p_init.HTMLElements.sectionForm</code>“ ermitteln.


Im Objekt  „<code>text</code>“ sind alle Texte enthalten, die zu gegebener Zeit in das HTML-Dokument an die geeignete
== Weitere Modularisierung ==
Stelle geschrieben werden sollen. Der Text  „<code>text</code>“ enthält  „<code>$1</code>“ als Platzhalter.
Dieser Platzhalter soll von der Anwendung durch den Namen des Benutzers ersetzt werden, nachdem er ihn eingegeben hat.


In Objekt „<code>css</code>“ sind alle Namen von CSS-Klassen enthalten, die von der Web-Anwendung zur Laufzeit
Teilen Sie die Datei <code>greet.js</code> in zwei Dateien <code>greet.js</code> und <code>greet_on_enter.js</code> mit jeweils eines Funktion auf, um dem  
zu HTML-Elementen hinzugefügt oder aus HTML-Elementen entfernt werden. Der Bezeichner, der der Anwendung bekannt ist
[[Programmierprinzipien#Single_Responsibility_Principle.5B9.5D|Single Responsibility Principle]] zu genügen.
(„<code>hidden</code>“), stimmt in diesem Fall mit dem Namen der zugehörigen CSS-Klasse überein. Das ist nicht verboten.
Versuchen Sie weitere Modularisierungsmöglichkeiten zu finden, um dem Single Responsibility Principle noch besser zu genügen.


Erstellen Sie analog die Datei „<code>web/init/hello_en.json</code>. Diese Datei soll für die zweite Web-Anwendung
Musterlösung:<br/>
definiert werden, die allerdings englischsprachige Texte enthält (Sie erinnern sich? In <code>index4.html</code>  
v03: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web/v04_3/index.html <code>index.html</code>], Git:
laufen zwei unabhängige Web-Anwendungen parallel).
[{{Git-Server}}/kowa/wk_hello_world_04/-/tree/master/web/v04_5?ref_type=heads v04_3]<br />
v04: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web/v04_4/index.html <code>index.html</code>], Git:
[{{Git-Server}}/kowa/wk_hello_world_04/-/tree/master/web/v04_5?ref_type=heads v04_4]


===Das Laden der JSON-Dateien===
== Konfigurierbarkeit ==


Für das [[asynchron]]e Laden von beliebigen Dateien von einem Web-Server gibt es in JavaScript den Befehl „<code>XMLHttpRequest</code>“.
Entfernen Sie programmspezifische Konstanten aus den JavaScript-Dateien und definieren Sie diese in einer Datei <code>config.js</code> oder (sehr viel besser) <code>config.json</code>, um dem Prinzip der [[Programmierprinzipien#Konfigurierbarkeit.2C_Customizability|Konfigurierbarkeit]] zu genügen.  
Wie man am Namen sieht, war er ursprünglich für das Laden XML-Dateien vorgesehen. Heute kann dieser Befehl allerdings für
jede Art von Datei verwendet werden. Sie könnten jetzt natürlich die Dokumentation (https://wiki.selfhtml.org/wiki/JavaScript/XMLHttpRequest)
lesen und den Befehl dann verwenden. Einfacher ist es jedoch, wenn Sie von mir erstellte die Datei  
<code>[https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/js/lib/wk/wk_ajax.js wk_ajax.js] </code>
aus dem Glossar-Repository herunterladen und in Ihrem Projekt unter dem Namen  „<code>web/js/lib/wk/wk_ajax.js</code>“ speichern.


Dieses Module stellt unter der globalen Variablen „<code>wk</code>“ – diese globale Variable ist für meine Module reserviert. :-) –
Musterlösung:<br/>
im Modul  „<code>wk.ajax</code>“ mehrere Methoden zum Laden von Dateien bereit. Zum Laden von JSON-Dateien
v03: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web/v04_5/index.html <code>index.html</code>], Git: [{{Git-Server}}/kowa/wk_hello_world_04/-/tree/master/web/v04_5?ref_type=heads v04_5]
sollten Sie „<code>wk.ajax.getJSON</code>“ verwenden (obwohl es mit „<code>wk.ajax.getFile</code>“ auch ginge; Sie müssten
dann allerdings den Inhalt der Datei, der als String geliefert wird, noch in ein JavaScript-Objekt umwandeln).


Diese Methode erwartet drei Argumente, einen URI, der den Speicherort der JSON-Datei beschreibt und zwei Callback-Funktionen.
Ich verwende hier die Funktionen <code>getJson</code> und <code>controlerKey</code> (bzw. die allgemeinere Variante <code>ControllerKey</code>, eine ECMAScript-Klasse), die erst später eingeführt und erklärt werden.
Die erste Funktion wird aufgerufen, sobald die JSON-Datei erfolgreich geladen wurde. Ihr wird das geladen JSON-Objekt als Argument
übergeben. Die zweite Callback-Funktion wird aufgerufen, falls beim Laden etwas schief gehen sollte. Die Angabe dieser Funktion ist
optional. In der Musterlösung wurde Sie definiert, hier wird darauf verzichtet.


Die Initialisierungsfunktion (Datei „<code>main4.js</code>“) sieht nun folgendermaßen aus:
Die Musterlösung müsste noch ausgebaut werden:
* Für die Internationalisierung müsste eine interaktive Auswahlmöglichkeit geschaffen werden. Zurzeit kann der Sprachwechsel nur erfolgen, indem in <code>main.js</code> der Array-Zugriff <code>["de"]</code> durch <code>["en"]</code> ersetzt wird.
* Für die Internationalisierung müssten eigene Module zur Verfügung gestellt werden.


<source lang="javascript">
==Fortsetzung des Tutoriums==
// Initialize the app.
 
(function(p_window, p_wk, p_hello)
Wenn man die Anwendung noch einmal mit Google testet, erhält man eine neue Empfehlung
{ "use strict";


  var l_json_files = ['init/hello_de.json', 'init/hello_en.json'];
[https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_3%2Findex.html], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_4%2Findex.html], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_5%2Findex.html]:
<blockquote cite="https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v01%2Fv01_1%2Findex.html">
* [https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains?hl=de Verkettung kritischer Anfragen vermeiden] – 1/2/4/5 Ketten gefunden
* [https://developer.chrome.com/docs/lighthouse/performance/resource-summary?hl=de Halten Sie die Anfrageanzahl niedrig und die Übertragungsgröße gering] – 5 Anfragen • 5 KiB.
</blockquote>


  for (var i in l_json_files)
''Anmerkung: Die aktuelle Version von PageSpeed Insights zeigt die zweite Empfehlung nicht mehr an, vermutlich weil das Hello-World-Beispiel so klein ist, dass die vorgeschlagene Verbesserung keine Vorteile mehr bringt. Wir beachten die ehemalige Empfehlung trotzdem.''
  p_wk.ajax.
    getJSON
    ( l_json_files[i],                     // Load the JSON file.
      function(p_init)                    // If ready, perform the callback function.
      { p_hello.init(p_window, p_init); } // Initialize the corresponding hello section.
    );
}(window, wk, hello));
</source>


Die Initialisierungsfunktion liest die beiden zuvor erstellten JSON-Dateien ein
Das heißt, wir sollten die verschiedenen Dateien  
und reicht jeweils das daraus erzeugte JSON-Objekt  „<code>main4.js</code>“ an die Initialisierungsfunktion der Web-Anwendung weiter.
<code>main.js</code>, <code>greet.js</code> etc. wieder zu einer Datei zusammenfügen.
Beachten Sie, dass die Reihen folge, in der die beiden JSON-Dateien geladen wird, nicht feststeht. Sie werden parallel geladen. Sobald eine
Und wir sollten alle Dateien komprimieren: Überflüssige Kommentare, Leerzeichen und Zeilenumbrüche
Datei vollständig geladen wurde, wird die zugehörige Callback-Funktion
löschen, lange Variablen-, Konstanten- und Funktionsnamen verkürzen etc.


<source lang="javascript">
Dies machen wir im [[HTML5-Tutorium: JavaScript: Hello World 05|5. Teil des Tutoriums]].
function(p_init) { p_hello.init(p_window, p_init); }
Wenn man die Vorschläge von Google beachtet,
</source>
beschleunigt dies die Übertragung der Dateien vom Server zum Client, macht aber  den Code unlesbar.
Die Zusammenführung zweier Dateien widerspricht dem Prinzip der Modularisierung. Das Entfernen von Kommentaren,
Leerzeichen und Leerzeilen ist aus Entwickler-Sicht vollkommen kontraproduktiv.


ausgeführt. Dieser wird im Parameter „<code>p_init</code>“ das zugehörige JSON-Objekt überreicht.
Daher hat es sich eingebürgert, modularen Code mit Kommentaren und Leerzeichen zu schreiben. Dieser wird dann, bevor er an einen Browser übergeben wird automatisch zusammengefasst und komprimiert (minifiziert).  
Sie reicht es (zusammen mit die Window-Objekt) an die Methode „<code>p_hello.init</code>“ weiter.


==App5==
Sie sollten nun [[HTML5-Tutorium: JavaScript: Hello World 05|Teil 5 des Tutoriums]] bearbeiten.
{{TBD}}
Dort erfüllen wir die Google-Vorgaben mittels <code>vite</code>, einem mächtiges Werkzeug zur Umwandlung von Web-Dateien. Leider ist die Konfiguration dieses Werkzeugs sehr komplex.


==Quellen==
==Quellen==
<references/>
<references/>
<ol>
<ol>
<li value="3"> {{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
<li value="1"> {{Quelle|Kowarschick, W.: Web-Programmierung}}</li>
</ol>
</ol>
<noinclude>[[Kategorie: HTML5-Tutorium: JavaScript: Hello World]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]</noinclude>
<noinclude>[[Kategorie: HTML5-Tutorium: JavaScript: Hello World]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]</noinclude>

Aktuelle Version vom 8. April 2025, 06:36 Uhr

Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:

Korrektheit: 3
(zu größeren Teilen überprüft)
Umfang: 4
(unwichtige Fakten fehlen)
Quellenangaben: 3
(wichtige Quellen vorhanden)
Quellenarten: 5
(ausgezeichnet)
Konformität: 3
(gut)

Vorlesung WebProg

Inhalt | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5 | Teil 6 | Vue 1 | Vue 2 | Vue 3 | Vue 4 | Vue 5 | Vue 6

Musterlösung
v01_1: index.html (HTML validate, CSS validate, Google PageSpeed)
v01_2: index.html (HTML validate, CSS validate, Google PageSpeed)
v01_3: index.html (HTML validate, CSS validate, Google PageSpeed)
v01_4: index.html (HTML validate, CSS validate, Google PageSpeed)
v01_5: index.html (HTML validate, CSS validate, Google PageSpeed)

Git-Repository

Anwendungsfälle (Use Cases)

Gegenüber dem dritten Teil des Tutoriums ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.

In diesem Teil des Tutoriums geht es darum, die Anwendung besser zu strukturieren, d. h. zu modularisieren (vgl. Programmierprinzipien).

Modularisierung

Eine Anwendung, wie z. B. ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgaben. Bei einem Web-Frontend müssen beispielsweise verschieden Komponenten (Buttons, Scrolbars, Textfelder, Datumsfelder etc.) erstellt werden. Bei einem HTML5-Spiel müssen die Spielfiguren, die Spielszenen, die Spiellogik, die Benutzereingaben, die Darstellung das Spiels im Browser etc. erstellt und verwaltet werden, wobei das Erstellen und Verwalten teilweise während der Spielentwicklung und teilweise während der Spielausführung (Runtime) erfolgt.

An der Entwicklung eines Web-Systems oder eines Spiels sind üblicherweise mehrere oder gar viele Entwickler gleichzeitig beteiligt. Oft müssen einzelnen Komponenten an neue Endgeräte oder Betriebssysteme angepasst werden. Beispielsweise muss eine Tastatursteuerung durch eine Touch- oder Gestensteuerung ersetzt werden, wenn eine Anwendung so erweitert werden soll, dass es auch auf einem Smartphone oder Tablet läuft.

Das alles ist nur machbar, wenn man die Anwendung modularisiert, d. h. in kleine, möglichst unabhängige Komponenten unterteilt. Ein großer Feind der Modularisierung sind globale Variablen.

Je mehr Dateien von unterschiedlichen Autoren erstellt werden, desto größer ist die Gefahr, dass es beim Zugriff auf globale Variablen zu Namenskollisionen kommt. Außerdem weiß niemand, welche globale Größe noch benötigt wird und welche nicht. Sicherheitshalber löscht man daher keine dieser Variablen, auch wenn sie vollkommen veraltet sind.

Daher gilt der Grundsatz: Verwende so wenig globale Größen wie möglich.

Erstellen eines neuen Projekts

Erstellen Sie ein neues Projekt hello_world_04 als Fork von hello_world_03 (siehe Hello-World-02-Tutorium).

Die App

Erstellen einer Ordnerstruktur

Grundsätzlich gilt auch bei der Programmierung: Ordnung ist das halbe Leben.

Web-Anwendungen werden sehr schnell sehr groß. Also sollte man sich eine geeignete Ordnerstruktur überlegen. Üblicherweise legt man CSS-Dateien in einen Ordner namens „css“ und JavaScript-Dateien in einen Ordner namens „js“ oder „lib“. Sollten es viele CSS- und/oder JavaScript-Dateien werden, legt man im entsprechenden Ordner geeignete Unterordner an.

Legen Sie im web-Verzeichnis Ihres Projekts folgende Ordner an:

  • web/css: Hier werden die eigentlichen CSS-Dateien der Web-Anwendung gespeichert, die von der App dynamisch nachgeladen werden sollen. In diesem Ordner könnten zur weiteren Strukturierung Unterordner angelegt werden. Allerdings ist das hier nicht notwendig. Es wird nur die Dateien „head.css“ und „body.css“ geben. Verschieben Sie die Datei web/main.css in diesen Ordner.
  • web/js: Hierher kommen die JavaScript-Dateien, die von der Anwendung benötigt werden. Verschieben Sie die Datei web/main.js in diesen Ordner.

Nehmen Sie in der Datei web/index.html folgende beiden Ersetzungen vor:

  • main.csscss/main.css
  • main.jsjs/main.js

Wenn Sie jetzt die Datei index.html mit dem Live Server ausführen, sollte Ihre Anwendung wieder funktionieren.

Sollte dies der Fall sein, so sollten Sie die Änderungen committen und evtl. auch gleich per git push auf den Git-Server hochladen. Es schadet übrigens nie, vor einen Commit oder gar einen Push noch einmal zu überprüfen, ob die Anwendung noch läuft.

Modularisierung der Datei main.js

Dateien wie die ECMAScript-Datei main.js aus dem dritten Teil des Tutoriums werden sehr schnell groß, wenn man alle Funktionalität in diese eine Datei einfügt. Dies reduziert die Lesbarkeit und die Wartbbarkeit des Codes. Bertrand Meyer, einer der wesentlichen Begründer der modularen und objektorientierten Programmierung, fordert, dass ein Programmcode in sogenannte Module zerlegt werden sollen. Ein Modul stellt eine gewisse Funktionalität zur Verfügung. Die zugehörigen Funktionen werden exportiert und können von anderen Modulen importiert werden, sofern sie diese Funktionalität zur Erfüllung ihrer Aufgabe benötigen. Meyer hält es für sinnvoll, dass ein Modul jeweils nur eine oder sehr wenige Aufgaben erfüllt. Damit können Module von vielen anderen Modulen wiederverwendet werden.

Die Datei main.js wird im Sinne von Meyer in zwei Teile aufgespalten: greet.js und main.js. Die erste Datei enthält die eigentliche Begrüßungsfunktionalität, die zweite initialisiert die App, d. h. aktiviert die Begrüßungsfunktiontionalität.

Erstellen Sie die JavaScript-Datei web/js/greet.js und verschieben Sie die beiden Funktionen sayHello und sayHelloOnEnter (jeweils samt Funktionsrumpf :-) ) in diese Datei.

Schreiben Sie vor jede der beiden Funktionsdefinitionen das Schlüsselwort export. Mit dieser ES-6-Anweisung legen Sie fest, dass die beiden Funktionen von anderen ES-6-Dateien mittels eines Import-Befehls importiert werden können. (Funktionen ohne Export-Anweisung könnten von anderen ES-6-Dateien nicht importiert werden.)

In der Datei web/js/main.js werden jetzt diese beiden Funktionen importiert. Fügen Sie ganz am Anfang der Datei (d. h. vor der verbliebenen Init-Funktion) die folgende Import-Anweisung ein:

import * as greet from './greet.js';

Damit wird erreicht, dass alle (*) Elemente, die in der Datei greet.js exportiert werden, unter ihrem jeweiligen Namen im Objekt greet gespeichert werden. Das heißt, innerhalb der Datei main.js gibt es nun die beiden Funktionen greet.sayHello und greet.sayHelloOnEnter.

Fügen Sie jetzt noch das Attribut type="module" in das Script-Element in der Datei index.html ein. Damit teilen Sie dem Browser mit, dass Sie die ECMAScript-6-Befehle import und export verwenden möchten.

Anderenfalls hätten sie Zugriff auf alle Objekte, Konstanten, Variablen und Funktionen einer geladenen Datei, so wie die bis ECMAScript 5 üblich war. Das heißt, das Laden einer Datei konnte zu massiven Problemen führen, wenn in zwei verschiedenen Dateien unterschiedliche Objekte etc. zufälligerweise gleich benannt wurden. Um diese Probleme zu umgehen gab es zahlreiche ziemlich aufwändige Hacks. Mit dem Modul-Konzept von ECMAScript gehören diese Probleme der Vergangenheit an.

Damit sollte Ihre Anwendung wieder funktionieren.

Vergessen Sie nicht, Ihr aktuelles Projekt zu committen und auf dem Git-Server zu speichern, sobald alles funktioniert.´

Musterlösung v04_1

Verison v04_1 index.html (HTML validate, CSS validate, Google PageSpeed)

Laden der CSS- und der JavaScript-Dateien

Führen Sie Google PageSpeed für die Musterlösung der vorherigen Teilaufgabe aus und sehen Sie sich an, welche Verbesserungsvorschläge Google macht.

Folgendes findet Google PageSpeed verbesserungswürdig:

  1. Ressourcen entfernen, die das Rendering blockieren.
    Mögliche Einsparung von 300 ms.
  2. Verkettung kritischer Anfragen vermeiden
    2 Ketten gefunden
  3. [Largest Contentful Paint-Element 1.350 ms https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint?hl=de]
  4. Dokument enthält keine Meta-Beschreibung

Anmerkung: Google Pagespped ist auch über die Chroeme-Entwickler-Konsole zugänglich. Damit können auch lokale Server (<ocde>localhost o. Ä.) getestet werden. Echte Prformanzprobleme erkennt man aber eher, wenn man seinen Live-Server testet.

Eine Meta-Beschreibung hinzuzufügen, ist kein Problem. Fügen Sie folgende Zeile in den Head-Bereich der HTML-Datei ein.

<meta name="description" 
      content="A Hello World application that demonstrates best practices for HTML, CSS, and JavaScript files."
>

Das effiziente Laden der JavaScript- und CSS-Dateien ist viel komplexer, aber auch viel wichtiger, da die CSS- und JavaScript-Dateien werden im Laufe der Zeit immer zahlreicher und/oder größer werden. Diese Dateien zu laden, dauert seine Zeit. Auch das Laden von vielen kleinen Dateien dauert deutlich länger, als das Laden einer einzigen großen Datei. Das aber widerspricht der Modularisierung, die wir vorher vorgenommen haben. Modularisierung und Performance widersprechen sich zunächst einmal.

In Web-Anwendung aus dem dritten Teil des Tutoriums stehen die Verweise auf die CSS- und JavaScript-Dateien im head-Bereich des Dokuments. Die CSS-Datei wird vollständig geladen, bevor der body-Bereich eingelesen wird. Die JavaScript-Datei main.js wird dagegen parallel zum Body-Bereich geladen. Dies wird durch die Angabe von async im Script-Tag wird erreicht.

Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments darstellen kann, solange er die CSS-Datei (und evtl. weitere Dateien wie Fonts, Hintergrundbilder etc.) lädt. Wenn dies zu lange dauert, verliert der Besucher die Geduld und verlässt die Seite vorzeitig.

Besser wäre es daher andersherum vorzugehen: Es wird zuerst der Body-Bereich geladen und dann die JavaScript- und die CSS-Dateien. Im Falle von JavaScript ist das durchaus sinnvoll (und durch die Angabe von async auch machbar), aber im Falle von CSS hat das den Effekt, dass der Browser noch keine Layout-Vorgaben erhalten hat, wenn er mit dem Rendern der Seite beginnt. Also verwendet er die browserspezifischen Defaultwerte. Das heißt, die Seite sieht zunächst ganz anders aus, als vom Designer geplant. Wenn dann die CSS-Dateien geladen wurden, wird die Seite erneut gerendert und verändert ihr Aussehen.

Auch das ist verwirrend und wirkt unprofessionell: https://de.wikipedia.org/wiki/Ampel

Was also machen?

CSS-Dateien

Damit das Problem mit dem falschen Layout nicht auftritt, empfiehlt Google (PageSpeed Insights), ganz wenige, wichtige CSS-Befehle direkt – d. h. als CSS-Befehle innerhalb eines style-Elements – in dem HTML-Head-Bereich einzufügen. Alle anderen CSS-Anweisungen sollen erst am Ende der HTML-Datei dynamisch mittels JavaScript (oder mittels des PageSpeed-Optimization-Modul von Google) gelesen werden.

PageSpeed Insights (für Musterlösung v04_1):

Ressourcen blockieren den First Paint deiner Seite. Versuche, wichtiges JS und wichtige CSS inline anzugeben und alle nicht kritischen JS und Stile zurückzustellen.

Gehen Sie so vor, wie es Google empfohlen hat. Genauer gesagt, gehen Sie so vor, wie es unter Multimedia-Programmierung: Best Practices beschrieben ist. In diesem und den folgenden Tutorien werden die dort beschriebenen Prinzipien berücksichtigt.

Legen Sie die Datei src/css/head.css an und fügen Sie folgenden (sehr kurzen!) Code ein:

body
{ background-color: #C5EFFC; }

.hidden
{ display: none; }

In der Datei src/index.html müssen Sie das Link-Element in Header folgendermaßen

<link rel="stylesheet" href="css/head.css"/>

anpassen. Das heißt, Sie laden im HTML-Head-Bereich nur noch die kleine CSS-Datei. Damit funktioniert Ihre Web-Anwendung derzeit nur noch rudimentär: Es gibt kaum noch CSS-Anweisungen, die berücksichtigt werden. Noch besser ist es gemäß den Google-Empfehlungen, wenn man die CSS-Regeln direkt in die HTML-Datei einfügt. Ersetzen Sie also das Link-Element durch folgendes Style-Element:

<style>
body
{ background-color: #C5EFFC; }

.hidden
{ display: none; }
</style>

In der Datei src/css/main.css können Sie nun das .hidden-Element und das Farbattribut background-color: #C5EFFC; entfernen.

Es ist sinnvoll, die Datei main.css in body.css umzubenennen: head.css wird im HTML-Head-Bereich vor dem Body-Bereich geladen, body.css soll parallel zum Body-Bereich geladen werden und dann zur Verfügung stehen, wenn der Body vollständig geladen wurde.

Leider ist CSS-Link-Element nicht möglich, async anzugeben, wie wir dies beim Skript-Element gemacht haben, um JavaScript-Code parallel zum Body-Element zu laden. Hier hilft derzeit nur ein kleiner Hack:

<!-- Load body.css asynchronously, i.e. parallel to the body content. -->
<link rel="preload" href="css/body.css" as="style" onload="this.rel='stylesheet'">

Die Datei body.css wird für den Medientyp style parallel zum HTML-Dokument geladen, aber nicht aktiviert. Um aus dem Inhalt der vorab geladenen Datei für den Browser ein Stylesheet zu machen, muss man den Wert des Attributs rel in 'stylesheet' abändern. Das sollte erst erfolgen, wenn das Dokument vollständig geladen wurde, wenn also das das Fensterobjekt des Browsers, in dem das HTML-Dokument gerendert werden soll, das Ereignis load feuert. Für dieses Ereignis kann man entweder im link-Tag den Händler onload einfügen oder man schreibt den Aktivierungsbefehl in die Init-Funktion in der Datei main.js.

Für die zweite Variante sollte man dem link-Element eine eindeutigen Identifikator zuweisen:

<link id="body_css" rel="preload" href="css/body.css" as="style">

Danach kann man den Aktivierungsbefehl in die Funktion init in der Datei main.js einfügen.

document.getElementById('body_css').rel = 'stylesheet';

Zusammenfassung:

  • In head.css stehen möglichst wenig CSS-Anweisungen. Sie sollten ausreichen, um die aktuelle Seite “above the fold” („über dem Zeitungsknick“) fehlerfrei zu rendern. Das heißt, es sollte alles korrekt gerendert werden, was nach dem Laden im größtmöglichen Browserfenster sichtbar ist.
  • Der Inhalt der head.css sollte direkt in den Head-Bereich der HTML-Seite eingefügt werden.
  • Die restlichen CSS-Anweisungen werden in die Datei body.css eingetragen.
  • Diese Datei wird mittels eines kleinen Hacks asynchron, d. h. parallel zum Body-Element geladen.

index.html

Da die Datei head.css nur die Hintergrundfarbe sowie die .hidden-Regel enthält, sollten alle HTML-Elemente, die gerendert werden sollen, zunächst unsichtbar gemacht werden. Bei einer Web-Anwendung, wie einem Spiel, die zum Laden länger braucht, könnte man eine Sanduhr oder einen Ladebalken einfügen, um anzuzeigen, dass man noch etwas warten muss. Üblicherweise, sollte man dafür sorgen, dass das Laden einer Seite so schnell geht, dass der Benutzer gar nicht erst ungeduldig wird.

Fügen Sie die CSS-Klasse hidden in beide HTML-Sektions ein:

<section id="section_form" class="hidden">
...
</section>
<section id="section_hello" class="hidden">
...
</section>

Wenn man jetzt das Dokument im Browser öffnet, sieht man nichts, außer einem hellblauen Hintergrund.

Abhilfe schafft hier ein kleiner JavaScript-Befehl, der hidden von der Eingabe-Section hidden-form entfernt, sobald das Dokument vollständig geladen wurde (samt allen CSS- und JavaScript-Dateien):

document.getElementById('section_form').classList.remove('hidden');

An welche Stelle dieser Befehl eingefügt wird, erfahren Sie im nachfolgenden Abschnitt.

JavaScript-Dateien

Für JavaScript-Dateien empfiehlt Google ebenfalls, diese nicht schon zu Beginn zu laden, sondern das Laden „aufzuschieben“. Das machen wir bereits mit Hilfe des Async-Attributs.

Fügen Sie nun den zuvor erwähnten Befehl

document.getElementById('section_form').classList.remove('hidden');

als letzen Befehl in den Rumpf der Funktion init in der Datei web/js/main.js ein.

Wenn Sie jetzt die Web-App starten, sollte sie wieder funktionieren.

Was haben Sie erreicht? Solange die Anwendung geladen wird, sehen Sie nur einen blauen Hintergrund ohne Inhalt, bzw. i. Allg. einen so genannten Splash Screen. Wenn dann alles, d. h. alle CSS- und JavaScript-Dateien geladen wurden, wird die eigentliche Anwendung sichtbar.

Vergessen Sie nicht: Commit!

Verbesserung der Init-Funktion

Bislang sieht die Init-Funktion folgendermaßen aus.

function init()
{ document.getElementById('button_submit')
          .addEventListener('click', greet.sayHello);
  window.addEventListener('keydown', greet.sayHelloOnEnter);

  document.getElementById('body_css').rel = 'stylesheet';

  document.getElementById('section_form').classList.remove('hidden');
}

Man kann auch einen anderen Import-Befehl verwenden, um auf das Objekt greet verzichten zu können.

import { sayHello, sayHelloOnEnter } from './greet.js'
 
function init()
{ document.getElementById('button_submit')
          .addEventListener('click', sayHello);
  window.addEventListener('keydown', sayHelloOnEnter);

  document.getElementById('body_css').rel = 'stylesheet';

  document.getElementById('section_form').classList.remove('hidden');
}

Darüber hinaus kann man die Autofokus-Funktionalität verbessern. Das Autofokus-Attribut reagiert laut HTML-Standard nur in manchen Situation. Wenn man die Situationen selbst definiert werden, in den Autofokus aktiv wird, muss man den Fokus per JavaSript setzen, sobald eine dieser Situationen eintritt.

function autofocus()
{ document.getElementById('input_name').focus();   
}

function reset()
{ document.getElementById('input_name').value = '';
  autofocus();  
}

function init()
{ // Say hello, if the submit button is clicked or the buttoen 'Enter' is pressed.
  document.getElementById('button_submit').addEventListener('click', sayHello);
  window.addEventListener('keydown', sayHelloOnEnter);

  // Set the focus it the window is reentered.
  window.addEventListener('visibilitychange', autofocus);
  
  // If the reset button is clicked, not only the textares are reset but also the focus is replaced.
  document.getElementById('button_reset').addEventListener('click', reset);

  // Activate the body css file that had been preloaded asynchronously.
  document.getElementById('body_css').rel = 'stylesheet';

  // Now that all files have been loaded, the app can be started.
  // So the form is made visible!
  document.getElementById('section_form').classList.remove('hidden');
  autofocus();
}

Musterlösung v04_2

Verison v04_2 index.html (HTML validate, CSS validate, Google PageSpeed)

Weitere Modularisierung

Teilen Sie die Datei greet.js in zwei Dateien greet.js und greet_on_enter.js mit jeweils eines Funktion auf, um dem Single Responsibility Principle zu genügen. Versuchen Sie weitere Modularisierungsmöglichkeiten zu finden, um dem Single Responsibility Principle noch besser zu genügen.

Musterlösung:
v03: index.html, Git: v04_3
v04: index.html, Git: v04_4

Konfigurierbarkeit

Entfernen Sie programmspezifische Konstanten aus den JavaScript-Dateien und definieren Sie diese in einer Datei config.js oder (sehr viel besser) config.json, um dem Prinzip der Konfigurierbarkeit zu genügen.

Musterlösung:
v03: index.html, Git: v04_5

Ich verwende hier die Funktionen getJson und controlerKey (bzw. die allgemeinere Variante ControllerKey, eine ECMAScript-Klasse), die erst später eingeführt und erklärt werden.

Die Musterlösung müsste noch ausgebaut werden:

  • Für die Internationalisierung müsste eine interaktive Auswahlmöglichkeit geschaffen werden. Zurzeit kann der Sprachwechsel nur erfolgen, indem in main.js der Array-Zugriff ["de"] durch ["en"] ersetzt wird.
  • Für die Internationalisierung müssten eigene Module zur Verfügung gestellt werden.

Fortsetzung des Tutoriums

Wenn man die Anwendung noch einmal mit Google testet, erhält man eine neue Empfehlung

[1], [2], [3], [4]:

Anmerkung: Die aktuelle Version von PageSpeed Insights zeigt die zweite Empfehlung nicht mehr an, vermutlich weil das Hello-World-Beispiel so klein ist, dass die vorgeschlagene Verbesserung keine Vorteile mehr bringt. Wir beachten die ehemalige Empfehlung trotzdem.

Das heißt, wir sollten die verschiedenen Dateien main.js, greet.js etc. wieder zu einer Datei zusammenfügen. Und wir sollten alle Dateien komprimieren: Überflüssige Kommentare, Leerzeichen und Zeilenumbrüche löschen, lange Variablen-, Konstanten- und Funktionsnamen verkürzen etc.

Dies machen wir im 5. Teil des Tutoriums. Wenn man die Vorschläge von Google beachtet, beschleunigt dies die Übertragung der Dateien vom Server zum Client, macht aber den Code unlesbar. Die Zusammenführung zweier Dateien widerspricht dem Prinzip der Modularisierung. Das Entfernen von Kommentaren, Leerzeichen und Leerzeilen ist aus Entwickler-Sicht vollkommen kontraproduktiv.

Daher hat es sich eingebürgert, modularen Code mit Kommentaren und Leerzeichen zu schreiben. Dieser wird dann, bevor er an einen Browser übergeben wird automatisch zusammengefasst und komprimiert (minifiziert).

Sie sollten nun Teil 5 des Tutoriums bearbeiten. Dort erfüllen wir die Google-Vorgaben mittels vite, einem mächtiges Werkzeug zur Umwandlung von Web-Dateien. Leider ist die Konfiguration dieses Werkzeugs sehr komplex.

Quellen

  1. Kowarschick (WebProg): Wolfgang Kowarschick; Vorlesung „Web-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2024; Quellengüte: 3 (Vorlesung)