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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Kowa (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
Kowa (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
 
(92 dazwischenliegende Versionen desselben Benutzers 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_HelloWorld05/web/index.html <code>index.html</code>],
'''Musterlösung'''<br/>
[([https://glossar.hs-augsburg.de/beispiel/tutorium/es6/hello_world/WK_HelloWorld05/ SVN-Repository])
[https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_06/web/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_06%2Fweb%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_06%2Fweb%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_06%2Fweb%2Findex.html Google PageSpeed])<br/>
==Anwendungsfälle (Use Cases)==
[{{Git-Server}}/kowa/wk_hello_world_06.git Git-Repository]
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_03|dritten Teil des Tutoriums]]
 
== Anwendungsfälle (Use Cases) ==
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_05|dritten, vierten und fünften 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, den CSS-Code „[[DRY]] zu machen“.
 
===Modularisierung===
 
Eine Anwendung, wie {{zB}} ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgaben.
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.
Beispielsweise muss eine Tastatursteuerung durch eine Touch- oder Gestensteuerung ersetzt werden,
wenn das Spiel auf einem Smartphone oder Tablet laufen soll.
 
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.
Je mehr Dateien von unterschiedlichen Autoren erstellt werden, desto größer ist die Gefahr, dass es zu Namenskollisionen kommt.
Daher gilt der [[Multimedia-Programmierung: Best Practices#Modularisierung|Grundsatz]]: Verwende so wenig globale Größen wie möglich.
 
==Erstellen eines neuen Projektes==
 
Erstellen Sie ein neues Projekt  „<code>HelloWorld04</code>“ und legen Sie dieses in Ihrem Repository ab.
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==
 
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  „<code>css</code>“ und JavaScript-Dateien in einen Ordner namens
„<code>js</code>“ oder  „<code>lib</code>“. Sollten es viele CSS- und/oder JavaScript-Dateien werden,
legt man im entsprechenden Ordner geeignete Unterordner an.
 
Legen Sie im Root-Verzeichnis Ihres Projektes 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 CSS-Dateien der Web-Anwendung gespeichert. In diesem Ordner können zur weiteren Strukturierung Unterordner angelegt werden.
* <code>web/js</code>: Hierher kommt die Datei „<code>main.js</code>“, die für den Start der Web-Anwendung zuständig ist. Üblicherweise liest sie eine JSON-Datei mit Initialwerten ein, die sie der Anwendung übergeben kann.
* <code>web/js/app</code>: Hier speichern Sie die von Ihnen geschriebenen JavaScript-Dateien Ihrer Web-Anwendung. In diesem Ordner werden üblicherweise Unterordner  zur weiteren Strukturierung der Anwendung angelegt.
* <code>web/js/lib</code>: Hier speichern Sie die JavaScript-Bibliotheken, die andere JavaScript-Entwickler erstellt haben und die Sie in Ihrer Anwendung einsetzen. Für jede externe Bibliothek gibt es einen Unterordner.
 
Fremde Bibliotheken sind nicht unbedingt fehlerfrei und können durchaus auch unsauberen Code enthalten.
Damit Ihnen bei einem Commit diese Fehler nicht angezeigt werden, schließen Sie den Ordner
<code>web/js/lib</code>“ von der WebStorm-Fehlerüberwachung aus:
 
* Rechtklick auf den Ordner „<code>web/js/lib</code>“ → <code>Mar Directory as</code> → <code>Excluded</code>
 
Da in diesem Teil des Tutoriums fünf Web-Anwendungen mit jeweils unterschiedlichem Modularisierungsgrad
erstellt werden, werden hier anstelle des Ordners „<code>web/js/app</code>“ fünf App-Ordner angelegt:
* <code>web/js/app1</code>: Aufteilung der App in mehrere Objekte und Dateien.
* <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/app5</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,
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.
Daher ist es möglich, im Root-Verzeichnis des Projektes weitere projektspezifische Ordner und Dateien anzulegen wie „<code>conf</code>“ und „<code>doc</code>“,
die später nicht auf den Web-Server, auf dem die Anwendung schließlich laufen soll, kopiert werden.
 
==App1==
 
Benennen Sie die Dateien „<code>index.html</code>“ und „<code>main.js</code>“ in
„<code>index1.html</code>“ und „<code>main1.js</code>“ um. (Im Laufe des Tutoriums kommen weitere Versionen dieser beiden Dateien dazu.)
 
===Laden der CSS- und der JavaScript-Dateien===
 
Eine Web-Anwendung funktioniert nur – wie Sie bereits erfahren haben –, wenn nicht nur die HTML-Datei, sondern auch die zugehörigen
CSS- und JavaScript-Dateien geladen werden. Allerdings gibt es dabei ein Problem: Die CSS- und JavaScript-Dateien werden im Laufe der
Zeit immer zahlreicher und/oder größer. Diese Dateien zu laden, dauert seine Zeit.
 
In der aktuellen Version der Web-Anwendung stehen die Verweise auf diese Dateien im <code>head</code>-Bereich des Dokuments.
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
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
vorzeitig.
 
Besser wäre es daher andersherum vorzugehen: Es wird zuerst der <code>body</code>-Bereich geladen und dann die JavaScript- und die CSS-Dateien.
Im Falle von JavaScript ist das durchaus sinnvoll, 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.
 
Was also machen?


===CSS-Dateien===
== Erstellen eines neuen Projekts ==
Für CSS-Dateien empfiehlt Google ernsthaft, die Link-Element ganz ans Ende der HTML-Datei  zu stellen, also '''nach''' dem
schließenden <code>html</code>-Tag.
Damit das Problem mit dem falschen Layout nicht auftritt, sollen wichtige CSS-Befehle direkt – {{dh}} als CSS-Befehle innerhalb eines
<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,
Erstellen Sie ein neues Projekt <code>hello_world_06</code> als Fork von <code>hello_world_05</code>.
dass diverse Browser diese Syntax verstehen, korrekt ist sie trotzdem nicht. Der zweite Vorschlag hat zur Folge, dass Struktur (HTML) und Layout (CSS)
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.
== CSS-Erweiterungen ==


In unserer Anwendung haben wir bislang alles richtig gemacht, bis auf die fehlende automatische Komprimierung.
CSS bietet heute dieselbe Funktionalität wie Transpiler ([[SASS]], [[Less]] etc.), die früher verwendet wurden, um fehlende Funktionalität in CSS zu integrieren. Ich werde in diesem Semester CSS an Stelle von SCSS einsetzen, so wie es von vite empfohlen wird:
Diese wird im [[HTML5-Tutorium: JavaScript: Hello World 05|fünften Teil des Tutoriums]] beschrieben.


===JavaScript-Dateien===
''Because Vite targets modern browsers only, it is recommended to use native CSS variables with PostCSS plugins that implement CSSWG drafts (e.g. postcss-nesting) and author plain, future-standards-compliant CSS.'' (https://vite.dev/guide/features)


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.
Eine ganz wesentliche Erweiterung ist die Möglichkeit Konstanten zu definieren.
Auch die Möglichkeit, Werte zu berechnen, gibt es in der Zwischenzeit (SCSS: <code>math</code>, CSS: <code>calc</code>). Es ist jetzt auch möglich CSS-Elemente zu verschachteln.
Das heißt, die Bedeutung von SCSS, Less etc. schwindet.


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:
== CSS-Konstanten ==


<source lang="html5">
Eine ganz wesentliche Erweiterung ist die Möglichkeit Konstanten zu definieren, da man damit CSS-Code [[DRY]] formulieren kann.  
  ...
  <script type="text/javascript" src="js/main1.js"></script>
</body>
</source>


===Globale Funktionen als Methoden===
Wenn eine Konstante definiert wurde, kann man
im übrigen CSS-Code mittels <code>var</code> darauf zugreifen (und nicht etwa mittels <code>const</code>, :-( )


Grundsätzlich sollten so wenige globale Funktionen wie möglich in einen JavaScript-Programm definiert werden,
<syntaxhighlight lang="CSS">
um Namenskonflikte mit JavaScript-Modulen und -Bibliotheken anderer Autoren zu vermeiden.
:root
In der Hello-World-Anwendung gibt es drei globale Funktionen: „<code>sayHello</code>“, „<code>sayHelloOnEnter</code>“ und „<code>init</code>“.
{ --font-family-serif: "Times New Roman", Times, serif; }
Die ersten beiden werden in [[Methode]]n umgewandelt. Das heißt, es wird ein Objekt „<code>greet</code>“ erstellt, das diese beiden Methoden
enthält. Die dritte Funktion „<code>init</code>“ wird durch eine anonyme Funktion ersetzt, die sofort ausgeführt wird.


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>“
p
ein ('''vor''' dem Skript-Element, das „<code>main1.js</code>“ lädt):
{ font-family: var(--font-family-serif); }


<source lang="html5">
label
  ...
{ font-family: var(--font-family-serif); }
  <script type = "text/javascript" src = "js/app1/greet.js"></script>
</syntaxhighlight>
  <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
Auch die Möglichkeit, Werte zu berechnen, gibt es in der Zwischenzeit (SCSS: <code>math</code>, CSS: <code>calc</code>).
<code>sayHello</code>“ und „<code>sayHelloOnEnter</code>“ enthält.
Das heißt, die Bedeutung von SCSS, Less etc. schwindet. In diesem Semester verwende ich nicht mehr SCSS, sondern nur noch CSS.


<source lang="javascript">
Fügen Sie <code>"config"</code> als Abkürzung für <code>"./src/css/_config.css"</code> in die Datei <code>vite.config.js</code> ein.
/**
* @namespace greet
* 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');
      },


    /**
<syntaxhighlight lang="javascript">
    * 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
   resolve:
    * disabled.
  { alias:
    * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
    { ...
    *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
       'config.css': fileURLToPath(new URL('./src/css/_config.css', import.meta.url)),
    */
       ...
    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>
 
In der Datei  „<code>main1.js</code>“ werden dagegen alle globalen Funktionen entfernt.
Dies gilt auch für die Definition des Eventlisteners am Ende der Datei, der dafür sorgt, dass die
Funktion „<code>init</code>“ ausgeführt wird, sobald die HTML-Seite sowie alle Unterseiten vollständig geladen wurden.
 
Die Funktion „<code>init</code>“ wird durch eine anonyme Funktion ersetzt, die '''sofort''' nach ihrer Definition auch ausgeführt wird.
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">
// Initialize the app.
(function()
{
  document.getElementById('button_submit')
          .addEventListener('click', greet.sayHello);
 
   window.addEventListener('keydown', greet.sayHelloOnEnter);
}());
</source>
 
Testen Sie Ihre Anwendung und speichern Sie sie zu guter Letzt im Repository.
 
==App2==
 
Ein wesentliches [[Programmierprinzip]] ist das Prinzip der „Wiederverwendbarkeit“.
Man sollte Code so schreiben, dass man ihn möglichst häufig wiederverwenden kann.
Jede Wiederverwendung bereits bestehenden 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
ein Objekt  „<code>greet</code>“, das nicht für mehrere Anwendungen parallel eingesetzt werden kann.
Wir müssen also bei Bedarf neue <code>greet</code>-Objekte erzeugen können.
Das geschieht in JavaScript mit Hilfe von sogenannten [[Konstruktor]]funktionen. 
 
Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihre Anwendungsdateien:
 
* <code>index1.html</code> → <code>index2.html</code>
* <code>js/main1.js</code> → <code>js/main2.js</code>
* <code>js/app1/greet.js</code> → <code>js/app2/greet.js</code>
 
In der Datei „<code>index2.html</code>“ müssen Sie dafür sorgen, dass
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>“
wird durch der folgende Konstruktorfunktion  „<code>Greet</code>“ ersetzt:
 
<source lang="javascript">
/**
* @class
* @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
 
  /**
  * 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 =  // 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>
</syntaxhighlight>
 
In JavaScript kann jede Funktion auch als Konstruktor, {{dh}}
zum Erzeugen von neuen Objekte verwendet werden, indem man das Schlüsselwort
„<code>new</code>“ voranstellt:


<source lang="javascript">
In der Datei <code>_config.css</code> werden Konstanten definiert, die von allen anderen CSS-Dateien verwendet werden können und sollen.
var l_greet = new Greet();
</source>


In einer Konstruktorfunktion wird üblicherweise kein <code>return</code>-Befehl eingefügt,
Das heißt, das DRY-Prinzip „Konstanten gehören nicht in den Code, sondern in Konfigurationsdateien“ wird ab sofort beachtet.
da sie sich wie eine [[Prozedur (Informatik)|Prozedur]] und nicht wie
eine [[Funktion (Informatik)|Funktion]] verhält. Sie erstellt  – als [[Seiteneffekt]] – ein neues Objekt und initialisiert es.
Allerdings wird dieses Objekt nirgendwo gespeichert, sondern – automatisch – als Ergebnis 
des Befehls <code>new Greet()</code> zurückgeliefert. Der Programmierer muss anschließend selbst dafür sorgen,
dass das neu erzeugte Objekt
direkt im Anschluss an dessen Erzeugung irgendwo gespeichert wird,
in einer [[Variable]]n, in einem [[Array]], in einem [[Objekt]] oder in einem [[Funktionsparameter]].
Im obigen Beispiel wird es in der Variablen „<code>l_greet</code>“ abgelegt.


Im Funktionsrumpf selbst kann über die Spezialvariable „<code>this</code>“ auf das neu erstellte Objekt
Wenn man künftig am Layout einen Zeichensatz, eine Farbe, einen Hintergrund oder Ähnliches ändern will,  
zugegriffen werden. Dem neu erstellten Objekt werden mittels
reicht es, die entsprechenden Konstanten anzupassen und schon erzeugt Vite automatisch aktualisiere
„<code>this.sayHello = ...</code>“ und  „<code>this.sayHelloOnEnter = ...</code>“
CSS-Dateien.
etc. [[Attribut (Objekt)|Attribute]] zugewiesen, die Funktionen (welche [[Methode]]n genannt werden) als Werte enthalten.
Dem neu erstellten Objekt werden dieselben Methoden zugewiesen, wie dem Objekt „<code>greet</code>“ der Anwendung „App1“.
Daher kann das mittels Konstruktorfunktion erstellte Objekt „<code>l_greet</code>“ auch genauso verwendet werden,  
wie das globale Objekt „<code>greet</code>“ der Anwendung „App1“.


Beachten Sie bitte, dass in der Definition der Konstruktorfunktion der so genannte This-Hack zum Einsatz kommt.
Durch den CSS-Import-Befehl ist es möglich, CSS-Dateien zu modularisieren.
Das Objekt <code>this</code> wird in einer lokalen Variablen <code>l_this</code> gespeichert.
Der Underscore <code>_</code> am Beginn des Dateinamens deutet an, dass diese CSS-Datei nur von anderen CSS-Dateien
Und anschließend wird diese lokale Variable anstelle von <code>this</code> benutzt.
aber nicht von HTML- oder JavaScript-Dateien importiert werden sollte.  
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
'''<code>src/css/_config.css</code>'''
Objekt zugegriffen: <code>l_this.sayHello();</code>. Und hier könnte nun nicht <code>this.sayHello();</code>
<syntaxhighlight lang="CSS">
verwendet werden. Der Grund ist, dass der Methodenrumpf erst ausgeführt wird, wen die Methode „<code>sayHelloOnEnter</code>
:root
zu irgendeinem anderen Zeitpunkt von irgendeinem anderen Objekt aufgerufen wird. Und  <code>this</code> bezeichnet
{ --background-color:  #C5EFFC;
bei „normalen“ Funktionsaufrufen ({{dh}} bei Aufrufen ohne das Schlüsselwort „<code>new</code>“)
'''immer''' den Aufrufer der Methode, und nicht das Objekt, das die Methode beinhaltet. Aber im Aufrufer der
Methode „<code>sayHelloOnEnter</code>“ ist die Methode „<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 „<code>l_this</code>“,
auf die die Methode  „<code>sayHelloOnEnter</code>“ immer Zugriff hat, unabhängig davon, wer sie aufruft.


In der Anwendung „App1“ wurde der This-Hack nicht benötigt, da die Methode „<code>sayHelloOnEnter</code>“
  --font-family-sansVerdana, Helvetica, sans-serif;
einfach die globale Variable Methode „<code>greet</code>“ verwendet hat, um auf die Methode „<code>sayHello</code>“
  --font-family-serif: "Times New Roman", Times, serif;
zuzugreifen (<code>greet.sayHello();</code>). Aber ein globales Greet-Objekt gibt es nun nicht mehr. Es gibt nur noch einen
    
globale Konstruktorfunktion „<code>Greet</code>“, mit der beliebig viele Greet-Objekte erzeugt werden können.
   --font-size-base:    3vw;
  --font-size-factor: 1.25;  /* cmp. https://type-scale.com/ */
'''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
  --font-size-h5:     calc(pow(var(--font-size-factor),1)*var(--font-size-base));
„<code>this</code>“ verwenden. Übliche Bezeichnungen sind „<code>that</code>“, „<code>self</code>“ oder  „<code>me</code>“.)
   --font-size-h4:      calc(pow(var(--font-size-factor),2)*var(--font-size-base));
 
   --font-size-h3:      calc(pow(var(--font-size-factor),3)*var(--font-size-base));
'''Anmerkung''': Eigentlich ist der This-Hack unsauber. Besser wäre es, die Methode „<code>bind</code>“ zu verwenden
  --font-size-h2:      calc(pow(var(--font-size-factor),4)*var(--font-size-base));
(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]):
  --font-size-h1:      calc(pow(var(--font-size-factor),5)*var(--font-size-base));
 
    
<source lang="javascript">
   --font-size-p:      calc(pow(var(--font-size-factor),0)*var(--font-size-base));
this.sayHelloOnEnter =
   --font-size-p-large: calc(pow(var(--font-size-factor),2)*var(--font-size-base));
   function(p_event)
 
   {
  --font-size-small:  calc(pow(var(--font-size-factor),-1)*var(--font-size-base));
    ...
      this.sayHello();
    ...
  }.bind(this);
</source>
Aber für JavaScript-Anfänger ist die Verwendung des This-Hacks deutlich einfacher.
 
Fügen Sie als nächstes die zuvor erwähnte Zeile
 
<source lang="javascript">
var l_greet = new Greet();
</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.
 
==App3==
 
Leider bringt uns hinsichtlich der Wiederverwendbarkeit die „Verbesserung“, die wir in App2 gegenüber App1
vorgenommen haben, gar nichts. Wir können jetzt zwar beliebig viele <code>greet</code>-Objekte
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 der Konstruktorfunktion
„<code>greet</code>“ verbannen.
 
Erstellen Sie in Ihrem WebStorm-Projekt folgende Kopien Ihre Anwendungsdateien:
 
* <code>index2.html</code> → <code>index3.html</code>
* <code>js/main2.js</code> → <code>js/main3.js</code>
* <code>js/app2/greet.js</code> → <code>js/app3/greet.js</code>
 
In der Datei „<code>index3.html</code>“ müssen Sie wiederum dafür sorgen, dass
die neuen JavaScript-Dateien anstelle der alten geladen werden.
Und passen Sie auch wieder den Titel im HTML-Header an.
 
Ersetzen Sie nun die Konstruktorfunktion der Datei „<code>js/app3/greet.js</code>“
durch folgende Konstruktorfunktion:
 
<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>
</syntaxhighlight>
 
In dieser CSS-Datei werden folgende modernen CSS-Konstrukte verwendet:
Dieser Konstruktor enthält diverse Konstanten nicht mehr, wie {{zB}} <code>'input_name'</code>. Anstelle dessen gibt es diverse Parameter,
in denen diese Werte dem Konstruktor übergeben werden können, wie {{zB}} <code>p_input_name</code>. Der Zugriff auf das globale Objekt
„<code>document</code>“ wurde auch gleich noch durch einen 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.
 
Ersetzen Sie zunächst die Initialisierungsmethode in der Datei  „<code>main3.js</code>“
durch folgenden Code:
 
<source lang="javascript">
(function(p_window, Greet)
{
  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>
 
In dieser Datei werden noch gibt es noch zwei globale Variablen, die Browser-Variable  „<code>window</code>“
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,
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>index3b.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>index3b.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>-Elemente (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>main3b.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/main3b.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
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.
 
<source lang="javascript">
<!DOCTYPE html>
<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/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>
 
===Modularisierung===
 
Um möglichst wenige globale Größen definieren zu müssen, legt man für eine
Anwendung oder eine Bibliothek typischerweise eine einzige globale Variable an – Namensraum-Variable genannt –, die ein Objekt enthält.
In diesem Objekt wird die gesamte Anwendung (Daten, Klassen, Funktionen etc.) gespeichert.
 
Für die Hello-World-Anwendung wird die globale Namensraum-Variable „<code>hello</code>“ verwendet.
In jeder Datei, in der ein Teilaspekt der Anwendung definiert wird, kommt folgender Rahmencode zum Einsatz:
 
<source lang="javascript">
/** @namespace hello */
var hello =
(function(p_hello)
{ "use strict";
 
  // anwendungsspezifischer Code
 
  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 und gibt es dann als Ergebnis zurück.
 
Dies 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 eines derartigen Codes wird
das bestehende <code>hello</code>-Objekt einfach um weitere Inhalte ergänzt. Wenn man die Definition
eines Namensraums über mehrere Dateien verteilt – die Forderung „Jedes Modul erledigt nach Möglichkeit
nur eine einzige Aufgabe.“ erzwingt diese Vorgehensweise geradezu –,
ist es egal in welcher Reihenfolge diese Dateien geladen werden. Sie können daher auch [[asynchron]],
{{dh}} parallel 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">
/** @namespace hello */
var hello =
(function(p_hello)
{ "use strict";
 
  /**
  * @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
 
    /**
    * 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_init_elements = p_init.HTMLElements,
          l_init_text    = p_init.text,
          l_init_hidden  = p_init.css.hidden,
          l_name          = p_document.getElementById(l_init_elements.inputName).value;
 
      p_document.getElementById(l_init_elements.welcome).innerHTML
        = l_init_text.welcome;
      p_document.getElementById(l_init_elements.hello).innerHTML
        = l_init_text.hello.replace('$1', l_name);
 
      p_document.getElementById(l_init_elements.sectionHello)
        .classList.remove(l_init_hidden);
      p_document.getElementById(l_init_elements.sectionForm)
        .classList.add(l_init_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>
 
Gegenüber der Version 3 dieser Datei hat sich Folgendes geändert:
 
* 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">
/** @namespace hello */
var hello =
(function(p_hello)
{ "use strict";
 
  /**
  * 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_init_elements = p_init.HTMLElements,
        l_init_text    = p_init.text,
        l_greet        = new p_hello.Greet(l_document, p_init);
 
    l_document.getElementById(l_init_elements.headingForm)
              .innerHTML = l_init_text.title;
    l_document.getElementById(l_init_elements.inputNameLabel)
              .innerHTML = l_init_text.query;
    l_document.getElementById(l_init_elements.buttonReset)
              .innerHTML = l_init_text.reset;
    l_document.getElementById(l_init_elements.buttonSubmit)
              .innerHTML = l_init_text.submit;
    l_document.getElementById(l_init_elements.buttonSubmit)
              .addEventListener('click', l_greet.sayHello);
    p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
  };
 
  return p_hello;
}(hello ? hello : {}));
</source>


Sie ist wieder aufgebaut wie zuvor beschrieben: Sie definiert, falls notwendig, das globale Objekt „<code>hello</code>“
* Definition von Konstanten (die teilweise per Berechnung ermittelt werden)
und fügt anschließend eine Funktion „<code>init</code>“ in dieses Objekt ein. Die Methode „<code>hello.init</code>“
* Mathematische Berechnungen: <code>calc</code>
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 „<code> l_greet</code>“ 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> l_greet.sayHelloOnEnter</code>
ausführt.


===Die JSON-Datei===
Konstanten starten in CSS mit zwei Minuszeichen. Sobald eine Konstante definiert wurde,
Zur Initialisierung der Web-Anwendung werden JSON-Dateien benötigt.
kann sie in allen weiteren CSS-Konstrukten verwendet werden. In CSS stehen einen Operatoren zur Verfügung wie
<code>+</code>, <code>-</code>, <code>*</code> und <code>/</code>, um Maße zu berechnen. Es gibt
weitere Operatoren zum Vergleichen von Zahlen und Maßen sowie zur Berechnung von Strings und booleschen Werten:
https://sass-lang.com/documentation/operators.<ref>https://sass-lang.com/documentation/operators</ref>


Eine JSON-Datei enthält genau ein JavaScript-Objekt in textueller Darstellung.
Die Datei <code>_config.css</code> kann nun mit Hilfe des CSS-Befehls <code>@import 'config.css';</code> (ohne Underscore und ohne <code>.css</code>, wegen des zuvor eingeführten Vite-Aliases)
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>:
in die anderen beiden CSS-Dateien eingefügt werden.
* 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
=== PostCSS ===
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:
Der Inhalt der Datei <code>_config.css</code> wird defaultmäßig an den Browser ausgeliefert, obwohl sie nur CSS-Konstanten enthält.
Beim Einsatz vom SCSS werden Konstanten vom SCSS-Präprozessor auf dem Server während des Build-Prozesses durch ihre Werte ersetzt.
In CSS muss dies defaultmäßig der Browser übernehmen. Es gibt allerdings ein PostCSS-Plugin [https://www.npmjs.com/package/postcss-var-replace/v/1.0.0 postcss-var-replace], das diese Ersetzungen ebenfalls während des Build-Prozesses vornimmt. Dieses kann man in die Vite-Konfiguration integrieren:


<source lang="javascript">
Laden sie das Pluging <code>postcss-var-replace</code>:
{ a: 55, 'b': 'Wolfgang' }
</source>


In JSON muss dieses Objekt folgendermaßen geschrieben werden:
<syntaxhighlight lang="bash">
npm i -D postcss-var-replace
</syntaxhighlight>


<source lang="javascript">
Anschließend aktivieren Sie es in <code>vite.config.js</code>
{ "a": 55, "b": "Wolfgang" }
</source>


Diese Objekt ist auch ein korrektes JavaScript-Objekt. Da die Syntaxregeln von JSON strenger sind
<syntaxhighlight lang="javascript">
als von JavaScript, gilt folgende Regel: Jedes JSON-Objekt ist auch ein JavaScript-Objekt, aber nicht
import { postcssVarReplace }  from 'postcss-var-replace';
jedes JavaScript-Objekt ist auch ein JSON-Objekt.


Ein Problem bereitet die Verwendung von JSON. Da ein JSON-Objekt beliebige Attribute enthalten kann,
...
kann eine IDE nicht überprüfen, ob ein bestimmtes Attribut oder Element in einem JSON-Objekt
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
„<code>Unresolved variable ...</code>“ versieht.


Ein Lösung für dieses Problem wäre die Verwendung von [http://json-schema.org/ JSON Schema].
export default
Aber darauf wird zunächst verzichtet.
{ ...
 
   css:
Legen Sie für die zuvor definierte HTML-Datei „<code>index4.html</code>“ folgende JSON-Initialisierungsdatei
   { postcss:
unter dem Namen „<code>web/json/hello_de.json</code>“ an:
     { plugins:  
 
      [ postcssVarReplace(),
<source lang="javascript">
      ],
{
     },
   "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>
</syntaxhighlight>


Diese Datei besteht aus einem anonymen Objekt. Es enthält seinerseits drei Objekte  „<code>HTMLElements</code>“,
Der Import-Befehl hat ab sofort erst dann einen Effekt, wenn in den anderen Dateien auf diese Konstanten zugegriffen wird.
„<code>text</code>“ und „<code>css</code>“, die für die Initialisierung der Web-Anwendung gedacht sind.
Aber er schadet auch nicht, wenn auf keine der dort definierten Konstanten zugegriffen wird. Der Import-Befehl selbst
Das Objekt „<code>HTMLElements</code>“ enthält sämtliche ID-Attribut-Bezeichner der Web-Anwendung.
taucht ebenso wenig in den generierten CSS-Dateien auf, wie die Konstantendefinitionen. Der PostCSS-Transpiler ersetzt Konstanten in CSS-Definitionen durch ihren jeweiligen Wert.
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.
Wenn Sie in Ihrer <code>index4.html</code> andere ID-Attibute-Bezeichner verwenden, müssen Sie die JSON-Datei
entsprechend anpassen, {{dh}}, Sie müssen beispielsweise den Attribut''wert'' <code>"section1_form"</code>
durch den von Ihnen verwendeten Bezeichner ersetzen. Die Attribut''namen'' (<code>"sectionForm"</code> etc.) dürfen Sie dagegen nicht ändern,
da sonst die Methoden (<code>init</code>, <code>sayHello</code> und <code>sayHelloOnEnter</code>)
Ihrer Web-Anwendung nicht mehr funktionieren würden.  


Im Objekt  „<code>text</code>“ sind alle Texte enthalten, die zu gegebener Zeit in das HTML-Dokument an die geeignete
Sie können nun beispielsweise in der Datei <code>head.css</code> die Konstante <code>--background-color</code>
Stelle geschrieben werden sollen. Der Text  „<code>hello</code>“ enthält  „<code>$1</code>“ als Platzhalter.
verwenden. In dem daraus durch den Build-Befehl <code>npm run build</code> erzeugten CSS-Style-Element steht weiterhin der Farbwert <code>#C5EFFC</code>;
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
'''<code>src/css/head.css</code>'''
zu HTML-Elementen hinzugefügt oder aus HTML-Elementen entfernt werden. Der Bezeichner, der der Anwendung bekannt ist
<syntaxhighlight lang="CSS">
(„<code>hidden</code>“), stimmt in diesem Fall mit dem Namen der zugehörigen CSS-Klasse überein. Das ist nicht verboten.
@import 'config.css';


Erstellen Sie analog die Datei „<code>web/json/hello_en.json</code>“. Diese Datei soll für die zweite Web-Anwendung
body
definiert werden, die allerdings englischsprachige Texte enthält (Sie erinnern sich? In <code>index4.html</code>  
{ background-color: var(--background-color); }
laufen zwei unabhängige Web-Anwendungen parallel).
.hidden
{ display: none; }
</syntaxhighlight>


===Das Laden der JSON-Dateien===
Die Datei <code>body.css</code> ist deutlich umfangreicher. Daher wird der zugehörige CSS-Code
ja auch erst dynamisch gemeinsam mit der Datei <code>web/js/main.js</code> geladen.


Für das [[asynchron]]e Laden von beliebigen Dateien von einem Web-Server gibt es in JavaScript den Befehl „<code>XMLHttpRequest</code>“.
'''<code>src/css/body.css</code>'''
Wie man am Namen sieht, war er ursprünglich für das Laden XML-Dateien vorgesehen. Heute kann dieser Befehl allerdings für
<syntaxhighlight lang="CSS">
jede Art von Datei verwendet werden. Sie könnten jetzt natürlich die Dokumentation (https://wiki.selfhtml.org/wiki/JavaScript/XMLHttpRequest)
@import 'config.css';
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/ajax.js ajax.js] </code>
aus dem Glossar-Repository herunterladen und in Ihrem Projekt unter dem Namen  „<code>web/js/lib/wk/ajax.js</code>“ speichern.


Dieses Module stellt unter der globalen Variablen „<code>wk</code>“ – diese globale Variable ist für meine Module reserviert :-) –
html
im Modul  „<code>wk.ajax</code>“ mehrere Methoden zum Laden von Dateien bereit.  Zum Laden von JSON-Dateien
{ height: 100%;
sollten Sie „<code>wk.ajax.getJSON</code>“ verwenden (obwohl es mit „<code>wk.ajax.getFile</code>“ auch ginge; Sie müssten
  width:  100%;
dann allerdings den Inhalt der Datei, der als String geliefert wird, noch in ein JavaScript-Objekt umwandeln).
  display: table;
}


Die Methode „<code>getJSON</code>“ erwartet drei Argumente, einen URI, der den Speicherort der JSON-Datei beschreibt und zwei Callback-Funktionen.
body
Die erste Funktion wird aufgerufen, sobald die JSON-Datei erfolgreich geladen wurde. Ihr wird das geladen JSON-Objekt als Argument
{ display:          table-cell;
übergeben. Die zweite Callback-Funktion wird aufgerufen, falls beim Laden etwas schief gehen sollte. Die Angabe dieser Funktion ist
  text-align:      center;
optional. In der Musterlösung wurde Sie definiert, hier wird darauf verzichtet.
  vertical-align:  middle;
  font-size:        var(--font-size-base);
  font-family:      var(--font-family-sans);
}


Die Initialisierungsfunktion (Datei „<code>main4.js</code>“) sieht nun folgendermaßen aus:
h1
{ padding:  0;
  margin:    0;
  font-size: var(--font-size-h1);
}


<source lang="javascript">
p, label
// Initialize the app.
{ font-family: var(--font-family-serif);
(function(p_window, p_wk, p_hello)
  font-size:  var(--font-size-p);
{ "use strict";
}


   // The list of all json files to be loaded
label, input
   var l_json_files = ['json/hello_de.json', 'json/hello_en.json'];
{ width:      10em;
  font-size:  100%;
  display:    inline-block;
   box-sizing: border-box;
   margin:    0.5ex;
}


  // For each element in the JSON file list:
label
  for (var i = 0, n = l_json_files.length; i < n; i++)
{ text-align: right;
  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
#section_form
und reicht jeweils das daraus erzeugte JSON-Objekt „<code>main4.js</code>“ an die Initialisierungsfunktion der Web-Anwendung weiter.
{ text-align:  center;
Beachten Sie, dass die Reihenfolge, in der die beiden JSON-Dateien geladen wird, nicht feststeht. Sie werden parallel geladen. Sobald eine
  margin-left: auto;
Datei vollständig geladen wurde, wird die zugehörige Callback-Funktion
  margin-right: auto;


<source lang="javascript">
  h1
function(p_init) { p_hello.init(p_window, p_init); }
  { margin-bottom: 0.5ex; }
</source>
}


ausgeführt. Dieser wird im Parameter „<code>p_init</code>“ das zugehörige JSON-Objekt überreicht.
#section_hello
Sie reicht es (zusammen mit die Window-Objekt) an die Methode „<code>p_hello.init</code>“ weiter.
{ p
  { font-size: var(--font-size-p-large) !important; }
}
</syntaxhighlight>


'''Anmerkung''': Man könnte die beiden JSON-Dateien natürlich auch mittels zweier Aufrufe von „<code>getJSON</code>“
In dieser CSS-Datei werden folgende modernen CSS-Befehle verwendet:
laden:


  <source lang="javascript">
* Import-Befehl: <code>@import 'config.css';</code>
// Initialize the app.
* Konstanten: <code>--font-family-sans</code>, <code>--font-size-base</code> etc. (Es sind noch viele weitere konstante Werte in <code>body.css</code> enthalten, für die in <code>_config.css</code> Konstanten definiert werden sollten)
(function(p_window, p_wk, p_hello)
* Verschachtelte Anweisungen: {{zB}} <code>h1</code> innerhalb von  <code>#section_form</code>
{ "use strict";


  p_wk.ajax.
Die verschachtelten Anweisungen strukturieren den CSS-Code deutlich besser. Es ist nicht mehr
    getJSON
notwendig, einen Selektor wie <code>#section_form</code> mehrfach zu verwenden.
    ( 'json/hello_de.json',               // Load the JSON file 'json/hello_de.json'.
      function(p_init)                    // If ready, perform the callback function.
      { p_hello.init(p_window, p_init); } // Initialize the corresponding hello section.
    );


  p_wk.ajax.
Sehen Sie sich den Unterschied an. In der Datei <code>src/css/body.css</code> der Musterlösungen vom dritten  Teil des Tutoriums hatte ich
    getJSON
Folgendes geschrieben:
    ( 'json/hello_en.json',              // Load the JSON file 'json/hello_en.json'.
      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>


Diese Verdopplung von Code würde allerdings gegen das Programmierprinzip „[[Don't repeat yourself]]“ (DRY) verstoßen.
<syntaxhighlight lang="CSS">
#section_form
{ text-align:  center;
  margin-left: auto;
  margin-right: auto;
}


==App5==
#section_form > h1
{ margin-bottom: 0.5ex; }
</syntaxhighlight>


Im Prinzip kann die in App4 vorgestellte Lösung zur Modularisierung einer Web-Anwendung
Die modernen CSS-Variante ist deutlich strukturierter und prägnanter.
gut eingesetzt werden. Allerdings hat sie immer noch zwei Schwächen:


* Der Rahmencode, der in jeder Datei der Anwendung verwendet werden muss, ist lästig. (Das ist ein kleineres Problem.)
<syntaxhighlight lang="CSS">
* Es gibt immer noch die globale Variable „<code>hello</code>“. Wenn man viele Bibliotheken verwendet, die auf diese Weise realisiert werden, gibt es wieder viele globale Variable. Und damit steigt das Risiko von Namenskollisionen.
#section_form
{ text-align:  center;
  margin-left:  auto;
  margin-right: auto;


Schöner wäre es, wenn JavaScript die Modularisierung von sich aus unterstützen würde. Das ist tatsächlich geplant. In EcmaScript 6
  h1
gibt es zu diesem Zweck die Befehle „<code>[https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/import import]</code>“
  { margin-bottom: 0.5ex; }
und „<code>[https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/export export]</code>“.
}
Leider unterstützen die meisten Browser diese Befehle noch nicht.
</syntaxhighlight>
 
Solange dies der Fall ist, kann man auf JavaScript-Bibliotheken zurückgreifen, die dieselbe Aufgabe (wenn auch weniger elegant) erfüllen.
Hier könnte die Bibliothek „<code>RequireJS</code>“ zum Einsatz (http://requirejs.org/). An der Struktur der Anwendung ändert sich nichts.
Nur die Art, wie Module definiert und geladen werden, wird angepasst.


Erstellen Sie in Ihrem WebStorm-Projekt letztmalig Kopien Ihre Anwendungsdateien:
Insbesondere bei großen CSS-Dateien erweist sich diese kompaktere Schreibweise als vorteilhaft.


* <code>index4.html</code> → <code>index5.html</code>
==Fortsetzung des Tutoriums==
* <code>js/main4.js</code> → <code>js/main5.js</code>
* <code>js/app4/greet.js</code> → <code>js/app5/greet.js</code>
* <code>js/app4/hello.js</code> → <code>js/app5/init.js</code>
 
In der Datei „<code>index5.html</code>“ müssen Sie wiederum dafür sorgen, dass
die neuen JavaScript-Dateien anstelle der alten geladen werden.
Und passen Sie auch wieder den Titel im HTML-Header an.
 
Als nächstes müssen Sie, um <code>RequireJS</code> nutzen zu können, den Ordner „<code>web/js/lib/require</code>“
erstellen und folgende drei Dateien (einschließlich des Inhalts, der sich hinter den drei Links verbirgt) dort speichern:
* <code>[http://requirejs.org/docs/release/2.3.2/comments/require.js require.js]</code>
* <code>[https://raw.githubusercontent.com/millermedeiros/requirejs-plugins/master/lib/text.js text.js]</code>
* <code>[https://raw.githubusercontent.com/millermedeiros/requirejs-plugins/master/src/json.js json.js]</code>
 
===Modularisierung mit RequireJS===
 
Wenn man konsequent RequireJS zur Modularisierung seine Anwendung
einsetzt, gibt es neben den globalen Objekten des Browsers (<code>window</code>,
<code>document</code> etc.) nur noch das globales Objekt „<code>require</code>“ sowie die globale
Funktion „<code>define</code>“ zum Erstellen neuer Module.
 
Es wird auch nur noch eine einzige JavaScript-Datei statisch geladen:
„<code>require.js</code>“. Alle anderen Dateien werden dynamisch geladen.
Die in diesen Dateien enthaltenen Module werden mittels
Callback-Funktionen an andere Module übergeben. Auf diese
Weise ist sicher gestellt, dass man keine anderen globalen
Variablen und Funktionen mehr benötigt.
 
RequireJS-Moduldefinitionen sollten gemäß der AMD-Spezifikation
(AMD = [[WikipediaEN:Asynchronous_module_definition|Asynchronous Module Definition]]) erfolgen. RequireJS
kann allerdings auch Bibliotheken wie {{zB}} „<code>[https://jquery.com/ jQuery]</code>“ laden, die nicht
gemäß dieser Spezifikation erstellt wurden.
 
Für RequireJS gibt es einige Plugins. Eines davon ermöglicht
das Lesen von JSON-Dateien („<code>json.js</code>“). Dieses Plugin wird verwendet,
um die Dateien „<code>json/hello_xx.json</code>“ einzulesen.
 
Wenn man mit Hilfe von „<code>define</code>“ ein neues (AMD-)Modul definiert,
gibt man {{iAllg}} eine ganze Liste von Modulen an, auf denen das neue Modul basiert.
Beispielsweise benötigt das Modul „<code>hello</code>“ (welches in der Datei „<code>js/app5/hello.js</code>“ definiert wird)
das Modul „<code>greet</code>“  (welches in der Datei „<code>js/app5/greet.js</code>“ definiert wird).
 
Die Moduldefinition von „<code>hello</code>“  sieht in diesem Fall folgendermaßen aus:
 
<source lang="javascript">
define
( ['app/greet'],
  function (Greet)
  {
    return ...
  }
);
</source>
 
Die Funktion „<code>define</code>“ lädt zunächst alle Module, die in der Modulliste aufgeführt werden, asynchron (und damit meist auch parallel).
Allerdings wird jedes Modul nur einmal geladen. Das heißt, wenn zwei Module dasselbe Modul benötigen, wird dieses Modul trotzdem
nur einmal geladen. Sobald alle Module geladen sind, wird die im  <code>define</code>-Aufruf definierte Callback-Funktion ausgeführt.
Die Funktion muss für jedes Modul, das in der Modulliste enthalten ist, einen Parameter zur Verfügung stellen,
in der das jeweilige Modul-Objekt an die Callback-Funktion übergeben wird.
 
Mann könnte im  <code>define</code>-Aufruf auch noch einen Modulnamen angeben. Das ist aber {{iAllg}}
nicht notwendig, da RequireJS defaultmäßig den Dateinamen (ohne Pfad und ohne Endung „<code>js</code>“)
als Modulnamen verwendet.
 
Wenn man in der Definition eines Moduls auf keine anderen Module zurückgreifen will, kann man auf die Callback-Funktion verzichten.
Um ein Modul zu schreiben, das lediglich ein Objekt enthält, schreibt man einfach:
 
<source lang="javascript">
define
({
    // Inhalt des Objekts, den das Modul definiert.
});
</source>
 
Wenn das Modul lediglich eine Funktion, wie {{zB}} eine Konstruktorfunktion „<code>C</code>“ zur Verfügung stellen soll, schreibt man
 
<source lang="javascript">
define
( function ()
  { 
    function C(...) {...}
 
    return C;
  }
);
</source>
 
Hier gibt es zwar eine Callback-Funktion. Diese wird jedoch sofort ausgeführt, da nicht auf das Laden anderer Module gewartet werden muss.
 
====Laden von RequireJS====
 
Damit Module auch gefunden werden, muss man RequireJS zunächst konfigurieren.
Das erfolgt üblicherweise in der Datei  „<code>main.js</code>“ (hier: „<code>main5.js</code>“).
Diese Datei ist auch dafür verantwortlich, das erste Modul zu laden und damit den Ladevorgang für
alle benötigten Module in Gang zu setzten. Bei uns ist das das Modul „<code>hello</code>“.
 
Allerdings kann man <code>main.js</code> nicht direkt laden, sondern man muss den
Browser anweisen, zuvor <code>require.js</code> zu laden. Sobald diese Datei vollständig geladen (und übersetzt) wurde,
lädt <code>require.js</code> von sich aus <code>main.js</code>  (bzw <code>main5.js</code>).
Um dies zu erreichen, fügen Sie folgendes Script-Element als letztes und einziges Script-Element
in die Datei „<code>index5.html</code>“ ein:
 
<source lang="html5">
<script data-main="js/main5" src="js/lib/require/require.js"></script>
</source>
 
Weitere Script-Elemente werden nicht mehr benötigt, das RequireJS Module jeweils dynamisch
nachlädt, sobald sie benötigt werden.
 
====Landen von <code>main.js</code>====
 
Nachdem <code>require.js</code> geladen wurde, liest das Programm das Script-Attribut „<code>data-main</code>“
und erfährt so, dass die Datei „<code>js/main5.js</code>“ für die Initialisierung und den Start der Web-Anwendung zuständig ist (die fehlende Dateiendung
„<code>.js</code>“ ergänzt RequireJS automatisch).
 
Die Datei „<code>js/main5.js</code>“ konfiguriert zunächst RequireJS:
 
<source lang="javascript">
requirejs.config
({
  baseUrl: 'js', // By default load any modules from directory js
  paths :
  {
    app:      'app5',
    loadjson: 'lib/require/json',
    text:    'lib/require/text',
    json:    '../json'
  }
});
</source>
 
Hier wird festgelegt, dass sich die JavaScript-Dateien im Ordner „<code>js</code>“ befinden. Das Attribut „<code>path</code>“
legt ein paar spezielle Unterordner fest. Modulnamen, die mit „<code>app/</code>“ beginnen (wie {{zB}} „<code>app/greet</code>“),
werden im Ordner „<code>js/app5/</code>“ gesucht. Für die Module „<code>json</code>“ (ein RequireJS-Plugin zum Laden von
JSON-Dateien) und „<code>text</code>“ (welches von
„<code>json</code>“ benötigt wird) wurde der Pfad direkt angegeben. Die JSON-Dateien finden sich im Ordner
„<code>js/../json</code>“, {{dh}} im Ordner „<code>init</code>“.
 
Nun kann die eigentliche Anwendung gestartet werden:
 
<source lang="javascript">
requirejs
( ['loadjson!json/hello_de.json', 'loadjson!json/hello_en.json', 'app/init'],
  function(helloDeJSON, helloEnJSON, init)
  {
    init(window, helloDeJSON);
    init(window, helloEnJSON);
  }
);
</source>
 
Die Anwendung benötigt zwei JSON-Dateien (das dafür notwendige JSON-Plugin wird automatisch geladen)
und das Modul  „<code>app/hello</code>“ (welches sich gemäß Konfiguration in der Datei  „<code>js/app5/hello.js</code>“ befindet).
 
Sobald die drei Dateien vollständig geladen wurden, wird die Callback-Funktion aufgerufen.
Dieser werden die beiden JSON-Objekte sowie das Hello-Initialisierungs-Modul-Objekt übergeben.
Die Callback-Funktion kann somit problemlos die beiden Hello-World-Anwendungen mittels  „<code>p_hello.init</code>“ initialisieren und starten.
 
====Das Modul „<code>hello</code>“====
 
Der Code des Moduls „<code>init</code>“ unterscheidet sich nicht nur hinsichtlich des Modulrahmens, sondern auch noch in
eine zweiten in einem wesentlichen Aspekt vom
Code des Moduls „<code>hello</code>“ der <code>App4</code>: In der App4 gibt es ein globales Objekt namens „<code>hello</code>“, das die gesamte Web-Anwendung enthält. Insbesondere gibt es die Methode „<code>hello.init</code>“ zum Initialisieren der App. Im Gegensatz dazu gibt es in der App5 überhaupt keine anwendungsspezifischen globalen Objekte. Daher ist es nicht notwendig die Funktion „<code>init</code>“ als Methode in irgendein Objekt einzufügen.
Dementsprechend wurde auch der Modulname von „<code>hello</code>“  in „<code>init</code>“ geändert.
 
<source lang="javascript">
define
( ['app/greet'],
  function (Greet) // The module 'app/greet' contains the constructor 'Greet'.
  {
    /**
    * 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.
    */
    function init(p_window, p_init)
    {
      var l_document      = p_window.document,
          l_init_elements = p_init.HTMLElements,
          l_init_text    = p_init.text,
          l_greet        = new Greet(l_document, p_init);
 
      l_document.getElementById(l_init_elements.headingForm)
                .innerHTML = l_init_text.title;
      l_document.getElementById(l_init_elements.inputNameLabel)
                .innerHTML = l_init_text.query;
      l_document.getElementById(l_init_elements.buttonReset)
                .innerHTML = l_init_text.reset;
      l_document.getElementById(l_init_elements.buttonSubmit)
                .innerHTML = l_init_text.submit;
      l_document.getElementById(l_init_elements.buttonSubmit)
                .addEventListener('click', l_greet.sayHello);
      p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
    }
 
    return init;
  }
);
</source>
 
Sobald das Modul „<code>app/greet</code>“ ({{dh}} die Datei „<code>js/app5/greet.js</code>“) vollständig geladen wurde,
wird die von diesem Modul bereitgestellte Konstruktorfunktion „<code>Greet</code>“ der Callback-Funktion
im Parameter „<code>Greet</code>“ übergeben. Die Callback-Funktion erstellt daraufhin ein Objekt, welches eine einzige
Methode enthält: „<code>init</code>“. Die Aufgabe dieser Methode ist es, die Web-Anwendung zu initialiseren, sobald sie
(von „<code>main5.js</code>“) aufgerufen wird. Sie erstellt insbesondere mit Hilfe der Konstruktorfunktion „<code>Greet</code>“
ein Objekt „<code>l_greet</code>“, welche speziell angepasste Methoden „<code>sayHello</code>“ und „<code>sayHelloOnEnter</code>“
zur Verfügung stellt.
 
====Das Modul „<code>greet</code>“====
 
Das Modul „<code>greet</code>“ benötigt überhaupt keine anderen Module.
Daher ist die Moduldefinition noch etwas einfacher (die Liste mit benötigten Modulen entfällt ganz). Es ändert sich gegenüber App4 ebenfalls nur der Modulrahmen.
 
<source lang="javascript">
define
(function ()
{"use strict";
 
  /**
  * @class
  * @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.
  */
  function Greet(p_document, p_init)
  {
    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 ()
      {
        var l_init_elements = p_init.HTMLElements,
            l_init_text    = p_init.text,
            l_init_hidden  = p_init.css.hidden,
            l_name          = p_document.getElementById(l_init_elements.inputName).value;
 
        p_document.getElementById(l_init_elements.welcome)
                  .innerHTML = l_init_text.welcome;
        p_document.getElementById(l_init_elements.hello)
                  .innerHTML = l_init_text.hello.replace('$1', l_name);
 
        p_document.getElementById(l_init_elements.sectionHello)
                  .classList.remove(l_init_hidden);
        p_document.getElementById(l_init_elements.sectionForm)
                  .classList.add(l_init_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 Greet; // Return the constructor defined above.
Sie sollten nun [[HTML5-Tutorium: JavaScript: Hello World Vue 01|Teil 1 des Vue-Tutoriums]] bearbeiten.
});
In diesem Teil des Tutoriums wird die oben erstelle Variante in das Vue-Framework transferiert.
</source>


==Quellen==
==Quellen==
<references/>
<references/>
<ol>
<ol>
<li value="4"> {{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
<li value="5"> {{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 11. April 2025, 09:25 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
index.html (HTML validate, CSS validate, Google PageSpeed)
Git-Repository

Anwendungsfälle (Use Cases)

Gegenüber dem dritten, vierten und fünften 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, den CSS-Code „DRY zu machen“.

Erstellen eines neuen Projekts

Erstellen Sie ein neues Projekt hello_world_06 als Fork von hello_world_05.

CSS-Erweiterungen

CSS bietet heute dieselbe Funktionalität wie Transpiler (SASS, Less etc.), die früher verwendet wurden, um fehlende Funktionalität in CSS zu integrieren. Ich werde in diesem Semester CSS an Stelle von SCSS einsetzen, so wie es von vite empfohlen wird:

Because Vite targets modern browsers only, it is recommended to use native CSS variables with PostCSS plugins that implement CSSWG drafts (e.g. postcss-nesting) and author plain, future-standards-compliant CSS. (https://vite.dev/guide/features)

Eine ganz wesentliche Erweiterung ist die Möglichkeit Konstanten zu definieren. Auch die Möglichkeit, Werte zu berechnen, gibt es in der Zwischenzeit (SCSS: math, CSS: calc). Es ist jetzt auch möglich CSS-Elemente zu verschachteln. Das heißt, die Bedeutung von SCSS, Less etc. schwindet.

CSS-Konstanten

Eine ganz wesentliche Erweiterung ist die Möglichkeit Konstanten zu definieren, da man damit CSS-Code DRY formulieren kann.

Wenn eine Konstante definiert wurde, kann man im übrigen CSS-Code mittels var darauf zugreifen (und nicht etwa mittels const, :-( )

:root 
{ --font-family-serif: "Times New Roman", Times, serif; }

p
{ font-family: var(--font-family-serif); }

label
{ font-family: var(--font-family-serif); }

Auch die Möglichkeit, Werte zu berechnen, gibt es in der Zwischenzeit (SCSS: math, CSS: calc). Das heißt, die Bedeutung von SCSS, Less etc. schwindet. In diesem Semester verwende ich nicht mehr SCSS, sondern nur noch CSS.

Fügen Sie "config" als Abkürzung für "./src/css/_config.css" in die Datei vite.config.js ein.

...
  resolve:
  { alias:
    { ...
      'config.css': fileURLToPath(new URL('./src/css/_config.css', import.meta.url)),
      ...
    }
  }
...

In der Datei _config.css werden Konstanten definiert, die von allen anderen CSS-Dateien verwendet werden können und sollen.

Das heißt, das DRY-Prinzip „Konstanten gehören nicht in den Code, sondern in Konfigurationsdateien“ wird ab sofort beachtet.

Wenn man künftig am Layout einen Zeichensatz, eine Farbe, einen Hintergrund oder Ähnliches ändern will, reicht es, die entsprechenden Konstanten anzupassen und schon erzeugt Vite automatisch aktualisiere CSS-Dateien.

Durch den CSS-Import-Befehl ist es möglich, CSS-Dateien zu modularisieren. Der Underscore _ am Beginn des Dateinamens deutet an, dass diese CSS-Datei nur von anderen CSS-Dateien aber nicht von HTML- oder JavaScript-Dateien importiert werden sollte.

src/css/_config.css

:root
{ --background-color:  #C5EFFC;

  --font-family-sans:  Verdana, Helvetica, sans-serif;
  --font-family-serif: "Times New Roman", Times, serif;
  
  --font-size-base:    3vw;
  --font-size-factor:  1.25;  /* cmp. https://type-scale.com/ */
   
  --font-size-h5:      calc(pow(var(--font-size-factor),1)*var(--font-size-base));
  --font-size-h4:      calc(pow(var(--font-size-factor),2)*var(--font-size-base));
  --font-size-h3:      calc(pow(var(--font-size-factor),3)*var(--font-size-base));
  --font-size-h2:      calc(pow(var(--font-size-factor),4)*var(--font-size-base));
  --font-size-h1:      calc(pow(var(--font-size-factor),5)*var(--font-size-base));
  
  --font-size-p:       calc(pow(var(--font-size-factor),0)*var(--font-size-base));
  --font-size-p-large: calc(pow(var(--font-size-factor),2)*var(--font-size-base));
  
  --font-size-small:   calc(pow(var(--font-size-factor),-1)*var(--font-size-base));
}

In dieser CSS-Datei werden folgende modernen CSS-Konstrukte verwendet:

  • Definition von Konstanten (die teilweise per Berechnung ermittelt werden)
  • Mathematische Berechnungen: calc

Konstanten starten in CSS mit zwei Minuszeichen. Sobald eine Konstante definiert wurde, kann sie in allen weiteren CSS-Konstrukten verwendet werden. In CSS stehen einen Operatoren zur Verfügung wie +, -, * und /, um Maße zu berechnen. Es gibt weitere Operatoren zum Vergleichen von Zahlen und Maßen sowie zur Berechnung von Strings und booleschen Werten: https://sass-lang.com/documentation/operators.[1]

Die Datei _config.css kann nun mit Hilfe des CSS-Befehls @import 'config.css'; (ohne Underscore und ohne .css, wegen des zuvor eingeführten Vite-Aliases) in die anderen beiden CSS-Dateien eingefügt werden.

PostCSS

Der Inhalt der Datei _config.css wird defaultmäßig an den Browser ausgeliefert, obwohl sie nur CSS-Konstanten enthält. Beim Einsatz vom SCSS werden Konstanten vom SCSS-Präprozessor auf dem Server während des Build-Prozesses durch ihre Werte ersetzt. In CSS muss dies defaultmäßig der Browser übernehmen. Es gibt allerdings ein PostCSS-Plugin postcss-var-replace, das diese Ersetzungen ebenfalls während des Build-Prozesses vornimmt. Dieses kann man in die Vite-Konfiguration integrieren:

Laden sie das Pluging postcss-var-replace:

npm i -D postcss-var-replace

Anschließend aktivieren Sie es in vite.config.js

import { postcssVarReplace }  from 'postcss-var-replace';

...

export default
{ ...
  css:
  { postcss:
    { plugins: 
      [ postcssVarReplace(),
      ],
    },
  },
  ...
}

Der Import-Befehl hat ab sofort erst dann einen Effekt, wenn in den anderen Dateien auf diese Konstanten zugegriffen wird. Aber er schadet auch nicht, wenn auf keine der dort definierten Konstanten zugegriffen wird. Der Import-Befehl selbst taucht ebenso wenig in den generierten CSS-Dateien auf, wie die Konstantendefinitionen. Der PostCSS-Transpiler ersetzt Konstanten in CSS-Definitionen durch ihren jeweiligen Wert.

Sie können nun beispielsweise in der Datei head.css die Konstante --background-color verwenden. In dem daraus durch den Build-Befehl npm run build erzeugten CSS-Style-Element steht weiterhin der Farbwert #C5EFFC;

src/css/head.css

@import 'config.css';

body
{ background-color: var(--background-color); }
 
.hidden
{ display: none; }

Die Datei body.css ist deutlich umfangreicher. Daher wird der zugehörige CSS-Code ja auch erst dynamisch gemeinsam mit der Datei web/js/main.js geladen.

src/css/body.css

@import 'config.css';

html
{ height:  100%;
  width:   100%;
  display: table;
}

body
{ display:          table-cell;
  text-align:       center;
  vertical-align:   middle;
  font-size:        var(--font-size-base);
  font-family:      var(--font-family-sans);
}

h1
{ padding:   0;
  margin:    0;
  font-size: var(--font-size-h1);
}

p, label
{ font-family: var(--font-family-serif);
  font-size:   var(--font-size-p);
}

label, input
{ width:      10em;
  font-size:  100%;
  display:    inline-block;
  box-sizing: border-box;
  margin:     0.5ex;
}

label
{ text-align: right;
}

#section_form
{ text-align:   center;
  margin-left:  auto;
  margin-right: auto;

  h1
  { margin-bottom: 0.5ex; }
}

#section_hello
{ p
  { font-size: var(--font-size-p-large) !important; }
}

In dieser CSS-Datei werden folgende modernen CSS-Befehle verwendet:

  • Import-Befehl: @import 'config.css';
  • Konstanten: --font-family-sans, --font-size-base etc. (Es sind noch viele weitere konstante Werte in body.css enthalten, für die in _config.css Konstanten definiert werden sollten)
  • Verschachtelte Anweisungen: z. B. h1 innerhalb von #section_form

Die verschachtelten Anweisungen strukturieren den CSS-Code deutlich besser. Es ist nicht mehr notwendig, einen Selektor wie #section_form mehrfach zu verwenden.

Sehen Sie sich den Unterschied an. In der Datei src/css/body.css der Musterlösungen vom dritten Teil des Tutoriums hatte ich Folgendes geschrieben:

#section_form
{ text-align:   center;
  margin-left:  auto;
  margin-right: auto;
}

#section_form > h1
{ margin-bottom: 0.5ex; }

Die modernen CSS-Variante ist deutlich strukturierter und prägnanter.

#section_form
{ text-align:   center;
  margin-left:  auto;
  margin-right: auto;

  h1
  { margin-bottom: 0.5ex; }
}

Insbesondere bei großen CSS-Dateien erweist sich diese kompaktere Schreibweise als vorteilhaft.

Fortsetzung des Tutoriums

Sie sollten nun Teil 1 des Vue-Tutoriums bearbeiten. In diesem Teil des Tutoriums wird die oben erstelle Variante in das Vue-Framework transferiert.

Quellen

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