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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
(23 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 3: Zeile 3:


'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld05/web/index.html <code>index.html</code>],
'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld05/web/index.html <code>index.html</code>],
[([https://glossar.hs-augsburg.de/beispiel/tutorium/es6/hello_world/WK_HelloWorld05/ SVN-Repository])
([https://glossar.hs-augsburg.de/beispiel/tutorium/es6/hello_world/WK_HelloWorld05/ SVN-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_04|vierten 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.


In diesem Teil des Tutoriums geht es darum, die Anwendung besser zu strukturieren, {{dh}} zu [[Modul|modularisieren]].
In diesem und im nächsten Teil des Tutoriums werden zwei wesentliche [[Programmierprinzipien|Grundprinzipien der Programmierung]]
 
thematisiert:
===Modularisierung===
* [[Don't repeat yourself]] (DRY, “Keep your code DRY!”).
 
* Konstanten gehören nicht in den Code, sondern in Konfigurationsdateien.
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 eines neuen Projektes==


Erstellen Sie ein neues Projekt <code>HelloWorld04</code>“ und legen Sie dieses in Ihrem Repository ab.
Erzeugen Sie wie im  [[HTML5-Tutorium:_JavaScript:_Hello_World_04|vierten Teil des Tutoriums]] ein neues Projekt,
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.
diesmal allerdings mit dem schönen Namen <code>HelloWorld05</code>“ und speichern Sie dieses wieder
in Ihrem SVN-Repository (Achtung: <code>node_modules</code> muss existieren und auf der Ignore-Liste stehen).


==Erstellen einer Ordnerstruktur==
Kopieren Sie nun aus Ihrer Lösung des vierten Teil des Tutoriums die Ordner <code>src</code> und <code>web</code>
(jeweils samt allen darin enthaltenen Ordnern und Dateien) sowie die Dateien <code>gruntfile.js</code>,
<code>package.json</code>, <code>package-lock.json</code> und <code>webpack.config.js</code>
fügen Sie sie ins Wurzelverzeichnis des fünften Teils ein.


Grundsätzlich gilt auch bei der Programmierung: Ordnung ist das halbe Leben.
Weisen Sie WebStorm wiederum an, die Inhalte der Ordner <code>node_modules</code> und <code>web/js</code>
nicht auf Syntaxfehler zu überprüfen. Gegebenenfalls müssen Sie auch wieder die Dateien <code>gruntfile.js</code>
... <code>webpack.config.js</code> unter die Versionsverwaltung von SVN stellen (<code>Add to VCS</code>).


Web-Anwendungen werden sehr schnell sehr groß. Also sollte man sich eine geeignete Ordnerstruktur überlegen.
Ändern Sie in den Dateien <code>package.json</code> und <code>web/index.html</code> die Versionsnummern,
Üblicherweise legt man CSS-Dateien in einen Ordner namens  „<code>css</code>und JavaScript-Dateien in einen Ordner namens
die Beschreibung und gegebenenfalls den Pfad zum Repository Ihres Projektes.
<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:
Zu guter Letzt öffnen Sie das WebStorm-Terminal und führen den Befehl <code>npm install</code>aus.
* <code>web</code>: Der Inhalt dieses Ordners kommt später auf einen echten Web-Server.  
Damit werden dieselben Node.js-Pakete installiert wie im vierten Teil des Tutoriums.
* <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.
Löschen Sie die Datei <code>web/js/app.bundle.js</code> und
Damit Ihnen bei einem Commit diese Fehler nicht angezeigt werden, schließen Sie den Ordner
rufen Sie anschließend <code>grunt</code> auf, um diese Datei erneut zu erzeugen.
<code>web/js/lib</code>“ von der WebStorm-Fehlerüberwachung aus:
Wenn Sie jetzt die Datei <code>index.html</code> im Browser öffnen, sollte die
Web-Anwendung so wie in Teil 4 der Aufgabe funktionieren.


* Rechtklick auf den Ordner „<code>web/js/lib</code>“ → <code>Mar Directory as</code> → <code>Excluded</code>
==Verfeinerung der Webpack-Umgebung==
Zunächst benötigen Sie ein paar weitere Node.js-Module
<source lang="bash">
npm install --save-dev es6-autobind
npm install --save-dev node-sass sass-loader extract-text-webpack-plugin
</source>


Da in diesem Teil des Tutoriums fünf Web-Anwendungen mit jeweils unterschiedlichem Modularisierungsgrad
Im vierten Teil des Tutoriums wurde die Datei <code>initial.css</code> nicht von Webpack verwaltet,
erstellt werden, werden hier anstelle des Ordners „<code>web/js/app</code>“ fünf App-Ordner angelegt:
sondern direkt in das Verzeichnis <code>web/css</code> eingefügt. Damit liegt auch die
* <code>web/js/app1</code>: Aufteilung der App in mehrere Objekte und Dateien.
Verantwortung für die Komprimierung dieser Datei beim Programmierer. Das soll sich ändern.
* <code>web/js/app2</code>: Definition einer Klasse <code>Greet</code>.
Sie erweitern die Webpack-Konfiguration so, dass künftig auch diese Datei unter der Kontrolle
* <code>web/js/app3</code>: Reduktion der Konstanten in <code>Greet</code>.
von Webpack steht.  
* <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,
===SCSS===
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.
Anstelle von CSS-Dateien verwenden Sie künftig SCSS-Dateien. Das ist zunächst einmal nicht weiter schwer,
Daher ist es möglich, im Root-Verzeichnis des Projektes weitere projektspezifische Ordner und Dateien anzulegen wie „<code>conf</code>“ und „<code>doc</code>“,
das jede CSS-Datei automatisch auch eine SCSS-Datei ist. Bei der Verwendung von CSS können Sie
die später nicht auf den Web-Server, auf dem die Anwendung schließlich laufen soll, kopiert werden.
allerdings das Prinzip  „[[Don't repeat yourself]]“ (DRY) nicht beachten. Sie müssen für verschiedene Elemente
ständig Informationen wiederholen (gewünschter Font, Hintergrundfarbe, Größenangaben etc.). Deshalb
gibt es mehrere Projekte wie {{zB}} [[LESS]] und [[Sass]] , die den CSS-Standard erweitern, um dieses Problem
zu vermeiden. Sass verwendet allerdings eine ganz andere Syntax als CSS. Allerdings
unterstützt das Sass-Projekt auch die Sprache SCSS, die CSS einfach um weitere syntaktische Elemente ergänzt.  
Zum Beispiel kann man in SCSS Konstanten definieren:


==App1==
<source lang="CSS">
$background-color:  #C5EFFC;
</source>


Benennen Sie die Dateien „<code>index.html</code>“ und „<code>main.js</code>“ in
Und nun kann man in der CSS-Datei anstelle von  <code>#C5EFFC</code> stets  <code>&#36;background-color</code>
„<code>index1.html</code>“ und „<code>main1.js</code>“ um. (Im Laufe des Tutoriums kommen weitere Versionen dieser beiden Dateien dazu.)
schreiben, um die Hintergrundfarbe von bestimmten Elementen zu definieren. Wenn man nun dieses Farbe ändern will,
muss das nicht mehr bei allen Elementen einzeln passieren. Es reicht, die Farbe in der Konstantendefinition abzuändern.
Der CSS-Code ist nun DRY.


===Laden der CSS- und der JavaScript-Dateien===
Leider unterstützt kein Browser SCSS direkt. Aber für was gibt es Transpiler wie Webpack, die eine SCSS-Datei
automatisch in eine CSS-Datei übersetzen können.


Eine Web-Anwendung funktioniert nur – wie Sie bereits erfahren haben –, wenn nicht nur die HTML-Datei, sondern auch die zugehörigen
Ersetzen Sie in Ihrem Projekt die Datei <code>src/css/main.css</code> durch
CSS- und JavaScript-Dateien geladen werden. Allerdings gibt es dabei ein Problem: Die CSS- und JavaScript-Dateien werden im Laufe der
drei SCSS-Dateien: <code>_config.scss</code>, <code>initial.scss</code> und </code>app.scss</code>.
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.
In der Datei <code>_config.scss</code> werden alle von den anderen SCSS-Dateien benötigten Konstanten definiert.
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
Das heißt, das Prinzip „Konstanten gehören nicht in den Code, sondern in Konfigurationsdateien“ wird ab sofort beachtet.
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
Wenn man künftig am Layout einen Zeichensatz, eine Farbe, einen Hintergrund oder Ähnliches ändern will,  
vorzeitig.
reicht es, die entsprechenden Konstanten anzupassen und schon erzeugt Webpack automatisch aktualisiere
CSS-Dateien.


Besser wäre es daher andersherum vorzugehen: Es wird zuerst der <code>body</code>-Bereich geladen und dann die JavaScript- und die CSS-Dateien.
Die Datei <code>initial.scss</code> enthält möglichst wenig Code. Das ist Vorlage für die Datei <code>initial.bundle.css</code>,
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,
die im HTML-Head-Bereich geladen vor dem Body-Inhalt werden soll.
wenn er mit dem [[Rendern]] der Seite beginnt. Also verwendet er die browserspezifischen Defaultwerte. Das heißt, die Seite sieht zunächst
Die eigentlichen CSS-Informationen der Web-Anwendung werden in <code>app.scss</code> gespeichert. Die zugehörigen
ganz anders aus, als vom Designer geplant. Wenn dann die CSS-Dateien geladen wurden, wird die Seite erneut gerendert und verändert ihr Aussehen.
CSS-Anweisungen werden von Webpack wie gehabt in <code>app.bundle.js</code> integriert.
Auch das ist verwirrend und wirkt unprofessionell.


Was also machen?
'''<code>src/css/_config.scss</code>'''
<source lang="CSS">
$font-family-sans:  Verdana, Helvetica, sans-serif;
$font-family-serif: "Times New Roman", Times, serif;
$background-color:  #C5EFFC;
</source>


===CSS-Dateien===
Diese Datei kann nun mit Hilfe des SCSS-Befehls <code>@import</code>  
Für CSS-Dateien empfiehlt Google ernsthaft, die Link-Element ganz ans Ende der HTML-Datei zu stellen, also '''nach''' dem
in die anderen beiden  SCSS-Dateien eingefügt werden. Webpack (genauer gesagt das SASS-Modul von Webpack)
schließenden <code>html</code>-Tag.
kennt diesen Befehl und führt im beim Erstellen der zugehörigen CSS-Dateien auch aus.
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,
Der Import-Befehl ist hier notwendig, da in der Datei <code>initial.scss</code> die Konstante <code>&#36;background-color</code>
dass diverse Browser diese Syntax verstehen, korrekt ist sie trotzdem nicht. Der zweite Vorschlag hat zur Folge, dass Struktur (HTML) und Layout (CSS)
verwendet wird. Ansonsten enthält dies Datei nur den schon bekannten CSS-Code.
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.
'''<code>src/css/initial.scss</code>'''
<source lang="CSS">
@import 'config';


In unserer Anwendung haben wir bislang alles richtig gemacht, bis auf die fehlende automatische Komprimierung.
body
Diese wird im [[HTML5-Tutorium: JavaScript: Hello World 05|fünften Teil des Tutoriums]] beschrieben.
{ background-color: $background-color;
}


===JavaScript-Dateien===
.hidden
{ display: none;
}
</source>


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.
Die Datei <code>app.scss</code> ist deutlich umfangreicher. Daher soll der zugehörige CSS-Code
ja auch erst gemeinsam mit der Datei  <code>app.bundle.js</code> geladen werden. Das meiste ist üblicher
CSS-Code, den Sie schon im vierten Teil des Tutoriums kennengelernt haben. Es gibt ein paar neue
Elemente, wie die CSS-Klassen-Elemente <code>.left</code> und <code>.right</code>, die in der HTML-Datei dieses
Tutoriums zusätzlich verwendet werden. Außerdem sehen Sie in der Datei noch ein  paar weitere SCSS-Elemente. Diese werden
im Anschluss an den folgenden code beschrieben.


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:
'''<code>src/css/app.scss</code>'''
<source lang="CSS">
@import 'config';


<source lang="html5">
html, body
  ...
{ height:          100%;
  <script type="text/javascript" src="js/main1.js"></script>
  font-size:        2vw !important;
</body>
  font-family:      $font-family-sans;
</source>
  background-color: $background-color;
}


===Globale Funktionen als Methoden===
html
{ display: table;
  width:  100%;
}


Grundsätzlich sollten so wenige globale Funktionen wie möglich in einen JavaScript-Programm definiert werden,
body
um Namenskonflikte mit JavaScript-Modulen und -Bibliotheken anderer Autoren zu vermeiden.
{ display:          table-cell;
In der Hello-World-Anwendung gibt es drei globale Funktionen: „<code>sayHello</code>“, „<code>sayHelloOnEnter</code>“ und „<code>init</code>“.
  text-align:      center;
Die ersten beiden werden in [[Methode]]n umgewandelt. Das heißt, es wird ein Objekt „<code>greet</code>“ erstellt, das diese beiden Methoden
  vertical-align:   middle;
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>“
h1
ein ('''vor''' dem Skript-Element, das „<code>main1.js</code>“ lädt):
{ padding-bottom: 0;
  margin-bottom: 0;
}


<source lang="html5">
#section_form
  ...
{ text-align:  center;
   <script type = "text/javascript" src = "js/app1/greet.js"></script>
   width:        21em;
   <script type = "text/javascript" src = "js/main1.js"    ></script>
   margin-left:  auto;
</body>
  margin-right: auto;
</source>


Fügen Sie in die Datei „<code>greet.js</code>“ ein Objekt ein, dass die beiden Methoden
  h1
„<code>sayHello</code>“ und „<code>sayHelloOnEnter</code>“ enthält.
  { font-size:    200%;
    margin-bottom: 0.5ex;
  }
}


<source lang="javascript">
#section_hello
/**
{ h1
* @namespace greet
   { font-size: 270%;
* 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');
      },


    /**
p, label
    * An keyboard event observer. It tests whether the enter key has been pressed.
{ font-family: $font-family-serif;
    * If so, the greet method is activated. Default reactions of the browser are
   font-size:   100%;
    * 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>


In der Datei  „<code>main1.js</code>“ werden dagegen alle globalen Funktionen entfernt.
label, input, button
Dies gilt auch für die Definition des Eventlisteners am Ende der Datei, der dafür sorgt, dass die
{ width:        10em;
Funktion „<code>init</code>“ ausgeführt wird, sobald die HTML-Seite sowie alle Unterseiten vollständig geladen wurden.
  font-size:    100%;
  display:      inline-block;
  box-sizing:    border-box;
  margin-bottom: 0.5ex;
}


Die Funktion „<code>init</code>“ wird durch eine anonyme Funktion ersetzt, die '''sofort''' nach ihrer Definition auch ausgeführt wird.
label
Da die Datei „<code>main1.js</code>“ als Letztes geladen wird, sind alle Objekte, auf die
{ text-align: right;
während der Initialisierung zugegriffen werden muss, bereits vorhanden.
}


<source lang="javascript">
@mixin panel()
// Initialize the app.
{ padding: 0;
(function()
   width:  50%;
{
}
   document.getElementById('button_submit')
          .addEventListener('click', greet.sayHello);


   window.addEventListener('keydown', greet.sayHelloOnEnter);
.left
}());
{ float: left;
   @include panel();
}
.right
{ float: right;
  @include panel();
}
</source>
</source>


Testen Sie Ihre Anwendung und speichern Sie sie zu guter Letzt im Repository.
In diese SCSS-Datei werden folgende SCSS-Erweiterungen verwendet:


==App2==
* Import-Befehl: <code>@import 'config';</code>
* Konstanten:  <code>&#36;font-family-sans</code>,  <code>&#36;font-family-serif</code>,  <code>&#36;background-color</code>
* Verschachtelte Anweisungen: {{zB}} <code>h1</code> innerhalb von  <code>#section_form</code>
* Mixins, um eine Liste von Attributen nur einmal zu definieren und und mehreren Elemente wieder verwenden zu können.


Ein wesentliches [[Programmierprinzip]] ist das Prinzip der „Wiederverwendbarkeit“.
Die verschachtelten Anweisungen strukturieren den CSS-Code deutlich besser. Es ist nicht mehr
Man sollte Code so schreiben, dass man ihn möglichst häufig wiederverwenden kann.
notwendig einen Selektor wie <code>#section_form</code> mehrfach zu verwenden.
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
Sehen Sie sich den Unterschied an. In der Datei <code>main.css</code> vom vierten Teil des Tutoriums hatten Sie
ein Objekt  „<code>greet</code>“, das nicht für mehrere Anwendungen parallel eingesetzt werden kann.
folgendes geschrieben:
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:
<source lang="CSS">
#section_form
{
  text-align:   center;
  width:        21em;
  margin-left:  auto;
  margin-right: auto;
}


* <code>index1.html</code> → <code>index2.html</code>
#section_form > h1
* <code>js/main1.js</code> → <code>js/main2.js</code>
{
* <code>js/app1/greet.js</code> → <code>js/app2/greet.js</code>
  font-size:    200%;
  margin-bottom: 0.5ex;
}
</source>


In der Datei „<code>index2.html</code>“ müssen Sie dafür sorgen, dass
Die SCSS-Variante ist deutlich strukturierter und prägnanter.
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 Konstruktorfunktion  „<code>Greet</code>“ ersetzt:
#section_form
{ text-align:  center;
  width:        21em;
  margin-left: auto;
  margin-right: auto;


<source lang="javascript">
  h1
/**
  { font-size:    200%;
* @class
    margin-bottom: 0.5ex;
* @classdesc Contains methods to say hello to the user of the web app.
  }
*/
}
function Greet()
</source>
{
  var l_this = this; // this-hack: l_this contains the new object


  /**
Insbesondere bei großen CSS-Dateien erweist sich diese kompaktere Schreibweise als vorteilhaft.
  * 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');
Ein weiteres Element, das nur SCSS bietet sind die Mixins. Sie definieren zunächst ein Mixin,
    document.getElementById('section_hello').classList.remove('hidden');
das eine Folge von CSS-Attributen zusammenfasst. (Auch hier könnten wieder Verschachtelungen und
  };
andere SCSS-Erweiterungen verwendet werden!)


  /**
<source lang="CSS">
  * An keyboard event observer. It tests whether the enter key has been pressed.
@mixin panel()
  * If so, the greet method is activated. Default reactions of the browser are
{ padding: 0;
  * disabled.
   width:   50%;
  * @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>


In JavaScript kann jede Funktion auch als Konstruktor, {{dh}}
Und nun können Sie dieses Mixin in diversen andere Elemente einbinden:
zum Erzeugen von neuen Objekte verwendet werden, indem man das Schlüsselwort
 
<code>new</code>“ voranstellt:
<source lang="CSS">
.left
{ float: left;
  @include panel();


<source lang="javascript">
.right
var l_greet = new Greet();
{ float: right;
  @include panel();
}
</source>
</source>


In einer Konstruktorfunktion wird üblicherweise kein <code>return</code>-Befehl eingefügt,
In normalen CSS-Code, müssten Sie denselben Code Non-DRY formulieren:
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
<source lang="CSS">
zugegriffen werden. Dem neu erstellten Objekt werden mittels
.left
„<code>this.sayHello = ...</code>“ und  „<code>this.sayHelloOnEnter = ...</code>“
{ float:  left;
etc. [[Attribut (Objekt)|Attribute]] zugewiesen, die Funktionen (welche [[Methode]]n genannt werden) als Werte enthalten.
  padding: 0;
Dem neu erstellten Objekt werden dieselben Methoden zugewiesen, wie dem Objekt „<code>greet</code>“ der Anwendung „App1“.
  width:  50%;
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.
.right
Das Objekt <code>this</code> wird in einer lokalen Variablen <code>l_this</code> gespeichert.
{ float:  right;
Und anschließend wird diese lokale Variable anstelle von <code>this</code> benutzt.
  padding: 0;
Für die Definition der Attribute „<code>sayHello</code>“ und  „<code>sayHelloOnEnter</code>“ macht es keinen Unterschied,
  width:  50%;
ob man „<code>this.sayHello = ...</code>“ oder „<code>l_this.sayHello = ...</code>“ schreibt.
}
</source>


Allerdings wird auch innerhalb des Methodenrumpfes von „<code>sayHelloOnEnter</code>“ auf das neu erstellt
Wenn Sie hier das Layout der Panels ändern wollten (andere Breite, andere Ränder etc.), müssen
Objekt zugegriffen: <code>l_this.sayHello();</code>. Und hier könnte nun nicht <code>this.sayHello();</code>
Sie den CSS-Code von jedem Panel einzeln ändern. In der SCSS-Datei erfolgt die Änderung dagegen DRY: Sie ändern einfach den Mixin-Code ab.   
verwendet werden. Der Grund ist, dass der Methodenrumpf erst ausgeführt wird, wen die Methode „<code>sayHelloOnEnter</code>“
zu irgendeinem anderen Zeitpunkt von irgendeinem anderen Objekt aufgerufen wird. Und  <code>this</code> bezeichnet
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>“
Wenn die aktuellen Browser SCSS verstehen würden, könnte sie <code>initial.scss</code> und <code>app.scss</code> direkt
einfach die globale Variable Methode „<code>greet</code>“ verwendet hat, um auf die Methode „<code>sayHello</code>
vom HTML-Code aus laden. Allerdings geht das derzeit leider nicht. Somit muss Webpack angewiesen werden, CSS-Code aus den
zuzugreifen (<code>greet.sayHello();</code>). Aber ein globales Greet-Objekt gibt es nun nicht mehr. Es gibt nur noch einen
SCSS-Dateien zu erzeugen. Dies ist mit den beiden Node.js-Modulen <code>node-sass</code> und <code>sass-loader</code>,
globale Konstruktorfunktion „<code>Greet</code>“, mit der beliebig viele Greet-Objekte erzeugt werden können.
die Sie bereits installiert haben, ganz einfach möglich.
'''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 This-Hack unsauber. Besser wäre es, die Methode „<code>bind</code>“ zu verwenden
Ersetzen Sie in der Datei <code>webpack.config.js</code> folgenden Code
(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]):


<source lang="javascript">
<source lang="javascript">
this.sayHelloOnEnter =
{ test:  /\.css$/,
   function(p_event)
   use:    [ 'style-loader',
  {
            { loader: 'css-loader', options: { minimize: true } }
    ...
          ]
      this.sayHello();
},
    ...
  }.bind(this);
</source>
</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
durch folgenden:


<source lang="javascript">
<source lang="javascript">
var l_greet = new Greet();
{ test:  /\.(css|scss|sass)$/,
  use:    [ 'style-loader',
            { loader: 'css-loader', options: { minimize: true } },
            'sass-loader'
          ]
}
</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.
==App3==


Leider bringt uns hinsichtlich der Wiederverwendbarkeit die „Verbesserung“, die wir in App2 gegenüber App1
Der Test wird so erweitert, dass diese Regel jetzt nicht nur für die Verarbeitung von Dateien mit der
vorgenommen haben, gar nichts. Wir können jetzt zwar beliebig viele <code>greet</code>-Objekte
Endung <code>.css</code> zuständig ist, sondern auch für Dateien mit den Endungen
mit Hilfe von „<code>new Greet()</code>“ erstellen.
<code>.sass</code> und <code>.scss</code>.
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:
Die Liste der zugehörigen Loader wird einfach um einen Loader erweitert:  <code>sass-loader</code>.
Diese Liste wird von hinten nach vorne abgearbeitet. Das heißt, eine Datei wie beispielsweise <code>app.scss</code>
wird erst mit Hilfe des Sass-Loaders in eine CSS-Datei umgewandelt, diese wird mit Hilfe des CSS-Loaders komprimiert
und das Ergebnis dieser Datei wird mit Hilfe des Style-Loaders in die zugehörige JavaScript-Ausgabedatei integriert.


* <code>index2.html</code> → <code>index3.html</code>
Jetzt müssen nur noch die zugehörigen JavaScript-Dateien erstellt werden.
* <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
Im <code>entry</code>-Objekt der Datei <code>webpack.config.js</code> ist bereits die Datei <code>src/js/app.js</code>
die neuen JavaScript-Dateien anstelle der alten geladen werden.
eingetragen. Für Sie wird von Webpack die Datei <code>web/js/app.bundle.js</code> erzeugt.  
Und passen Sie auch wieder den Titel im HTML-Header an.


Ersetzen Sie nun die Konstruktorfunktion der Datei „<code>js/app3/greet.js</code>
Der Name der Zieldatei wurde im <code>output</code>-Objekt von <code>webpack.config.js</code> spezifiziert.
durch folgende Konstruktorfunktion:
'''Achtung:''' Überprüfen Sie, ob das <code>output</code>-Objekt folgendermaßen definiert wurde.


<source lang="javascript">
<source lang="javascript">
/**
   output:
* @class
   { filename: 'js/[name].bundle.js',
* @classdesc Contains methods to say hello to the user of the web app.
     path:    absolutePath('web')
*
* @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,
Damit diese Zieldatei insbesondere den komprimierten Code der Datei <code>src/css/app.scss</code> enthält,
in denen diese Werte dem Konstruktor übergeben werden können, wie {{zB}} <code>p_input_name</code>. Der Zugriff auf das globale Objekt
muss <code>src/js/app.js</code> leicht modifiziert werden.
„<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 in dieser Datei den Import-Befehl


Ersetzen Sie zunächst die Initialisierungsmethode in der Datei  „<code>main3.js</code>
<source lang="javascript">
durch folgenden Code:
import '../css/main.css';
</source>
durch den Import-Befehl


<source lang="javascript">
<source lang="javascript">
(function(p_window, Greet)
import '../css/app.scss';
{
  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>
Die Datei <code>web/css/main.css</code> gibt es ja nicht mehr; sie wurde durch
sowie die von uns definierte Konstruktorfunktion „<code>Greet</code>“. Diese beiden globalen Größen werden der
<code>web/css/app.scss</code> ersetzt.
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,
Damit auch <code>web/css/initial.scss</code> durch webpack verwaltet wird, benötigen wir eine zweite
explizit übergeben. Dies ist zum einen das HTML-Dokument, das mit Hilfe der von ihm erstellten
JavaScript-Datei.
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
'''src/js/initial.js'''
im Repository zu speichern.
<source lang="javascript">
import '../css/initial.scss';
</source>


Anschließend sollten Sie in Ihrem WebStorm-Projekt folgende Kopien erstellen:
Sie sehen das richtig, die Datei <code>src/js/initial.js</code> enthält eine einzige Code-Zeile.
Sie erinnern sich? Webpack dient eigentlich nur dazu JavaScript-Dateien zu packen. Also geben wir diesen
Tool eine JavaScript-Datei, die die gewünschte CSS-Datei einfach mittels eine ES6-Import-Befehls importiert (ES6 = EcmaScript 6).
Fügen Sie nun in das <code>entry</code>-Objekt der Datei <code>webpack.config.js</code> vor der Zeile


* <code>index3.html</code> → <code>index3b.html</code>
<source lang="javascript">
* <code>js/main3.js</code> → <code>js/main3b.js</code>
app: absolutePath('src/js/app.js')
</source>
folgende zweite Zeile ein:


Die Datei  „<code>index3b.html</code>“ soll abermals die JavaScript-Datei „<code>js/app3/greet.js</code>“
<source lang="javascript">
verwenden, dafür aber „<code>js/main3b.js</code>“ an Stelle von  „<code>js/main3.js</code>“ laden.
initial: absolutePath('src/js/initial.js')
</source>


Fügen Sie in der Datei „<code>index3b.html</code>“ in alle ID-Attribut-Bezeichner eine <code>1</code> ein:
Beachten Sie, dass in einem JavaScript-Objekt die Attribute durch Kommas voneinander getrennt werden. Fügen Sie also
<code>section_form</code> → <code>section1_form</code> etc.
an passender Stelle auch noch ein Komma ein.


Schließen Sie dann die beiden Sections „<code>section1_form</code>und <code>section1_hello</code>
Wenn Sie jetzt Grunt aufrufen, sollten zwei Dateien generiert werden: <code>web/js/app.bundle.js</code> und <code>web/js/initial.bundle.js</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.
Löschen Sie nun den Ordner <code>web/css</code> (die darin befindliche Datei <code>initial.css</code> wird nicht mehr benötigt)
Das ist nicht weiter verwunderlich, da Sie ja insbesondere alle ID-Attribut-Bezeichner dupliziert haben.
und ersetzen Sie in der Datei <code>index.html</code> die Zeile
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
<source lang="html">
angestrebten Test, ob Sie die Hello-World-App in einem HTML-Dokument zweimal starten können, ist das allerdings irrelevant.
<link rel = "stylesheet" type = "text/css" href = "css/initial.css"/>
</source>


Passen Sie nun Ihre Datei „<code>main3b.js</code>“ an die neuen Gegebenheiten an:
durch folgende Zeile:
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>
<source lang="html">
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>]
<script type = "text/javascript" src = "js/initial.bundle.js"></script>
</source>


==App4==
Wenn Sie jetzt die Datei <code>index.html</code> im Browser öffnen, sollte sie wieder genauso funktionieren wie zuvor.
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.
'''Anmerkung''': Die Datei <code>src/js/initial.js</code> könnte natürlich noch mehr machen, als einfach nur eine CSS-Datei zu laden.
# Die Anzahl der Parameter der Konstruktorfunktion ist zu groß.
Sie könnte beispielsweise eine kleine Animation einblenden (die berühmte Sanduhr, nur etwas moderner), die solange läuft, bis
# In den Initialisierungsdateien ist immer noch zu viel Anwendungscode enthalten.
die eigentliche Web-Anwendung, {{dh}} <code>app.bundle.js</code> vollständig geladen wurde und die Animation wieder beendet.
# Die potentielle Zahl der globalen Größen ist noch zu hoch.


Diese Probleme werden folgendermaßen gelöst:
===Erzeugen einer CSS-Datei===


# Die Konstanten werden in eine [[JSON]]-Datei ausgelagert, die im ersten Schritt von der jeweiligen Initialisierungsdatei eingelesen wird.
Zurzeit enthält die JavaScript-Datei <code>web/js/initial.bundle.js</code> nur eine komprimierte CSS-Datei und etwas Code,
# Der Konstruktorfunktion wird das gesamte JSON-Objekt mit allen Initialisierungswerten in einem einzigen Parameter übergeben.
um diesen in das HTML-Dokument einzufügen. Die Google-Suche bewertet HTML-Dateien, die sauberen CSS-Code {{iAllg}} enthalten,
# 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.
höher, als HTML-Datei, die nur JavaScript-Code enthalten. Daher wäre es in diesem Fall sinnvoll, die komprimierte
# Es wird '''ein''' globales Objekt namens  „<code>hello</code>“ erstellt, das die gesamte Web-Anwendung enthält.
CSS-Datei direkt in den HTML-Code einzubinden. Das ist aber gar nicht so einfach, da Webpack keine CSS-Dateien erstellt,
sondern nur JavaScript-Dateien. Aber mit einem kleine Hack funktioniert das trotzdem.


Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihrer Anwendungsdateien:
Sie haben vor auch noch die Node.js-Module <code>extract-text-webpack-plugin</code>
installiert. Mit diesem Sie eine CSS-Datei aus einer JavaScript-Datei extrahieren.


* <code>index3b.html</code> → <code>index4.html</code>
Ersetzen Sie zunächst in der Datei <code>webpack.config.js</code> den Test
* <code>js/main3b.js</code> → <code>js/main4.js</code>
<source lang="javascript">
* <code>js/app3/greet.js</code> → <code>js/app4/greet.js</code>
test:  /\.(css|scss|sass)$/,
</source>
durch
<source lang="javascript">
test:  /app\.(css|scss|sass)$/,
</source>


Im Folgenden wird die HTML-Datei <code>index4.html</code>
Damit erreichen Sie dass diese Regel jetzt nur noch für Dateien funktioniert, die auf <code>app.css</code> oder
als HTML-[[Template]] verwendet, {{dh}} als Strukturrahmen. '''Sämtliche''' Texte,
<code>app.scss</code> oder <code>app.sass</code> enden. Die Datei <code>src/css/app.scss</code> in die
die in diesem Dokument enthalten sind, werden dynamisch mittels JavaScript eingefügt.  
Datei <code>src/js/app.bundle.js</code> integriert werden.


In der Datei <code>index4.html</code>“ müssen Sie also nicht nur dafür sorgen, dass
Laden Sie zunächst die ExtractTextPlugin-Erweiterung, indem Sie in die Datei <code>webpack.config.js</code>  
die neuen JavaScript-Dateien anstelle der alten geladen werden und dass der Titel angepasst wird.
folgende zweite Zeile einfügen:
Sie müssen auch sämtliche Text-Elemente mit eindeutigen ID-Attributen versehen.
<source lang="javascript">
 
const ExtractTextPlugin = require('extract-text-webpack-plugin');
Die HTML-Datei der Musterlösung sieht folgendermaßen aus. Sie können auch Ihre
</source>
eigene Datei verwenden, müssen dann aber die JSON-Datei, die anschließend erstellt wird,
entsprechend anpassen.


Für die Datei <code>src/css/initial.scss</code> fügen Sie nun in die Liste mit den Regeln eine weitere Regel  ein (Komma nicht vergessen):
<source lang="javascript">
<source lang="javascript">
<!DOCTYPE html>
{ test:   /initial\.(css|scss|sass)$/,
<html lang="en">
   use:   ExtractTextPlugin.extract
 
        ({fallback: "style-loader",
<head>
          use:      [{ loader: 'css-loader', options: { minimize: true } },
   <meta charset = "UTF-8">
                      'sass-loader'
  <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>
</source>


===Modularisierung===
Diese benutzt das zuvor erwähnte Plugin, um die komprimierte CSS-Datei aus der Datei <code>src/js/initial.bundle.js</code>
zu extrahieren. Das erledigt sie aber nur fast. Man muss dem Plugin noch mitteilen wohin Sie die CSS-Datei speichern soll.


Um möglichst wenige globale Größen definieren zu müssen, legt man für eine
Dazu müssen Sie die bislang leere Plugin-Liste am Ende der Datei
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">
<source lang="javascript">
/** @namespace hello */
plugins:
var hello =
[]
(function(p_hello)
{ "use strict";
 
  // anwendungsspezifischer Code
 
  return p_hello;
}(hello ? hello : {}));
</source>
</source>


In der Variablen „<code>hello</code>“ wird das Ergebnis eines Aufrufs einer anonymen Funktion gespeichert.
mit Inhalt füllen:
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">
<source lang="javascript">
/** @namespace hello */
plugins:
var hello =
[ new ExtractTextPlugin("css/[name].bundle.css") ]
(function(p_hello)
</source>
{ "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)
Führen Sie nun <code>grunt</code> aus. Es sollte die Datei <code>web/css/initial.bundle.css</code>
        .classList.remove(l_init_hidden);
erzeugt worden sein.
      p_document.getElementById(l_init_elements.sectionForm)
        .classList.add(l_init_hidden);
    };


Nun müssen sie in der Datei <code>index.html</code> die Zeile


    /**
<source lang="html">
    * An keyboard event observer. It tests whether the enter key has been pressed.
<script type = "text/javascript" src = "js/initial.bundle.js"></script>
    * 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:
wieder durch eine CSS-Link-Zeile ersetzen (da ja in der Datei <code>initial.bundle.js</code> kein sinnvoller Inhalt mehr enthalten ist;
 
sie wurde durch das Extract-Text-Plugin „ausgelutscht“).
* 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;
<source lang="html">
}(hello ? hello : {}));
<link rel = "stylesheet" type = "text/css" href = "css/initial.bundle.css"/>
</source>
</source>


Sie ist wieder aufgebaut wie zuvor beschrieben: Sie definiert, falls notwendig, das globale Objekt „<code>hello</code>“
Sie könnten nun noch die überflüssige Datei <code>web/js/initial.bundle.js</code> von webpack wieder entfernen lassen.
und fügt anschließend eine Funktion „<code>init</code>“ in dieses Objekt ein. Die Methode „<code>hello.init</code>
Wie das geht entnehmen Sie bitte der Seite https://stackoverflow.com/questions/37408873/delete-or-not-create-a-file-for-each-entry-in-webpack
hat dieselben Parameter, wie die zuvor definierte Konstruktorfunktion und erwartet denselben Input.  
sowie der [https://glossar.hs-augsburg.de/beispiel/tutorium/es6/hello_world/WK_HelloWorld05/ Musterlösung]. Wichtig ist das nicht.
Ihre Aufgabe ist es, die derzeit noch leeren Text-Elemente des HTML-Templates mit Inhalt zu füllen
Wenn Sie später mal eine kleine Warteanimation erstellen sollten, bräuchten Sie dieses Datei sowieso wieder.
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===
==JSON==
Zur Initialisierung der Web-Anwendung werden JSON-Dateien benötigt.


Eine JSON-Datei enthält genau ein JavaScript-Objekt in textueller Darstellung.
Bislang haben sie den CSS-Code unter Webpack-Kontrolle gestellt und damit DRY gemacht.  
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>:
Als nächstes gehen wir das nächste Problem an: Ihr Code enthält ebenfalls Konstanten und verstößt damit gegen das
* Nullwert: <code>null</code>
[[Programmierprinzipien|Programmierprinzip]] „Konstante Werte gehören nicht in den Code, sondern in Konfigurationsdateien.“
* 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
Eine Ausnahme bilden triviale Konstanten wie <code>0</code>, <code>1</code>, <code>null</code>, <code>true</code> oder <code>false</code>, sofern sie mit großer Sicherheit nie im Code geändert werden müssen. Aber schon eine Konstante wie
Strings immer in doppelte Anführungszeichen eingeschlossen sein, einfache Anführungszeichen sind nicht erlaubt.
<code>3.1415926</code> gehört nicht in den Code. Man verwendet stattdessen besser <code>Math.PI</code> und erhält damit
Attributnamen werden ebenfalls durch Strings dargestellt. Auch diese müssen gemäß ECMA-Standard in doppelte
die maximal mögliche Stellenzahl. (Eventuell hängt diese von der Rechnerplattform ab.)
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:
In JavaScript-Anwendungen verwendet man [[JSON]] zur Speicherung von (konstanten) Daten und zur Initialisierung der Anwendung.
Wenn ein Spiel mehrere Level hat, wir jedes Level üblicherweise in einer eigenen JSON-Datei beschrieben. Eine Level-Designerin muss
im Optimalfall nicht mehr machen, als die Assets (Grafiken, Töne, 3D-Modell etc.) zu erstellen und mit Hilfe einer
JSON-Datei die Zusammenhänge zwischen den einzelnen Elementen herstellen. Es sollte nicht notwendig sein, dass sie
den Programmcode modifizieren muss, da das Level-Design schon aufwändig genug ist.


<source lang="javascript">
JSON (JavaScript Object Notation) ist ein einfaches Datenformat, mit dessen Hilfe sich komplexe Objekte sehr einfach beschreiben lassen.
{ a: 55, 'b': 'Wolfgang' }
Eine Alternative wäre [[XML]], allerdings ist diese Auszeichnungsprache deutlich komplexer. (Ein Beispiel für XML ist
</source>
XHMLT 1.0<ref>XHTML 1.0: https://www.w3.org/TR/2002/REC-xhtml1-20020801/</ref>, eine HTML-Variante.)


In JSON muss dieses Objekt folgendermaßen geschrieben werden:
Eine JSON-Datei ist im Prinzip eine JavaScript-Datei, die einen der folgenden Datentypen enthält<ref>http://www.json.org/json-de.html</ref>:


<source lang="javascript">
* Nullwert (<code>null</code>)
{ "a": 55, "b": "Wolfgang" }
* Boolescher Wert (<code>true</code> oder <code>false</code>)
</source>
* Zahl (Ziffernfolge mit optionalem Vorzeichen und optionalen Exponenten)
* Zeichenkette (beliebige UTF-8-Zeichen, die in doppelten geraden Anführungszeichen (<code>"</code>) eingeschlossen sind)
* Array (eine durch Kommata getrennte Listen von null oder mehr JSON-Elementen; die Liste beginnt mit <code>[</code> und endet mit <code>]</code>)
* Objekt/Hasharray/Paarliste (eine durch Kommata getrennte Listen von null oder mehr Paaren; die Liste beginnt mit <code>{</code> und endet mit <code>}</code>; jedes Paar besteht aus einer Zeichenketten, die als Schlüssel fungiert, gefolgt von einem Doppelpunkt  (<code>:</code>) gefolgt von einem beliebigen JSON-Element; in einem Objekt darf derselbe Schlüsselwert nicht zweimal vorkommen)


Diese Objekt ist auch ein korrektes JavaScript-Objekt. Da die Syntaxregeln von JSON strenger sind
Beachten Sie, dass die eine [[Rekursion|rekursive]] Definition ist: Arrays dürfen andere Arrays und Objekte enthalten, Objekte dürfen
als von JavaScript, gilt folgende Regel: Jedes JSON-Objekt ist auch ein JavaScript-Objekt, aber nicht
andere Objekte und Arrays enthalten. JSON-Elemente können also beliebig tief geschachtelt sein.
jedes JavaScript-Objekt ist auch ein JSON-Objekt.


Ein Problem bereitet die Verwendung von JSON. Da ein JSON-Objekt beliebige Attribute enthalten kann,
Andere Werte dürfen in einer JSON-Datei nicht enthalten sein. Das heißt, es gibt keine einfachen geraden Anführungszeichen (außer '''innerhalb''' von Zeichenketten), keine Kommentare, keine Funktionen etc. '''Insbesondere überflüssige Kommas sind nicht erlaubt!'''
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].
Beachten Sie bitte, dass jeden JSON-Datei auch eine
Aber darauf wird zunächst verzichtet.
JavaScript-Datei ist (die Umkehrung dieser aussage gilt nicht.). Wenn man eine JSON-Datei auswerten würde, erhielte man jedoch kein anderes Ergebnis als den Wert oder das Objekt, das in der Datei gespeichert ist.


Legen Sie für die zuvor definierte HTML-Datei „<code>index4.html</code>“ folgende JSON-Initialisierungsdatei
Erstellen Sie in Ihrem Projekt  folgende zwei JSON-Dateien:
unter dem Namen „<code>web/json/hello_de.json</code>“ an:


'''src/json/config_de.json'''
<source lang="javascript">
<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":
   "text":
   {
   {
     "title":   "Hallo, Fremder!",
     "title":   "Hallo, Fremder!",
     "query":   "Wie heißen Sie?",
     "question": "Wie heißen Sie?",
     "reset":   "Reset",
     "reset":   "Reset",
     "submit": "Begrüßung",
     "submit":   "Begrüßung",
     "hello":   "Hallo, $1!",
     "hello":   "Hallo, $1!",
     "welcome": "Willkommen bei Multimedia-Programmierung!"
     "welcome": "Willkommen bei Multimedia-Programmierung!"
   },
   },


Zeile 854: Zeile 543:
</source>
</source>


Diese Datei besteht aus einem anonymen Objekt. Es enthält seinerseits drei Objekte  „<code>HTMLElements</code>“,
'''src/json/config_en.json'''
„<code>text</code>“ und „<code>css</code>“, die für die Initialisierung der Web-Anwendung gedacht sind.
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.
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
Stelle geschrieben werden sollen. Der Text  „<code>hello</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
zu HTML-Elementen hinzugefügt oder aus HTML-Elementen entfernt werden. Der Bezeichner, der der Anwendung bekannt ist
(„<code>hidden</code>“), stimmt in diesem Fall mit dem Namen der zugehörigen CSS-Klasse überein. Das ist nicht verboten.
 
Erstellen Sie analog die Datei „<code>web/json/hello_en.json</code>“. Diese Datei soll für die zweite Web-Anwendung
definiert werden, die allerdings englischsprachige Texte enthält (Sie erinnern sich? In <code>index4.html</code>
laufen zwei unabhängige Web-Anwendungen parallel).
 
===Das Laden der JSON-Dateien===
 
Für das [[asynchron]]e Laden von beliebigen Dateien von einem Web-Server gibt es in JavaScript den Befehl „<code>XMLHttpRequest</code>“.
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/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 :-) –
im Modul  „<code>wk.ajax</code>“ mehrere Methoden zum Laden von Dateien bereit.  Zum Laden von JSON-Dateien
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).
 
Die Methode „<code>getJSON</code>“ erwartet drei Argumente, einen URI, der den Speicherort der JSON-Datei beschreibt und zwei Callback-Funktionen.
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:
 
<source lang="javascript">
// Initialize the app.
(function(p_window, p_wk, p_hello)
{ "use strict";
 
  // The list of all json files to be loaded
  var l_json_files = ['json/hello_de.json', 'json/hello_en.json']; 
 
  // For each element in the JSON file list:
  for (var i = 0, n = l_json_files.length; i < n; i++)
  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
und reicht jeweils das daraus erzeugte JSON-Objekt  „<code>main4.js</code>“ an die Initialisierungsfunktion der Web-Anwendung weiter.
Beachten Sie, dass die Reihenfolge, in der die beiden JSON-Dateien geladen wird, nicht feststeht. Sie werden parallel geladen. Sobald eine
Datei vollständig geladen wurde, wird die zugehörige Callback-Funktion
 
<source lang="javascript">
<source lang="javascript">
function(p_init) { p_hello.init(p_window, p_init); }
{
</source>
  "text":
  {
    "title":    "Hello, Stranger!",
    "question": "What's your name?",
    "reset":    "Reset",
    "submit":  "Say hello",
    "hello":    "Hello, $1!",
    "welcome":  "Welcome to Multimedia Programming!"
  },


ausgeführt. Dieser wird im Parameter „<code>p_init</code>“ das zugehörige JSON-Objekt überreicht.
  "css":
Sie reicht es (zusammen mit die Window-Objekt) an die Methode „<code>p_hello.init</code>“ weiter.
 
'''Anmerkung''': Man könnte die beiden JSON-Dateien natürlich auch mittels zweier Aufrufe von „<code>getJSON</code>“
laden:
 
<source lang="javascript">
// Initialize the app.
(function(p_window, p_wk, p_hello)
{ "use strict";
 
  p_wk.ajax.
    getJSON
    ( '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.
    getJSON
    ( '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.
 
==App5==
 
Im Prinzip kann die in App4 vorgestellte Lösung zur Modularisierung einer Web-Anwendung
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.)
* 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.
 
Schöner wäre es, wenn JavaScript die Modularisierung von sich aus unterstützen würde. Das ist tatsächlich geplant. In EcmaScript 6
gibt es zu diesem Zweck die Befehle „<code>[https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/import import]</code>“
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.
 
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:
 
* <code>index4.html</code> → <code>index5.html</code>
* <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 ...
    "hidden": "hidden"
   }
   }
);
}
</source>
</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).
Importieren Sie die erste der beiden JSON-Dateien in Ihre Datei <code>src/js/app,js</code>,
Allerdings wird jedes Modul nur einmal geladen. Das heißt, wenn zwei Module dasselbe Modul benötigen, wird dieses Modul trotzdem
indem Sie folgenden Import-Befehl einfügen:
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">
<source lang="javascript">
define
import * as config from '../json/config_de.json';
({
    // Inhalt des Objekts, den das Modul definiert.  
});
</source>
</source>


Wenn das Modul lediglich eine Funktion, wie {{zB}} eine Konstruktorfunktion „<code>C</code>“ zur Verfügung stellen soll, schreibt man
In  <code>src/js/app/greet,js</code> fügen Sie folgenden
 
Import-Befehl ein:
<source lang="javascript">
<source lang="javascript">
define
import * as config from '../../json/config_de.json';
( function ()
  { 
    function C(...) {...}
 
    return C;
  }
);
</source>
</source>


Hier gibt es zwar eine Callback-Funktion. Diese wird jedoch sofort ausgeführt, da nicht auf das Laden anderer Module gewartet werden muss.
Beachten Sie, dass hier eine Zugriff auf das „Großvater“-Verzeichnis <code>src</code> mittel <code>../../</code> erfolgen muss.
Grundsätzlich sollten Sie in Import-Befehlen immer relative Pfadnamen angeben, da sonst eine Änderung der globalen Ordnerstruktur
zu massiven Problemen führen kann. Achten Sie bei Pfad-Angaben insbesondere auf Groß- und Kleinschreibung.
Gemeinerweise macht es auf Windows-Rechnern keinen Unterschied, ob sie in einem Pfadnamen Groß- oder Kleinbuchstaben
verwenden. Wenn Sie die Anwendung später auf einen Linux-Server kopieren, kommt es dann allerdings zu Problemen,
weil der Server die Pfade mit den falschen Groß- oder Kleinbuchstaben nicht mehr findet. Sie sollten in Dateinamen
am Besten überhaupt keine Großbuchstaben verwenden.  


====Laden von RequireJS====
Wenn Sie jetzt <code>grunt</code> aufrufen und anschließend <code>index.html</code> im Browser öffnen,
hat sich noch nicht viel getan.


Damit Module auch gefunden werden, muss man RequireJS zunächst konfigurieren.
Ändern Sie nun den Inhalt der <code>index.html</code>. Löschen Sie alle englischen Text-Elemente.
Das erfolgt üblicherweise in der Datei  „<code>main.js</code>“ (hier: „<code>main5.js</code>“).
sie sollten sie durch die angegebenen Dummy-Texte ersetzen, notwendig wäre das aber nicht:
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
* <code>Hello, Stranger!</code> → <code>TITLE</code>
Browser anweisen, zuvor <code>require.js</code> zu laden. Sobald diese Datei vollständig geladen (und übersetzt) wurde,
* <code>What's your name?</code> → <code>QUESTION</code>
lädt <code>require.js</code> von sich aus <code>main.js</code> (bzw <code>main5.js</code>).
* <code>Reset</code> → <code>RESET</code>
Um dies zu erreichen, fügen Sie folgendes Script-Element als letztes und einziges Script-Element
* <code>Say hello</code> <code>SUBMIT</code>
in die Datei „<code>index5.html</code>“ ein:
* <code>Hello, ...</code> → <code>HELLO</code>
* <code>Welcome to Multimedia Programming!</code> → <code>WELCOME</code>


<source lang="html5">
Sie müssen außerdem noch einigen HTML-Tags zusätzlich ein <code>id</code>-Attribut zuordnen:
<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
* H1-Element: <code>&lt;h1 id="heading_form"&gt;</code>
nachlädt, sobald sie benötigt werden.
* Label-Element: <code>&lt;label id="input_label" ..&gt;</code>
* Reset-Button:  <code>&lt;input id="button_reset" type="reset" ...&gt;</code>
* Welcome-Paragraph: <code>&lt;p id="paragraph_hello">WELCOME&lt;/p&gt;</code>


====Landen von <code>main.js</code>====
Wenn Sie jetzt die <code>index.html</code> nochmals im Browser öffnen, sehen Sie nur noch die DUMMY-Texte.


Nachdem <code>require.js</code> geladen wurde, liest das Programm das Script-Attribut „<code>data-main</code>“
Das ändert sich allerdings, wenn Sie folgende JavaScript-Befehle in die Datei <code>src/js/app.js</code> nach den
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
Import-Befehlen einfügen:
„<code>.js</code>“ ergänzt RequireJS automatisch).
 
Die Datei „<code>js/main5.js</code>“ konfiguriert zunächst RequireJS:


<source lang="javascript">
<source lang="javascript">
requirejs.config
const c_config_text = config.text;
({
  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>“
// Initialize the app.
legt ein paar spezielle Unterordner fest. Modulnamen, die mit „<code>app/</code>“ beginnen (wie {{zB}} „<code>app/greet</code>“),
document.getElementById('button_reset').value
werden im Ordner „<code>js/app5/</code>“ gesucht. Für die Module „<code>json</code>“ (ein RequireJS-Plugin zum Laden von
  = c_config_text.reset;
JSON-Dateien) und „<code>text</code>“ (welches von
document.getElementById('button_submit').value
„<code>json</code>“ benötigt wird) wurde der Pfad direkt angegeben. Die JSON-Dateien finden sich im Ordner
  = c_config_text.submit;
„<code>js/../json</code>“, {{dh}} im Ordner „<code>init</code>“.
 
Nun kann die eigentliche Anwendung gestartet werden:


<source lang="javascript">
document.getElementById('heading_form').innerHTML
requirejs
   = c_config_text.title;
( ['loadjson!json/hello_de.json', 'loadjson!json/hello_en.json', 'app/init'],
document.getElementById('input_label').innerHTML
   function(helloDeJSON, helloEnJSON, init)
   = c_config_text.question;
  {
    init(window, helloDeJSON);
    init(window, helloEnJSON);
   }
);
</source>
</source>


Die Anwendung benötigt zwei JSON-Dateien (das dafür notwendige JSON-Plugin wird automatisch geladen)  
(Die restlichen Zeilen bleiben erhalten.) Mit diesen fünf Befehlen speichern Sie den Inhalt des Attributs
und das Modul  „<code>app/hello</code>“ (welches sich gemäß Konfiguration in der Datei  „<code>js/app5/hello.js</code>“ befindet).
<code>text</code> aus der eingelesenen JSON-Datei in der Konstanten
<code>c_config_text</code>. Anschließend ersetzen Sie mit Hilfe von vier weiteren Befehle
die Dummy-Texte im der Form-Section durch sinnvolle Texte.


Sobald die drei Dateien vollständig geladen wurden, wird die Callback-Funktion aufgerufen.
Rufen Sie <code>grunt</code> auf und öffnen Sie anschließend <code>index.html</code> im Browser.
Dieser werden die beiden JSON-Objekte sowie das Hello-Initialisierungs-Modul-Objekt übergeben.
Jetzt sollten Sie deutsche Text an Stelle von Dummy-Texten sehen. Wenn Sie jetzt allerdings einen Namen eingeben
Die Callback-Funktion kann somit problemlos die beiden Hello-World-Anwendungen mittels  „<code>p_hello.init</code>“ initialisieren und starten.
und auf den Begrüßungs-Button klicken, werden Sie englisch begrüßt und sehen einen weiteren Dummy-Text.


====Das Modul „<code>hello</code>“====
Das heißt, Sie müssen auch noch die Datei <code>src/js/app/greet.js</code> erweitern.


Der Code des Moduls „<code>init</code>“ unterscheidet sich nicht nur hinsichtlich des Modulrahmens, sondern auch noch in
Ersetzen Sie in dieser Datei im Rumpf der Funktion <code>sayHello</code>
eine zweiten in einem wesentlichen Aspekt vom
den darin enthalten Code durch folgenden Code:
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">
<source lang="javascript">
define
const l_config_text = config.text,
( ['app/greet'],
       l_name     = document.getElementById("input_name").value;
  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)
document.getElementById('paragraph_hello').innerHTML
                .innerHTML = l_init_text.title;
  = l_config_text.welcome;
      l_document.getElementById(l_init_elements.inputNameLabel)
document.getElementById('heading_hello').innerHTML
                .innerHTML = l_init_text.query;
  = l_config_text.hello.replace('$1', l_name);
      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;
document.getElementById('section_hello')
   }
  .classList.remove('hidden');
);
document.getElementById('section_form')
   .classList.add('hidden');
</source>
</source>


Sobald das Modul „<code>app/greet</code>“ ({{dh}} die Datei „<code>js/app5/greet.js</code>“) vollständig geladen wurde,
Die Funktion <code>sayHelloOnEnter</code> ändert sich dagegen nicht.
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>“====
Nun sollte (nach einen <code>grunt</code>-Lauf) die Web-App wie gewünscht funktionieren.


Das Modul „<code>greet</code>“ benötigt überhaupt keine anderen Module.
Sie können jetzt testweise mal in beiden Dateien <code>app.js</code> und <code>greet.js</code>
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.
die englischsprachige JSON-Datei landen, <code>grunt</code> laufen lassen und <code>index.html</code> öffnen.
 
Dann sollten Sie auf Englisch begrüßt werden. Das ist schon nicht schlecht, aber richtig DRY ist das noch nicht.
<source lang="javascript">
Sie müssen den Namen der JSON-Datei in '''zwei''' Dateien ändern. Also braucht es noch eine sechstes und letztes Hello-World-Tutorium ...
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.
});
</source>


==Quellen==
==Quellen==
<references/>
<references/>
<ol>
<ol>
<li value="4"> {{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
<li value="3"> {{Quelle|Kowarschick, W.: Multimedia-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>

Version vom 3. November 2017, 16:06 Uhr

Dieser Artikel wird derzeit von einem Autor gründlich bearbeitet. Die Inhalte sind daher evtl. noch inkonsistent.

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, (SVN-Repository)

Anwendungsfälle (Use Cases)

Gegenüber dem vierten Teil des Tutoriums ändern sich die die Anwendungsfälle nicht.

In diesem und im nächsten Teil des Tutoriums werden zwei wesentliche Grundprinzipien der Programmierung thematisiert:

  • Don't repeat yourself (DRY, “Keep your code DRY!”).
  • Konstanten gehören nicht in den Code, sondern in Konfigurationsdateien.

Erstellen eines neuen Projektes

Erzeugen Sie wie im vierten Teil des Tutoriums ein neues Projekt, diesmal allerdings mit dem schönen Namen HelloWorld05“ und speichern Sie dieses wieder in Ihrem SVN-Repository (Achtung: node_modules muss existieren und auf der Ignore-Liste stehen).

Kopieren Sie nun aus Ihrer Lösung des vierten Teil des Tutoriums die Ordner src und web (jeweils samt allen darin enthaltenen Ordnern und Dateien) sowie die Dateien gruntfile.js, package.json, package-lock.json und webpack.config.js fügen Sie sie ins Wurzelverzeichnis des fünften Teils ein.

Weisen Sie WebStorm wiederum an, die Inhalte der Ordner node_modules und web/js nicht auf Syntaxfehler zu überprüfen. Gegebenenfalls müssen Sie auch wieder die Dateien gruntfile.js ... webpack.config.js unter die Versionsverwaltung von SVN stellen (Add to VCS).

Ändern Sie in den Dateien package.json und web/index.html die Versionsnummern, die Beschreibung und gegebenenfalls den Pfad zum Repository Ihres Projektes.

Zu guter Letzt öffnen Sie das WebStorm-Terminal und führen den Befehl npm installaus. Damit werden dieselben Node.js-Pakete installiert wie im vierten Teil des Tutoriums.

Löschen Sie die Datei web/js/app.bundle.js und rufen Sie anschließend grunt auf, um diese Datei erneut zu erzeugen. Wenn Sie jetzt die Datei index.html im Browser öffnen, sollte die Web-Anwendung so wie in Teil 4 der Aufgabe funktionieren.

Verfeinerung der Webpack-Umgebung

Zunächst benötigen Sie ein paar weitere Node.js-Module

npm install --save-dev es6-autobind
npm install --save-dev node-sass sass-loader extract-text-webpack-plugin

Im vierten Teil des Tutoriums wurde die Datei initial.css nicht von Webpack verwaltet, sondern direkt in das Verzeichnis web/css eingefügt. Damit liegt auch die Verantwortung für die Komprimierung dieser Datei beim Programmierer. Das soll sich ändern. Sie erweitern die Webpack-Konfiguration so, dass künftig auch diese Datei unter der Kontrolle von Webpack steht.

SCSS

Anstelle von CSS-Dateien verwenden Sie künftig SCSS-Dateien. Das ist zunächst einmal nicht weiter schwer, das jede CSS-Datei automatisch auch eine SCSS-Datei ist. Bei der Verwendung von CSS können Sie allerdings das Prinzip „Don't repeat yourself“ (DRY) nicht beachten. Sie müssen für verschiedene Elemente ständig Informationen wiederholen (gewünschter Font, Hintergrundfarbe, Größenangaben etc.). Deshalb gibt es mehrere Projekte wie z. B. LESS und Sass , die den CSS-Standard erweitern, um dieses Problem zu vermeiden. Sass verwendet allerdings eine ganz andere Syntax als CSS. Allerdings unterstützt das Sass-Projekt auch die Sprache SCSS, die CSS einfach um weitere syntaktische Elemente ergänzt. Zum Beispiel kann man in SCSS Konstanten definieren:

$background-color:  #C5EFFC;

Und nun kann man in der CSS-Datei anstelle von #C5EFFC stets $background-color schreiben, um die Hintergrundfarbe von bestimmten Elementen zu definieren. Wenn man nun dieses Farbe ändern will, muss das nicht mehr bei allen Elementen einzeln passieren. Es reicht, die Farbe in der Konstantendefinition abzuändern. Der CSS-Code ist nun DRY.

Leider unterstützt kein Browser SCSS direkt. Aber für was gibt es Transpiler wie Webpack, die eine SCSS-Datei automatisch in eine CSS-Datei übersetzen können.

Ersetzen Sie in Ihrem Projekt die Datei src/css/main.css durch drei SCSS-Dateien: _config.scss, initial.scss und app.scss.

In der Datei _config.scss werden alle von den anderen SCSS-Dateien benötigten Konstanten definiert. Das heißt, das 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 Webpack automatisch aktualisiere CSS-Dateien.

Die Datei initial.scss enthält möglichst wenig Code. Das ist Vorlage für die Datei initial.bundle.css, die im HTML-Head-Bereich geladen vor dem Body-Inhalt werden soll. Die eigentlichen CSS-Informationen der Web-Anwendung werden in app.scss gespeichert. Die zugehörigen CSS-Anweisungen werden von Webpack wie gehabt in app.bundle.js integriert.

src/css/_config.scss

$font-family-sans:  Verdana, Helvetica, sans-serif;
$font-family-serif: "Times New Roman", Times, serif;
$background-color:  #C5EFFC;

Diese Datei kann nun mit Hilfe des SCSS-Befehls @import in die anderen beiden SCSS-Dateien eingefügt werden. Webpack (genauer gesagt das SASS-Modul von Webpack) kennt diesen Befehl und führt im beim Erstellen der zugehörigen CSS-Dateien auch aus.

Der Import-Befehl ist hier notwendig, da in der Datei initial.scss die Konstante $background-color verwendet wird. Ansonsten enthält dies Datei nur den schon bekannten CSS-Code.

src/css/initial.scss

@import 'config';

body
{ background-color: $background-color;
}

.hidden
{ display: none;
}

Die Datei app.scss ist deutlich umfangreicher. Daher soll der zugehörige CSS-Code ja auch erst gemeinsam mit der Datei app.bundle.js geladen werden. Das meiste ist üblicher CSS-Code, den Sie schon im vierten Teil des Tutoriums kennengelernt haben. Es gibt ein paar neue Elemente, wie die CSS-Klassen-Elemente .left und .right, die in der HTML-Datei dieses Tutoriums zusätzlich verwendet werden. Außerdem sehen Sie in der Datei noch ein paar weitere SCSS-Elemente. Diese werden im Anschluss an den folgenden code beschrieben.

src/css/app.scss

@import 'config';

html, body
{ height:           100%;
  font-size:        2vw !important;
  font-family:      $font-family-sans;
  background-color: $background-color;
}

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

body
{ display:          table-cell;
  text-align:       center;
  vertical-align:   middle;
}

h1
{ padding-bottom: 0;
  margin-bottom:  0;
}

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

  h1
  { font-size:     200%;
    margin-bottom: 0.5ex;
  }
}

#section_hello
{ h1
  { font-size: 270%;
  }
}

p, label
{ font-family: $font-family-serif;
  font-size:   100%;
}

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

label
{ text-align: right;
}

@mixin panel()
{ padding: 0;
  width:   50%;
}

.left
{ float: left;
  @include panel();
}
.right
{ float: right;
  @include panel();
}

In diese SCSS-Datei werden folgende SCSS-Erweiterungen verwendet:

  • Import-Befehl: @import 'config';
  • Konstanten: $font-family-sans, $font-family-serif, $background-color
  • Verschachtelte Anweisungen: z. B. h1 innerhalb von #section_form
  • Mixins, um eine Liste von Attributen nur einmal zu definieren und und mehreren Elemente wieder verwenden zu können.

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 main.css vom vierten Teil des Tutoriums hatten Sie folgendes geschrieben:

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

#section_form > h1
{
  font-size:     200%;
  margin-bottom: 0.5ex;
}

Die SCSS-Variante ist deutlich strukturierter und prägnanter.

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

  h1
  { font-size:     200%;
    margin-bottom: 0.5ex;
  }
}

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

Ein weiteres Element, das nur SCSS bietet sind die Mixins. Sie definieren zunächst ein Mixin, das eine Folge von CSS-Attributen zusammenfasst. (Auch hier könnten wieder Verschachtelungen und andere SCSS-Erweiterungen verwendet werden!)

@mixin panel()
{ padding: 0;
  width:   50%;
}

Und nun können Sie dieses Mixin in diversen andere Elemente einbinden:

.left
{ float: left;
  @include panel();

.right
{ float: right;
  @include panel();
}

In normalen CSS-Code, müssten Sie denselben Code Non-DRY formulieren:

.left
{ float:   left;
  padding: 0;
  width:   50%;
}

.right
{ float:   right;
  padding: 0;
  width:   50%;
}

Wenn Sie hier das Layout der Panels ändern wollten (andere Breite, andere Ränder etc.), müssen Sie den CSS-Code von jedem Panel einzeln ändern. In der SCSS-Datei erfolgt die Änderung dagegen DRY: Sie ändern einfach den Mixin-Code ab.

Wenn die aktuellen Browser SCSS verstehen würden, könnte sie initial.scss und app.scss direkt vom HTML-Code aus laden. Allerdings geht das derzeit leider nicht. Somit muss Webpack angewiesen werden, CSS-Code aus den SCSS-Dateien zu erzeugen. Dies ist mit den beiden Node.js-Modulen node-sass und sass-loader, die Sie bereits installiert haben, ganz einfach möglich.

Ersetzen Sie in der Datei webpack.config.js folgenden Code

{ test:   /\.css$/,
  use:    [ 'style-loader',
            { loader: 'css-loader', options: { minimize: true } }
          ]
},

durch folgenden:

{ test:   /\.(css|scss|sass)$/,
  use:    [ 'style-loader',
            { loader: 'css-loader', options: { minimize: true } },
            'sass-loader'
          ]
}

Der Test wird so erweitert, dass diese Regel jetzt nicht nur für die Verarbeitung von Dateien mit der Endung .css zuständig ist, sondern auch für Dateien mit den Endungen .sass und .scss.

Die Liste der zugehörigen Loader wird einfach um einen Loader erweitert: sass-loader. Diese Liste wird von hinten nach vorne abgearbeitet. Das heißt, eine Datei wie beispielsweise app.scss wird erst mit Hilfe des Sass-Loaders in eine CSS-Datei umgewandelt, diese wird mit Hilfe des CSS-Loaders komprimiert und das Ergebnis dieser Datei wird mit Hilfe des Style-Loaders in die zugehörige JavaScript-Ausgabedatei integriert.

Jetzt müssen nur noch die zugehörigen JavaScript-Dateien erstellt werden.

Im entry-Objekt der Datei webpack.config.js ist bereits die Datei src/js/app.js eingetragen. Für Sie wird von Webpack die Datei web/js/app.bundle.js erzeugt.

Der Name der Zieldatei wurde im output-Objekt von webpack.config.js spezifiziert. Achtung: Überprüfen Sie, ob das output-Objekt folgendermaßen definiert wurde.

  output:
  { filename: 'js/[name].bundle.js',
    path:     absolutePath('web')
  }

Damit diese Zieldatei insbesondere den komprimierten Code der Datei src/css/app.scss enthält, muss src/js/app.js leicht modifiziert werden.

Ersetzen Sie in dieser Datei den Import-Befehl

import '../css/main.css';

durch den Import-Befehl

import '../css/app.scss';

Die Datei web/css/main.css gibt es ja nicht mehr; sie wurde durch web/css/app.scss ersetzt.

Damit auch web/css/initial.scss durch webpack verwaltet wird, benötigen wir eine zweite JavaScript-Datei.

src/js/initial.js

import '../css/initial.scss';

Sie sehen das richtig, die Datei src/js/initial.js enthält eine einzige Code-Zeile. Sie erinnern sich? Webpack dient eigentlich nur dazu JavaScript-Dateien zu packen. Also geben wir diesen Tool eine JavaScript-Datei, die die gewünschte CSS-Datei einfach mittels eine ES6-Import-Befehls importiert (ES6 = EcmaScript 6). Fügen Sie nun in das entry-Objekt der Datei webpack.config.js vor der Zeile

app: absolutePath('src/js/app.js')

folgende zweite Zeile ein:

initial: absolutePath('src/js/initial.js')

Beachten Sie, dass in einem JavaScript-Objekt die Attribute durch Kommas voneinander getrennt werden. Fügen Sie also an passender Stelle auch noch ein Komma ein.

Wenn Sie jetzt Grunt aufrufen, sollten zwei Dateien generiert werden: web/js/app.bundle.js und web/js/initial.bundle.js.

Löschen Sie nun den Ordner web/css (die darin befindliche Datei initial.css wird nicht mehr benötigt) und ersetzen Sie in der Datei index.html die Zeile

<link rel = "stylesheet" type = "text/css" href = "css/initial.css"/>

durch folgende Zeile:

<script type = "text/javascript" src = "js/initial.bundle.js"></script>

Wenn Sie jetzt die Datei index.html im Browser öffnen, sollte sie wieder genauso funktionieren wie zuvor.

Anmerkung: Die Datei src/js/initial.js könnte natürlich noch mehr machen, als einfach nur eine CSS-Datei zu laden. Sie könnte beispielsweise eine kleine Animation einblenden (die berühmte Sanduhr, nur etwas moderner), die solange läuft, bis die eigentliche Web-Anwendung, d. h. app.bundle.js vollständig geladen wurde und die Animation wieder beendet.

Erzeugen einer CSS-Datei

Zurzeit enthält die JavaScript-Datei web/js/initial.bundle.js nur eine komprimierte CSS-Datei und etwas Code, um diesen in das HTML-Dokument einzufügen. Die Google-Suche bewertet HTML-Dateien, die sauberen CSS-Code i. Allg. enthalten, höher, als HTML-Datei, die nur JavaScript-Code enthalten. Daher wäre es in diesem Fall sinnvoll, die komprimierte CSS-Datei direkt in den HTML-Code einzubinden. Das ist aber gar nicht so einfach, da Webpack keine CSS-Dateien erstellt, sondern nur JavaScript-Dateien. Aber mit einem kleine Hack funktioniert das trotzdem.

Sie haben vor auch noch die Node.js-Module extract-text-webpack-plugin installiert. Mit diesem Sie eine CSS-Datei aus einer JavaScript-Datei extrahieren.

Ersetzen Sie zunächst in der Datei webpack.config.js den Test

test:   /\.(css|scss|sass)$/,

durch

test:   /app\.(css|scss|sass)$/,

Damit erreichen Sie dass diese Regel jetzt nur noch für Dateien funktioniert, die auf app.css oder app.scss oder app.sass enden. Die Datei src/css/app.scss in die Datei src/js/app.bundle.js integriert werden.

Laden Sie zunächst die ExtractTextPlugin-Erweiterung, indem Sie in die Datei webpack.config.js folgende zweite Zeile einfügen:

const ExtractTextPlugin = require('extract-text-webpack-plugin');

Für die Datei src/css/initial.scss fügen Sie nun in die Liste mit den Regeln eine weitere Regel ein (Komma nicht vergessen):

{ test:   /initial\.(css|scss|sass)$/,
  use:   ExtractTextPlugin.extract
         ({fallback: "style-loader",
           use:      [{ loader: 'css-loader', options: { minimize: true } },
                      'sass-loader'
                     ]
         })
}

Diese benutzt das zuvor erwähnte Plugin, um die komprimierte CSS-Datei aus der Datei src/js/initial.bundle.js zu extrahieren. Das erledigt sie aber nur fast. Man muss dem Plugin noch mitteilen wohin Sie die CSS-Datei speichern soll.

Dazu müssen Sie die bislang leere Plugin-Liste am Ende der Datei

plugins:
[]

mit Inhalt füllen:

plugins:
[ new ExtractTextPlugin("css/[name].bundle.css") ]

Führen Sie nun grunt aus. Es sollte die Datei web/css/initial.bundle.css erzeugt worden sein.

Nun müssen sie in der Datei index.html die Zeile

<script type = "text/javascript" src = "js/initial.bundle.js"></script>

wieder durch eine CSS-Link-Zeile ersetzen (da ja in der Datei initial.bundle.js kein sinnvoller Inhalt mehr enthalten ist; sie wurde durch das Extract-Text-Plugin „ausgelutscht“).

<link rel = "stylesheet" type = "text/css" href = "css/initial.bundle.css"/>

Sie könnten nun noch die überflüssige Datei web/js/initial.bundle.js von webpack wieder entfernen lassen. Wie das geht entnehmen Sie bitte der Seite https://stackoverflow.com/questions/37408873/delete-or-not-create-a-file-for-each-entry-in-webpack sowie der Musterlösung. Wichtig ist das nicht. Wenn Sie später mal eine kleine Warteanimation erstellen sollten, bräuchten Sie dieses Datei sowieso wieder.

JSON

Bislang haben sie den CSS-Code unter Webpack-Kontrolle gestellt und damit DRY gemacht. Als nächstes gehen wir das nächste Problem an: Ihr Code enthält ebenfalls Konstanten und verstößt damit gegen das Programmierprinzip „Konstante Werte gehören nicht in den Code, sondern in Konfigurationsdateien.“

Eine Ausnahme bilden triviale Konstanten wie 0, 1, null, true oder false, sofern sie mit großer Sicherheit nie im Code geändert werden müssen. Aber schon eine Konstante wie 3.1415926 gehört nicht in den Code. Man verwendet stattdessen besser Math.PI und erhält damit die maximal mögliche Stellenzahl. (Eventuell hängt diese von der Rechnerplattform ab.)

In JavaScript-Anwendungen verwendet man JSON zur Speicherung von (konstanten) Daten und zur Initialisierung der Anwendung. Wenn ein Spiel mehrere Level hat, wir jedes Level üblicherweise in einer eigenen JSON-Datei beschrieben. Eine Level-Designerin muss im Optimalfall nicht mehr machen, als die Assets (Grafiken, Töne, 3D-Modell etc.) zu erstellen und mit Hilfe einer JSON-Datei die Zusammenhänge zwischen den einzelnen Elementen herstellen. Es sollte nicht notwendig sein, dass sie den Programmcode modifizieren muss, da das Level-Design schon aufwändig genug ist.

JSON (JavaScript Object Notation) ist ein einfaches Datenformat, mit dessen Hilfe sich komplexe Objekte sehr einfach beschreiben lassen. Eine Alternative wäre XML, allerdings ist diese Auszeichnungsprache deutlich komplexer. (Ein Beispiel für XML ist XHMLT 1.0[1], eine HTML-Variante.)

Eine JSON-Datei ist im Prinzip eine JavaScript-Datei, die einen der folgenden Datentypen enthält[2]:

  • Nullwert (null)
  • Boolescher Wert (true oder false)
  • Zahl (Ziffernfolge mit optionalem Vorzeichen und optionalen Exponenten)
  • Zeichenkette (beliebige UTF-8-Zeichen, die in doppelten geraden Anführungszeichen (") eingeschlossen sind)
  • Array (eine durch Kommata getrennte Listen von null oder mehr JSON-Elementen; die Liste beginnt mit [ und endet mit ])
  • Objekt/Hasharray/Paarliste (eine durch Kommata getrennte Listen von null oder mehr Paaren; die Liste beginnt mit { und endet mit }; jedes Paar besteht aus einer Zeichenketten, die als Schlüssel fungiert, gefolgt von einem Doppelpunkt (:) gefolgt von einem beliebigen JSON-Element; in einem Objekt darf derselbe Schlüsselwert nicht zweimal vorkommen)

Beachten Sie, dass die eine rekursive Definition ist: Arrays dürfen andere Arrays und Objekte enthalten, Objekte dürfen andere Objekte und Arrays enthalten. JSON-Elemente können also beliebig tief geschachtelt sein.

Andere Werte dürfen in einer JSON-Datei nicht enthalten sein. Das heißt, es gibt keine einfachen geraden Anführungszeichen (außer innerhalb von Zeichenketten), keine Kommentare, keine Funktionen etc. Insbesondere überflüssige Kommas sind nicht erlaubt!

Beachten Sie bitte, dass jeden JSON-Datei auch eine JavaScript-Datei ist (die Umkehrung dieser aussage gilt nicht.). Wenn man eine JSON-Datei auswerten würde, erhielte man jedoch kein anderes Ergebnis als den Wert oder das Objekt, das in der Datei gespeichert ist.

Erstellen Sie in Ihrem Projekt folgende zwei JSON-Dateien:

src/json/config_de.json

{
  "text":
  {
    "title":    "Hallo, Fremder!",
    "question": "Wie heißen Sie?",
    "reset":    "Reset",
    "submit":   "Begrüßung",
    "hello":    "Hallo, $1!",
    "welcome":  "Willkommen bei Multimedia-Programmierung!"
  },

  "css":
  {
    "hidden": "hidden"
  }
}

src/json/config_en.json

{
  "text":
  {
    "title":    "Hello, Stranger!",
    "question": "What's your name?",
    "reset":    "Reset",
    "submit":   "Say hello",
    "hello":    "Hello, $1!",
    "welcome":  "Welcome to Multimedia Programming!"
  },

  "css":
  {
    "hidden": "hidden"
  }
}

Importieren Sie die erste der beiden JSON-Dateien in Ihre Datei src/js/app,js, indem Sie folgenden Import-Befehl einfügen:

import * as config from '../json/config_de.json';

In src/js/app/greet,js fügen Sie folgenden Import-Befehl ein:

import * as config from '../../json/config_de.json';

Beachten Sie, dass hier eine Zugriff auf das „Großvater“-Verzeichnis src mittel ../../ erfolgen muss. Grundsätzlich sollten Sie in Import-Befehlen immer relative Pfadnamen angeben, da sonst eine Änderung der globalen Ordnerstruktur zu massiven Problemen führen kann. Achten Sie bei Pfad-Angaben insbesondere auf Groß- und Kleinschreibung. Gemeinerweise macht es auf Windows-Rechnern keinen Unterschied, ob sie in einem Pfadnamen Groß- oder Kleinbuchstaben verwenden. Wenn Sie die Anwendung später auf einen Linux-Server kopieren, kommt es dann allerdings zu Problemen, weil der Server die Pfade mit den falschen Groß- oder Kleinbuchstaben nicht mehr findet. Sie sollten in Dateinamen am Besten überhaupt keine Großbuchstaben verwenden.

Wenn Sie jetzt grunt aufrufen und anschließend index.html im Browser öffnen, hat sich noch nicht viel getan.

Ändern Sie nun den Inhalt der index.html. Löschen Sie alle englischen Text-Elemente. sie sollten sie durch die angegebenen Dummy-Texte ersetzen, notwendig wäre das aber nicht:

  • Hello, Stranger!TITLE
  • What's your name?QUESTION
  • ResetRESET
  • Say helloSUBMIT
  • Hello, ...HELLO
  • Welcome to Multimedia Programming!WELCOME

Sie müssen außerdem noch einigen HTML-Tags zusätzlich ein id-Attribut zuordnen:

  • H1-Element: <h1 id="heading_form">
  • Label-Element: <label id="input_label" ..>
  • Reset-Button: <input id="button_reset" type="reset" ...>
  • Welcome-Paragraph: <p id="paragraph_hello">WELCOME</p>

Wenn Sie jetzt die index.html nochmals im Browser öffnen, sehen Sie nur noch die DUMMY-Texte.

Das ändert sich allerdings, wenn Sie folgende JavaScript-Befehle in die Datei src/js/app.js nach den Import-Befehlen einfügen:

const c_config_text = config.text;

// Initialize the app.
document.getElementById('button_reset').value
  = c_config_text.reset;
document.getElementById('button_submit').value
  = c_config_text.submit;

document.getElementById('heading_form').innerHTML
  = c_config_text.title;
document.getElementById('input_label').innerHTML
  = c_config_text.question;

(Die restlichen Zeilen bleiben erhalten.) Mit diesen fünf Befehlen speichern Sie den Inhalt des Attributs text aus der eingelesenen JSON-Datei in der Konstanten c_config_text. Anschließend ersetzen Sie mit Hilfe von vier weiteren Befehle die Dummy-Texte im der Form-Section durch sinnvolle Texte.

Rufen Sie grunt auf und öffnen Sie anschließend index.html im Browser. Jetzt sollten Sie deutsche Text an Stelle von Dummy-Texten sehen. Wenn Sie jetzt allerdings einen Namen eingeben und auf den Begrüßungs-Button klicken, werden Sie englisch begrüßt und sehen einen weiteren Dummy-Text.

Das heißt, Sie müssen auch noch die Datei src/js/app/greet.js erweitern.

Ersetzen Sie in dieser Datei im Rumpf der Funktion sayHello den darin enthalten Code durch folgenden Code:

const l_config_text = config.text,
      l_name      = document.getElementById("input_name").value;

document.getElementById('paragraph_hello').innerHTML
  = l_config_text.welcome;
document.getElementById('heading_hello').innerHTML
  = l_config_text.hello.replace('$1', l_name);

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

Die Funktion sayHelloOnEnter ändert sich dagegen nicht.

Nun sollte (nach einen grunt-Lauf) die Web-App wie gewünscht funktionieren.

Sie können jetzt testweise mal in beiden Dateien app.js und greet.js die englischsprachige JSON-Datei landen, grunt laufen lassen und index.html öffnen. Dann sollten Sie auf Englisch begrüßt werden. Das ist schon nicht schlecht, aber richtig DRY ist das noch nicht. Sie müssen den Namen der JSON-Datei in zwei Dateien ändern. Also braucht es noch eine sechstes und letztes Hello-World-Tutorium ...

Quellen

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