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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Kowa (Diskussion | Beiträge)
Änderung 42067 von Kowa (Diskussion) rückgängig gemacht.
Kowa (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
{{TBD|Anpassung an Teil 4 des Tutoriums. Derzeit ist diese Version noch an Teil 3 des Tutoriums angepasst.}}
{{In Bearbeitung}}
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}
'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld05/index.html <code>index.html</code>]
 
([https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld05/ SVN-Repository]; nach Download: <code>npm update</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])
==Anwendungsfälle (Use Cases)==
==Anwendungsfälle (Use Cases)==
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_03|dritten]] und
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_03|dritten Teil des Tutoriums]]
[[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 Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.
 
In diesem Teil des Tutoriums geht es darum, die Anwendung besser zu strukturieren, {{dh}} zu [[Modul|modularisieren]].
 
===Modularisierung===


In diesem Teil des Tutoriums geht es darum, die Anwendung zu verbessern, so dass sie schneller ausgeliefert werden kann.
Eine Anwendung, wie {{zB}} ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgaben.
Außerdem wird die Entwicklungsarbeit mit Hilfe von Automatisierungstools erleichtert.
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>HelloWorld05</code>“ und legen Sie dieses in Ihrem Repository ab.
Erstellen Sie ein neues Projekt  „<code>HelloWorld04</code>“ und legen Sie dieses in Ihrem Repository ab.
Kopieren Sie anschließend alle Dateien aus dem vierten Teil des Tutoriums und passen Sie den Titel in der HTML-Datei an  
Kopieren Sie anschließend alle Dateien aus dem dritten Teil des Tutoriums, passen Sie den Titel in der HTML-Datei an und committen Sie abermals.
 
==Erstellen einer Ordnerstruktur==
 
Grundsätzlich gilt auch bei der Programmierung: Ordnung ist das halbe Leben.
 
Web-Anwendungen werden sehr schnell sehr groß. Also sollte man sich eine geeignete Ordnerstruktur überlegen.
Üblicherweise legt man CSS-Dateien in einen Ordner namens  „<code>css</code>“ und JavaScript-Dateien in einen Ordner namens
„<code>js</code>“ oder  „<code>lib</code>“. Sollten es viele CSS- und/oder JavaScript-Dateien werden,
legt man im entsprechenden Ordner geeignete Unterordner an.
 
Legen Sie im Root-Verzeichnis Ihres Projektes folgende Ordner an:
* <code>web</code>: Der Inhalt dieses Ordners kommt später auf einen echten Web-Server.
* <code>web/css</code>: Hier werden die CSS-Dateien der Web-Anwendung gespeichert. In diesem Ordner können zur weiteren Strukturierung Unterordner angelegt werden.
* <code>web/js</code>: Hierher kommt die Datei „<code>main.js</code>“, die für den Start der Web-Anwendung zuständig ist. Üblicherweise liest sie eine JSON-Datei mit Initialwerten ein, die sie der Anwendung übergeben kann.
* <code>web/js/app</code>: Hier speichern Sie die von Ihnen geschriebenen JavaScript-Dateien Ihrer Web-Anwendung. In diesem Ordner werden üblicherweise Unterordner  zur weiteren Strukturierung der Anwendung angelegt.
* <code>web/js/lib</code>: Hier speichern Sie die JavaScript-Bibliotheken, die andere JavaScript-Entwickler erstellt haben und die Sie in Ihrer Anwendung einsetzen. Für jede externe Bibliothek gibt es einen Unterordner.
 
Fremde Bibliotheken sind nicht unbedingt fehlerfrei und können durchaus auch unsauberen Code enthalten.
Damit Ihnen bei einem Commit diese Fehler nicht angezeigt werden, schließen Sie den Ordner
„<code>web/js/lib</code>“ von der WebStorm-Fehlerüberwachung aus:
 
* Rechtklick auf den Ordner „<code>web/js/lib</code>“ → <code>Mar Directory as</code> → <code>Excluded</code>
 
Da in diesem Teil des Tutoriums fünf Web-Anwendungen mit jeweils unterschiedlichem Modularisierungsgrad
erstellt werden, werden hier anstelle des Ordners „<code>web/js/app</code>“ fünf App-Ordner angelegt:
* <code>web/js/app1</code>: Aufteilung der App in mehrere Objekte und Dateien.
* <code>web/js/app2</code>: Definition einer Klasse <code>Greet</code>.
* <code>web/js/app3</code>: Reduktion der Konstanten in <code>Greet</code>.
* <code>web/js/app4</code>: Weitere Modularisierung der Web-App mit JavaScript-Bordmitteln.
* <code>web/js/app5</code>: Alternative Modularisierung der Web-App mit der JavaScript-Bibliothek <code>RequireJS</code>.
 
Öffnen Sie im Editorbereich die Datei  „<code>index.html</code>“ und verschieben sie in den Ordner „<code>web</code>“, wobei Sie darauf achten sollten,
dass im Verschiebe-Dialogfenster die Option  „<code>Search for references</code>“ aktiviert ist.
.
Verschieben sie anschließend die Dateien „<code>main.css</code>“ und „<code>main.js</code>“
in die passenden Ordner „<code>web/css</code>“ bzw. „<code>web/js</code>“. Sie werden feststellen, dass die Verweise in den Link- und Script-Elementen der
Datei „<code>index.html</code>“ automatisch angepasst werden. Verschieben Sie nun die Datei „<code>index.html</code>“ in den Ordner „<code>web</code>“.
 
Das Ergebnis dieser Aktion ist, dass Ihre Web-Anwendung nicht mehr im Root-Verzeichnis des Projektes liegt, sondern (sauber strukturiert) in unterschiedlichen Unterordnern.
Daher ist es möglich, im Root-Verzeichnis des Projektes weitere projektspezifische Ordner und Dateien anzulegen wie „<code>conf</code>“ und „<code>doc</code>“,
die später nicht auf den Web-Server, auf dem die Anwendung schließlich laufen soll, kopiert werden.
 
==App1==
 
Benennen Sie die Dateien „<code>index.html</code>“ und „<code>main.js</code>“ in
„<code>index1.html</code>“ und „<code>main1.js</code>“ um. (Im Laufe des Tutoriums kommen weitere Versionen dieser beiden Dateien dazu.)


Fügen Sie ins Wurzelverzeichnis Ihres Porjektes einen neuen Ordner namens „<code>src</code>“ ein und verschieben
===Laden der CSS- und der JavaScript-Dateien===
Sie die Ordner „<code>web/css</code>“ und „<code>web/js</code>“ in diesen Ordner. Der <code>src</code>-Ordner enthält
künftig die von Ihnen erstellten JavaScript- und CSS-Sourcen. Mit Hilfe von Node.js-Tools werden im <code>web</code>-Ordner
komprimierte Versionen dieses Dateien erstellt.


Zu guter Letzt committen Sie abermals.
Eine Web-Anwendung funktioniert nur – wie Sie bereits erfahren haben –, wenn nicht nur die HTML-Datei, sondern auch die zugehörigen
CSS- und JavaScript-Dateien geladen werden. Allerdings gibt es dabei ein Problem: Die CSS- und JavaScript-Dateien werden im Laufe der
Zeit immer zahlreicher und/oder größer. Diese Dateien zu laden, dauert seine Zeit.  


===Node.js===
In der aktuellen Version der Web-Anwendung stehen die Verweise auf diese Dateien im <code>head</code>-Bereich des Dokuments.
Dieser wird vollständig geladen, bevor der <code>body</code>-Bereich eingelesen wird. Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments
darstellen kann, solange er die JavaScript- und CSS-Dateien lädt. Wenn dies zu lange dauert, verliert der Besucher die Geduld und verlässt die Seite
vorzeitig.


Dieses Tutorium befasst sich zwar nicht mit der Erstellung von Node.js-Server-Anwendungen, aber trotzdem benötigen wir
Besser wäre es daher andersherum vorzugehen: Es wird zuerst der <code>body</code>-Bereich geladen und dann die JavaScript- und die CSS-Dateien.
[[Node.js]], da für die Komprimierung von Dateien und das automatische Erstellen von JavaScript-Dokumentationen
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,
Tools zum Einsatz kommen, die mit Hilfe von Node entwicklet wurden.
wenn er mit dem [[Rendern]] der Seite beginnt. Also verwendet er die browserspezifischen Defaultwerte. Das heißt, die Seite sieht zunächst
ganz anders aus, als vom Designer geplant. Wenn dann die CSS-Dateien geladen wurden, wird die Seite erneut gerendert und verändert ihr Aussehen.
Auch das ist verwirrend und wirkt unprofessionell.


Installieren Sie zunächst Node.js: <code>https://nodejs.org/en/</code>, Version 6.x. Diverse Versionen finden Sie unter https://nodejs.org/en/download/.
Was also machen?
Achten Sie darauf, dass die Option „<code>Add to PATH</code>“ ausgewählt wurde.


Starten Sie WebStorm neu und öffnen Sie das Terminal, um zu testen, ob <code>node</code> funktioniert:
===CSS-Dateien===
Für CSS-Dateien empfiehlt Google ernsthaft, die Link-Element ganz ans Ende der HTML-Datei  zu stellen, also '''nach''' dem
schließenden <code>html</code>-Tag.
Damit das Problem mit dem falschen Layout nicht auftritt, sollen wichtige CSS-Befehle direkt – {{dh}} als CSS-Befehle innerhalb eines
<code>style</code>-Elements – in die HTML-Datei eingefügt
werden.<ref>{{Quelle|Google (Web)}}, Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery</ref>


* Klick auf Icon in der linken unteren Ecke → <code>Terminal</code>
Von beiden Vorschlägen rate ich dringend ab. Der erste Vorschlag ist nicht [[HTML]]-konform, ja noch nicht einmal [[SGML]]-konform. Es mag sein,
* Geben Sie Folgendes ins Terminal ein: <code>node --version</code>
dass diverse Browser diese Syntax verstehen, korrekt ist sie trotzdem nicht. Der zweite Vorschlag hat zur Folge, dass Struktur (HTML) und Layout (CSS)
vermischt werden. Dies war einer der Kardinalfehler von frühen HTML-Versionen aus der Zeit, bevor es das CSS-Format gab. '''Tun Sie das nicht.'''


Wenn Node.js korrekt installiert und der Pfad mit den <code>node</code>-Binaries in die Systemvariable <code>PATH</code> eingetragen wurde,
Gehen Sie so vor, wie es unter [[Multimedia-Programmierung: Best Practices#CSS|Multimedia-Programmierung: Best Practices]] beschrieben ist.
sollte die Versionsnummer von <code>node</code> ausgegeben werden.


Sie werden feststellen, das Node.js zum Großteil über Terminal-Befehle gesteuert wird.
In unserer Anwendung haben wir bislang alles richtig gemacht, bis auf die fehlende automatische Komprimierung.
Zu Beginn mag das für eingefleischte [[Klickibunti]]-Fans ungewohnt sein, aber Sie werden sich schnell daran gewöhnen.
Diese wird im [[HTML5-Tutorium: JavaScript: Hello World 05|fünften Teil des Tutoriums]] beschrieben.


'''Tipp'''<br/>
===JavaScript-Dateien===
Profis verwenden unter Windows lieber ein Terminal, das eine [[Unix]]-[[Shell]] simuliert.
(Unter Linux und Mac OS X ist das nicht notwendig. Dort läuft sowieso schon eine UNIX-Shell.)


Installieren Sie  „Git for Windows“ (https://git-for-windows.github.io/). In diesem Paket ist die „Git BASH“ enthalten.
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.
Das ist eine Terminal-Anwendung, die eine Linux-Bash emuliert und zahlreiche nützliche Befehle wie „<code>which</code>“, „<code>[[vi]]</code>“ ( :-) ) etc. zur
Verfügung stellt.


Öffnen Sie nun ein beliebiges Terminal (WebStorm-Terminal, Git Bash, Mac-OS-X-Terminal ...)
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:
und geben Sie folgenden Befehl ein (npm = node package manager, https://www.npmjs.com/)


<source lang="bash">
<source lang="html5">
npm install -g bower grunt-cli jsdoc yuglify mincss coffee-script jshint
  ...
  <script type="text/javascript" src="js/main1.js"></script>
</body>
</source>
</source>


Damit werden die wichtigsten Node.js-Pakete ''global'' (Option „<code>-g</code>“) installiert, die wir im Folgenden noch benötigen.
===Globale Funktionen als Methoden===
Die globale Installation hat zur Folge, dass jedes Projekt auf diese Pakete zugreifen kann, Pakete also nicht mehrfach installiert werden müssen.


Als nächstes initialisieren Sie den Node-Paket-Manager „<code>npm</code>“ (https://docs.npmjs.com/) sowie Grunt (http://gruntjs.com/) .
Grundsätzlich sollten so wenige globale Funktionen wie möglich in einen JavaScript-Programm definiert werden,
Grunt ist '''der''' “JavaScript Task Runner”, also ein Programm, das Aufgaben (''tasks'') wie {{zB}} das Komprimieren von Programmdateien
um Namenskonflikte mit JavaScript-Modulen und -Bibliotheken anderer Autoren zu vermeiden.
durchführt.  
In der Hello-World-Anwendung gibt es drei globale Funktionen: „<code>sayHello</code>“, „<code>sayHelloOnEnter</code>“ und „<code>init</code>“.
Die ersten beiden werden in [[Methode]]n umgewandelt. Das heißt, es wird ein Objekt „<code>greet</code>“ erstellt, das diese beiden Methoden
enthält. Die dritte Funktion „<code>init</code>“ wird durch eine anonyme Funktion ersetzt, die sofort ausgeführt wird.


Die Initialisierung erfolgt durch die Bereitstellung von zwei Dateien „<code>package.json</code>“
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>“
und „<code>gruntfile.json</code>“ im Root-Verzeichnis der Web-Anwendung. Eine initiale Version der <code>package.json</code> könnten Sie theoretisch mit
ein ('''vor''' dem Skript-Element, das „<code>main1.js</code>“ lädt):
<code>npm init</code> erstellen. Einfacher ist es jedoch, wenn Sie die beiden Dateien von Hand erstellen und folgenden Code einfügen:


'''<code>package.json</code>''' (https://docs.npmjs.com/files/package.json)
<source lang="html5">
<source lang="bash">
  ...
{
   <script type = "text/javascript" src = "js/app1/greet.js"></script>
  "name": "hello_world_04",
   <script type = "text/javascript" src = "js/main1.js"    ></script>
  "version": "1.0.0",
</body>
   "description": "Hello World tutorial",
  "main": "gruntfile.js",
  "directories": {
    "doc": "./doc"
   },
  "dependencies": {},
  "devDependencies": {
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "svn",
    "url": "https://glossar.hs-augsburg.de/kowa/tutorium/es5/hello_world/WK_HelloWorld05"
  },
  "author": "Wolfgang Kowarschick",
  "license": "CC-BY-NC-SA-4.0"
}
</source>
</source>


Anmerkung: Diese Datei wird durch <code>npm</code> automatisch erstellt und geändert. Daher ist das Layout (welches mir nicht gefällt) vorgegeben.
Fügen Sie in die Datei <code>greet.js</code>“ ein Objekt ein, dass die beiden Methoden
„<code>sayHello</code>und „<code>sayHelloOnEnter</code>“ enthält.


Hier sollten Sie natürlich nicht meinen Repositorypfad und meinen Namen einfügen, sondern jeweils Ihren. Sie können außerdem die
<source lang="javascript">
Lizenz, unter die Sie Ihre Software stellen, anpassen (https://spdx.org/licenses/), wenn Sie das möchten.
/**
* @namespace greet
* Contains methods to say hello to the user of the web app.
*/
var
  greet =
  {
    /**
    * Welcomes the user of the web app1 by displaying a welcome message
    * that includes his name. The name is fetched from a text input field.
    */
    sayHello:
      function()
      {
        document.getElementById('heading_hello').innerHTML =
          'Hello, ' + document.getElementById('input_name').value + '!';
        document.getElementById('section_form').classList.add('hidden');
        document.getElementById('section_hello').classList.remove('hidden');
      },


Die meisten Attribute sind selbsterklärend. Wichtig sind für uns vor allem zwei Attribute: „<code>main</code>“ und
    /**
„<code>devDependencies</code>“. Das erste enthält den Namen der Konfigurationsdatei von Grunt. Diese fehlt noch. Also: Legen Sie sie an:
    * An keyboard event observer. It tests whether the enter key has been pressed.
    * If so, the greet method is activated. Default reactions of the browser are
    * disabled.
    * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
    *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
    */
    sayHelloOnEnter:
      function(p_event)
      {
        if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
            document.activeElement === document.getElementById('input_name')
          )
        {
          p_event.preventDefault();
          p_event.stopPropagation();
          greet.sayHello();
        }
      }
  };
</source>
 
In der Datei  „<code>main1.js</code>“ werden dagegen alle globalen Funktionen entfernt.
Dies gilt auch für die Definition des Eventlisteners am Ende der Datei, der dafür sorgt, dass die
Funktion „<code>init</code>“ ausgeführt wird, sobald die HTML-Seite sowie alle Unterseiten vollständig geladen wurden.
 
Die Funktion „<code>init</code>“ wird durch eine anonyme Funktion ersetzt, die '''sofort''' nach ihrer Definition auch ausgeführt wird.
Da die Datei „<code>main1.js</code>“ als Letztes geladen wird, sind alle Objekte, auf die
während der Initialisierung zugegriffen werden muss, bereits vorhanden.


'''<code>gruntfile.js</code>'''
<source lang="javascript">
<source lang="javascript">
module.exports = function(grunt)
// Initialize the app.
(function()
{
{
   // Project configuration.
   document.getElementById('button_submit')
  grunt.initConfig
          .addEventListener('click', greet.sayHello);
  ({
    pkg: grunt.file.readJSON('package.json'),
  });
 
  // Load the plugin that provides the "uglify" task.


   // Default task(s).
   window.addEventListener('keydown', greet.sayHelloOnEnter);
};
}());
</source>
</source>


Diese Datei ist noch ziemlich leer, aber das wird sich ändern.
Testen Sie Ihre Anwendung und speichern Sie sie zu guter Letzt im Repository.


Vorausschauend sollten Sie eine überflüssige WebStorm-Fehlerprüfung deaktivieren, da anderenfalls künftige Versionen
==App2==
Versionen der Datei „<code>gruntfile.js</code>“ jedes Mal eine Fehlermeldung zur Folge haben, wenn die
Datei ins SVN-Repository geschrieben werden soll:


* <code>File</code> bzw. <code>WebStorm</code> → <code>Settings</code> bzw. <code>Preferences</code>  → <code>Editor</code> → <code>Inspections</code>
Ein wesentliches [[Programmierprinzip]] ist das Prinzip der „Wiederverwendbarkeit“.
* <code>JavaScript</code> aufklappen → <code>General</code> aufklappen → <code>Unsued JavaScript / ActionScript global symbol</code> deselektieren
Man sollte Code so schreiben, dass man ihn möglichst häufig wiederverwenden kann.
* <code>OK</code>
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.


Spannender ist in der Datei „<code>package.json</code>“ das Attribut „<code>devDependencies</code>“ (dev = developer).
Zurzeit geht das bei unserer Anwendung noch nicht. Es gibt nämlich nur
Diese enthält eine Liste von von NodeJS-Paketen, die für die Entwicklung der Web-Anwendung benötigt werden.  
ein Objekt  „<code>greet</code>“, das nicht für mehrere Anwendungen parallel eingesetzt werden kann.
Fügen wir einmal das Paket „<code>ink-docstrap</code>“ in diese Liste ein.  
Wir müssen also bei Bedarf neue <code>greet</code>-Objekte erzeugen können.
(Diese Pakte wird nachfolgend für die Erstellung der JavaScript-Dokumentation mittels JSDoc verwendet.
Das geschieht in JavaScript mit Hilfe von sogenannten [[Konstruktor]]funktionen.
Es enthält ein Theme, welches deutlich schöner ist als das Standard-JSDoc-Theme.)


Führen Sie folgenden Befehl im WebStorm-Terminal (oder mittels eine beliebigen anderen Shell im Wurzelverzeichnis Ihrer Web-Anwendung) aus:
Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihre Anwendungsdateien:


<source lang="bash">
* <code>index1.html</code> → <code>index2.html</code>
npm install ink-docstrap --save-dev
* <code>js/main1.js</code> → <code>js/main2.js</code>
</source>
* <code>js/app1/greet.js</code> → <code>js/app2/greet.js</code>


Dies bewirkt zweierlei:
In der Datei „<code>index2.html</code>“ müssen Sie dafür sorgen, dass
die neuen JavaScript-Dateien anstelle der alten geladen werden.
Und passen Sie den Titel im HTML-Header an.


# Das Node.js-Paket „<code> ink-docstrap</code>“ sowie diverse weitere Node.js-Pakete, die diese Paket benötigt, werden einem Projektordner namens „<code>node_modules</code>“ abgelegt.  
Das Objekt „<code>greet</code>“ der Datei „<code>js/app2/greet.js</code>“  
# Das Name und die Versionsnummer des Paketes werden in das Attribut „<code>devDependencies</code>“ in der  Datei „<code>package.json</code>“ eingefügt (wegen der Option „<code>--save-dev</code>“).
wird durch der folgende Konstruktorfunktion „<code>Greet</code>“ ersetzt:


Nun steht Ihnen der Befehl
<source lang="javascript">
<source lang="bash">
/**
npm update
* @class
</source>
* @classdesc Contains methods to say hello to the user of the web app.
zur Verfügung. Mit diesem können Sie die lokal gespeicherte Versionen der im Attribut „<code>devDependencies</code>“ aufgeführte Node.js-Pakete
*/
jederzeit auf die neueste Version aktualisieren. Sie können ein Paket auch löschen und dann mittels <code>npm update</code> (https://docs.npmjs.com/cli/update) wieder herstellen.
function Greet()
Versuchen Sie es:
{
  var l_this = this; // this-hack: l_this contains the new object


<source lang="bash">
  /**
npm remove ink-docstrap
  * Welcomes the user of the web app1 by displaying a welcome message
npm list
  * that includes his name. The name is fetched from a text input field.
</source>
  */
  l_this.sayHello =  // this.sayHello = ...
  function()
  {
    document.getElementById('heading_hello').innerHTML =
      'Hello, ' + document.getElementById("input_name").value + '!';


Jetzt ist der Ordner „<code>node_modules</code>“  wieder leer.
    document.getElementById('section_form').classList.add('hidden');
    document.getElementById('section_hello').classList.remove('hidden');
  };


<source lang="bash">
  /**
npm update
  * An keyboard event observer. It tests whether the enter key has been pressed.
npm list
  * If so, the greet method is activated. Default reactions of the browser are
  * disabled.
  * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
  *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
  */
  l_this.sayHelloOnEnter = // this.sayHelloOnEnter = ...
  function(p_event)
  {
    if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
        document.activeElement === document.getElementById('input_name')
      )
    {
      p_event.preventDefault();
      p_event.stopPropagation();
      l_this.sayHello();  // NOT this.sayHello();
    }
  }
}
</source>
</source>


Und jetzt enthält der wieder „<code>ink-docstrap</code>“ sowie andere dafür notwendige Pakete.
In JavaScript kann jede Funktion auch als Konstruktor, {{dh}}
zum Erzeugen von neuen Objekte verwendet werden, indem man das Schlüsselwort
„<code>new</code>“ voranstellt:


Sie können auch überprüfen, ob Sie in Ihrem Projekt irgendwelche veralteten Pakete benutzen (https://docs.npmjs.com/cli/outdated):
<source lang="javascript">
<source lang="bash">
var l_greet = new Greet();
npm outdated
</source>
</source>


Kein Ergebnis ist hier ein gutes Ergebnis!
In einer Konstruktorfunktion wird üblicherweise kein <code>return</code>-Befehl eingefügt,
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.


Und natürlich funktioniert das Ganze auch für global gespeicherte Pakete:
Im Funktionsrumpf selbst kann über die Spezialvariable „<code>this</code>“ auf das neu erstellte Objekt
zugegriffen werden. Dem neu erstellten Objekt werden mittels
„<code>this.sayHello = ...</code>“ und  „<code>this.sayHelloOnEnter = ...</code>“
etc. [[Attribut (Objekt)|Attribute]] zugewiesen, die Funktionen (welche [[Methode]]n genannt werden) als Werte enthalten.
Dem neu erstellten Objekt werden dieselben Methoden zugewiesen, wie dem Objekt „<code>greet</code>“ der Anwendung „App1“.
Daher kann das mittels Konstruktorfunktion erstellte Objekt „<code>l_greet</code>“ auch genauso verwendet werden,
wie das globale Objekt „<code>greet</code>“ der Anwendung „App1“.


<source lang="bash">
Beachten Sie bitte, dass in der Definition der Konstruktorfunktion der so genannte This-Hack zum Einsatz kommt.
npm -g outdated
Das Objekt <code>this</code> wird in einer lokalen Variablen <code>l_this</code> gespeichert.
npm -g list
Und anschließend wird diese lokale Variable anstelle von <code>this</code> benutzt.
</source>
Für die Definition der Attribute „<code>sayHello</code>“ und  „<code>sayHelloOnEnter</code>“ macht es keinen Unterschied,
ob man „<code>this.sayHello = ...</code>“ oder „<code>l_this.sayHello = ...</code>“ schreibt.


Allerdings muss man veraltete globale Pakete erneut installieren. Zum Beispiel muss <code>npm</code>
Allerdings wird auch innerhalb des Methodenrumpfes von „<code>sayHelloOnEnter</code>“ auf das neu erstellt
relativ häufig aktualisiert werden (sobald „<code>npm -g outdated</code>“ das Paket als veraltet meldet):
Objekt zugegriffen: <code>l_this.sayHello();</code>. Und hier könnte nun nicht <code>this.sayHello();</code>
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.


<source lang="bash">
In der Anwendung „App1“ wurde der This-Hack nicht benötigt, da die Methode „<code>sayHelloOnEnter</code>“
npm install -g npm
einfach die globale Variable Methode „<code>greet</code>“ verwendet hat, um auf die Methode „<code>sayHello</code>“
</source>
zuzugreifen (<code>greet.sayHello();</code>). Aber ein globales Greet-Objekt gibt es nun nicht mehr. Es gibt nur noch einen
globale Konstruktorfunktion „<code>Greet</code>“, mit der beliebig viele Greet-Objekte erzeugt werden können.
'''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>“.)


==Ausschließen diverser Dateien von automatischer Speicherung und/oder Überprüfung==
'''Anmerkung''': Eigentlich ist der This-Hack unsauber. Besser wäre es, die Methode „<code>bind</code>“ zu verwenden
(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]):


Sie sollten unbedingt verhindern das Node.js-Modulateien ins Repository geschrieben werden. Da sit, wie Se zuvor gesehen haben,
<source lang="javascript">
überflüssig und nimmt '''sehr viel''' Zeit in Anspruch, da es sich um Hunderte von Dateien handelt:
this.sayHelloOnEnter =
  function(p_event)
  {
    ...
      this.sayHello();
    ...
  }.bind(this);
</source>
Aber für JavaScript-Anfänger ist die Verwendung des This-Hacks deutlich einfacher.


* Rechtsklick im Dateibaum auf <code>node_modules</code>  → <code>Subversion</code> → <code>Ignore</code> → <code>node_modules</code>
Fügen Sie als nächstes die zuvor erwähnte Zeile
* Sollte der vorangegangene Schritt schief gegangen sein (WebStorms Subversion-Tools sind in dieser Hinsicht ziemlich schlecht), müssen Sie den letzten Schritt gegebenenfalls mit einem anderen Tool bewerkstelligen. Unter Windows ist Tortoise dafür recht gut geeignet.


Außerdem sollten Sie die automatische Überprüfung auf Fehler für alle Ordner ausschalten, die nicht von Ihnen stammen:
<source lang="javascript">
var l_greet = new Greet();
</source>
als erste Zeile '''in''' Ihre Initialisierungsfunktion (Datei „<code>main2.js</code>“)
ein und ersetzen Sie in derselben Funktion die beiden Zugriffe auf das globale Objekt
„<code>greet</code>“ durch Zugriffe auf das lokale Objekt „<code>l_greet</code>“.


* Rechtsklick im Dateibaum auf  „<code>node_modules</code>“ → <code>Mark Directory as</code> → <code>excluded</code>
Danach sollte Ihre Anwendung (Datei „<code>index2.html</code>“) wieder wie gewohnt laufen.
* Rechtsklick im Dateibaum auf  „<code>doc</code>“ → <code>Mark Directory as</code> → <code>excluded</code>


Beide Ordner enthalten Dateien, die nicht von Ihnen stammen. Diese Dateien können Fehler und Hinweise enthalten,
==App3==
veraltete (“deprecated”) Schnittstellen verwenden etc. (Oh ja, auch andere Programmierer machen Fehler. :-) )
Und wenn Sie diese beiden Ordner nicht als „<code>excluded markieren</code>“, prüft WebStorm bei jedem Commit die
darin enthaltenen Dateien und weist Sie auf jedes Problem hin. Das können so viele Probleme sein, dass Sie die
Probleme in Ihren eigenen Dateien vollkommen übersehen.


==Automatische Generierung der Schnittstellenbeschreibung==
Leider bringt uns hinsichtlich der Wiederverwendbarkeit die „Verbesserung“, die wir in App2 gegenüber App1
vorgenommen haben, gar nichts. Wir können jetzt zwar beliebig viele <code>greet</code>-Objekte
mit Hilfe von „<code>new Greet()</code>“ erstellen.
Aber wir haben nichts davon, da alle diese Objekte dieselben HTML-Elemente
im HTML-Dokument lesen und ändern. Das heißt, wir müssen die Konstanten und
am Besten auch gleich noch die Zugriffe auf globale Variablen aus der Konstruktorfunktion
„<code>greet</code>“ verbannen.


Installieren Sie zunächst JSDoc:
Erstellen Sie in Ihrem WebStorm-Projekt folgende Kopien Ihre Anwendungsdateien:


* Öffnen Sie das WebStorm-Terminal.
* <code>index2.html</code> → <code>index3.html</code>
* <code>npm install -g jsdoc</code> (installiert JSDoc als Node.js-Anwendung; das haben Sie eigentlich schon gemacht, aber das macht nix :-) )
* <code>js/main2.js</code> <code>js/main3.js</code>
* <code>npm install ink-docstrap</code> (installiert ein JSDoc-Theme namens „<code>docstrap</code>“ ([https://docstrap.github.io/docstrap/]); das haben Sie auch schon gemacht, aber das macht auch nix.)
* <code>js/app2/greet.js</code> → <code>js/app3/greet.js</code>


Übrigens: Es gibt auch noch andere Themes für JSDoc.
In der Datei „<code>index3.html</code>“ müssen Sie wiederum dafür sorgen, dass
die neuen JavaScript-Dateien anstelle der alten geladen werden.
Und passen Sie auch wieder den Titel im HTML-Header an.


Erstellen Sie im Projektordner  „<code>conf</code>“ eine JSON-Datei namens „<code>jsdoc.json</code>“.
Ersetzen Sie nun die Konstruktorfunktion der Datei „<code>js/app3/greet.js</code>“  
Fügen Sie in diese Datei folgenden Code ein:
durch folgende Konstruktorfunktion:


<source lang="javascript">
<source lang="javascript">
/**
* @class
* @classdesc Contains methods to say hello to the user of the web app.
*
* @param p_document {object}
*        A DOM object (usually the global object <code>document</code>)
* @param p_section_form {string}
*        The ID of the HTML element that contains the form section.
* @param p_input_name string}
*        The ID of the text field via which the user passes her/his name.
* @param p_section_hello {string}
*        The ID of the HTML element that contains the welcome section.
* @param p_output_hello {string}
*        The ID of the HTML element into which the greeting is to be put.
* @param p_hello {string}
*        The hello test to be displayed.
*/
function Greet(p_document,
              p_section_form, p_input_name,
              p_section_hello, p_output_hello,
              p_hello
              )
{
{
   "tags":
   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()
   {
   {
     "allowUnknownTags": true
     p_document.getElementById(p_output_hello).innerHTML =
   },
      p_hello + ', ' + p_document.getElementById(p_input_name).value + '!';
   "source":
 
    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)
   {
   {
     "include":        ["./src/js"],
     if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
    "includePattern": ".+\\.js$",
        p_document.activeElement === p_document.getElementById(p_input_name)
     "excludePattern": "doc"
      )
  },
     {
  "plugins":
      p_event.preventDefault();
  [],
      p_event.stopPropagation();
  "templates":
       l_this.sayHello();  // NOT this.sayHello();
  {
     }
    "systemName":      "WK_HelloWorld04",
    "footer":          "",
    "copyright":       "Copyright © 2016 by Wolfgang Kowarschick, License <a href=\"https://creativecommons.org/licenses/by-nc-sa/4.0/\">CC-BY-NC-SA-4.0</a>",
    "navType":        "vertical",
    "theme":          "cerulean",
    "linenums":        true,
     "cleverLinks":    false,
    "monospaceLinks":  true,
    "collapseSymbols": true,
    "inverseNav":      false
  },
  "opts":
  {
    "template":    "node_modules/ink-docstrap/template",
    "encoding":    "utf8",
    "destination": "doc/jsdoc",
    "recurse":    true,
    "private":    true
   }
   }
}
}
</source>  
</source>
 
Dieser Konstruktor enthält diverse Konstanten nicht mehr, wie {{zB}} <code>'input_name'</code>. Anstelle dessen gibt es diverse Parameter,
in denen diese Werte dem Konstruktor übergeben werden können, wie {{zB}} <code>p_input_name</code>. Der Zugriff auf das globale Objekt
„<code>document</code>“ wurde auch gleich noch durch einen Zugriff auf einen lokalen Parameter „<code>p_document</code>“ ersetzt.
 
Damit ist es jetzt möglich, <code>greet</code>-Objekte für ganz unterschiedliche HTML-Elemente zu definieren.
 
Ersetzen Sie zunächst die Initialisierungsmethode in der Datei  „<code>main3.js</code>“
durch folgenden Code:
 
<source lang="javascript">
(function(p_window, Greet)
{
  var l_document = p_window.document, // The window object contains the document object.
      l_greet    = new Greet(l_document,
                            'section_form', 'input_name',
                            'section_hello', 'heading_hello',
                            'Hello'
                            );
 
  l_document.getElementById('button_submit')
            .addEventListener('click', l_greet.sayHello);
 
  p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
}(window, Greet));
</source>
 
In dieser Datei werden noch gibt es noch zwei globale Variablen, die Browser-Variable  „<code>window</code>“
sowie die von uns definierte Konstruktorfunktion „<code>Greet</code>“. Diese beiden globalen Größen werden der
anonymen Funktion als Argumente übergeben. Innerhalb des Rumpfes der Initialisierungsfunktion wird dann nicht
mehr auf globale Größen zugegriffen, sondern nur noch auch Funktionsparameter. Dies ist ein übliches
Vorgehen, um explizit klar zu machen, auf welche globalen Größen zugegriffen wird.
 
Dem Konstruktor  „<code>Greet</code>“ werden jetzt alle Information, die er benötigt,
explizit übergeben. Dies ist zum einen das HTML-Dokument, das mit Hilfe der von ihm erstellten
Methoden manipuliert werden soll, dann eine Reihe von ID-Attribut-Namen, die diejenigen
HTML-Elemente bezeichnen, die die Greet-Methoden lesen oder ändern sollen, sowie
der String „<code>'Hello'</code>“ der zur Begrüßung des Benutzers verwendet werden soll.
Alternativ könnte er auch mit „<code>Hallo</code>“ oder „<code>Howdy</code>“ begrüßt werden.
 
Nun müsste Ihre Anwendung „<code>index3.html</code>“ wieder laufen. Wenn Sie es denn tut, vergessen Sie nicht, sie
im Repository zu speichern.
 
Anschließend sollten Sie in Ihrem WebStorm-Projekt folgende Kopien erstellen:
 
* <code>index3.html</code> → <code>index3b.html</code>
* <code>js/main3.js</code> → <code>js/main3b.js</code>
 
Die Datei  „<code>index3b.html</code>“ soll abermals die JavaScript-Datei „<code>js/app3/greet.js</code>“
verwenden, dafür aber „<code>js/main3b.js</code>“ an Stelle von  „<code>js/main3.js</code>“ laden.
 
Fügen Sie in der Datei „<code>index3b.html</code>“ in alle ID-Attribut-Bezeichner eine <code>1</code> ein:
<code>section_form</code> → <code>section1_form</code> etc.
 
Schließen Sie dann die beiden Sections „<code>section1_form</code>“ und „<code>section1_hello</code>“
in ein <code>div</code>-Element ein und duplizieren Sie diese <code>div</code>-Elemente (natürlich samt Inhalt) anschließend.
Sie werden feststellen, dass WebStorm eine Reihe von Fehlern der Art  „<code>Duplicate id reference</code>“ meldet.
Das ist nicht weiter verwunderlich, da Sie ja insbesondere alle ID-Attribut-Bezeichner dupliziert haben.
Sie müssen daher im zweiten  <code>div</code>-Element alle Einser in den  ID-Attribut-Bezeichnern durch Zweier ersetzen.
 
Nun können Sie noch versuchen, die beiden <code>div</code>-Elemente mittels CSS nebeneinander zu legen. Für den
angestrebten Test, ob Sie die Hello-World-App in einem HTML-Dokument zweimal starten können, ist das allerdings irrelevant.
 
Passen Sie nun Ihre Datei „<code>main3b.js</code>“ an die neuen Gegebenheiten an:
Erzeugen Sie zwei Greet-Objekte „<code>greet1</code>“ und „<code>greet2</code>“ (für jedes der beiden neuen <code>div</code>-Elemente eines),
fügen Sie passende <code>click</code>-Event-Listener für die beiden Buttons „<code>button1_submit</code>“ sowie
„<code>button2_submit</code>“ hinzu und sorgen Sie zu guter Letzt dafür, dass beide Methoden  „<code>l_greet1.sayHelloOnEnter</code>“
und  „<code>l_greet2.sayHelloOnEnter</code>“ bei jedem Tastendruck aufgerufen werden.
 
Wenn Sie alles richtig gemacht haben, sollten jetzt in Ihrer Web-Anwendung „<code>index3b.html</code>“
zwei Hello-World-Anwendungen parallel laufen: [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/index3b.html <code>index3b.html</code>]
 
==App4==
Hinsichtlich der Wiederverwendbarkeit der Module ist die Version 3 der Web-Anwendung schon ganz gut.
Allerdings gibt es noch ein paar Unsauberkeiten, die beseitigt werden sollten:
 
# In den Initialisierungsdateien  „<code>main3.js</code>“ und  „<code>main3b.js</code>“ sind immer noch konstante Werte enthalten.
# Die Anzahl der Parameter der Konstruktorfunktion ist zu groß.
# In den Initialisierungsdateien ist immer noch zu viel Anwendungscode enthalten.
# Die potentielle Zahl der globalen Größen ist noch zu hoch.
 
Diese Probleme werden folgendermaßen gelöst:
 
# Die Konstanten werden in eine [[JSON]]-Datei ausgelagert, die im ersten Schritt von der jeweiligen Initialisierungsdatei eingelesen wird.
# Der Konstruktorfunktion wird das gesamte JSON-Objekt mit allen Initialisierungswerten in einem einzigen Parameter übergeben.
# Es wird eine anwendungsspezifische Initialisierungsdatei „<code>hello.js</code>“ erstellt. Die Initialisierungsfunktion in der Datei „<code>main4.js</code>“ hat nur noch die Aufgabe, die JSON-Datei zu lesen und die darin enthalten Informationen anschließend an die Methode  „<code>hello.init</code>“ weiterzuleiten.
# Es wird '''ein''' globales Objekt namens  „<code>hello</code>“ erstellt, das die gesamte Web-Anwendung enthält.
 
Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihrer Anwendungsdateien:
 
* <code>index3b.html</code> → <code>index4.html</code>
* <code>js/main3b.js</code> → <code>js/main4.js</code>
* <code>js/app3/greet.js</code> → <code>js/app4/greet.js</code>
 
Im Folgenden wird die HTML-Datei „<code>index4.html</code>“
als HTML-[[Template]] verwendet, {{dh}} als Strukturrahmen. '''Sämtliche''' Texte,
die in diesem Dokument enthalten sind, werden dynamisch mittels JavaScript eingefügt.
 
In der Datei „<code>index4.html</code>“ müssen Sie also nicht nur dafür sorgen, dass
die neuen JavaScript-Dateien anstelle der alten geladen werden und dass der Titel angepasst wird.
Sie müssen auch sämtliche Text-Elemente mit eindeutigen ID-Attributen versehen.
 
Die HTML-Datei der Musterlösung sieht folgendermaßen aus. Sie können auch Ihre
eigene Datei verwenden, müssen dann aber die JSON-Datei, die anschließend erstellt wird,
entsprechend anpassen.
 
<source lang="javascript">
<!DOCTYPE html>
<html lang="en">
 
<head>
  <meta charset = "UTF-8">
  <meta name    = "viewport"
        content = "width=device-width, initial-scale=1.0, user-scalable=yes"
  >
 
  <title>WK_HelloWorld04 (4)</title>
 
  <link rel = "stylesheet" type = "text/css" href = "css/main.css"/>
</head>
 
<body>
  <div class="left">
    <section id="section1_form">
      <h1 id="heading1_form"></h1>
      <form>
        <div>
          <label id="label1_name" for="input1_name"></label>
          <input id="input1_name" type="text" autofocus="autofocus"/>
        </div>
        <div>
          <button id="button1_reset"  type="reset"></button>
          <button id="button1_submit" type="button"></button>
        </div>
      </form>
    </section>
    <section id="section1_hello" class="hidden">
      <h1 id="heading1_hello"></h1>
      <p id="paragraph1_hello"></p>
    </section>
  </div>


Ersetzen Sie im Attribut „<code>copyright</code>“ meinen Namen durch Ihren, denn es ist Ihr Projekt, nicht meines. :-)
  <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>


In dieser Datei wird festgelegt, wie die HTML-Dokumentation des Codes, die JSDoc erstellt, aussehen soll.
  <script type = "text/javascript" src = "js/lib/wk/ajax.js"></script>
Insbesondere ist das Template angegeben, das zur Erstellung dieser HTML-Dokumente verwendet werden soll.
  <script type = "text/javascript" src = "js/app4/greet.js"></script>
Es handelt sich um „<code>docstrap</code>“, welches wir zuvor heruntergeladen haben. Von der
  <script type = "text/javascript" src = "js/app4/hello.js"></script>
Projektroot aus gesehen, befinden sich die Template-Dateien im Ordner
  <script type = "text/javascript" src = "js/main4.js"></script>
<code>node_modules/ink-docstrap/template</code>“.
</body>


Weitere Konfigurationsparameter werden unter http://usejsdoc.org/about-configuring-jsdoc.html beschrieben.
</html>
</source>


Sie können nun eine Dokumentation für Ihr Projekt erstellen:
===Modularisierung===


* Öffnen Sie das WebStorm-Terminal.
Um möglichst wenige globale Größen definieren zu müssen, legt man für eine
* <code>jsdoc -c conf/jsdoc.json</code>
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.


Nach kurzer Zeit sollte der Ordner „<code>doc/jsdoc</code>“ erstellt und mit Inhalten gefüllt worden sein.
Für die Hello-World-Anwendung wird die globale Namensraum-Variable „<code>hello</code>“ verwendet.
Sehen Sie sich die Datei „<code>doc/jsdoc/index.html</code>“ im Browser an. Diese enthält nicht viel,
In jeder Datei, in der ein Teilaspekt der Anwendung definiert wird, kommt folgender Rahmencode zum Einsatz:
außer einem Drop-Down-Menü „<code>global</code>“, in dem die drei von Ihnen erstellen globalen Funktionen
„<code>sayHello</code>“, „<code>sayHelloOnEnter</code>“ und „<code>init</code>“ enthalten sind.
Klicken Sie auf eine dieser Funktionen.


===Vermeidung globaler Funktionen===
<source lang="javascript">
/** @namespace hello */
var hello =
(function(p_hello)
{ "use strict";


Die Verwendung von globalen Variablen oder Funktionen in JavaScript ist extrem gefährlich.
  // anwendungsspezifischer Code
Wenn man eine JavaScript-Datei verwendet, in der
globale Funktionen enthalten sind, die zufälligerweise genauso heißen, wie
globale Funktionen in der eigenen JavaScript-Datei, kann man nicht beide
Dateien laden, da globale Funktionen, die in der ersten Datei enthalten sind,
durch globale Funktionen der zweiten Datei überschrieben werden.


Daher ist es besser, Funktionen wie „<code>sayHello</code>“, „<code>sayHelloOnEnter</code>“ und „<code>init</code>“
  return p_hello;
in ein JavaScript-Objekt einzufügen. In unseren Fall könnte man beispielsweise ein
}(hello ? hello : {}));
JavaScript-Objekt „<code>main</code>“ erzeugen, dass diese drei Funktionen enthält.
</source>
Damit hat man nur noch eine globale Variable (<code>main</code>) und die Gefahr
einer zufälligen Namenskollision bei Verwendung mehrerer JavaScript-Dateien ist geringer.


Am besten ist es natürlich, auf globale Größen ganz zu verzichten. Dazu braucht man aber ein
In der Variablen „<code>hello</code>“ wird das Ergebnis eines Aufrufs einer anonymen Funktion gespeichert.
Modul-System. Dieses ist für [[EcmaScript 6]] geplant ([https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/import
Dieser Funktion wird als Argument der Inhalt der Variablen „<code>hello</code>“ oder, falls dieses noch nicht
<code>import</code>] und [https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/export <code>export</code>]), wird
existiert, ein leeres Objekt als Argument übergeben (<code>hello ? hello : {}</code>). Die anonyme Funktion
aber derzeit nur von wenigen Browsern unterstützt.
fügt irgendwelche Daten, Klassen oder Funktionen in dieses Objekt ein und gibt es dann als Ergebnis zurück.  


Erserten Sie den Code in Ihrer Datei „<code>main.js</code>“ durch folgenden Code:
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 main */
/** @namespace hello */
var
var hello =
main =
(function(p_hello)
{
{ "use strict";
 
   /**
   /**
   * Welcomes the user of the web app by displaying a welcome message
   * @class    hello.Greet
   * that includes his name. The name is fetched from a text input field.
  * @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.
   */
   */
   sayHello:
   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()
     function()
     {
     {
       document.getElementById('text_hello').innerHTML =
       var l_init_elements = p_init.HTMLElements,
        'Hello, ' + document.getElementById("input_name").value + '!';
          l_init_text    = p_init.text,
      document.getElementById('section_form').classList.add('hidden');
          l_init_hidden  = p_init.css.hidden,
       document.getElementById('section_welcome').classList.remove('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.
    * An keyboard event observer. It tests whether the enter key has been pressed.
  * If so, the sayHello method is activated. Default reactions of the browser are
    * If so, the greet method is activated. Default reactions of the browser are
  * disabled.
    * disabled.
  * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
    *
  *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
    * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
  */
    *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
  sayHelloOnEnter:
    */
    l_this.sayHelloOnEnter =
     function(p_event)
     function(p_event)
     {
     {
       if (p_event.code === 'Enter' || p_event.keyCode === 13)
       if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
          p_document.activeElement ===
          p_document.getElementById(p_init.HTMLElements.inputName)
        )
       {
       {
         p_event.preventDefault();
         p_event.preventDefault();
         p_event.stopPropagation();
         p_event.stopPropagation();
         main.sayHello();
         l_this.sayHello();  // NOT this.sayHello();
       }
       }
     },
     };
  };
 
  return p_hello;
}(hello ? hello : {}));
</source>
 
Gegenüber der Version 3 dieser Datei hat sich Folgendes geändert:
 
* Es wird keine globale Konstruktorfunktion „<code>Greet</code>“ mehr definiert. Diese Konstruktorfunktion wird vielmehr in der Namensraum-Variablen „<code>hello</code>“ gespeichert:  „<code>hello.Greet</code>“ 
* Die Konstruktorfunktion hat nur noch zwei Parameter: „<code>p_window</code>“ und „<code>p_init</code>“. Der erste Parameter enthält wie in Version 3 das globale Window-Objekt, der zweite ein JSON-Objekt, in dem alle Initialisierungsinformationen enthalten sind.
* Im Rumpf der Konstruktorfunktion werden alle notwendigen Informationen aus  <code>p_init</code> extrahiert: Der Name der Formular-Section, der Name des Text-Eingabefelds, der Text der Hello-Nachricht etc.
 
Legen Sie gleich noch die Datei „<code>js/app4/hello.js</code>“ für die Web-App an:
<source lang="javascript">
/** @namespace hello */
var hello =
(function(p_hello)
{ "use strict";


   /**
   /**
   * Initializes the web app.
   * Initializes the HTML sections of the app that are
   * Is to be called when the web app has been loaded completely.
  * 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.
   */
   */
   init:
   p_hello.init =
    function()
  function(p_window, p_init)
    {
  {
      document.getElementById('button_submit')
    var l_document      = p_window.document,
              .addEventListener('click', main.sayHello);
        l_init_elements = p_init.HTMLElements,
        l_init_text    = p_init.text,
        l_greet        = new p_hello.Greet(l_document, p_init);


      window.addEventListener('keydown', main.sayHelloOnEnter);
    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);
  };


// Call main.init, when you are ready with loading.
  return p_hello;
window.addEventListener('load', main.init);
}(hello ? hello : {}));
</source>
</source>


Mit dem JSDoc-Kommentar „<code>@namespace main</code>“
Sie ist wieder aufgebaut wie zuvor beschrieben: Sie definiert, falls notwendig, das globale Objekt „<code>hello</code>“
wird angezeigt, dass die globale Variable „<code>main</code>“
und fügt anschließend eine Funktion „<code>init</code>“ in dieses Objekt ein. Die Methode „<code>hello.init</code>“
als [[Namensraum]]fungiert. Das heißt, <code>main</code>
hat dieselben Parameter, wie die zuvor definierte Konstruktorfunktion und erwartet denselben Input.
enthält diverse Funktionen und evtl. andere Elemente, die irgendeinen inneren
Ihre Aufgabe ist es, die derzeit noch leeren Text-Elemente des HTML-Templates mit Inhalt zu füllen
Zusammenhang haben.
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===
Zur Initialisierung der Web-Anwendung werden JSON-Dateien benötigt.


Wen Sie jetzt JSDoc erneut ausführen, beschreibt Ihre Dokumentation keine globale
Eine JSON-Datei enthält genau ein JavaScript-Objekt in textueller Darstellung.
Funktionen mehr, sondern einen Namensraum „<code>main</code>“, der die
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>:
Funktionen „<code>sayHello</code>“, „<code>sayHelloOnEnter</code>“ und „<code>init</code>
* Nullwert: <code>null</code>
enthält. Auf diese Funktionen kann mittels „<code>main.sayHello</code>, <code>main.sayHelloOnEnter</code>
* Boolesche Werte: <code>true</code>, <code>false</code>
und „<code>main.init</code>“ zugegriffen werden.
* Zahlen
* Strings
* Arrays
* Objekte


===Erstellung der JSDoc-Dokumentation mittels Klickibunti===
Insbesondere Funktionen können in einer JSON-Datei nicht enthalten sein. Außerdem müssen
Strings immer in doppelte Anführungszeichen eingeschlossen sein, einfache Anführungszeichen sind nicht erlaubt.
Attributnamen werden ebenfalls durch Strings dargestellt. Auch diese müssen gemäß ECMA-Standard in doppelte
Anführungszeichen eingeschlossen werden. Außerdem können in JSON-Dateien (leider) keine Kommentare
enthalten sein.


Falls Sie die Dokumentation nicht immer über das Terminal erstellen möchten, können Sie
Folgendes Objekt ist zwar ein korrektes Javascript-Objekt, aber kein gültiges JSON-Objekt:
in WebStorm einen neuen Menüeintrag erstellen, über den Sie die Erzeugung der HTML-Dokumentation starten können.


Sie müssen dazu den Pfad ermitteln, unter dem Ihre <code>jsdoc</code>-Datei zu finden ist.
<source lang="javascript">
Unter Unix (Mac OS X, Linux, FreeBSD ...) lautet der Befehl, den Sie in ein Terminal eingeben müssen:
{ a: 55, 'b': 'Wolfgang' }
<source lang="bash">
which jsdoc
</source>
</source>
Unter '''Windows''' funktioniert dieser Befehl nur in der '''Git BASH'''.
 
Außerdem müssen Sie unter Windows den Bash-Pfad modifizieren, damit auch Windows etwas damit anfangen kann.
In JSON muss dieses Objekt folgendermaßen geschrieben werden:
Bei mir gibt dieser Befehl Folgendes aus:
 
<source lang="bash">
<source lang="javascript">
/c/Users/kowa/AppData/Roaming/npm/jsdoc
{ "a": 55, "b": "Wolfgang" }
</source>
</source>
Um <code>jsdoc</code> in WebStorm aufrufen zu können, müssen Sie in diesem Pfad „<code>/c</code>“ durch
„<code>c:</code>“ und  „<code>jsdoc</code>“ durch „<code>jsdoc.cmd</code>“ ersetzen.
(Mac-User brauchen nichts zu ändern. :-) )


Erstellen Sie nun den neuen WebStorm-Menü-Eintrag:
Diese Objekt ist auch ein korrektes JavaScript-Objekt. Da die Syntaxregeln von JSON strenger sind
als von JavaScript, gilt folgende Regel: Jedes JSON-Objekt ist auch ein JavaScript-Objekt, aber nicht
jedes JavaScript-Objekt ist auch ein JSON-Objekt.


* <code>File</code> → <code>Settings</code> → <code>Tools</code> → <code>External Tools</code>
Ein Problem bereitet die Verwendung von JSON. Da ein JSON-Objekt beliebige Attribute enthalten kann,
* Klick auf das Plus-Icon
kann eine IDE nicht überprüfen, ob ein bestimmtes Attribut oder Element in einem JSON-Objekt
** <code>Name</code>: <code>JSDoc</code>
mit Sicherheit enthalten ist oder nicht. Das hat {{zB}} zur Folge, das WebStorm bei einem Zugriff
** <code>Group</code>: <code>Documentation</code>
auf eine Attribut in einem JSON-Objekt den Attributnamen unterringelt und mit der Warnung
** <code>Description</code>: <code>Generates JavaScript documentation</code>
„<code>Unresolved variable ...</code>“ versieht.
** Hacken bei allen Optionen (<code>Options</code>)
** Hacken bei allen Show-in-Optionen (<code>Show i</code>) mit Ausnahme von „<code>Search results</code>“
* <code>Program</code>: <code>c:/Users/kowa/AppData/Roaming/npm/jsdoc.cmd</code> (hier kommt Ihr zuvor ermittelter Pfad hinein)
* <code>Parameters</code>: <code>-c $ProjectFileDir$/conf/jsdoc.json</code>
* <code>Working directory</code>: <code>$ProjectFileDir$</code>
* <code>OK</code>
* <code>OK</code>


Nun finden Sie an mehreren Stellen in WebStorm einen neuens Menüpunkt  <code>Documentation</code> mit einem Unterpunkt <code>JSDoc</code>:
Ein Lösung für dieses Problem wäre die Verwendung von [http://json-schema.org/ JSON Schema].
Aber darauf wird zunächst verzichtet.


* Hauptmenü : <code>Tools</code> → <code>Documentation</code> → <code>JSDoc</code>
Legen Sie für die zuvor definierte HTML-Datei „<code>index4.html</code>“ folgende JSON-Initialisierungsdatei
* Rechtsklick auf Datei im Projekt-Dateibaum → <code>Documentation</code> → <code>JSDoc</code>
unter dem Namen „<code>web/json/hello_de.json</code>“ an:
* Rechtsklick auf Datei im Editor-Tab-Menü → <code>Documentation</code> → <code>JSDoc</code>


Wenn Sie auf einen dieser Unterpunkte  „<code>JSDoc</code>“ klicken, erhalten Sie entweder eine Fehlermeldung ( :-( ) oder es wird eine neue Dokumentation erstellt
<source lang="javascript">
({{dh}}, Sie haben alles richtig gemacht :-) ).
{
  "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"
  },


Um sicher zu gehen und um nicht mehr verwendete Dokumentationsdateien wie „<code>doc/jsdoc/global.html</code>“ zu entfernen, sollten Sie den Ordner „<code>doc/jsdoc</code>“ gemeinsam mit seinem gesamten Inhalt löschen und <code>JSDoc</code> nochmals aufrufen.
  "text":
  {
    "title":  "Hallo, Fremder!",
    "query":  "Wie heißen Sie?",
    "reset":  "Reset",
    "submit":  "Begrüßung",
    "hello":  "Hallo, $1!",
    "welcome": "Willkommen bei Multimedia-Programmierung!"
  },


===Sicherung im Repository===
  "css":
  {
    "hidden": "hidden"
  }
}
</source>


Bevor Sie weitermachen: Haben Sie Ihre aktuelle Version von <code>Hello World 04</code> schon im Repository gesichert?
Diese Datei besteht aus einem anonymen Objekt. Es enthält seinerseits drei Objekte  „<code>HTMLElements</code>“,
„<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.


Bevor Sie die Projekt-Dateien per ''Commit'' ins Repository übertragen, sollten Sie überprüfen,
Im Objekt  „<code>text</code>“ sind alle Texte enthalten, die zu gegebener Zeit in das HTML-Dokument an die geeignete
dass der Ordner „<code>node_modules</code>“ auf der sogenannten „Ignore List“ steht (und damit nicht
Stelle geschrieben werden sollen. Der Text  „<code>hello</code>“ enthält  „<code>$1</code>“ als Platzhalter.
ins Repository eingespielt wird):
Dieser Platzhalter soll von der Anwendung durch den Namen des Benutzers ersetzt werden, nachdem er ihn eingegeben hat.


* <code>Files</code> bzw. <code>WebStorm</code> → <code>Settings</code> bzw. <code>Preferences</code> → <code>Ignored Files</code>
In Objekt „<code>css</code>“ sind alle Namen von CSS-Klassen enthalten, die von der Web-Anwendung zur Laufzeit
* Wenn in dieser Liste der Ordner „<code>node_modules</code>“ nicht aufgeführt wird, fügen Sie ihn mittels des Plus-Icons ein.
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.


Sie sollten allerdings den Ordner „<code>doc</code>“ zu Ihrem
Erstellen Sie analog die Datei „<code>web/json/hello_en.json</code>“. Diese Datei soll für die zweite Web-Anwendung
Repository hinzufügen:
definiert werden, die allerdings englischsprachige Texte enthält (Sie erinnern sich? In <code>index4.html</code>
laufen zwei unabhängige Web-Anwendungen parallel).


* Klick auf „<code>doc</code>“ im Dateibaum → <code>Subversion</code> → <code>Add to VCS</code>
===Das Laden der JSON-Dateien===


Nun können Sie Ihr Projekt committen.
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.


==Automatische Komprimierung von CSS- und Javascript-Dateien==
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).


Nun folgt die letzte Optimierung der Anwendung. Die von Ihnen erstellten CSS- und Javascript-Dateien
Die Methode „<code>getJSON</code>“ erwartet drei Argumente, einen URI, der den Speicherort der JSON-Datei beschreibt und zwei Callback-Funktionen.
enthalten viele Leerzeichen, Kommentare, „sprechende“ anstelle von kurzen Variablenbezeichnern etc.
Die erste Funktion wird aufgerufen, sobald die JSON-Datei erfolgreich geladen wurde. Ihr wird das geladen JSON-Objekt als Argument
Das ist für Sie und alle anderen Entwickler von Vorteil. Für die Übertragung der Daten zu einem
übergeben. Die zweite Callback-Funktion wird aufgerufen, falls beim Laden etwas schief gehen sollte. Die Angabe dieser Funktion ist
Client (Browser) bedeutet das nur unnötiges Datenvolumen. Gerade für mobile Tarife mit Volumenbegrenzung
optional. In der Musterlösung wurde Sie definiert, hier wird darauf verzichtet.
sollten so wenig Daten wie möglich übertragen werden.


Glücklicherweise gibt es Tools, die bestehende CSS- oder Javascript-Dateien komprimieren (meist sogar so, dass
Die Initialisierungsfunktion (Datei „<code>main4.js</code>“) sieht nun folgendermaßen aus:
sich die [[Semantik]] der Dateien nicht ändert; aber wie gesagt: Auch andere Programmierer machen hin und wieder Fehler).


Wir verwenden die Node.js-Tools „<code>cssmin</code>“ für CSS-Dateien (https://www.npmjs.com/package/cssmin) und
<source lang="javascript">
„<code>UglifyJS2</code>“ für JavaScript-Dateien (https://github.com/mishoo/UglifyJS2). Gesteuert werden diese  “minimizer”
// Initialize the app.
oder  “minifier” mittels dem bereits installierten ''JavaScript Task Runner'' <code>grunt</code>“.
(function(p_window, p_wk, p_hello)
{ "use strict";


Die für das Minimieren mittels Grunt notwendige Pakete wurden ebenfalls bereits installiert:
  // The list of all json files to be loaded
  var l_json_files = ['json/hello_de.json', 'json/hello_en.json']; 


<source lang="bash">
  // For each element in the JSON file list:
npm install grunt grunt-contrib-watch grunt-contrib-cssmin grunt-contrib-uglify --save-dev
  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>
</source>


Nun müssen die Aufgaben zu Komprimieren der JavaScript- und CSS-Dateien in die Konfigurationsdatei
Die Initialisierungsfunktion liest die beiden zuvor erstellten JSON-Dateien ein
eingefügt werden (vgl. http://gruntjs.com/getting-started):
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


'''<code>gruntfile.js</code>'''
<source lang="javascript">
<source lang="javascript">
module.exports = function(grunt)
function(p_init) { p_hello.init(p_window, p_init); }
{
</source>
  // Project configuration.
 
  grunt.initConfig
ausgeführt. Dieser wird im Parameter „<code>p_init</code>“ das zugehörige JSON-Objekt überreicht.
  ({
Sie reicht es (zusammen mit die Window-Objekt) an die Methode „<code>p_hello.init</code>“ weiter.
    pkg: grunt.file.readJSON('package.json'),


    cssmin:
'''Anmerkung''': Man könnte die beiden JSON-Dateien natürlich auch mittels zweier Aufrufe von „<code>getJSON</code>“
    { options: /* https://github.com/jakubpawlowicz/clean-css */
laden:
      { roundingPrecision:  -1,
        keepSpecialComments: 1
      },
      minimize:
      { files:
        { 'web/css/main.css': ['src/css/terminal.css', 'src/css/main.css']
        }
      }
    },


    uglify:
<source lang="javascript">
    { options: /* https://github.com/gruntjs/grunt-contrib-uglify */
// Initialize the app.
      { mangle:          true,
(function(p_window, p_wk, p_hello)
        preserveComments: function(p_node, p_comment)
{ "use strict";
                          { return /@license/.test(p_comment.value); }
      },
      minimize:
      { files: { 'web/js/main.js': ['src/js/terminal.js', 'src/js/main.js']
              }
      }
    }
  });


   // Load the plugin that provides the "uglify" task.
   p_wk.ajax.
  grunt.loadNpmTasks('grunt-contrib-uglify');
    getJSON
  grunt.loadNpmTasks('grunt-contrib-cssmin');
    ( '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.
    );


   // Default task(s).
   p_wk.ajax.
  grunt.registerTask('default', ['uglify', 'cssmin']);
    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>
</source>


Die Funktion <code>grunt.initConfig</code>“ aufgerufen und es wird ihr ein [[JavaScript-Objekt]] übergeben.
Diese Verdopplung von Code würde allerdings gegen das Programmierprinzip „[[Don't repeat yourself]]“ (DRY) verstoßen.
Im Attribut „<code>pkg</code>“ wird grund der Inhalt der Datei  „<code>package.json</code>“ übergeben, damit Grunt ermitteln kann, welche Pakete verfügbar sind.
Die beiden anderen Attribute „<code>cssmin</code>“ und „<code>uglify</code>“ (deren Namen frei wählbar sind), werden die beiden Minifier konfiguriert.


In beiden Fällen können Optionen angegeben werden, wie {{zB}}, dass CSS-Maß-Angaben nicht gerundet werden sollen (es gibt Designer, die bestehen
==App5==
beim Pixelmaß auf vier Nachkommastellen) oder dass geeignete JavaScript-Variablen-Namen „durch die Mangel gedreht“ (''mangle''), also durch kürze Namen ersetzt werden sollen.
Wichtiger ist jedoch die Angabe, welche Dateien überhaupt erstellt werden und was für Sourcedaten dafür verwendet werden solle.


Zu jeder Datei, die erstellt wird, kann man eine ganze Liste von Sourcedateien angeben. Das hat zur Folge, dass alle  diese Sourcedateien nacheinander
Im Prinzip kann die in App4 vorgestellte Lösung zur Modularisierung einer Web-Anwendung  
komprimiert und dann in die Zieldatei eingefügt werden. Auch das ist eine Optimierung, um das Laden einer Web-Anwendung zu beschleunigen.
gut eingesetzt werden. Allerdings hat sie immer noch zwei Schwächen:
Eine große Datei wird per HTTP schneller übertragen als viele kleine. Künftig werden wir daher möglichst wenige komprimierte Dateien verwenden
und möglichst viele nicht-komprimierte JavaScript-Dateien in komprimierter Form nacheinander in nur eine einzige JavaScript-Datei einfügen.


Nach der Initialisierungsfunktion werden noch zwei weitere Funktionen aufgerufen. Mit <code>grunt.loadNpmTasks</code>
* Der Rahmencode, der in jeder Datei der Anwendung verwendet werden muss, ist lästig. (Das ist ein kleineres Problem.)
werden die Grunt-Plugins geladen, die die eigentliche Komprimierung vornehmen. Und mittels <code>grunt.loadNpmTasks</code>  
* 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.
wird festgelegt, welche Aufgaben standardmäßig ausgeführt werden, sobald der Befehl „<code>grunt</code>“ ausgeführt wird.


So, nun sollte alles funktionieren. Öffnen Sie das Web-Storm-Terminal und tippen Sie nacheinander folgende Befehle ein:
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.


<source lang="bash">
Solange dies der Fall ist, kann man auf JavaScript-Bibliotheken zurückgreifen, die dieselbe Aufgabe (wenn auch weniger elegant) erfüllen.
grunt cssmin
Hier könnte die Bibliothek „<code>RequireJS</code>“ zum Einsatz (http://requirejs.org/). An der Struktur der Anwendung ändert sich nichts.
grunt uglify
Nur die Art, wie Module definiert und geladen werden, wird angepasst.
grunt
</source>


Sie werden feststellen, das jedes mal bestimmte Dateien in den Ordnern „<code>web/css</code>“ oder/und „<code>web/js</code>“
Erstellen Sie in Ihrem WebStorm-Projekt letztmalig Kopien Ihre Anwendungsdateien:
erstellt werden, die mit dem Namensbestandteil „<code>min</code>“ beginnen. Sehen Sie sich die erzeugten Dateien an.
Sie enthalten denselben Code wie die Original-Dateien, nur in komprimierter (und daher für den Menschen schwer lesbarer) Form.


Als nächstes muss die Datei „<code>index.html</code>“ angepasst werden. Diese lädt noch die unkomprimierten Dateien aus dem
* <code>index4.html</code> → <code>index5.html</code>
Sourceverzeichnis. Künftig soll sie die komprimierten Dateien aus dem eigentlichen Web-Ordner laden:
* <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>


<source lang="html5">
In der Datei „<code>index5.html</code>“ müssen Sie wiederum dafür sorgen, dass
...
die neuen JavaScript-Dateien anstelle der alten geladen werden.
<link rel="stylesheet" type="text/css" href="css/main.css"/>
Und passen Sie auch wieder den Titel im HTML-Header an.
...
<script type="text/javascript" src="js/main.js"></script>
</source>


Ab sofort werden nicht mehr die Sourcedateien geladen, sondern die komprimierten, durch die Minifier erstellten Dateien.
Als nächstes müssen Sie, um <code>RequireJS</code> nutzen zu können, den Ordner „<code>web/js/lib/require</code>“
Das heißt aber für uns, dass wir bei jeder Änderung an den Sourcedateien den Befehl „<code>grunt</code>“
erstellen und folgende drei Dateien (einschließlich des Inhalts, der sich hinter den drei Links verbirgt) dort speichern:
ausführen müssen, um die Aktualisierungen in die komprimierten Versionen der JavaScript bzw. CSS-Dateien zu übertragen.
* <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>


Das ist lästig und geht besser.
===Modularisierung mit RequireJS===


==Automatische Aktualisierung der komprimierten Dateien==
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.


Mit Hilfe des Node.js-Paketes „<code>grunt-contrib-watch</code>“ kann man beliebige Dateibäume überwachen,
Es wird auch nur noch eine einzige JavaScript-Datei statisch geladen:
ob sich darin bestimmte Dateien ändern. In einem derartigen Fall kann man automatisch andere Grunt Tasks ausführen lassen.
„<code>require.js</code>“. Alle anderen Dateien werden dynamisch geladen.
Das lässt sich ausnutzen, um nach Änderungen einer CSS-Datei oder einer JavaScript-Datei die zugehörige komprimierte
Die in diesen Dateien enthaltenen Module werden mittels
Datei automatisch neu zu erstellen.
Callback-Funktionen an andere Module übergeben. Auf diese
Weise ist sicher gestellt, dass man keine anderen globalen
Variablen und Funktionen mehr benötigt.


Öffnen Sie das WebStorm-Terminal und laden Sie das besagte Paket:
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.


<source lang="bash">
Für RequireJS gibt es einige Plugins. Eines davon ermöglicht
npm install grunt-contrib-watch --save-dev
das Lesen von JSON-Dateien („<code>json.js</code>“). Dieses Plugin wird verwendet,
</source>  
um die Dateien „<code>json/hello_xx.json</code>“ einzulesen.


Erweitern Sie nun die Datei „<code>gruntfile.js</code>“ um einen Watch-Task:
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).


<source lang="javascript">
Die Moduldefinition von „<code>hello</code>“  sieht in diesem Fall folgendermaßen aus:
watch:
{ css:
  { files:  ['src/css/**/*.css'],
    tasks:  ['cssmin']
  },
  js:
  { files:  ['src/js/**/*.js'],
    tasks:  ['uglify']
  },
  web:
  { files:  ['web/**/*'],
    options: { livereload: true,
              spawn:      false
            }
  }
}
</source>  


Vergessen Sie nicht, das Plugin zu laden:
<source lang="javascript">
<source lang="javascript">
grunt.loadNpmTasks('grunt-contrib-watch');
define
</source>  
( ['app/greet'],
  function (Greet)
  {
    return ...
  }
);
</source>


Der Watch-Task überwacht drei verschiedene Ordner:
Die Funktion „<code>define</code>“ lädt zunächst alle Module, die in der Modulliste aufgeführt werden, asynchron (und damit meist auch parallel).
Allerdings wird jedes Modul nur einmal geladen. Das heißt, wenn zwei Module dasselbe Modul benötigen, wird dieses Modul trotzdem
nur einmal geladen. Sobald alle Module geladen sind, wird die im  <code>define</code>-Aufruf definierte Callback-Funktion ausgeführt.
Die Funktion muss für jedes Modul, das in der Modulliste enthalten ist, einen Parameter zur Verfügung stellen,
in der das jeweilige Modul-Objekt an die Callback-Funktion übergeben wird.


* Wann immer sich eine CSS-Datei im Ordner „<code>src/css</code>“ oder einem darin enthaltenen Unterordner ändert, wird der Task <code>cssmin</code>“ ausgeführt.
Mann könnte im  <code>define</code>-Aufruf auch noch einen Modulnamen angeben. Das ist aber {{iAllg}}
* Wann immer sich eine JavaScript-Datei im Ordner „<code>src/js</code>“ oder einem darin enthaltenen Unterordner ändert, wird der Task  „<code>uglify</code>“ ausgeführt.
nicht notwendig, da RequireJS defaultmäßig den Dateinamen (ohne Pfad und ohne Endung „<code>js</code>“)
* Wann immer sich irgendeine Datei im Web-Ordner ändert, wird eine <code>livereload</code>-Nachricht (http://livereload.com/) an einen lokalen Node-Server mit der Portnummer 35729 geschickt. (Keine Angst: Sie brauchen diesen Server nicht zu starten oder zu stoppen.)
als Modulnamen verwendet.


Damit kann man Browser anweisen, die HTML-Dateien neu zu laden, wann immer sich etwas ändert.
Wenn man in der Definition eines Moduls auf keine anderen Module zurückgreifen will, kann man auf die Callback-Funktion verzichten.
Dies erfolgt entweder mit speziellen Browser-Plugins (http://livereload.com/extensions/) oder mit einem kleinen JavaScript-Hack:
Um ein Modul zu schreiben, das lediglich ein Objekt enthält, schreibt man einfach:
Fügen Sie ans Ende der Datei „<code>main.js</code>“ folgenden Code ein:


<source lang="javascript">
<source lang="javascript">
// Enable live reloading for the WebStorm web server.
define
if (location.port === '63342')  // WebStorm server port
({
{
    // Inhalt des Objekts, den das Modul definiert.  
  var l_script = document.createElement('script');
});
  l_script.src = 'http://'+(location.host||'localhost').split(':')[0]+':35729/livereload.js';
  document.body.appendChild(l_script);
}
</source>
 
Dieser Code sorgt dafür, dass in eine HTML-Datei, die die Datei „<code>main.js</code>“ einbindet,
ein zusätzliches <code>script</code>-Element
<source lang="html5">
<script src="http://localhost:35729/livereload.js></script>
</source>
</source>
erstellt und in jede HTML-Datei eingefügt wird, die von einem WebStorm-Test-Server ausgeliefert wird.
(Der URI kann anstelle des Servernamens „<code>localhost</code>“ auf den echten Namen des Entwicklungsrechners enthalten, sofern
der Entwicklungsrechner einen derartigen Namen hat.)


Unter Port 35729 wird auf dem Entwicklungsrechner von <code>grunt-contrib-watch</code> der bereits erwähnte Livereload-Server gestartet und
Wenn das Modul lediglich eine Funktion, wie {{zB}} eine Konstruktorfunktion „<code>C</code>“ zur Verfügung stellen soll, schreibt man
die JavaScript-Datei „<code>livereload.js</code>“ zur Verfügung gestellt. Wenn man diese Datei in seine HTML-Datei einbindet,
aktualisiert sich die HTML-Datei fortan automatisch, sobald <code>grunt-contrib-watch</code> eine Änderung, wie {{zB}}
die Aktualisierung einer CSS-Datei, einer JavaScript-Datei, der HTML-Datei selbst oder einer anderen Datei, die von der HTML-Datei eingebunden wird,
meldet.


Allerdings wäre es falsch, <code>livereload.js</code> auf einem Live-Server ohne Testumgebung einzubinden. Dort läuft der Livereload-Server überhaupt nicht.
<source lang="javascript">
Daher wird zunächst ermittelt, über welchen Port die aktuelle HTML-Datei von Server ausgeliefert wurde.
define
Üblicherweise ist dies 80, 443, 8080, 8888 oder 8443. Andere Ports werden selten verwendet.
( function ()
Der Port 63342 wird mit fast absoluter Sicherheit nicht von irgendeinem realen Server verwendet. Daher verwendet ihn WebStorm.
  { 
Und daher kann mit dem Test <code>if (location.port == 63342)</code> ermittelt werden, ob die aktuelle HTML-Datei von einem WebStorm-Server
    function C(...) {...}
ausgeliefert wurde oder von einem anderen Server. (Die globale JavaScript-Variable „<code>location</code>“ enthält den URI, über den die HTML-Datei
vom Browser geladen wurde.)


Nun ist es an der Zeit, den <code>grunt-contrib-watch</code> zu testen. Öffnen Sie „<code>index.html</code>“ im Browser
    return C;
(Rechtsklick auf <code>index.html</code> → <code>Run 'index.html'</code>) und tippen Sie dann
  }
<source lang="bash">
);
grunt watch
</source>
</source>
ins WebStorm-Terminal.
Ändern Sie nun einen Eintrag in der Datei „<code>main.css</code>“ ({{zB}} <code>font-family: "Comic Sans MS";</code> :-) ).
Wenn Sie diese Änderungen speichern (Strg-S, Apfel-S), sollten Sie zweierlei Reaktionen bemerken.
Zum einem wird ins Terminal die Meldung
<source lang="bash">
>> File "web/css/main.css" changed.
</source>
geschrieben und zum anderen ändert sich das Layout der HTML-Datei im Browser, da die aktualisierte CSS-Datei
automatisch vom Browser nachgeladen wurde.


Wenn Sie die Designsünde  „Comic Sans MS“ rückgängig machen, ändert sich die Datei kurz darauf auch im Browser wieder.
Hier gibt es zwar eine Callback-Funktion. Diese wird jedoch sofort ausgeführt, da nicht auf das Laden anderer Module gewartet werden muss.


Das ist schon ziemlich „cool“, hat aber noch einen Schönheitsfehler: Das WebStorm-Terminal kann nicht anderweitig genutzt werden, solange
====Laden von RequireJS====
der Grunt-Watcher läuft. Um dieses Problem zu beheben, wird ein WebStorm-Startup-Task definiert, der den Watcher bei jedem
Programmstart von WebStorm automatisch in einem eigenständigen [[Task]] startet. Der Watcher läuft dann, solange WebStorm geöffnet ist.
Für seine Ausgaben steht im ein eigenes Ausgabefenster zur Verfügung. Was will man mehr?


* <code>File</code> → <code>Settings</code> → <code>Tools</code> → <code>Startup Tasks</code> → <code></code>
Damit Module auch gefunden werden, muss man RequireJS zunächst konfigurieren.
* Klick auf grünes Plus-Icon → <code>Add New Configuration</code> → <code>Grunt.js</code>
Das erfolgt üblicherweise in der Datei  „<code>main.js</code>“ (hier: <code>main5.js</code>).
** Name: <code>Watch</code>
Diese Datei ist auch dafür verantwortlich, das erste Modul zu laden und damit den Ladevorgang für
** Gruntfile: <code>.../HelloWorld04/gruntfile.js</code> (sollte bereits korrekt eingetragen sein)
alle benötigten Module in Gang zu setzten. Bei uns ist das das Modul „<code>hello</code>“.
** Tasks: <code>watch</code>
** Der Node-Interpreter und das Node.js-Paket <code>grunt-cli</code> sollten ebenfalls bereits korrekt eingetragen sein.
* <code>OK</code> (Unter „<code>Run configuration</code>“ sollte jetzt „<code>Watch</code>“ stehen.)
* <code>OK</code>


Wenn Sie nun WebStorm neu starten, sollte und der Menüzeile am unteren Fensterrand ein Tabulator mit dem Namen „<code>Run</code>“ vorhanden sein.
Allerdings kann man <code>main.js</code> nicht direkt laden, sondern man muss den
Wenn Sie das zugehörige Fenster öffnen sollte dort Folgendes stehen:
Browser anweisen, zuvor <code>require.js</code> zu laden. Sobald diese Datei vollständig geladen (und übersetzt) wurde,
lädt <code>require.js</code> von sich aus <code>main.js</code>  (bzw <code>main5.js</code>).  
Um dies zu erreichen, fügen Sie folgendes Script-Element als letztes und einziges Script-Element
in die Datei „<code>index5.html</code>“ ein:


<source lang="bash">
<source lang="html5">
grunt watch
<script data-main="js/main5" src="js/lib/require/require.js"></script>
Running "watch" task
Waiting...
</source>
</source>


Sobald Sie irgendeine Änderung an eine <code>web</code>- oder <code>src</code>-Datei der Web-App vornehmen, werden alle im Watcher definierten Aktionen
Weitere Script-Elemente werden nicht mehr benötigt, das RequireJS Module jeweils dynamisch
(automatische Komprimierung bzw. Neuladen des HTML-Datei im Browser) automatisch ausgeführt. Der Watcher läuft solange, bis Sie WebStorm beenden.
nachlädt, sobald sie benötigt werden.
Es erfolgt dabei sogar noch eine Sicherheitsabfrage, on Sie den Prozess „<code>Watch</code>“ beenden wollen.
<!-- „<code></code>“ npm install grunt-contrib-watch --save-dev-->


==JSHint==
====Landen von <code>main.js</code>====


Ein weiteres sehr interessantes Tool, das den JavaScript-Entwickler unterstützt, ist JSHint (http://jshint.com/).
Nachdem <code>require.js</code> geladen wurde, liest das Programm das Script-Attribut „<code>data-main</code>“
JSHint analysiert vorhandenen JavaScript-Code und weist auf Fehler und Strukturprobleme hin.
und erfährt so, dass die Datei „<code>js/main5.js</code>“ für die Initialisierung und den Start der Web-Anwendung zuständig ist (die fehlende Dateiendung
„<code>.js</code>“ ergänzt RequireJS automatisch).


Installieren Sie zunächst die zugehörigen Node.js-Pakete:
Die Datei „<code>js/main5.js</code>“ konfiguriert zunächst RequireJS:


<source lang="bash">
<source lang="javascript">
npm install grunt-contrib-jshint jshint-stylish --save-dev
requirejs.config
({
  baseUrl: 'js', // By default load any modules from directory js
  paths :
  {
    app:      'app5',
    loadjson: 'lib/require/json',
    text:    'lib/require/text',
    json:    '../json'
  }
});
</source>
</source>


Anschließend fügen Sie einen JSHit-Task in die Datei „<code>gruntfile.js</code>“ ein.
Hier wird festgelegt, dass sich die JavaScript-Dateien im Ordner „<code>js</code>“ befinden. Das Attribut „<code>path</code>“
legt ein paar spezielle Unterordner fest. Modulnamen, die mit „<code>app/</code>“ beginnen (wie {{zB}} „<code>app/greet</code>“),
werden im Ordner „<code>js/app5/</code>“ gesucht. Für die Module „<code>json</code>“ (ein RequireJS-Plugin zum Laden von
JSON-Dateien) und „<code>text</code>“ (welches von
„<code>json</code>“ benötigt wird) wurde der Pfad direkt angegeben. Die JSON-Dateien finden sich im Ordner
„<code>js/../json</code>“, {{dh}} im Ordner „<code>init</code>“.


<source lang="javascript">
Nun kann die eigentliche Anwendung gestartet werden:
jshint:
{
  all: ['gruntfile.js', 'src/**/*.js'],


   options:
<source lang="javascript">
requirejs
( ['loadjson!json/hello_de.json', 'loadjson!json/hello_en.json', 'app/init'],
   function(helloDeJSON, helloEnJSON, init)
   {
   {
     esversion: 5,
     init(window, helloDeJSON);
     eqeqeq:    true,
     init(window, helloEnJSON);
    eqnull:    true,
    browser:  true,
    reporter:  require('jshint-stylish')
   }
   }
}
);
</source>
</source>


Die Anwendung benötigt zwei JSON-Dateien (das dafür notwendige JSON-Plugin wird automatisch geladen)
und das Modul  „<code>app/hello</code>“ (welches sich gemäß Konfiguration in der Datei  „<code>js/app5/hello.js</code>“ befindet).


Nun können alle vom Entwickler erstellten JavaScript-Dateien analysiert werden. Geben Sie dazu im
Sobald die drei Dateien vollständig geladen wurden, wird die Callback-Funktion aufgerufen.
WebStorm-Terminal einfach folgenden Befehl ein:
Dieser werden die beiden JSON-Objekte sowie das Hello-Initialisierungs-Modul-Objekt übergeben.
Die Callback-Funktion kann somit problemlos die beiden Hello-World-Anwendungen mittels  „<code>p_hello.init</code>“ initialisieren und starten.


<source lang="bash">
====Das Modul „<code>hello</code>“====
grunt jshint
 
</source>
Der Code des Moduls „<code>init</code>“ unterscheidet sich nicht nur hinsichtlich des Modulrahmens, sondern auch noch in
eine zweiten in einem wesentlichen Aspekt vom
Code des Moduls „<code>hello</code>“ der <code>App4</code>: In der App4 gibt es ein globales Objekt namens „<code>hello</code>“, das die gesamte Web-Anwendung enthält. Insbesondere gibt es die Methode „<code>hello.init</code>“ zum Initialisieren der App. Im Gegensatz dazu gibt es in der App5 überhaupt keine anwendungsspezifischen globalen Objekte. Daher ist es nicht notwendig die Funktion „<code>init</code>“ als Methode in irgendein Objekt einzufügen.
Dementsprechend wurde auch der Modulname von „<code>hello</code>“  in „<code>init</code>“ geändert.


Gut ist es, wenn JSHint mit der Meldung <code>v No problems</code> antwortet.
<source lang="javascript">
Ansonsten sollten Sie in Ihrem Code die von JSHint gemeldeten Probleme beheben.
define
( ['app/greet'],
  function (Greet) // The module 'app/greet' contains the constructor 'Greet'.
  {
    /**
    * Initializes the HTML sections of the app that are
    * stated in <code>p_init</code>.
    *
    * @param p_window The browser window that contains the HTML document to be initialized.
    * @param p_init  The initialization info for the form section and welcome section.
    */
    function init(p_window, p_init)
    {
      var l_document      = p_window.document,
          l_init_elements = p_init.HTMLElements,
          l_init_text    = p_init.text,
          l_greet        = new Greet(l_document, p_init);


Machen Sie ruhig mal die Probe auf's Exempel: Ersetzen Sie
      l_document.getElementById(l_init_elements.headingForm)
in der Datei „<code>main.js</code>“ „<code>=</code><code>==</code>“ durch „<code>==</code>“ und lassen Sie
                .innerHTML = l_init_text.title;
danach JSHint laufen. Dann sollte JSHint Folgendes melden:
      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);
    }


<source lang="bash">
    return init;
Expected '===' and instead saw '=='.
  }
);
</source>
</source>


Damit wird ausgedrückt, dass ein nicht-strikter Gleichheitstest verwendet wurde,
Sobald das Modul „<code>app/greet</code>“ ({{dh}} die Datei „<code>js/app5/greet.js</code>“) vollständig geladen wurde,
aber ein strikter Gleichheitstest verwendet werden sollte. Der Grund ist, dass
wird die von diesem Modul bereitgestellte Konstruktorfunktion „<code>Greet</code>“ der Callback-Funktion
nicht-strikte Fehlertests manchmal subtile Fehler zur Folge haben, die
im Parameter „<code>Greet</code>“ übergeben. Die Callback-Funktion erstellt daraufhin ein Objekt, welches eine einzige
nur sehr schwer zu entdecken sind.
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.


JSHint kennt zahlreiche Optionen, die festlegen, welche Fehler und Probleme gemeldet
====Das Modul „<code>greet</code>“====
werden sollen und welche nicht: http://jshint.com/docs/options/.
Hier muss jeder Programmierer seinen eigenen Stil finden.


Folgende Optionen wurden im obigen Beispiel verwendet:
Das Modul „<code>greet</code>“ benötigt überhaupt keine anderen Module.
Daher ist die Moduldefinition noch etwas einfacher (die Liste mit benötigten Modulen entfällt ganz). Es ändert sich gegenüber App4 ebenfalls nur der Modulrahmen.


<dl>
<source lang="javascript">
</dt><code>esversion: 5</code></dt>
define
<dd>Des sollen die Regeln von [[EcmaScript 5]] beachtet werden.</dd>
(function ()
</dt><code>eqeqeq: true</code></dt>
{"use strict";
<dd>Es sollt stets ein strikter Gleichheitstest verwendet werden.</dd>
</dt><code>eqnull</code></dt>
<dd>Der nicht-strikte Gleichheitstest <code>... == null</code> ist allerdings erlaubt.
</dd>
</dt><code>browser: true</code></dt>
<dd>Globale Variable sollte man eigentlich grundsätzlich nicht verwenden.
Mit dieser Option werden für die globalen Variablen, die die Browser bereitgestellt werden
(<code>document</code>, <code>window</code>, <code>console</code> etc) Ausnahmen definiert.</dd>
</dl>


'''Anmerkung''': Wäre der nicht-strikte Gleichheitstest auf „<code>null</code>“ nicht erlaubt, müsste man
  /**
in viele Fällen „
  * @class
<source lang="javascript">
  * @classdesc Contains methods to say hello to the user of the web app.
if (... === null || ...=== undefined)
  *
</source>
  * @param p_document A DOM object (usually the global object <code>document</code>)
schreiben. Geben Sie folgende Befehle einfach mal in die Konsole Ihres Web-Browsers ein
  * @param p_init A JSON object containing all init data.
(Firefox: <code>Strg-Shift-K</code>, Opera und Chrome: <code>Strg-Shift-I</code> → <code>Console</code>):
  */
  function Greet(p_document, p_init)
  {
    var l_this = this; // this-hack


<source lang="javascript">
    /**
var a;
    * Welcomes the user of the web app1 by displaying a welcome message
var b = null;
    * 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;


// Strikte Gleichheitstests
        p_document.getElementById(l_init_elements.welcome)
console.log(a === null);                    /* -> false */
                  .innerHTML = l_init_text.welcome;
console.log(a === undefined);               /* -> true  */
        p_document.getElementById(l_init_elements.hello)
console.log(b === null);                    /* -> true  */
                  .innerHTML = l_init_text.hello.replace('$1', l_name);
console.log(b === undefined);                /* -> false */
console.log(a === null || a === undefined);  /* -> true  */
console.log(b === null || b === undefined); /* -> true  */


// Nicht-strikte Gleichheitstests
        p_document.getElementById(l_init_elements.sectionHello)
console.log(a == null);                      /* -> true  */
                  .classList.remove(l_init_hidden);
console.log(a == undefined);                 /* -> true  */
        p_document.getElementById(l_init_elements.sectionForm)
console.log(b == null);                      /* -> true  */
                  .classList.add(l_init_hidden);
console.log(b == undefined);                 /* -> true  */
      };
</source>


Zum Abschluss können Sie entweder einen WebStorm-Menü-Eintrag hinzufügen, um den JSHint jederzeit per Klickibunti starten zu können.
    /**
Oder Sie fügen gleich „<code>jslint</code>“ in die Watch-Liste ein, damit jede Änderung sofort analysiert und Fehler sofort angezeigt werden:
    * 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();
        }
      };
  }


'''<code>gruntfile.js</code>'''
   return Greet; // Return the constructor defined above.
<source lang="javascript">
});
watch:
{ ...
  js:
   { files:  ['src/js/**/*.js'],
    tasks:  ['uglify', 'jshint']
  },
  ...
}
</source>
</source>
Allerdings kann JSHint manchmal nerven. :-) Dagegen helfen dann spezielle Kommentare im JavaScript-Code, die
JSHint anweisen für die Prüfung des nachfolgenden Codes spezielle Optionen zu benutzen, die dann {{iAllg}}
eine etwas weniger strenge Überprüfung des Codes zur Folge haben (http://jshint.com/docs/).


==Quellen==
==Quellen==
<references/>
<references/>
<ol>
<ol>
<li value="3"> {{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
<li value="4"> {{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 12. Oktober 2017, 08:14 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 dritten Teil des Tutoriums ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.

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

Modularisierung

Eine Anwendung, wie z. B. 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, d. h. 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 Grundsatz: Verwende so wenig globale Größen wie möglich.

Erstellen eines neuen Projektes

Erstellen Sie ein neues Projekt „HelloWorld04“ und legen Sie dieses in Ihrem Repository ab. Kopieren Sie anschließend alle Dateien aus dem dritten Teil des Tutoriums, passen Sie den Titel in der HTML-Datei an und committen Sie abermals.

Erstellen einer Ordnerstruktur

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

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

Legen Sie im Root-Verzeichnis Ihres Projektes folgende Ordner an:

  • web: Der Inhalt dieses Ordners kommt später auf einen echten Web-Server.
  • web/css: Hier werden die CSS-Dateien der Web-Anwendung gespeichert. In diesem Ordner können zur weiteren Strukturierung Unterordner angelegt werden.
  • web/js: Hierher kommt die Datei „main.js“, 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.
  • web/js/app: Hier speichern Sie die von Ihnen geschriebenen JavaScript-Dateien Ihrer Web-Anwendung. In diesem Ordner werden üblicherweise Unterordner zur weiteren Strukturierung der Anwendung angelegt.
  • web/js/lib: Hier speichern Sie die JavaScript-Bibliotheken, die andere JavaScript-Entwickler erstellt haben und die Sie in Ihrer Anwendung einsetzen. Für jede externe Bibliothek gibt es einen Unterordner.

Fremde Bibliotheken sind nicht unbedingt fehlerfrei und können durchaus auch unsauberen Code enthalten. Damit Ihnen bei einem Commit diese Fehler nicht angezeigt werden, schließen Sie den Ordner „web/js/lib“ von der WebStorm-Fehlerüberwachung aus:

  • Rechtklick auf den Ordner „web/js/lib“ → Mar Directory asExcluded

Da in diesem Teil des Tutoriums fünf Web-Anwendungen mit jeweils unterschiedlichem Modularisierungsgrad erstellt werden, werden hier anstelle des Ordners „web/js/app“ fünf App-Ordner angelegt:

  • web/js/app1: Aufteilung der App in mehrere Objekte und Dateien.
  • web/js/app2: Definition einer Klasse Greet.
  • web/js/app3: Reduktion der Konstanten in Greet.
  • web/js/app4: Weitere Modularisierung der Web-App mit JavaScript-Bordmitteln.
  • web/js/app5: Alternative Modularisierung der Web-App mit der JavaScript-Bibliothek RequireJS.

Öffnen Sie im Editorbereich die Datei „index.html“ und verschieben sie in den Ordner „web“, wobei Sie darauf achten sollten, dass im Verschiebe-Dialogfenster die Option „Search for references“ aktiviert ist. . Verschieben sie anschließend die Dateien „main.css“ und „main.js“ in die passenden Ordner „web/css“ bzw. „web/js“. Sie werden feststellen, dass die Verweise in den Link- und Script-Elementen der Datei „index.html“ automatisch angepasst werden. Verschieben Sie nun die Datei „index.html“ in den Ordner „web“.

Das Ergebnis dieser Aktion ist, dass Ihre Web-Anwendung nicht mehr im Root-Verzeichnis des Projektes liegt, sondern (sauber strukturiert) in unterschiedlichen Unterordnern. Daher ist es möglich, im Root-Verzeichnis des Projektes weitere projektspezifische Ordner und Dateien anzulegen wie „conf“ und „doc“, die später nicht auf den Web-Server, auf dem die Anwendung schließlich laufen soll, kopiert werden.

App1

Benennen Sie die Dateien „index.html“ und „main.js“ in „index1.html“ und „main1.js“ um. (Im Laufe des Tutoriums kommen weitere Versionen dieser beiden Dateien dazu.)

Laden der CSS- und der JavaScript-Dateien

Eine Web-Anwendung funktioniert nur – wie Sie bereits erfahren haben –, wenn nicht nur die HTML-Datei, sondern auch die zugehörigen CSS- und JavaScript-Dateien geladen werden. Allerdings gibt es dabei ein Problem: Die CSS- und JavaScript-Dateien werden im Laufe der Zeit immer zahlreicher und/oder größer. Diese Dateien zu laden, dauert seine Zeit.

In der aktuellen Version der Web-Anwendung stehen die Verweise auf diese Dateien im head-Bereich des Dokuments. Dieser wird vollständig geladen, bevor der body-Bereich eingelesen wird. Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments darstellen kann, solange er die JavaScript- und CSS-Dateien lädt. Wenn dies zu lange dauert, verliert der Besucher die Geduld und verlässt die Seite vorzeitig.

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

Was also machen?

CSS-Dateien

Für CSS-Dateien empfiehlt Google ernsthaft, die Link-Element ganz ans Ende der HTML-Datei zu stellen, also nach dem schließenden html-Tag. Damit das Problem mit dem falschen Layout nicht auftritt, sollen wichtige CSS-Befehle direkt – d. h. als CSS-Befehle innerhalb eines style-Elements – in die HTML-Datei eingefügt werden.[1]

Von beiden Vorschlägen rate ich dringend ab. Der erste Vorschlag ist nicht HTML-konform, ja noch nicht einmal SGML-konform. Es mag sein, dass diverse Browser diese Syntax verstehen, korrekt ist sie trotzdem nicht. Der zweite Vorschlag hat zur Folge, dass Struktur (HTML) und Layout (CSS) vermischt werden. Dies war einer der Kardinalfehler von frühen HTML-Versionen aus der Zeit, bevor es das CSS-Format gab. Tun Sie das nicht.

Gehen Sie so vor, wie es unter Multimedia-Programmierung: Best Practices beschrieben ist.

In unserer Anwendung haben wir bislang alles richtig gemacht, bis auf die fehlende automatische Komprimierung. Diese wird im fünften Teil des Tutoriums beschrieben.

JavaScript-Dateien

Für JavaScript-Dateien empfiehlt Google ebenfalls, diese nicht schon zu Beginn zu laden.[2] Dieser Vorschlag ist sehr sinnvoll.

Entfernen Sie das script-Element aus dem HTML-Header-Bereich und fügen Sie es vor dem schließenden body-Tag ein:

   ...
   <script type="text/javascript" src="js/main1.js"></script>
</body>

Globale Funktionen als Methoden

Grundsätzlich sollten so wenige globale Funktionen wie möglich in einen JavaScript-Programm definiert werden, um Namenskonflikte mit JavaScript-Modulen und -Bibliotheken anderer Autoren zu vermeiden. In der Hello-World-Anwendung gibt es drei globale Funktionen: „sayHello“, „sayHelloOnEnter“ und „init“. Die ersten beiden werden in Methoden umgewandelt. Das heißt, es wird ein Objekt „greet“ erstellt, das diese beiden Methoden enthält. Die dritte Funktion „init“ wird durch eine anonyme Funktion ersetzt, die sofort ausgeführt wird.

Erstellen sie die Datei „js/app1/greet.js“ und fügen Sie ein weiteres Script-Element in die Datei „index1.html“ ein (vor dem Skript-Element, das „main1.js“ lädt):

   ...
  <script type = "text/javascript" src = "js/app1/greet.js"></script>
  <script type = "text/javascript" src = "js/main1.js"     ></script>
</body>

Fügen Sie in die Datei „greet.js“ ein Objekt ein, dass die beiden Methoden „sayHello“ und „sayHelloOnEnter“ enthält.

/**
 * @namespace greet
 * Contains methods to say hello to the user of the web app.
 */
var
  greet =
  {
    /**
     * Welcomes the user of the web app1 by displaying a welcome message
     * that includes his name. The name is fetched from a text input field.
     */
    sayHello:
      function()
      {
        document.getElementById('heading_hello').innerHTML =
          'Hello, ' + document.getElementById('input_name').value + '!';
        document.getElementById('section_form').classList.add('hidden');
        document.getElementById('section_hello').classList.remove('hidden');
      },

    /**
     * An keyboard event observer. It tests whether the enter key has been pressed.
     * If so, the greet method is activated. Default reactions of the browser are
     * disabled.
     * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
     *   (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
     */
    sayHelloOnEnter:
      function(p_event)
      {
        if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
            document.activeElement === document.getElementById('input_name')
           )
        {
          p_event.preventDefault();
          p_event.stopPropagation();
          greet.sayHello();
        }
      }
  };

In der Datei „main1.js“ werden dagegen alle globalen Funktionen entfernt. Dies gilt auch für die Definition des Eventlisteners am Ende der Datei, der dafür sorgt, dass die Funktion „init“ ausgeführt wird, sobald die HTML-Seite sowie alle Unterseiten vollständig geladen wurden.

Die Funktion „init“ wird durch eine anonyme Funktion ersetzt, die sofort nach ihrer Definition auch ausgeführt wird. Da die Datei „main1.js“ als Letztes geladen wird, sind alle Objekte, auf die während der Initialisierung zugegriffen werden muss, bereits vorhanden.

// Initialize the app.
(function()
{
  document.getElementById('button_submit')
          .addEventListener('click', greet.sayHello);

  window.addEventListener('keydown', greet.sayHelloOnEnter);
}());

Testen Sie Ihre Anwendung und speichern Sie sie zu guter Letzt im Repository.

App2

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

Zurzeit geht das bei unserer Anwendung noch nicht. Es gibt nämlich nur ein Objekt „greet“, das nicht für mehrere Anwendungen parallel eingesetzt werden kann. Wir müssen also bei Bedarf neue greet-Objekte erzeugen können. Das geschieht in JavaScript mit Hilfe von sogenannten Konstruktorfunktionen.

Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihre Anwendungsdateien:

  • index1.htmlindex2.html
  • js/main1.jsjs/main2.js
  • js/app1/greet.jsjs/app2/greet.js

In der Datei „index2.html“ müssen Sie dafür sorgen, dass die neuen JavaScript-Dateien anstelle der alten geladen werden. Und passen Sie den Titel im HTML-Header an.

Das Objekt „greet“ der Datei „js/app2/greet.js“ wird durch der folgende Konstruktorfunktion „Greet“ ersetzt:

/**
 * @class
 * @classdesc Contains methods to say hello to the user of the web app.
 */
function Greet()
{
  var l_this = this; // this-hack: l_this contains the new object

  /**
   * Welcomes the user of the web app1 by displaying a welcome message
   * that includes his name. The name is fetched from a text input field.
   */
  l_this.sayHello =  // this.sayHello = ...
  function()
  {
    document.getElementById('heading_hello').innerHTML =
      'Hello, ' + document.getElementById("input_name").value + '!';

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

  /**
   * An keyboard event observer. It tests whether the enter key has been pressed.
   * If so, the greet method is activated. Default reactions of the browser are
   * disabled.
   * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
   *   (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
   */
  l_this.sayHelloOnEnter = // this.sayHelloOnEnter = ...
  function(p_event)
  {
    if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
         document.activeElement === document.getElementById('input_name')
       )
    {
      p_event.preventDefault();
      p_event.stopPropagation();
      l_this.sayHello();  // NOT this.sayHello();
    }
  }
}

In JavaScript kann jede Funktion auch als Konstruktor, d. h. zum Erzeugen von neuen Objekte verwendet werden, indem man das Schlüsselwort „new“ voranstellt:

var l_greet = new Greet();

In einer Konstruktorfunktion wird üblicherweise kein return-Befehl eingefügt, da sie sich wie eine Prozedur und nicht wie eine 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 new Greet() 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 Variablen, in einem Array, in einem Objekt oder in einem Funktionsparameter. Im obigen Beispiel wird es in der Variablen „l_greet“ abgelegt.

Im Funktionsrumpf selbst kann über die Spezialvariable „this“ auf das neu erstellte Objekt zugegriffen werden. Dem neu erstellten Objekt werden mittels „this.sayHello = ...“ und „this.sayHelloOnEnter = ...“ etc. Attribute zugewiesen, die Funktionen (welche Methoden genannt werden) als Werte enthalten. Dem neu erstellten Objekt werden dieselben Methoden zugewiesen, wie dem Objekt „greet“ der Anwendung „App1“. Daher kann das mittels Konstruktorfunktion erstellte Objekt „l_greet“ auch genauso verwendet werden, wie das globale Objekt „greet“ der Anwendung „App1“.

Beachten Sie bitte, dass in der Definition der Konstruktorfunktion der so genannte This-Hack zum Einsatz kommt. Das Objekt this wird in einer lokalen Variablen l_this gespeichert. Und anschließend wird diese lokale Variable anstelle von this benutzt. Für die Definition der Attribute „sayHello“ und „sayHelloOnEnter“ macht es keinen Unterschied, ob man „this.sayHello = ...“ oder „l_this.sayHello = ...“ schreibt.

Allerdings wird auch innerhalb des Methodenrumpfes von „sayHelloOnEnter“ auf das neu erstellt Objekt zugegriffen: l_this.sayHello();. Und hier könnte nun nicht this.sayHello(); verwendet werden. Der Grund ist, dass der Methodenrumpf erst ausgeführt wird, wen die Methode „sayHelloOnEnter“ zu irgendeinem anderen Zeitpunkt von irgendeinem anderen Objekt aufgerufen wird. Und this bezeichnet bei „normalen“ Funktionsaufrufen (d. h. bei Aufrufen ohne das Schlüsselwort „new“) immer den Aufrufer der Methode, und nicht das Objekt, das die Methode beinhaltet. Aber im Aufrufer der Methode „sayHelloOnEnter“ ist die Methode „sayHello“ i. Allg. gar nicht definiert oder falls doch, dann meist mit einer vollkommen anderen Funktionalität. Die Spezialvariable „this“ verhält sich hier vollkommen anders als sämtliche andere Variablen und Parameter. (Fachsprachlich: In JavaScript ist die Variable „thisdynamisch gebunden, alle übrigen Variablen sind statisch gebunden.) Daher kopiert man den Wert von „this“ in eine lokale Variable „l_this“, auf die die Methode „sayHelloOnEnter“ immer Zugriff hat, unabhängig davon, wer sie aufruft.

In der Anwendung „App1“ wurde der This-Hack nicht benötigt, da die Methode „sayHelloOnEnter“ einfach die globale Variable Methode „greet“ verwendet hat, um auf die Methode „sayHello“ zuzugreifen (greet.sayHello();). Aber ein globales Greet-Objekt gibt es nun nicht mehr. Es gibt nur noch einen globale Konstruktorfunktion „Greet“, mit der beliebig viele Greet-Objekte erzeugt werden können.

Tipp: Definieren Sie innerhalb einer Konstruktorfunktion immer eine lokale Variable „var l_this = this“ und verwenden Sie innerhalb des Konstruktors nur „l_this“ und nie „this“. (Sie können natürlich auch einen anderen Alternativnamen für „this“ verwenden. Übliche Bezeichnungen sind „that“, „self“ oder „me“.)

Anmerkung: Eigentlich ist der This-Hack unsauber. Besser wäre es, die Methode „bind“ zu verwenden (siehe MDN oder Stackoverflow):

this.sayHelloOnEnter =
  function(p_event)
  {
    ...
      this.sayHello();
    ...
  }.bind(this);

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

var l_greet = new Greet();

als erste Zeile in Ihre Initialisierungsfunktion (Datei „main2.js“) ein und ersetzen Sie in derselben Funktion die beiden Zugriffe auf das globale Objekt „greet“ durch Zugriffe auf das lokale Objekt „l_greet“.

Danach sollte Ihre Anwendung (Datei „index2.html“) wieder wie gewohnt laufen.

App3

Leider bringt uns hinsichtlich der Wiederverwendbarkeit die „Verbesserung“, die wir in App2 gegenüber App1 vorgenommen haben, gar nichts. Wir können jetzt zwar beliebig viele greet-Objekte mit Hilfe von „new Greet()“ erstellen. Aber wir haben nichts davon, da alle diese Objekte dieselben HTML-Elemente im HTML-Dokument lesen und ändern. Das heißt, wir müssen die Konstanten und am Besten auch gleich noch die Zugriffe auf globale Variablen aus der Konstruktorfunktion „greet“ verbannen.

Erstellen Sie in Ihrem WebStorm-Projekt folgende Kopien Ihre Anwendungsdateien:

  • index2.htmlindex3.html
  • js/main2.jsjs/main3.js
  • js/app2/greet.jsjs/app3/greet.js

In der Datei „index3.html“ müssen Sie wiederum dafür sorgen, dass die neuen JavaScript-Dateien anstelle der alten geladen werden. Und passen Sie auch wieder den Titel im HTML-Header an.

Ersetzen Sie nun die Konstruktorfunktion der Datei „js/app3/greet.js“ durch folgende Konstruktorfunktion:

/**
 * @class
 * @classdesc Contains methods to say hello to the user of the web app.
 *
 * @param p_document {object} 
 *        A DOM object (usually the global object <code>document</code>)
 * @param p_section_form {string} 
 *        The ID of the HTML element that contains the form section.
 * @param p_input_name string} 
 *        The ID of the text field via which the user passes her/his name.
 * @param p_section_hello {string} 
 *        The ID of the HTML element that contains the welcome section.
 * @param p_output_hello {string} 
 *        The ID of the HTML element into which the greeting is to be put.
 * @param p_hello {string} 
 *        The hello test to be displayed.
 */
function Greet(p_document,
               p_section_form, p_input_name,
               p_section_hello, p_output_hello,
               p_hello
              )
{
  var l_this = this; // this-hack

  /**
   * Welcomes the user of the web app1 by displaying a welcome message
   * that includes his name. The name is fetched from a text input field.
   */
  l_this.sayHello =
  function()
  {
    p_document.getElementById(p_output_hello).innerHTML =
      p_hello + ', ' + p_document.getElementById(p_input_name).value + '!';

    p_document.getElementById(p_section_form).classList.add('hidden');
    p_document.getElementById(p_section_hello).classList.remove('hidden');
  };

  /**
   * An keyboard event observer. It tests whether the enter key has been pressed.
   * If so, the greet method is activated. Default reactions of the browser are
   * disabled.
   * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
   *   (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
   */
  l_this.sayHelloOnEnter =
  function(p_event)
  {
    if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
         p_document.activeElement === p_document.getElementById(p_input_name)
       )
    {
      p_event.preventDefault();
      p_event.stopPropagation();
      l_this.sayHello();  // NOT this.sayHello();
    }
  }
}

Dieser Konstruktor enthält diverse Konstanten nicht mehr, wie z. B. 'input_name'. Anstelle dessen gibt es diverse Parameter, in denen diese Werte dem Konstruktor übergeben werden können, wie z. B. p_input_name. Der Zugriff auf das globale Objekt „document“ wurde auch gleich noch durch einen Zugriff auf einen lokalen Parameter „p_document“ ersetzt.

Damit ist es jetzt möglich, greet-Objekte für ganz unterschiedliche HTML-Elemente zu definieren.

Ersetzen Sie zunächst die Initialisierungsmethode in der Datei „main3.js“ durch folgenden Code:

(function(p_window, Greet)
{
  var l_document = p_window.document, // The window object contains the document object.
      l_greet    = new Greet(l_document,
                             'section_form', 'input_name',
                             'section_hello', 'heading_hello',
                             'Hello'
                            );

  l_document.getElementById('button_submit')
            .addEventListener('click', l_greet.sayHello);

  p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
}(window, Greet));

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

Dem Konstruktor „Greet“ werden jetzt alle Information, die er benötigt, explizit übergeben. Dies ist zum einen das HTML-Dokument, das mit Hilfe der von ihm erstellten Methoden manipuliert werden soll, dann eine Reihe von ID-Attribut-Namen, die diejenigen HTML-Elemente bezeichnen, die die Greet-Methoden lesen oder ändern sollen, sowie der String „'Hello'“ der zur Begrüßung des Benutzers verwendet werden soll. Alternativ könnte er auch mit „Hallo“ oder „Howdy“ begrüßt werden.

Nun müsste Ihre Anwendung „index3.html“ wieder laufen. Wenn Sie es denn tut, vergessen Sie nicht, sie im Repository zu speichern.

Anschließend sollten Sie in Ihrem WebStorm-Projekt folgende Kopien erstellen:

  • index3.htmlindex3b.html
  • js/main3.jsjs/main3b.js

Die Datei „index3b.html“ soll abermals die JavaScript-Datei „js/app3/greet.js“ verwenden, dafür aber „js/main3b.js“ an Stelle von „js/main3.js“ laden.

Fügen Sie in der Datei „index3b.html“ in alle ID-Attribut-Bezeichner eine 1 ein: section_formsection1_form etc.

Schließen Sie dann die beiden Sections „section1_form“ und „section1_hello“ in ein div-Element ein und duplizieren Sie diese div-Elemente (natürlich samt Inhalt) anschließend. Sie werden feststellen, dass WebStorm eine Reihe von Fehlern der Art „Duplicate id reference“ meldet. Das ist nicht weiter verwunderlich, da Sie ja insbesondere alle ID-Attribut-Bezeichner dupliziert haben. Sie müssen daher im zweiten div-Element alle Einser in den ID-Attribut-Bezeichnern durch Zweier ersetzen.

Nun können Sie noch versuchen, die beiden div-Elemente mittels CSS nebeneinander zu legen. Für den angestrebten Test, ob Sie die Hello-World-App in einem HTML-Dokument zweimal starten können, ist das allerdings irrelevant.

Passen Sie nun Ihre Datei „main3b.js“ an die neuen Gegebenheiten an: Erzeugen Sie zwei Greet-Objekte „greet1“ und „greet2“ (für jedes der beiden neuen div-Elemente eines), fügen Sie passende click-Event-Listener für die beiden Buttons „button1_submit“ sowie „button2_submit“ hinzu und sorgen Sie zu guter Letzt dafür, dass beide Methoden „l_greet1.sayHelloOnEnter“ und „l_greet2.sayHelloOnEnter“ bei jedem Tastendruck aufgerufen werden.

Wenn Sie alles richtig gemacht haben, sollten jetzt in Ihrer Web-Anwendung „index3b.html“ zwei Hello-World-Anwendungen parallel laufen: index3b.html

App4

Hinsichtlich der Wiederverwendbarkeit der Module ist die Version 3 der Web-Anwendung schon ganz gut. Allerdings gibt es noch ein paar Unsauberkeiten, die beseitigt werden sollten:

  1. In den Initialisierungsdateien „main3.js“ und „main3b.js“ sind immer noch konstante Werte enthalten.
  2. Die Anzahl der Parameter der Konstruktorfunktion ist zu groß.
  3. In den Initialisierungsdateien ist immer noch zu viel Anwendungscode enthalten.
  4. Die potentielle Zahl der globalen Größen ist noch zu hoch.

Diese Probleme werden folgendermaßen gelöst:

  1. Die Konstanten werden in eine JSON-Datei ausgelagert, die im ersten Schritt von der jeweiligen Initialisierungsdatei eingelesen wird.
  2. Der Konstruktorfunktion wird das gesamte JSON-Objekt mit allen Initialisierungswerten in einem einzigen Parameter übergeben.
  3. Es wird eine anwendungsspezifische Initialisierungsdatei „hello.js“ erstellt. Die Initialisierungsfunktion in der Datei „main4.js“ hat nur noch die Aufgabe, die JSON-Datei zu lesen und die darin enthalten Informationen anschließend an die Methode „hello.init“ weiterzuleiten.
  4. Es wird ein globales Objekt namens „hello“ erstellt, das die gesamte Web-Anwendung enthält.

Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihrer Anwendungsdateien:

  • index3b.htmlindex4.html
  • js/main3b.jsjs/main4.js
  • js/app3/greet.jsjs/app4/greet.js

Im Folgenden wird die HTML-Datei „index4.html“ als HTML-Template verwendet, d. h. als Strukturrahmen. Sämtliche Texte, die in diesem Dokument enthalten sind, werden dynamisch mittels JavaScript eingefügt.

In der Datei „index4.html“ müssen Sie also nicht nur dafür sorgen, dass die neuen JavaScript-Dateien anstelle der alten geladen werden und dass der Titel angepasst wird. Sie müssen auch sämtliche Text-Elemente mit eindeutigen ID-Attributen versehen.

Die HTML-Datei der Musterlösung sieht folgendermaßen aus. Sie können auch Ihre eigene Datei verwenden, müssen dann aber die JSON-Datei, die anschließend erstellt wird, entsprechend anpassen.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset = "UTF-8">
  <meta name    = "viewport"
        content = "width=device-width, initial-scale=1.0, user-scalable=yes"
  >

  <title>WK_HelloWorld04 (4)</title>

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

<body>
  <div class="left">
    <section id="section1_form">
      <h1 id="heading1_form"></h1>
      <form>
        <div>
          <label id="label1_name" for="input1_name"></label>
          <input id="input1_name" type="text" autofocus="autofocus"/>
        </div>
        <div>
          <button id="button1_reset"  type="reset"></button>
          <button id="button1_submit" type="button"></button>
        </div>
      </form>
    </section>
    <section id="section1_hello" class="hidden">
      <h1 id="heading1_hello"></h1>
      <p id="paragraph1_hello"></p>
    </section>
  </div>

  <div class="right">
    <section id="section2_form">
      <h1 id="heading2_form"></h1>
      <form>
        <div>
          <label id="label2_name" for="input2_name"></label>
          <input id="input2_name" type="text" autofocus="autofocus"/>
        </div>
        <div>
          <button id="button2_reset"  type="reset"></button>
          <button id="button2_submit" type="button"></button>
        </div>
      </form>
    </section>
    <section id="section2_hello" class="hidden">
      <h1 id="heading2_hello"></h1>
      <p id="paragraph2_hello"></p>
    </section>
  </div>

  <script type = "text/javascript" src = "js/lib/wk/ajax.js"></script>
  <script type = "text/javascript" src = "js/app4/greet.js"></script>
  <script type = "text/javascript" src = "js/app4/hello.js"></script>
  <script type = "text/javascript" src = "js/main4.js"></script>
</body>

</html>

Modularisierung

Um möglichst wenige globale Größen definieren zu müssen, legt man für eine Anwendung oder eine Bibliothek typischerweise eine einzige globale Variable an – Namensraum-Variable genannt –, die ein Objekt enthält. In diesem Objekt wird die gesamte Anwendung (Daten, Klassen, Funktionen etc.) gespeichert.

Für die Hello-World-Anwendung wird die globale Namensraum-Variable „hello“ verwendet. In jeder Datei, in der ein Teilaspekt der Anwendung definiert wird, kommt folgender Rahmencode zum Einsatz:

/** @namespace hello */
var hello =
(function(p_hello)
{ "use strict";

  // anwendungsspezifischer Code

  return p_hello;
}(hello ? hello : {}));

In der Variablen „hello“ wird das Ergebnis eines Aufrufs einer anonymen Funktion gespeichert. Dieser Funktion wird als Argument der Inhalt der Variablen „hello“ oder, falls dieses noch nicht existiert, ein leeres Objekt als Argument übergeben (hello ? hello : {}). 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 „hello“ gespeichert wird, bei jedem weiteren Aufruf eines derartigen Codes wird das bestehende hello-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, d. h. parallel geladen werden. Wichtig ist nur, dass eine Datei nicht die Inhalte einer anderen Datei überschreiben darf.

Die Datei „js/app4/greet.js“ sieht nun folgendermaßen aus:

/** @namespace hello */
var hello =
(function(p_hello)
{ "use strict";

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

    /**
     * Welcomes the user of the web app1 by displaying a welcome message
     * that includes his name. The name is fetched from a text input field.
     */
    l_this.sayHello =
    function()
    {
      var l_init_elements = p_init.HTMLElements,
          l_init_text     = p_init.text,
          l_init_hidden   = p_init.css.hidden,
          l_name          = p_document.getElementById(l_init_elements.inputName).value;

      p_document.getElementById(l_init_elements.welcome).innerHTML 
        = l_init_text.welcome;
      p_document.getElementById(l_init_elements.hello).innerHTML
        = l_init_text.hello.replace('$1', l_name);

      p_document.getElementById(l_init_elements.sectionHello)
        .classList.remove(l_init_hidden);
      p_document.getElementById(l_init_elements.sectionForm)
        .classList.add(l_init_hidden);
    };


    /**
     * An keyboard event observer. It tests whether the enter key has been pressed.
     * If so, the greet method is activated. Default reactions of the browser are
     * disabled.
     *
     * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
     *   (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
     */
    l_this.sayHelloOnEnter =
    function(p_event)
    {
      if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
           p_document.activeElement ===
           p_document.getElementById(p_init.HTMLElements.inputName)
         )
      {
        p_event.preventDefault();
        p_event.stopPropagation();
        l_this.sayHello();  // NOT this.sayHello();
      }
    };
  };

  return p_hello;
}(hello ? hello : {}));

Gegenüber der Version 3 dieser Datei hat sich Folgendes geändert:

  • Es wird keine globale Konstruktorfunktion „Greet“ mehr definiert. Diese Konstruktorfunktion wird vielmehr in der Namensraum-Variablen „hello“ gespeichert: „hello.Greet
  • Die Konstruktorfunktion hat nur noch zwei Parameter: „p_window“ und „p_init“. 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 p_init extrahiert: Der Name der Formular-Section, der Name des Text-Eingabefelds, der Text der Hello-Nachricht etc.

Legen Sie gleich noch die Datei „js/app4/hello.js“ für die Web-App an:

/** @namespace hello */
var hello =
(function(p_hello)
{ "use strict";

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

    l_document.getElementById(l_init_elements.headingForm)
              .innerHTML = l_init_text.title;
    l_document.getElementById(l_init_elements.inputNameLabel)
              .innerHTML = l_init_text.query;
    l_document.getElementById(l_init_elements.buttonReset)
              .innerHTML = l_init_text.reset;
    l_document.getElementById(l_init_elements.buttonSubmit)
              .innerHTML = l_init_text.submit;
    l_document.getElementById(l_init_elements.buttonSubmit)
              .addEventListener('click', l_greet.sayHello);
    p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
  };

  return p_hello;
}(hello ? hello : {}));

Sie ist wieder aufgebaut wie zuvor beschrieben: Sie definiert, falls notwendig, das globale Objekt „hello“ und fügt anschließend eine Funktion „init“ in dieses Objekt ein. Die Methode „hello.init“ hat dieselben Parameter, wie die zuvor definierte Konstruktorfunktion und erwartet denselben Input. Ihre Aufgabe ist es, die derzeit noch leeren Text-Elemente des HTML-Templates mit Inhalt zu füllen soweit der Inhalt schon bekannt ist (der Name des Benutzers ist noch unbekannt). Außerdem erstellt sie ein passendes Greet-Objekt „ l_greet“ mit Hilfe der Konstruktorfunktion „hello.Greet“ und weist dem Submit-Button die darin enthaltene Methode „sayHello“ zu. Außerdem definiert sie eine Eventlistener, der im Falle eines Key-Down-Ereignisses die Methode „ l_greet.sayHelloOnEnter“ ausführt.

Die JSON-Datei

Zur Initialisierung der Web-Anwendung werden JSON-Dateien benötigt.

Eine JSON-Datei enthält genau ein JavaScript-Objekt in textueller Darstellung. Allerdings werden nur folgende Datentypen unterstützt[3]:

  • Nullwert: null
  • Boolesche Werte: true, false
  • Zahlen
  • Strings
  • Arrays
  • Objekte

Insbesondere Funktionen können in einer JSON-Datei nicht enthalten sein. Außerdem müssen Strings immer in doppelte Anführungszeichen eingeschlossen sein, einfache Anführungszeichen sind nicht erlaubt. Attributnamen werden ebenfalls durch Strings dargestellt. Auch diese müssen gemäß ECMA-Standard in doppelte Anführungszeichen eingeschlossen werden. Außerdem können in JSON-Dateien (leider) keine Kommentare enthalten sein.

Folgendes Objekt ist zwar ein korrektes Javascript-Objekt, aber kein gültiges JSON-Objekt:

{ a: 55, 'b': 'Wolfgang' }

In JSON muss dieses Objekt folgendermaßen geschrieben werden:

{ "a": 55, "b": "Wolfgang" }

Diese Objekt ist auch ein korrektes JavaScript-Objekt. Da die Syntaxregeln von JSON strenger sind als von JavaScript, gilt folgende Regel: Jedes JSON-Objekt ist auch ein JavaScript-Objekt, aber nicht jedes JavaScript-Objekt ist auch ein JSON-Objekt.

Ein Problem bereitet die Verwendung von JSON. Da ein JSON-Objekt beliebige Attribute enthalten kann, kann eine IDE nicht überprüfen, ob ein bestimmtes Attribut oder Element in einem JSON-Objekt mit Sicherheit enthalten ist oder nicht. Das hat z. B. zur Folge, das WebStorm bei einem Zugriff auf eine Attribut in einem JSON-Objekt den Attributnamen unterringelt und mit der Warnung „Unresolved variable ...“ versieht.

Ein Lösung für dieses Problem wäre die Verwendung von JSON Schema. Aber darauf wird zunächst verzichtet.

Legen Sie für die zuvor definierte HTML-Datei „index4.html“ folgende JSON-Initialisierungsdatei unter dem Namen „web/json/hello_de.json“ an:

{
  "HTMLElements":
  {
    "sectionForm":    "section1_form",
    "headingForm":    "heading1_form",
    "inputNameLabel": "label1_name",
    "inputName":      "input1_name",
    "buttonReset":    "button1_reset",
    "buttonSubmit":   "button1_submit",
    "sectionHello":   "section1_hello",
    "hello":          "heading1_hello",
    "welcome":        "paragraph1_hello"
  },

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

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

Diese Datei besteht aus einem anonymen Objekt. Es enthält seinerseits drei Objekte „HTMLElements“, „text“ und „css“, die für die Initialisierung der Web-Anwendung gedacht sind. Das Objekt „HTMLElements“ enthält sämtliche ID-Attribut-Bezeichner der Web-Anwendung. Wenn das JSON-Objekt im beispielsweise im Parameter „p_init“ der Web-Anwendung übergeben wird, kann diese den Namen des Formularabschnitts mittels „p_init.HTMLElements.sectionForm“ ermitteln. Wenn Sie in Ihrer index4.html andere ID-Attibute-Bezeichner verwenden, müssen Sie die JSON-Datei entsprechend anpassen, d. h., Sie müssen beispielsweise den Attributwert "section1_form" durch den von Ihnen verwendeten Bezeichner ersetzen. Die Attributnamen ("sectionForm" etc.) dürfen Sie dagegen nicht ändern, da sonst die Methoden (init, sayHello und sayHelloOnEnter) Ihrer Web-Anwendung nicht mehr funktionieren würden.

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

In Objekt „css“ 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 („hidden“), stimmt in diesem Fall mit dem Namen der zugehörigen CSS-Klasse überein. Das ist nicht verboten.

Erstellen Sie analog die Datei „web/json/hello_en.json“. Diese Datei soll für die zweite Web-Anwendung definiert werden, die allerdings englischsprachige Texte enthält (Sie erinnern sich? In index4.html laufen zwei unabhängige Web-Anwendungen parallel).

Das Laden der JSON-Dateien

Für das asynchrone Laden von beliebigen Dateien von einem Web-Server gibt es in JavaScript den Befehl „XMLHttpRequest“. 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 „ajax.js “ aus dem Glossar-Repository herunterladen und in Ihrem Projekt unter dem Namen „web/js/lib/wk/ajax.js“ speichern.

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

Die Methode „getJSON“ 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 „main4.js“) sieht nun folgendermaßen aus:

// 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));

Die Initialisierungsfunktion liest die beiden zuvor erstellten JSON-Dateien ein und reicht jeweils das daraus erzeugte JSON-Objekt „main4.js“ 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

function(p_init) { p_hello.init(p_window, p_init); }

ausgeführt. Dieser wird im Parameter „p_init“ das zugehörige JSON-Objekt überreicht. Sie reicht es (zusammen mit die Window-Objekt) an die Methode „p_hello.init“ weiter.

Anmerkung: Man könnte die beiden JSON-Dateien natürlich auch mittels zweier Aufrufe von „getJSON“ laden:

// 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));

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 „hello“. 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 „import“ und „export“. 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 „RequireJS“ 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:

  • index4.htmlindex5.html
  • js/main4.jsjs/main5.js
  • js/app4/greet.jsjs/app5/greet.js
  • js/app4/hello.jsjs/app5/init.js

In der Datei „index5.html“ 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 RequireJS nutzen zu können, den Ordner „web/js/lib/require“ erstellen und folgende drei Dateien (einschließlich des Inhalts, der sich hinter den drei Links verbirgt) dort speichern:

Modularisierung mit RequireJS

Wenn man konsequent RequireJS zur Modularisierung seine Anwendung einsetzt, gibt es neben den globalen Objekten des Browsers (window, document etc.) nur noch das globales Objekt „require“ sowie die globale Funktion „define“ zum Erstellen neuer Module.

Es wird auch nur noch eine einzige JavaScript-Datei statisch geladen: „require.js“. 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 = Asynchronous Module Definition) erfolgen. RequireJS kann allerdings auch Bibliotheken wie z. B. „jQuery“ laden, die nicht gemäß dieser Spezifikation erstellt wurden.

Für RequireJS gibt es einige Plugins. Eines davon ermöglicht das Lesen von JSON-Dateien („json.js“). Dieses Plugin wird verwendet, um die Dateien „json/hello_xx.json“ einzulesen.

Wenn man mit Hilfe von „define“ ein neues (AMD-)Modul definiert, gibt man i. Allg. eine ganze Liste von Modulen an, auf denen das neue Modul basiert. Beispielsweise benötigt das Modul „hello“ (welches in der Datei „js/app5/hello.js“ definiert wird) das Modul „greet“ (welches in der Datei „js/app5/greet.js“ definiert wird).

Die Moduldefinition von „hello“ sieht in diesem Fall folgendermaßen aus:

define
( ['app/greet'],
  function (Greet) 
  {
     return ... 
  }
);

Die Funktion „define“ lädt zunächst alle Module, die in der Modulliste aufgeführt werden, asynchron (und damit meist auch parallel). Allerdings wird jedes Modul nur einmal geladen. Das heißt, wenn zwei Module dasselbe Modul benötigen, wird dieses Modul trotzdem nur einmal geladen. Sobald alle Module geladen sind, wird die im define-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 define-Aufruf auch noch einen Modulnamen angeben. Das ist aber i. Allg. nicht notwendig, da RequireJS defaultmäßig den Dateinamen (ohne Pfad und ohne Endung „js“) 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:

define 
({
    // Inhalt des Objekts, den das Modul definiert. 
});

Wenn das Modul lediglich eine Funktion, wie z. B. eine Konstruktorfunktion „C“ zur Verfügung stellen soll, schreibt man

define
( function ()
  {  
     function C(...) {...}

     return C;
  }
);

Hier gibt es zwar eine Callback-Funktion. Diese wird jedoch sofort ausgeführt, da nicht auf das Laden anderer Module gewartet werden muss.

Laden von RequireJS

Damit Module auch gefunden werden, muss man RequireJS zunächst konfigurieren. Das erfolgt üblicherweise in der Datei „main.js“ (hier: „main5.js“). 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 „hello“.

Allerdings kann man main.js nicht direkt laden, sondern man muss den Browser anweisen, zuvor require.js zu laden. Sobald diese Datei vollständig geladen (und übersetzt) wurde, lädt require.js von sich aus main.js (bzw main5.js). Um dies zu erreichen, fügen Sie folgendes Script-Element als letztes und einziges Script-Element in die Datei „index5.html“ ein:

<script data-main="js/main5" src="js/lib/require/require.js"></script>

Weitere Script-Elemente werden nicht mehr benötigt, das RequireJS Module jeweils dynamisch nachlädt, sobald sie benötigt werden.

Landen von main.js

Nachdem require.js geladen wurde, liest das Programm das Script-Attribut „data-main“ und erfährt so, dass die Datei „js/main5.js“ für die Initialisierung und den Start der Web-Anwendung zuständig ist (die fehlende Dateiendung „.js“ ergänzt RequireJS automatisch).

Die Datei „js/main5.js“ konfiguriert zunächst RequireJS:

requirejs.config
({
  baseUrl: 'js', // By default load any modules from directory js
  paths :
  {
    app:      'app5',
    loadjson: 'lib/require/json',
    text:     'lib/require/text',
    json:     '../json'
  }
});

Hier wird festgelegt, dass sich die JavaScript-Dateien im Ordner „js“ befinden. Das Attribut „path“ legt ein paar spezielle Unterordner fest. Modulnamen, die mit „app/“ beginnen (wie z. B. „app/greet“), werden im Ordner „js/app5/“ gesucht. Für die Module „json“ (ein RequireJS-Plugin zum Laden von JSON-Dateien) und „text“ (welches von „json“ benötigt wird) wurde der Pfad direkt angegeben. Die JSON-Dateien finden sich im Ordner „js/../json“, d. h. im Ordner „init“.

Nun kann die eigentliche Anwendung gestartet werden:

requirejs
( ['loadjson!json/hello_de.json', 'loadjson!json/hello_en.json', 'app/init'],
  function(helloDeJSON, helloEnJSON, init)
  {
    init(window, helloDeJSON);
    init(window, helloEnJSON);
  }
);

Die Anwendung benötigt zwei JSON-Dateien (das dafür notwendige JSON-Plugin wird automatisch geladen) und das Modul „app/hello“ (welches sich gemäß Konfiguration in der Datei „js/app5/hello.js“ befindet).

Sobald die drei Dateien vollständig geladen wurden, wird die Callback-Funktion aufgerufen. Dieser werden die beiden JSON-Objekte sowie das Hello-Initialisierungs-Modul-Objekt übergeben. Die Callback-Funktion kann somit problemlos die beiden Hello-World-Anwendungen mittels „p_hello.init“ initialisieren und starten.

Das Modul „hello

Der Code des Moduls „init“ unterscheidet sich nicht nur hinsichtlich des Modulrahmens, sondern auch noch in eine zweiten in einem wesentlichen Aspekt vom Code des Moduls „hello“ der App4: In der App4 gibt es ein globales Objekt namens „hello“, das die gesamte Web-Anwendung enthält. Insbesondere gibt es die Methode „hello.init“ zum Initialisieren der App. Im Gegensatz dazu gibt es in der App5 überhaupt keine anwendungsspezifischen globalen Objekte. Daher ist es nicht notwendig die Funktion „init“ als Methode in irgendein Objekt einzufügen. Dementsprechend wurde auch der Modulname von „hello“ in „init“ geändert.

define
( ['app/greet'],
  function (Greet) // The module 'app/greet' contains the constructor 'Greet'.
  {
    /**
     * Initializes the HTML sections of the app that are
     * stated in <code>p_init</code>.
     *
     * @param p_window The browser window that contains the HTML document to be initialized.
     * @param p_init   The initialization info for the form section and welcome section.
     */
    function init(p_window, p_init)
    {
      var l_document      = p_window.document,
          l_init_elements = p_init.HTMLElements,
          l_init_text     = p_init.text,
          l_greet         = new Greet(l_document, p_init);

      l_document.getElementById(l_init_elements.headingForm)
                .innerHTML = l_init_text.title;
      l_document.getElementById(l_init_elements.inputNameLabel)
                .innerHTML = l_init_text.query;
      l_document.getElementById(l_init_elements.buttonReset)
                .innerHTML = l_init_text.reset;
      l_document.getElementById(l_init_elements.buttonSubmit)
                .innerHTML = l_init_text.submit;
      l_document.getElementById(l_init_elements.buttonSubmit)
                .addEventListener('click', l_greet.sayHello);
      p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
    }

    return init;
  }
);

Sobald das Modul „app/greet“ (d. h. die Datei „js/app5/greet.js“) vollständig geladen wurde, wird die von diesem Modul bereitgestellte Konstruktorfunktion „Greet“ der Callback-Funktion im Parameter „Greet“ übergeben. Die Callback-Funktion erstellt daraufhin ein Objekt, welches eine einzige Methode enthält: „init“. Die Aufgabe dieser Methode ist es, die Web-Anwendung zu initialiseren, sobald sie (von „main5.js“) aufgerufen wird. Sie erstellt insbesondere mit Hilfe der Konstruktorfunktion „Greet“ ein Objekt „l_greet“, welche speziell angepasste Methoden „sayHello“ und „sayHelloOnEnter“ zur Verfügung stellt.

Das Modul „greet

Das Modul „greet“ benötigt überhaupt keine anderen Module. Daher ist die Moduldefinition noch etwas einfacher (die Liste mit benötigten Modulen entfällt ganz). Es ändert sich gegenüber App4 ebenfalls nur der Modulrahmen.

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.
});

Quellen

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