|
|
Zeile 5: |
Zeile 5: |
| [([https://glossar.hs-augsburg.de/beispiel/tutorium/es6/hello_world/WK_HelloWorld05/ SVN-Repository]) | | [([https://glossar.hs-augsburg.de/beispiel/tutorium/es6/hello_world/WK_HelloWorld05/ SVN-Repository]) |
| ==Anwendungsfälle (Use Cases)== | | ==Anwendungsfälle (Use Cases)== |
| Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_03|dritten Teil des Tutoriums]] | | Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_04|vierten Teil des Tutoriums]] |
| ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor. | | ändern sich die die Anwendungsfälle nur geringfügig. Das Begrüßungsformular soll ''zweimal'' |
| | im Browser angezeigt werden, einmal auf Deutsch und auf Englisch. Beide Begrüungsformulare sollen |
| | unabhängig voneinander funktionieren. |
|
| |
|
| In diesem Teil des Tutoriums geht es darum, die Anwendung besser zu strukturieren, {{dh}} zu [[Modul|modularisieren]].
| | Ein Grundprinzip der Programmierung lautet [[Don't repeat yourself]] (DRY). Sie könnten nun einfach den vorhandenen |
| | | Code duplizieren und entsprechend anpassen. Aber genau das würde gegen das Prinzip „Keep your code DRY“ verstoßen. |
| ===Modularisierung===
| | Code-Duplikation kann mit Hilfe von [[Klasse (objektorientert)|Klassen]] vermieden werden. Sie stellen eine Art |
| | | „Blaupause“ dar, um Objekte mit ähnlichen Eigenschaften ganz einfach erstellen zu können. |
| Eine Anwendung, wie {{zB}} ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgaben.
| |
| Bei einem HTML5-Spiel müssen die Spielfiguren, die Spielszenen, die Spiellogik, die Benutzereingaben, die Darstellung das Spiels im Browser
| |
| etc. erstellt und verwaltet werden, wobei das Erstellen und Verwalten teilweise während der Spiel-Entwicklung und teilweise
| |
| während der Spiel-Ausführung (Runtime) erfolgt. An der Entwicklung eines Spiels sind üblicherweise mehrere oder gar viele
| |
| Entwickler gleichzeitig beteiligt. Oft müssen einzelnen Komponenten an neue Endgeräte oder Betriebssysteme angepasst werden.
| |
| Beispielsweise muss eine Tastatursteuerung durch eine Touch- oder Gestensteuerung ersetzt werden,
| |
| wenn das Spiel auf einem Smartphone oder Tablet laufen soll.
| |
| | |
| Das alles ist nur machbar, wenn man die Anwendung modularisiert, {{dh}} in kleine, möglichst unabhängige
| |
| Komponenten unterteilt. Ein großer Feind der Modularisierung sind globale Variablen und Funktionen.
| |
| Je mehr Dateien von unterschiedlichen Autoren erstellt werden, desto größer ist die Gefahr, dass es zu Namenskollisionen kommt.
| |
| Daher gilt der [[Multimedia-Programmierung: Best Practices#Modularisierung|Grundsatz]]: Verwende so wenig globale Größen wie möglich.
| |
|
| |
|
| ==Erstellen eines neuen Projektes== | | ==Erstellen eines neuen Projektes== |
|
| |
|
| Erstellen Sie ein neues Projekt „<code>HelloWorld04</code>“ und legen Sie dieses in Ihrem Repository ab.
| | Erzeugen Sie wie im [[HTML5-Tutorium:_JavaScript:_Hello_World_04|vierten Teil des Tutoriums]] ein neues Projekt, |
| Kopieren Sie anschließend alle Dateien aus dem dritten Teil des Tutoriums, passen Sie den Titel in der HTML-Datei an und committen Sie abermals.
| | diesmal allerding mit dem schönen Namen <code>HelloWorld05</code>“, speichern Sie dieses wieder |
| | | in Ihrem SVN-Repository (Achtung: <code>node_modules</code> muss auf der Ignore-Liste stehen) |
| ==Erstellen einer Ordnerstruktur==
| | und erstellen Sie dann dieselbe Ordnerstruktur. |
| | |
| Grundsätzlich gilt auch bei der Programmierung: Ordnung ist das halbe Leben.
| |
| | |
| Web-Anwendungen werden sehr schnell sehr groß. Also sollte man sich eine geeignete Ordnerstruktur überlegen.
| |
| Üblicherweise legt man CSS-Dateien in einen Ordner namens „<code>css</code>“ und JavaScript-Dateien in einen Ordner namens
| |
| „<code>js</code>“ oder „<code>lib</code>“. Sollten es viele CSS- und/oder JavaScript-Dateien werden,
| |
| legt man im entsprechenden Ordner geeignete Unterordner an.
| |
| | |
| Legen Sie im Root-Verzeichnis Ihres Projektes folgende Ordner an:
| |
| * <code>web</code>: Der Inhalt dieses Ordners kommt später auf einen echten Web-Server.
| |
| * <code>web/css</code>: Hier werden die CSS-Dateien der Web-Anwendung gespeichert. In diesem Ordner können zur weiteren Strukturierung Unterordner angelegt werden.
| |
| * <code>web/js</code>: Hierher kommt die Datei „<code>main.js</code>“, die für den Start der Web-Anwendung zuständig ist. Üblicherweise liest sie eine JSON-Datei mit Initialwerten ein, die sie der Anwendung übergeben kann.
| |
| * <code>web/js/app</code>: Hier speichern Sie die von Ihnen geschriebenen JavaScript-Dateien Ihrer Web-Anwendung. In diesem Ordner werden üblicherweise Unterordner zur weiteren Strukturierung der Anwendung angelegt.
| |
| * <code>web/js/lib</code>: Hier speichern Sie die JavaScript-Bibliotheken, die andere JavaScript-Entwickler erstellt haben und die Sie in Ihrer Anwendung einsetzen. Für jede externe Bibliothek gibt es einen Unterordner.
| |
| | |
| Fremde Bibliotheken sind nicht unbedingt fehlerfrei und können durchaus auch unsauberen Code enthalten.
| |
| Damit Ihnen bei einem Commit diese Fehler nicht angezeigt werden, schließen Sie den Ordner
| |
| „<code>web/js/lib</code>“ von der WebStorm-Fehlerüberwachung aus:
| |
| | |
| * Rechtklick auf den Ordner „<code>web/js/lib</code>“ → <code>Mar Directory as</code> → <code>Excluded</code>
| |
| | |
| Da in diesem Teil des Tutoriums fünf Web-Anwendungen mit jeweils unterschiedlichem Modularisierungsgrad
| |
| erstellt werden, werden hier anstelle des Ordners „<code>web/js/app</code>“ fünf App-Ordner angelegt:
| |
| * <code>web/js/app1</code>: Aufteilung der App in mehrere Objekte und Dateien.
| |
| * <code>web/js/app2</code>: Definition einer Klasse <code>Greet</code>.
| |
| * <code>web/js/app3</code>: Reduktion der Konstanten in <code>Greet</code>.
| |
| * <code>web/js/app4</code>: Weitere Modularisierung der Web-App mit JavaScript-Bordmitteln.
| |
| * <code>web/js/app5</code>: Alternative Modularisierung der Web-App mit der JavaScript-Bibliothek <code>RequireJS</code>.
| |
| | |
| Öffnen Sie im Editorbereich die Datei „<code>index.html</code>“ und verschieben sie in den Ordner „<code>web</code>“, wobei Sie darauf achten sollten,
| |
| dass im Verschiebe-Dialogfenster die Option „<code>Search for references</code>“ aktiviert ist.
| |
| .
| |
| Verschieben sie anschließend die Dateien „<code>main.css</code>“ und „<code>main.js</code>“
| |
| in die passenden Ordner „<code>web/css</code>“ bzw. „<code>web/js</code>“. Sie werden feststellen, dass die Verweise in den Link- und Script-Elementen der
| |
| Datei „<code>index.html</code>“ automatisch angepasst werden. Verschieben Sie nun die Datei „<code>index.html</code>“ in den Ordner „<code>web</code>“.
| |
| | |
| Das Ergebnis dieser Aktion ist, dass Ihre Web-Anwendung nicht mehr im Root-Verzeichnis des Projektes liegt, sondern (sauber strukturiert) in unterschiedlichen Unterordnern.
| |
| Daher ist es möglich, im Root-Verzeichnis des Projektes weitere projektspezifische Ordner und Dateien anzulegen wie „<code>conf</code>“ und „<code>doc</code>“,
| |
| die später nicht auf den Web-Server, auf dem die Anwendung schließlich laufen soll, kopiert werden.
| |
| | |
| ==App1==
| |
| | |
| Benennen Sie die Dateien „<code>index.html</code>“ und „<code>main.js</code>“ in
| |
| „<code>index1.html</code>“ und „<code>main1.js</code>“ um. (Im Laufe des Tutoriums kommen weitere Versionen dieser beiden Dateien dazu.)
| |
| | |
| ===Laden der CSS- und der JavaScript-Dateien===
| |
| | |
| Eine Web-Anwendung funktioniert nur – wie Sie bereits erfahren haben –, wenn nicht nur die HTML-Datei, sondern auch die zugehörigen
| |
| CSS- und JavaScript-Dateien geladen werden. Allerdings gibt es dabei ein Problem: Die CSS- und JavaScript-Dateien werden im Laufe der
| |
| Zeit immer zahlreicher und/oder größer. Diese Dateien zu laden, dauert seine Zeit.
| |
| | |
| In der aktuellen Version der Web-Anwendung stehen die Verweise auf diese Dateien im <code>head</code>-Bereich des Dokuments.
| |
| Dieser wird vollständig geladen, bevor der <code>body</code>-Bereich eingelesen wird. Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments
| |
| darstellen kann, solange er die JavaScript- und CSS-Dateien lädt. Wenn dies zu lange dauert, verliert der Besucher die Geduld und verlässt die Seite
| |
| vorzeitig.
| |
| | |
| Besser wäre es daher andersherum vorzugehen: Es wird zuerst der <code>body</code>-Bereich geladen und dann die JavaScript- und die CSS-Dateien.
| |
| Im Falle von JavaScript ist das durchaus sinnvoll, aber im Falle von CSS hat das den Effekt, dass der Browser noch keine Layout-Vorgaben erhalten hat,
| |
| wenn er mit dem [[Rendern]] der Seite beginnt. Also verwendet er die browserspezifischen Defaultwerte. Das heißt, die Seite sieht zunächst
| |
| ganz anders aus, als vom Designer geplant. Wenn dann die CSS-Dateien geladen wurden, wird die Seite erneut gerendert und verändert ihr Aussehen.
| |
| Auch das ist verwirrend und wirkt unprofessionell.
| |
| | |
| Was also machen?
| |
| | |
| ===CSS-Dateien===
| |
| Für CSS-Dateien empfiehlt Google ernsthaft, die Link-Element ganz ans Ende der HTML-Datei zu stellen, also '''nach''' dem
| |
| schließenden <code>html</code>-Tag.
| |
| Damit das Problem mit dem falschen Layout nicht auftritt, sollen wichtige CSS-Befehle direkt – {{dh}} als CSS-Befehle innerhalb eines
| |
| <code>style</code>-Elements – in die HTML-Datei eingefügt
| |
| werden.<ref>{{Quelle|Google (Web)}}, Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery</ref>
| |
| | |
| Von beiden Vorschlägen rate ich dringend ab. Der erste Vorschlag ist nicht [[HTML]]-konform, ja noch nicht einmal [[SGML]]-konform. Es mag sein,
| |
| dass diverse Browser diese Syntax verstehen, korrekt ist sie trotzdem nicht. Der zweite Vorschlag hat zur Folge, dass Struktur (HTML) und Layout (CSS)
| |
| vermischt werden. Dies war einer der Kardinalfehler von frühen HTML-Versionen aus der Zeit, bevor es das CSS-Format gab. '''Tun Sie das nicht.'''
| |
| | |
| Gehen Sie so vor, wie es unter [[Multimedia-Programmierung: Best Practices#CSS|Multimedia-Programmierung: Best Practices]] beschrieben ist.
| |
| | |
| In unserer Anwendung haben wir bislang alles richtig gemacht, bis auf die fehlende automatische Komprimierung.
| |
| Diese wird im [[HTML5-Tutorium: JavaScript: Hello World 05|fünften Teil des Tutoriums]] beschrieben.
| |
| | |
| ===JavaScript-Dateien===
| |
| | |
| 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.
| |
| | |
| 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:
| |
| | |
| <source lang="html5">
| |
| ...
| |
| <script type="text/javascript" src="js/main1.js"></script>
| |
| </body>
| |
| </source>
| |
| | |
| ===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: „<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.
| |
| | |
| 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>“
| |
| ein ('''vor''' dem Skript-Element, das „<code>main1.js</code>“ lädt):
| |
| | |
| <source lang="html5">
| |
| ...
| |
| <script type = "text/javascript" src = "js/app1/greet.js"></script>
| |
| <script type = "text/javascript" src = "js/main1.js" ></script>
| |
| </body>
| |
| </source>
| |
| | |
| Fügen Sie in die Datei „<code>greet.js</code>“ ein Objekt ein, dass die beiden Methoden
| |
| „<code>sayHello</code>“ und „<code>sayHelloOnEnter</code>“ enthält.
| |
| | |
| <source lang="javascript">
| |
| /**
| |
| * @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();
| |
| }
| |
| }
| |
| };
| |
| </source>
| |
| | |
| In der Datei „<code>main1.js</code>“ werden dagegen alle globalen Funktionen entfernt.
| |
| Dies gilt auch für die Definition des Eventlisteners am Ende der Datei, der dafür sorgt, dass die
| |
| Funktion „<code>init</code>“ ausgeführt wird, sobald die HTML-Seite sowie alle Unterseiten vollständig geladen wurden.
| |
| | |
| Die Funktion „<code>init</code>“ wird durch eine anonyme Funktion ersetzt, die '''sofort''' nach ihrer Definition auch ausgeführt wird.
| |
| Da die Datei „<code>main1.js</code>“ als Letztes geladen wird, sind alle Objekte, auf die
| |
| während der Initialisierung zugegriffen werden muss, bereits vorhanden.
| |
| | |
| <source lang="javascript">
| |
| // Initialize the app.
| |
| (function()
| |
| {
| |
| document.getElementById('button_submit')
| |
| .addEventListener('click', greet.sayHello);
| |
| | |
| window.addEventListener('keydown', greet.sayHelloOnEnter);
| |
| }());
| |
| </source>
| |
| | |
| Testen Sie Ihre Anwendung und speichern Sie sie zu guter Letzt im Repository.
| |
| | |
| ==App2==
| |
| | |
| Ein wesentliches [[Programmierprinzip]] ist das Prinzip der „Wiederverwendbarkeit“.
| |
| Man sollte Code so schreiben, dass man ihn möglichst häufig wiederverwenden kann.
| |
| Jede Wiederverwendung bereits bestehenden Codes bedeutet Arbeitsersparnis.
| |
| Man kann mit einem einfachen Test überprüfen, ob man Code erstellt hat, der
| |
| sogar innerhalb desselben Projektes wiederverwendbar ist. Man baut die neu
| |
| erstellte Anwendung einfach zweimal in ein HTML-Dokument ein. Wenn beide
| |
| „Instanzen“ der Anwendung unabhängig voneinander funktionieren, hat man
| |
| alles richtig gemacht.
| |
| | |
| Zurzeit geht das bei unserer Anwendung noch nicht. Es gibt nämlich nur
| |
| ein Objekt „<code>greet</code>“, das nicht für mehrere Anwendungen parallel eingesetzt werden kann.
| |
| Wir müssen also bei Bedarf neue <code>greet</code>-Objekte erzeugen können.
| |
| Das geschieht in JavaScript mit Hilfe von sogenannten [[Konstruktor]]funktionen.
| |
| | |
| Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihre Anwendungsdateien:
| |
| | |
| * <code>index1.html</code> → <code>index2.html</code>
| |
| * <code>js/main1.js</code> → <code>js/main2.js</code>
| |
| * <code>js/app1/greet.js</code> → <code>js/app2/greet.js</code>
| |
| | |
| In der Datei „<code>index2.html</code>“ müssen Sie dafür sorgen, dass
| |
| die neuen JavaScript-Dateien anstelle der alten geladen werden.
| |
| Und passen Sie den Titel im HTML-Header an.
| |
| | |
| Das Objekt „<code>greet</code>“ der Datei „<code>js/app2/greet.js</code>“
| |
| wird durch der folgende Konstruktorfunktion „<code>Greet</code>“ ersetzt:
| |
| | |
| <source lang="javascript">
| |
| /**
| |
| * @class
| |
| * @classdesc Contains methods to say hello to the user of the web app.
| |
| */
| |
| function Greet()
| |
| {
| |
| var l_this = this; // this-hack: l_this contains the new object
| |
| | |
| /**
| |
| * Welcomes the user of the web app1 by displaying a welcome message
| |
| * that includes his name. The name is fetched from a text input field.
| |
| */
| |
| l_this.sayHello = // this.sayHello = ...
| |
| function()
| |
| {
| |
| document.getElementById('heading_hello').innerHTML =
| |
| 'Hello, ' + document.getElementById("input_name").value + '!';
| |
| | |
| document.getElementById('section_form').classList.add('hidden');
| |
| document.getElementById('section_hello').classList.remove('hidden');
| |
| };
| |
| | |
| /**
| |
| * An keyboard event observer. It tests whether the enter key has been pressed.
| |
| * If so, the greet method is activated. Default reactions of the browser are
| |
| * disabled.
| |
| * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
| |
| * (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
| |
| */
| |
| l_this.sayHelloOnEnter = // this.sayHelloOnEnter = ...
| |
| function(p_event)
| |
| {
| |
| if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
| |
| document.activeElement === document.getElementById('input_name')
| |
| )
| |
| {
| |
| p_event.preventDefault();
| |
| p_event.stopPropagation();
| |
| l_this.sayHello(); // NOT this.sayHello();
| |
| }
| |
| }
| |
| }
| |
| </source>
| |
| | |
| In JavaScript kann jede Funktion auch als Konstruktor, {{dh}}
| |
| zum Erzeugen von neuen Objekte verwendet werden, indem man das Schlüsselwort
| |
| „<code>new</code>“ voranstellt:
| |
| | |
| <source lang="javascript">
| |
| var l_greet = new Greet();
| |
| </source>
| |
| | |
| 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.
| |
| | |
| 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“.
| |
| | |
| Beachten Sie bitte, dass in der Definition der Konstruktorfunktion der so genannte This-Hack zum Einsatz kommt.
| |
| Das Objekt <code>this</code> wird in einer lokalen Variablen <code>l_this</code> gespeichert.
| |
| Und anschließend wird diese lokale Variable anstelle von <code>this</code> benutzt.
| |
| Für die Definition der Attribute „<code>sayHello</code>“ und „<code>sayHelloOnEnter</code>“ macht es keinen Unterschied,
| |
| ob man „<code>this.sayHello = ...</code>“ oder „<code>l_this.sayHello = ...</code>“ schreibt.
| |
| | |
| Allerdings wird auch innerhalb des Methodenrumpfes von „<code>sayHelloOnEnter</code>“ auf das neu erstellt
| |
| 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.
| |
| | |
| In der Anwendung „App1“ wurde der This-Hack nicht benötigt, da die Methode „<code>sayHelloOnEnter</code>“
| |
| einfach die globale Variable Methode „<code>greet</code>“ verwendet hat, um auf die Methode „<code>sayHello</code>“
| |
| 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>“.)
| |
| | |
| '''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]):
| |
| | |
| <source lang="javascript">
| |
| this.sayHelloOnEnter =
| |
| function(p_event)
| |
| {
| |
| ...
| |
| this.sayHello();
| |
| ...
| |
| }.bind(this);
| |
| </source>
| |
| Aber für JavaScript-Anfänger ist die Verwendung des This-Hacks deutlich einfacher.
| |
| | |
| Fügen Sie als nächstes die zuvor erwähnte Zeile
| |
| | |
| <source lang="javascript">
| |
| var l_greet = new Greet();
| |
| </source>
| |
| als erste Zeile '''in''' Ihre Initialisierungsfunktion (Datei „<code>main2.js</code>“)
| |
| ein und ersetzen Sie in derselben Funktion die beiden Zugriffe auf das globale Objekt
| |
| „<code>greet</code>“ durch Zugriffe auf das lokale Objekt „<code>l_greet</code>“.
| |
| | |
| Danach sollte Ihre Anwendung (Datei „<code>index2.html</code>“) wieder wie gewohnt laufen.
| |
| | |
| ==App3==
| |
| | |
| Leider bringt uns hinsichtlich der Wiederverwendbarkeit die „Verbesserung“, die wir in App2 gegenüber App1
| |
| vorgenommen haben, gar nichts. Wir können jetzt zwar beliebig viele <code>greet</code>-Objekte
| |
| mit Hilfe von „<code>new Greet()</code>“ erstellen.
| |
| Aber wir haben nichts davon, da alle diese Objekte dieselben HTML-Elemente
| |
| im HTML-Dokument lesen und ändern. Das heißt, wir müssen die Konstanten und
| |
| am Besten auch gleich noch die Zugriffe auf globale Variablen aus der Konstruktorfunktion
| |
| „<code>greet</code>“ verbannen.
| |
| | |
| Erstellen Sie in Ihrem WebStorm-Projekt folgende Kopien Ihre Anwendungsdateien:
| |
| | |
| * <code>index2.html</code> → <code>index3.html</code>
| |
| * <code>js/main2.js</code> → <code>js/main3.js</code>
| |
| * <code>js/app2/greet.js</code> → <code>js/app3/greet.js</code>
| |
| | |
| In der Datei „<code>index3.html</code>“ müssen Sie wiederum dafür sorgen, dass
| |
| die neuen JavaScript-Dateien anstelle der alten geladen werden.
| |
| Und passen Sie auch wieder den Titel im HTML-Header an.
| |
| | |
| Ersetzen Sie nun die Konstruktorfunktion der Datei „<code>js/app3/greet.js</code>“
| |
| durch folgende Konstruktorfunktion:
| |
| | |
| <source lang="javascript">
| |
| /**
| |
| * @class
| |
| * @classdesc Contains methods to say hello to the user of the web app.
| |
| *
| |
| * @param p_document {object}
| |
| * A DOM object (usually the global object <code>document</code>)
| |
| * @param p_section_form {string}
| |
| * The ID of the HTML element that contains the form section.
| |
| * @param p_input_name string}
| |
| * The ID of the text field via which the user passes her/his name.
| |
| * @param p_section_hello {string}
| |
| * The ID of the HTML element that contains the welcome section.
| |
| * @param p_output_hello {string}
| |
| * The ID of the HTML element into which the greeting is to be put.
| |
| * @param p_hello {string}
| |
| * The hello test to be displayed.
| |
| */
| |
| function Greet(p_document,
| |
| p_section_form, p_input_name,
| |
| p_section_hello, p_output_hello,
| |
| p_hello
| |
| )
| |
| {
| |
| var l_this = this; // this-hack
| |
| | |
| /**
| |
| * Welcomes the user of the web app1 by displaying a welcome message
| |
| * that includes his name. The name is fetched from a text input field.
| |
| */
| |
| l_this.sayHello =
| |
| function()
| |
| {
| |
| p_document.getElementById(p_output_hello).innerHTML =
| |
| p_hello + ', ' + p_document.getElementById(p_input_name).value + '!';
| |
| | |
| p_document.getElementById(p_section_form).classList.add('hidden');
| |
| p_document.getElementById(p_section_hello).classList.remove('hidden');
| |
| };
| |
| | |
| /**
| |
| * An keyboard event observer. It tests whether the enter key has been pressed.
| |
| * If so, the greet method is activated. Default reactions of the browser are
| |
| * disabled.
| |
| * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
| |
| * (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
| |
| */
| |
| l_this.sayHelloOnEnter =
| |
| function(p_event)
| |
| {
| |
| if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
| |
| p_document.activeElement === p_document.getElementById(p_input_name)
| |
| )
| |
| {
| |
| p_event.preventDefault();
| |
| p_event.stopPropagation();
| |
| l_this.sayHello(); // NOT this.sayHello();
| |
| }
| |
| }
| |
| }
| |
| </source>
| |
| | |
| Dieser Konstruktor enthält diverse Konstanten nicht mehr, wie {{zB}} <code>'input_name'</code>. Anstelle dessen gibt es diverse Parameter,
| |
| in denen diese Werte dem Konstruktor übergeben werden können, wie {{zB}} <code>p_input_name</code>. Der Zugriff auf das globale Objekt
| |
| „<code>document</code>“ wurde auch gleich noch durch einen Zugriff auf einen lokalen Parameter „<code>p_document</code>“ ersetzt.
| |
| | |
| Damit ist es jetzt möglich, <code>greet</code>-Objekte für ganz unterschiedliche HTML-Elemente zu definieren.
| |
| | |
| Ersetzen Sie zunächst die Initialisierungsmethode in der Datei „<code>main3.js</code>“
| |
| durch folgenden Code:
| |
| | |
| <source lang="javascript">
| |
| (function(p_window, Greet)
| |
| {
| |
| var l_document = p_window.document, // The window object contains the document object.
| |
| l_greet = new Greet(l_document,
| |
| 'section_form', 'input_name',
| |
| 'section_hello', 'heading_hello',
| |
| 'Hello'
| |
| );
| |
| | |
| l_document.getElementById('button_submit')
| |
| .addEventListener('click', l_greet.sayHello);
| |
| | |
| p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
| |
| }(window, Greet));
| |
| </source>
| |
| | |
| In dieser Datei werden noch gibt es noch zwei globale Variablen, die Browser-Variable „<code>window</code>“
| |
| sowie die von uns definierte Konstruktorfunktion „<code>Greet</code>“. Diese beiden globalen Größen werden der
| |
| anonymen Funktion als Argumente übergeben. Innerhalb des Rumpfes der Initialisierungsfunktion wird dann nicht
| |
| mehr auf globale Größen zugegriffen, sondern nur noch auch Funktionsparameter. Dies ist ein übliches
| |
| Vorgehen, um explizit klar zu machen, auf welche globalen Größen zugegriffen wird.
| |
| | |
| Dem Konstruktor „<code>Greet</code>“ werden jetzt alle Information, die er benötigt,
| |
| explizit übergeben. Dies ist zum einen das HTML-Dokument, das mit Hilfe der von ihm erstellten
| |
| Methoden manipuliert werden soll, dann eine Reihe von ID-Attribut-Namen, die diejenigen
| |
| HTML-Elemente bezeichnen, die die Greet-Methoden lesen oder ändern sollen, sowie
| |
| der String „<code>'Hello'</code>“ der zur Begrüßung des Benutzers verwendet werden soll.
| |
| Alternativ könnte er auch mit „<code>Hallo</code>“ oder „<code>Howdy</code>“ begrüßt werden.
| |
| | |
| Nun müsste Ihre Anwendung „<code>index3.html</code>“ wieder laufen. Wenn Sie es denn tut, vergessen Sie nicht, sie
| |
| im Repository zu speichern.
| |
| | |
| Anschließend sollten Sie in Ihrem WebStorm-Projekt folgende Kopien erstellen:
| |
| | |
| * <code>index3.html</code> → <code>index3b.html</code>
| |
| * <code>js/main3.js</code> → <code>js/main3b.js</code>
| |
| | |
| Die Datei „<code>index3b.html</code>“ soll abermals die JavaScript-Datei „<code>js/app3/greet.js</code>“
| |
| verwenden, dafür aber „<code>js/main3b.js</code>“ an Stelle von „<code>js/main3.js</code>“ laden.
| |
| | |
| Fügen Sie in der Datei „<code>index3b.html</code>“ in alle ID-Attribut-Bezeichner eine <code>1</code> ein:
| |
| <code>section_form</code> → <code>section1_form</code> etc.
| |
| | |
| Schließen Sie dann die beiden Sections „<code>section1_form</code>“ und „<code>section1_hello</code>“
| |
| in ein <code>div</code>-Element ein und duplizieren Sie diese <code>div</code>-Elemente (natürlich samt Inhalt) anschließend.
| |
| Sie werden feststellen, dass WebStorm eine Reihe von Fehlern der Art „<code>Duplicate id reference</code>“ meldet.
| |
| Das ist nicht weiter verwunderlich, da Sie ja insbesondere alle ID-Attribut-Bezeichner dupliziert haben.
| |
| Sie müssen daher im zweiten <code>div</code>-Element alle Einser in den ID-Attribut-Bezeichnern durch Zweier ersetzen.
| |
| | |
| Nun können Sie noch versuchen, die beiden <code>div</code>-Elemente mittels CSS nebeneinander zu legen. Für den
| |
| angestrebten Test, ob Sie die Hello-World-App in einem HTML-Dokument zweimal starten können, ist das allerdings irrelevant.
| |
| | |
| Passen Sie nun Ihre Datei „<code>main3b.js</code>“ an die neuen Gegebenheiten an:
| |
| Erzeugen Sie zwei Greet-Objekte „<code>greet1</code>“ und „<code>greet2</code>“ (für jedes der beiden neuen <code>div</code>-Elemente eines),
| |
| fügen Sie passende <code>click</code>-Event-Listener für die beiden Buttons „<code>button1_submit</code>“ sowie
| |
| „<code>button2_submit</code>“ hinzu und sorgen Sie zu guter Letzt dafür, dass beide Methoden „<code>l_greet1.sayHelloOnEnter</code>“
| |
| und „<code>l_greet2.sayHelloOnEnter</code>“ bei jedem Tastendruck aufgerufen werden.
| |
| | |
| Wenn Sie alles richtig gemacht haben, sollten jetzt in Ihrer Web-Anwendung „<code>index3b.html</code>“
| |
| zwei Hello-World-Anwendungen parallel laufen: [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/index3b.html <code>index3b.html</code>]
| |
| | |
| ==App4==
| |
| Hinsichtlich der Wiederverwendbarkeit der Module ist die Version 3 der Web-Anwendung schon ganz gut.
| |
| Allerdings gibt es noch ein paar Unsauberkeiten, die beseitigt werden sollten:
| |
| | |
| # In den Initialisierungsdateien „<code>main3.js</code>“ und „<code>main3b.js</code>“ sind immer noch konstante Werte enthalten.
| |
| # Die Anzahl der Parameter der Konstruktorfunktion ist zu groß.
| |
| # In den Initialisierungsdateien ist immer noch zu viel Anwendungscode enthalten.
| |
| # Die potentielle Zahl der globalen Größen ist noch zu hoch.
| |
| | |
| Diese Probleme werden folgendermaßen gelöst:
| |
| | |
| # Die Konstanten werden in eine [[JSON]]-Datei ausgelagert, die im ersten Schritt von der jeweiligen Initialisierungsdatei eingelesen wird.
| |
| # Der Konstruktorfunktion wird das gesamte JSON-Objekt mit allen Initialisierungswerten in einem einzigen Parameter übergeben.
| |
| # Es wird eine anwendungsspezifische Initialisierungsdatei „<code>hello.js</code>“ erstellt. Die Initialisierungsfunktion in der Datei „<code>main4.js</code>“ hat nur noch die Aufgabe, die JSON-Datei zu lesen und die darin enthalten Informationen anschließend an die Methode „<code>hello.init</code>“ weiterzuleiten.
| |
| # Es wird '''ein''' globales Objekt namens „<code>hello</code>“ erstellt, das die gesamte Web-Anwendung enthält.
| |
| | |
| Erstellen Sie in Ihrem WebStorm-Projekt Kopien Ihrer Anwendungsdateien:
| |
| | |
| * <code>index3b.html</code> → <code>index4.html</code>
| |
| * <code>js/main3b.js</code> → <code>js/main4.js</code>
| |
| * <code>js/app3/greet.js</code> → <code>js/app4/greet.js</code>
| |
| | |
| Im Folgenden wird die HTML-Datei „<code>index4.html</code>“
| |
| als HTML-[[Template]] verwendet, {{dh}} als Strukturrahmen. '''Sämtliche''' Texte,
| |
| die in diesem Dokument enthalten sind, werden dynamisch mittels JavaScript eingefügt.
| |
| | |
| In der Datei „<code>index4.html</code>“ müssen Sie also nicht nur dafür sorgen, dass
| |
| die neuen JavaScript-Dateien anstelle der alten geladen werden und dass der Titel angepasst wird.
| |
| Sie müssen auch sämtliche Text-Elemente mit eindeutigen ID-Attributen versehen.
| |
| | |
| Die HTML-Datei der Musterlösung sieht folgendermaßen aus. Sie können auch Ihre
| |
| eigene Datei verwenden, müssen dann aber die JSON-Datei, die anschließend erstellt wird,
| |
| entsprechend anpassen.
| |
| | |
| <source lang="javascript">
| |
| <!DOCTYPE html>
| |
| <html lang="en">
| |
| | |
| <head>
| |
| <meta charset = "UTF-8">
| |
| <meta name = "viewport"
| |
| content = "width=device-width, initial-scale=1.0, user-scalable=yes"
| |
| >
| |
| | |
| <title>WK_HelloWorld04 (4)</title>
| |
| | |
| <link rel = "stylesheet" type = "text/css" href = "css/main.css"/>
| |
| </head>
| |
| | |
| <body>
| |
| <div class="left">
| |
| <section id="section1_form">
| |
| <h1 id="heading1_form"></h1>
| |
| <form>
| |
| <div>
| |
| <label id="label1_name" for="input1_name"></label>
| |
| <input id="input1_name" type="text" autofocus="autofocus"/>
| |
| </div>
| |
| <div>
| |
| <button id="button1_reset" type="reset"></button>
| |
| <button id="button1_submit" type="button"></button>
| |
| </div>
| |
| </form>
| |
| </section>
| |
| <section id="section1_hello" class="hidden">
| |
| <h1 id="heading1_hello"></h1>
| |
| <p id="paragraph1_hello"></p>
| |
| </section>
| |
| </div>
| |
| | |
| <div class="right">
| |
| <section id="section2_form">
| |
| <h1 id="heading2_form"></h1>
| |
| <form>
| |
| <div>
| |
| <label id="label2_name" for="input2_name"></label>
| |
| <input id="input2_name" type="text" autofocus="autofocus"/>
| |
| </div>
| |
| <div>
| |
| <button id="button2_reset" type="reset"></button>
| |
| <button id="button2_submit" type="button"></button>
| |
| </div>
| |
| </form>
| |
| </section>
| |
| <section id="section2_hello" class="hidden">
| |
| <h1 id="heading2_hello"></h1>
| |
| <p id="paragraph2_hello"></p>
| |
| </section>
| |
| </div>
| |
| | |
| <script type = "text/javascript" src = "js/lib/wk/ajax.js"></script>
| |
| <script type = "text/javascript" src = "js/app4/greet.js"></script>
| |
| <script type = "text/javascript" src = "js/app4/hello.js"></script>
| |
| <script type = "text/javascript" src = "js/main4.js"></script>
| |
| </body>
| |
| | |
| </html>
| |
| </source>
| |
| | |
| ===Modularisierung===
| |
| | |
| Um möglichst wenige globale Größen definieren zu müssen, legt man für eine
| |
| Anwendung oder eine Bibliothek typischerweise eine einzige globale Variable an – Namensraum-Variable genannt –, die ein Objekt enthält.
| |
| In diesem Objekt wird die gesamte Anwendung (Daten, Klassen, Funktionen etc.) gespeichert.
| |
| | |
| Für die Hello-World-Anwendung wird die globale Namensraum-Variable „<code>hello</code>“ verwendet.
| |
| In jeder Datei, in der ein Teilaspekt der Anwendung definiert wird, kommt folgender Rahmencode zum Einsatz:
| |
| | |
| <source lang="javascript">
| |
| /** @namespace hello */
| |
| var hello =
| |
| (function(p_hello)
| |
| { "use strict";
| |
| | |
| // anwendungsspezifischer Code
| |
| | |
| return p_hello;
| |
| }(hello ? hello : {}));
| |
| </source>
| |
| | |
| In der Variablen „<code>hello</code>“ wird das Ergebnis eines Aufrufs einer anonymen Funktion gespeichert.
| |
| Dieser Funktion wird als Argument der Inhalt der Variablen „<code>hello</code>“ oder, falls dieses noch nicht
| |
| existiert, ein leeres Objekt als Argument übergeben (<code>hello ? hello : {}</code>). Die anonyme Funktion
| |
| fügt irgendwelche Daten, Klassen oder Funktionen in dieses Objekt ein und gibt es dann als Ergebnis zurück.
| |
| | |
| Dies hat den Effekt, dass beim ersten Aufruf einer derartigen Funktion eine neues Objekt erstellt und
| |
| in „<code>hello</code>“ gespeichert wird, bei jedem weiteren Aufruf eines derartigen Codes wird
| |
| das bestehende <code>hello</code>-Objekt einfach um weitere Inhalte ergänzt. Wenn man die Definition
| |
| eines Namensraums über mehrere Dateien verteilt – die Forderung „Jedes Modul erledigt nach Möglichkeit
| |
| nur eine einzige Aufgabe.“ erzwingt diese Vorgehensweise geradezu –,
| |
| ist es egal in welcher Reihenfolge diese Dateien geladen werden. Sie können daher auch [[asynchron]],
| |
| {{dh}} parallel geladen werden. Wichtig ist nur, dass eine Datei nicht die Inhalte einer anderen Datei überschreiben darf.
| |
| | |
| Die Datei „<code>js/app4/greet.js</code>“ sieht nun folgendermaßen aus:
| |
| | |
| <source lang="javascript">
| |
| /** @namespace hello */
| |
| var hello =
| |
| (function(p_hello)
| |
| { "use strict";
| |
| | |
| /**
| |
| * @class hello.Greet
| |
| * @classdesc Contains methods to say hello to the user of the web app.
| |
| *
| |
| * @param p_document A DOM object (usually the global object <code>document</code>)
| |
| * @param p_init A JSON object containing all init data.
| |
| */
| |
| p_hello.Greet =
| |
| function(p_document, p_init)
| |
| {
| |
| var l_this = this; // this-hack
| |
| | |
| /**
| |
| * Welcomes the user of the web app1 by displaying a welcome message
| |
| * that includes his name. The name is fetched from a text input field.
| |
| */
| |
| l_this.sayHello =
| |
| function()
| |
| {
| |
| var l_init_elements = p_init.HTMLElements,
| |
| l_init_text = p_init.text,
| |
| l_init_hidden = p_init.css.hidden,
| |
| l_name = p_document.getElementById(l_init_elements.inputName).value;
| |
| | |
| p_document.getElementById(l_init_elements.welcome).innerHTML
| |
| = l_init_text.welcome;
| |
| p_document.getElementById(l_init_elements.hello).innerHTML
| |
| = l_init_text.hello.replace('$1', l_name);
| |
| | |
| p_document.getElementById(l_init_elements.sectionHello)
| |
| .classList.remove(l_init_hidden);
| |
| p_document.getElementById(l_init_elements.sectionForm)
| |
| .classList.add(l_init_hidden);
| |
| };
| |
| | |
| | |
| /**
| |
| * An keyboard event observer. It tests whether the enter key has been pressed.
| |
| * If so, the greet method is activated. Default reactions of the browser are
| |
| * disabled.
| |
| *
| |
| * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
| |
| * (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
| |
| */
| |
| l_this.sayHelloOnEnter =
| |
| function(p_event)
| |
| {
| |
| if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
| |
| p_document.activeElement ===
| |
| p_document.getElementById(p_init.HTMLElements.inputName)
| |
| )
| |
| {
| |
| p_event.preventDefault();
| |
| p_event.stopPropagation();
| |
| l_this.sayHello(); // NOT this.sayHello();
| |
| }
| |
| };
| |
| };
| |
| | |
| return p_hello;
| |
| }(hello ? hello : {}));
| |
| </source>
| |
| | |
| Gegenüber der Version 3 dieser Datei hat sich Folgendes geändert:
| |
| | |
| * Es wird keine globale Konstruktorfunktion „<code>Greet</code>“ mehr definiert. Diese Konstruktorfunktion wird vielmehr in der Namensraum-Variablen „<code>hello</code>“ gespeichert: „<code>hello.Greet</code>“
| |
| * Die Konstruktorfunktion hat nur noch zwei Parameter: „<code>p_window</code>“ und „<code>p_init</code>“. Der erste Parameter enthält wie in Version 3 das globale Window-Objekt, der zweite ein JSON-Objekt, in dem alle Initialisierungsinformationen enthalten sind.
| |
| * Im Rumpf der Konstruktorfunktion werden alle notwendigen Informationen aus <code>p_init</code> extrahiert: Der Name der Formular-Section, der Name des Text-Eingabefelds, der Text der Hello-Nachricht etc.
| |
| | |
| Legen Sie gleich noch die Datei „<code>js/app4/hello.js</code>“ für die Web-App an:
| |
| <source lang="javascript">
| |
| /** @namespace hello */
| |
| var hello =
| |
| (function(p_hello)
| |
| { "use strict";
| |
| | |
| /**
| |
| * Initializes the HTML sections of the app that are
| |
| * stated in <code>p_init</code>.
| |
| *
| |
| * @param p_window The browser window that contains the HTML document to be initialized.
| |
| * @param p_init The initialization info for the form section and welcome section.
| |
| */
| |
| p_hello.init =
| |
| function(p_window, p_init)
| |
| {
| |
| var l_document = p_window.document,
| |
| l_init_elements = p_init.HTMLElements,
| |
| l_init_text = p_init.text,
| |
| l_greet = new p_hello.Greet(l_document, p_init);
| |
| | |
| l_document.getElementById(l_init_elements.headingForm)
| |
| .innerHTML = l_init_text.title;
| |
| l_document.getElementById(l_init_elements.inputNameLabel)
| |
| .innerHTML = l_init_text.query;
| |
| l_document.getElementById(l_init_elements.buttonReset)
| |
| .innerHTML = l_init_text.reset;
| |
| l_document.getElementById(l_init_elements.buttonSubmit)
| |
| .innerHTML = l_init_text.submit;
| |
| l_document.getElementById(l_init_elements.buttonSubmit)
| |
| .addEventListener('click', l_greet.sayHello);
| |
| p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
| |
| };
| |
| | |
| return p_hello;
| |
| }(hello ? hello : {}));
| |
| </source>
| |
| | |
| Sie ist wieder aufgebaut wie zuvor beschrieben: Sie definiert, falls notwendig, das globale Objekt „<code>hello</code>“
| |
| und fügt anschließend eine Funktion „<code>init</code>“ in dieses Objekt ein. Die Methode „<code>hello.init</code>“
| |
| hat dieselben Parameter, wie die zuvor definierte Konstruktorfunktion und erwartet denselben Input.
| |
| Ihre Aufgabe ist es, die derzeit noch leeren Text-Elemente des HTML-Templates mit Inhalt zu füllen
| |
| soweit der Inhalt schon bekannt ist (der Name des Benutzers ist noch unbekannt). Außerdem erstellt sie
| |
| ein passendes Greet-Objekt „<code> l_greet</code>“ mit Hilfe der Konstruktorfunktion „<code>hello.Greet</code>“ und weist dem
| |
| Submit-Button die darin enthaltene Methode „<code>sayHello</code>“ zu. Außerdem definiert sie
| |
| eine Eventlistener, der im Falle eines Key-Down-Ereignisses die Methode „<code> l_greet.sayHelloOnEnter</code>“
| |
| ausführt.
| |
| | |
| ===Die JSON-Datei===
| |
| Zur Initialisierung der Web-Anwendung werden JSON-Dateien benötigt.
| |
| | |
| Eine JSON-Datei enthält genau ein JavaScript-Objekt in textueller Darstellung.
| |
| Allerdings werden nur folgende Datentypen unterstützt<ref>[http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf ECMA International, The JSON Data Interchange Format, 2013]</ref>:
| |
| * Nullwert: <code>null</code>
| |
| * Boolesche Werte: <code>true</code>, <code>false</code>
| |
| * Zahlen
| |
| * Strings
| |
| * Arrays
| |
| * Objekte
| |
| | |
| Insbesondere Funktionen können in einer JSON-Datei nicht enthalten sein. Außerdem müssen
| |
| Strings immer in doppelte Anführungszeichen eingeschlossen sein, einfache Anführungszeichen sind nicht erlaubt.
| |
| Attributnamen werden ebenfalls durch Strings dargestellt. Auch diese müssen gemäß ECMA-Standard in doppelte
| |
| Anführungszeichen eingeschlossen werden. Außerdem können in JSON-Dateien (leider) keine Kommentare
| |
| enthalten sein.
| |
| | |
| Folgendes Objekt ist zwar ein korrektes Javascript-Objekt, aber kein gültiges JSON-Objekt:
| |
| | |
| <source lang="javascript">
| |
| { a: 55, 'b': 'Wolfgang' }
| |
| </source>
| |
| | |
| In JSON muss dieses Objekt folgendermaßen geschrieben werden:
| |
| | |
| <source lang="javascript">
| |
| { "a": 55, "b": "Wolfgang" }
| |
| </source>
| |
| | |
| 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 {{zB}} zur Folge, das WebStorm bei einem Zugriff
| |
| auf eine Attribut in einem JSON-Objekt den Attributnamen unterringelt und mit der Warnung | |
| „<code>Unresolved variable ...</code>“ versieht.
| |
| | |
| Ein Lösung für dieses Problem wäre die Verwendung von [http://json-schema.org/ JSON Schema].
| |
| Aber darauf wird zunächst verzichtet.
| |
| | |
| Legen Sie für die zuvor definierte HTML-Datei „<code>index4.html</code>“ folgende JSON-Initialisierungsdatei
| |
| unter dem Namen „<code>web/json/hello_de.json</code>“ an:
| |
| | |
| <source lang="javascript">
| |
| {
| |
| "HTMLElements":
| |
| {
| |
| "sectionForm": "section1_form",
| |
| "headingForm": "heading1_form",
| |
| "inputNameLabel": "label1_name",
| |
| "inputName": "input1_name",
| |
| "buttonReset": "button1_reset",
| |
| "buttonSubmit": "button1_submit",
| |
| "sectionHello": "section1_hello",
| |
| "hello": "heading1_hello",
| |
| "welcome": "paragraph1_hello"
| |
| },
| |
| | |
| "text":
| |
| {
| |
| "title": "Hallo, Fremder!",
| |
| "query": "Wie heißen Sie?",
| |
| "reset": "Reset",
| |
| "submit": "Begrüßung",
| |
| "hello": "Hallo, $1!",
| |
| "welcome": "Willkommen bei Multimedia-Programmierung!"
| |
| },
| |
| | |
| "css":
| |
| {
| |
| "hidden": "hidden"
| |
| }
| |
| }
| |
| </source>
| |
| | |
| 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.
| |
| | |
| Im Objekt „<code>text</code>“ sind alle Texte enthalten, die zu gegebener Zeit in das HTML-Dokument an die geeignete
| |
| Stelle geschrieben werden sollen. Der Text „<code>hello</code>“ enthält „<code>$1</code>“ als Platzhalter.
| |
| Dieser Platzhalter soll von der Anwendung durch den Namen des Benutzers ersetzt werden, nachdem er ihn eingegeben hat.
| |
| | |
| In Objekt „<code>css</code>“ sind alle Namen von CSS-Klassen enthalten, die von der Web-Anwendung zur Laufzeit
| |
| zu HTML-Elementen hinzugefügt oder aus HTML-Elementen entfernt werden. Der Bezeichner, der der Anwendung bekannt ist
| |
| („<code>hidden</code>“), stimmt in diesem Fall mit dem Namen der zugehörigen CSS-Klasse überein. Das ist nicht verboten.
| |
| | |
| Erstellen Sie analog die Datei „<code>web/json/hello_en.json</code>“. Diese Datei soll für die zweite Web-Anwendung
| |
| definiert werden, die allerdings englischsprachige Texte enthält (Sie erinnern sich? In <code>index4.html</code>
| |
| laufen zwei unabhängige Web-Anwendungen parallel).
| |
| | |
| ===Das Laden der JSON-Dateien===
| |
| | |
| Für das [[asynchron]]e Laden von beliebigen Dateien von einem Web-Server gibt es in JavaScript den Befehl „<code>XMLHttpRequest</code>“.
| |
| Wie man am Namen sieht, war er ursprünglich für das Laden XML-Dateien vorgesehen. Heute kann dieser Befehl allerdings für
| |
| jede Art von Datei verwendet werden. Sie könnten jetzt natürlich die Dokumentation (https://wiki.selfhtml.org/wiki/JavaScript/XMLHttpRequest)
| |
| lesen und den Befehl dann verwenden. Einfacher ist es jedoch, wenn Sie von mir erstellte die Datei
| |
| „<code>[https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld04/web/js/lib/wk/ajax.js ajax.js] </code>“
| |
| aus dem Glossar-Repository herunterladen und in Ihrem Projekt unter dem Namen „<code>web/js/lib/wk/ajax.js</code>“ speichern.
| |
| | |
| Dieses Module stellt unter der globalen Variablen „<code>wk</code>“ – diese globale Variable ist für meine Module reserviert :-) –
| |
| im Modul „<code>wk.ajax</code>“ mehrere Methoden zum Laden von Dateien bereit. Zum Laden von JSON-Dateien
| |
| sollten Sie „<code>wk.ajax.getJSON</code>“ verwenden (obwohl es mit „<code>wk.ajax.getFile</code>“ auch ginge; Sie müssten
| |
| dann allerdings den Inhalt der Datei, der als String geliefert wird, noch in ein JavaScript-Objekt umwandeln). | |
| | |
| Die Methode „<code>getJSON</code>“ erwartet drei Argumente, einen URI, der den Speicherort der JSON-Datei beschreibt und zwei Callback-Funktionen.
| |
| Die erste Funktion wird aufgerufen, sobald die JSON-Datei erfolgreich geladen wurde. Ihr wird das geladen JSON-Objekt als Argument
| |
| übergeben. Die zweite Callback-Funktion wird aufgerufen, falls beim Laden etwas schief gehen sollte. Die Angabe dieser Funktion ist
| |
| optional. In der Musterlösung wurde Sie definiert, hier wird darauf verzichtet.
| |
| | |
| Die Initialisierungsfunktion (Datei „<code>main4.js</code>“) sieht nun folgendermaßen aus:
| |
| | |
| <source lang="javascript">
| |
| // Initialize the app.
| |
| (function(p_window, p_wk, p_hello)
| |
| { "use strict";
| |
| | |
| // The list of all json files to be loaded
| |
| var l_json_files = ['json/hello_de.json', 'json/hello_en.json'];
| |
| | |
| // For each element in the JSON file list:
| |
| for (var i = 0, n = l_json_files.length; i < n; i++)
| |
| p_wk.ajax.
| |
| getJSON
| |
| ( l_json_files[i], // Load the JSON file.
| |
| function(p_init) // If ready, perform the callback function.
| |
| { p_hello.init(p_window, p_init); } // Initialize the corresponding hello section.
| |
| );
| |
| }(window, wk, hello));
| |
| </source>
| |
| | |
| Die Initialisierungsfunktion liest die beiden zuvor erstellten JSON-Dateien ein
| |
| und reicht jeweils das daraus erzeugte JSON-Objekt „<code>main4.js</code>“ an die Initialisierungsfunktion der Web-Anwendung weiter.
| |
| Beachten Sie, dass die Reihenfolge, in der die beiden JSON-Dateien geladen wird, nicht feststeht. Sie werden parallel geladen. Sobald eine
| |
| Datei vollständig geladen wurde, wird die zugehörige Callback-Funktion
| |
| | |
| <source lang="javascript">
| |
| function(p_init) { p_hello.init(p_window, p_init); }
| |
| </source>
| |
| | |
| 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.
| |
| | |
| '''Anmerkung''': Man könnte die beiden JSON-Dateien natürlich auch mittels zweier Aufrufe von „<code>getJSON</code>“
| |
| laden:
| |
| | |
| <source lang="javascript">
| |
| // Initialize the app.
| |
| (function(p_window, p_wk, p_hello)
| |
| { "use strict";
| |
| | |
| p_wk.ajax.
| |
| getJSON
| |
| ( 'json/hello_de.json', // Load the JSON file 'json/hello_de.json'.
| |
| function(p_init) // If ready, perform the callback function.
| |
| { p_hello.init(p_window, p_init); } // Initialize the corresponding hello section.
| |
| );
| |
| | |
| p_wk.ajax.
| |
| getJSON
| |
| ( 'json/hello_en.json', // Load the JSON file 'json/hello_en.json'.
| |
| function(p_init) // If ready, perform the callback function.
| |
| { p_hello.init(p_window, p_init); } // Initialize the corresponding hello section.
| |
| );
| |
| }(window, wk, hello));
| |
| </source>
| |
| | |
| Diese Verdopplung von Code würde allerdings gegen das Programmierprinzip „[[Don't repeat yourself]]“ (DRY) verstoßen.
| |
| | |
| ==App5==
| |
| | |
| Im Prinzip kann die in App4 vorgestellte Lösung zur Modularisierung einer Web-Anwendung
| |
| gut eingesetzt werden. Allerdings hat sie immer noch zwei Schwächen:
| |
| | |
| * Der Rahmencode, der in jeder Datei der Anwendung verwendet werden muss, ist lästig. (Das ist ein kleineres Problem.)
| |
| * Es gibt immer noch die globale Variable „<code>hello</code>“. Wenn man viele Bibliotheken verwendet, die auf diese Weise realisiert werden, gibt es wieder viele globale Variable. Und damit steigt das Risiko von Namenskollisionen.
| |
| | |
| Schöner wäre es, wenn JavaScript die Modularisierung von sich aus unterstützen würde. Das ist tatsächlich geplant. In EcmaScript 6
| |
| gibt es zu diesem Zweck die Befehle „<code>[https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/import import]</code>“
| |
| und „<code>[https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/export export]</code>“.
| |
| Leider unterstützen die meisten Browser diese Befehle noch nicht.
| |
| | |
| Solange dies der Fall ist, kann man auf JavaScript-Bibliotheken zurückgreifen, die dieselbe Aufgabe (wenn auch weniger elegant) erfüllen.
| |
| Hier könnte die Bibliothek „<code>RequireJS</code>“ zum Einsatz (http://requirejs.org/). An der Struktur der Anwendung ändert sich nichts.
| |
| Nur die Art, wie Module definiert und geladen werden, wird angepasst.
| |
| | |
| Erstellen Sie in Ihrem WebStorm-Projekt letztmalig Kopien Ihre Anwendungsdateien:
| |
| | |
| * <code>index4.html</code> → <code>index5.html</code>
| |
| * <code>js/main4.js</code> → <code>js/main5.js</code>
| |
| * <code>js/app4/greet.js</code> → <code>js/app5/greet.js</code>
| |
| * <code>js/app4/hello.js</code> → <code>js/app5/init.js</code>
| |
| | |
| In der Datei „<code>index5.html</code>“ müssen Sie wiederum dafür sorgen, dass
| |
| die neuen JavaScript-Dateien anstelle der alten geladen werden.
| |
| Und passen Sie auch wieder den Titel im HTML-Header an.
| |
| | |
| Als nächstes müssen Sie, um <code>RequireJS</code> nutzen zu können, den Ordner „<code>web/js/lib/require</code>“
| |
| erstellen und folgende drei Dateien (einschließlich des Inhalts, der sich hinter den drei Links verbirgt) dort speichern:
| |
| * <code>[http://requirejs.org/docs/release/2.3.2/comments/require.js require.js]</code>
| |
| * <code>[https://raw.githubusercontent.com/millermedeiros/requirejs-plugins/master/lib/text.js text.js]</code>
| |
| * <code>[https://raw.githubusercontent.com/millermedeiros/requirejs-plugins/master/src/json.js json.js]</code>
| |
| | |
| ===Modularisierung mit RequireJS===
| |
| | |
| Wenn man konsequent RequireJS zur Modularisierung seine Anwendung
| |
| einsetzt, gibt es neben den globalen Objekten des Browsers (<code>window</code>,
| |
| <code>document</code> etc.) nur noch das globales Objekt „<code>require</code>“ sowie die globale
| |
| Funktion „<code>define</code>“ zum Erstellen neuer Module.
| |
| | |
| Es wird auch nur noch eine einzige JavaScript-Datei statisch geladen:
| |
| „<code>require.js</code>“. Alle anderen Dateien werden dynamisch geladen.
| |
| Die in diesen Dateien enthaltenen Module werden mittels
| |
| Callback-Funktionen an andere Module übergeben. Auf diese
| |
| Weise ist sicher gestellt, dass man keine anderen globalen
| |
| Variablen und Funktionen mehr benötigt.
| |
| | |
| RequireJS-Moduldefinitionen sollten gemäß der AMD-Spezifikation
| |
| (AMD = [[WikipediaEN:Asynchronous_module_definition|Asynchronous Module Definition]]) erfolgen. RequireJS
| |
| kann allerdings auch Bibliotheken wie {{zB}} „<code>[https://jquery.com/ jQuery]</code>“ laden, die nicht
| |
| gemäß dieser Spezifikation erstellt wurden.
| |
| | |
| Für RequireJS gibt es einige Plugins. Eines davon ermöglicht
| |
| das Lesen von JSON-Dateien („<code>json.js</code>“). Dieses Plugin wird verwendet,
| |
| um die Dateien „<code>json/hello_xx.json</code>“ einzulesen.
| |
| | |
| Wenn man mit Hilfe von „<code>define</code>“ ein neues (AMD-)Modul definiert,
| |
| gibt man {{iAllg}} eine ganze Liste von Modulen an, auf denen das neue Modul basiert.
| |
| Beispielsweise benötigt das Modul „<code>hello</code>“ (welches in der Datei „<code>js/app5/hello.js</code>“ definiert wird)
| |
| das Modul „<code>greet</code>“ (welches in der Datei „<code>js/app5/greet.js</code>“ definiert wird).
| |
| | |
| Die Moduldefinition von „<code>hello</code>“ sieht in diesem Fall folgendermaßen aus:
| |
| | |
| <source lang="javascript">
| |
| define
| |
| ( ['app/greet'],
| |
| function (Greet)
| |
| {
| |
| return ...
| |
| }
| |
| );
| |
| </source>
| |
| | |
| Die Funktion „<code>define</code>“ lädt zunächst alle Module, die in der Modulliste aufgeführt werden, asynchron (und damit meist auch parallel).
| |
| Allerdings wird jedes Modul nur einmal geladen. Das heißt, wenn zwei Module dasselbe Modul benötigen, wird dieses Modul trotzdem
| |
| nur einmal geladen. Sobald alle Module geladen sind, wird die im <code>define</code>-Aufruf definierte Callback-Funktion ausgeführt.
| |
| Die Funktion muss für jedes Modul, das in der Modulliste enthalten ist, einen Parameter zur Verfügung stellen,
| |
| in der das jeweilige Modul-Objekt an die Callback-Funktion übergeben wird.
| |
| | |
| Mann könnte im <code>define</code>-Aufruf auch noch einen Modulnamen angeben. Das ist aber {{iAllg}}
| |
| nicht notwendig, da RequireJS defaultmäßig den Dateinamen (ohne Pfad und ohne Endung „<code>js</code>“)
| |
| als Modulnamen verwendet.
| |
| | |
| Wenn man in der Definition eines Moduls auf keine anderen Module zurückgreifen will, kann man auf die Callback-Funktion verzichten.
| |
| Um ein Modul zu schreiben, das lediglich ein Objekt enthält, schreibt man einfach:
| |
| | |
| <source lang="javascript">
| |
| define
| |
| ({
| |
| // Inhalt des Objekts, den das Modul definiert.
| |
| });
| |
| </source>
| |
| | |
| Wenn das Modul lediglich eine Funktion, wie {{zB}} eine Konstruktorfunktion „<code>C</code>“ zur Verfügung stellen soll, schreibt man
| |
| | |
| <source lang="javascript">
| |
| define
| |
| ( function ()
| |
| {
| |
| function C(...) {...}
| |
| | |
| return C;
| |
| }
| |
| );
| |
| </source>
| |
| | |
| Hier gibt es zwar eine Callback-Funktion. Diese wird jedoch sofort ausgeführt, da nicht auf das Laden anderer Module gewartet werden muss.
| |
| | |
| ====Laden von RequireJS====
| |
| | |
| Damit Module auch gefunden werden, muss man RequireJS zunächst konfigurieren.
| |
| Das erfolgt üblicherweise in der Datei „<code>main.js</code>“ (hier: „<code>main5.js</code>“).
| |
| Diese Datei ist auch dafür verantwortlich, das erste Modul zu laden und damit den Ladevorgang für
| |
| alle benötigten Module in Gang zu setzten. Bei uns ist das das Modul „<code>hello</code>“.
| |
| | |
| Allerdings kann man <code>main.js</code> nicht direkt laden, sondern man muss den
| |
| Browser anweisen, zuvor <code>require.js</code> zu laden. Sobald diese Datei vollständig geladen (und übersetzt) wurde,
| |
| lädt <code>require.js</code> von sich aus <code>main.js</code> (bzw <code>main5.js</code>).
| |
| Um dies zu erreichen, fügen Sie folgendes Script-Element als letztes und einziges Script-Element
| |
| in die Datei „<code>index5.html</code>“ ein:
| |
| | |
| <source lang="html5">
| |
| <script data-main="js/main5" src="js/lib/require/require.js"></script>
| |
| </source>
| |
| | |
| Weitere Script-Elemente werden nicht mehr benötigt, das RequireJS Module jeweils dynamisch
| |
| nachlädt, sobald sie benötigt werden.
| |
| | |
| ====Landen von <code>main.js</code>====
| |
| | |
| Nachdem <code>require.js</code> geladen wurde, liest das Programm das Script-Attribut „<code>data-main</code>“
| |
| und erfährt so, dass die Datei „<code>js/main5.js</code>“ für die Initialisierung und den Start der Web-Anwendung zuständig ist (die fehlende Dateiendung
| |
| „<code>.js</code>“ ergänzt RequireJS automatisch).
| |
| | |
| Die Datei „<code>js/main5.js</code>“ konfiguriert zunächst RequireJS:
| |
| | |
| <source lang="javascript">
| |
| requirejs.config
| |
| ({
| |
| baseUrl: 'js', // By default load any modules from directory js
| |
| paths :
| |
| {
| |
| app: 'app5',
| |
| loadjson: 'lib/require/json',
| |
| text: 'lib/require/text',
| |
| json: '../json'
| |
| }
| |
| });
| |
| </source>
| |
| | |
| Hier wird festgelegt, dass sich die JavaScript-Dateien im Ordner „<code>js</code>“ befinden. Das Attribut „<code>path</code>“
| |
| legt ein paar spezielle Unterordner fest. Modulnamen, die mit „<code>app/</code>“ beginnen (wie {{zB}} „<code>app/greet</code>“),
| |
| werden im Ordner „<code>js/app5/</code>“ gesucht. Für die Module „<code>json</code>“ (ein RequireJS-Plugin zum Laden von
| |
| JSON-Dateien) und „<code>text</code>“ (welches von
| |
| „<code>json</code>“ benötigt wird) wurde der Pfad direkt angegeben. Die JSON-Dateien finden sich im Ordner
| |
| „<code>js/../json</code>“, {{dh}} im Ordner „<code>init</code>“.
| |
| | |
| Nun kann die eigentliche Anwendung gestartet werden:
| |
| | |
| <source lang="javascript">
| |
| requirejs
| |
| ( ['loadjson!json/hello_de.json', 'loadjson!json/hello_en.json', 'app/init'],
| |
| function(helloDeJSON, helloEnJSON, init)
| |
| {
| |
| init(window, helloDeJSON);
| |
| init(window, helloEnJSON);
| |
| }
| |
| );
| |
| </source>
| |
| | |
| Die Anwendung benötigt zwei JSON-Dateien (das dafür notwendige JSON-Plugin wird automatisch geladen)
| |
| und das Modul „<code>app/hello</code>“ (welches sich gemäß Konfiguration in der Datei „<code>js/app5/hello.js</code>“ befindet).
| |
| | |
| Sobald die drei Dateien vollständig geladen wurden, wird die Callback-Funktion aufgerufen.
| |
| Dieser werden die beiden JSON-Objekte sowie das Hello-Initialisierungs-Modul-Objekt übergeben.
| |
| Die Callback-Funktion kann somit problemlos die beiden Hello-World-Anwendungen mittels „<code>p_hello.init</code>“ initialisieren und starten.
| |
| | |
| ====Das Modul „<code>hello</code>“====
| |
| | |
| Der Code des Moduls „<code>init</code>“ unterscheidet sich nicht nur hinsichtlich des Modulrahmens, sondern auch noch in
| |
| eine zweiten in einem wesentlichen Aspekt vom
| |
| Code des Moduls „<code>hello</code>“ der <code>App4</code>: In der App4 gibt es ein globales Objekt namens „<code>hello</code>“, das die gesamte Web-Anwendung enthält. Insbesondere gibt es die Methode „<code>hello.init</code>“ zum Initialisieren der App. Im Gegensatz dazu gibt es in der App5 überhaupt keine anwendungsspezifischen globalen Objekte. Daher ist es nicht notwendig die Funktion „<code>init</code>“ als Methode in irgendein Objekt einzufügen.
| |
| Dementsprechend wurde auch der Modulname von „<code>hello</code>“ in „<code>init</code>“ geändert.
| |
| | |
| <source lang="javascript">
| |
| define
| |
| ( ['app/greet'],
| |
| function (Greet) // The module 'app/greet' contains the constructor 'Greet'.
| |
| {
| |
| /**
| |
| * Initializes the HTML sections of the app that are
| |
| * stated in <code>p_init</code>.
| |
| *
| |
| * @param p_window The browser window that contains the HTML document to be initialized.
| |
| * @param p_init The initialization info for the form section and welcome section.
| |
| */
| |
| function init(p_window, p_init)
| |
| {
| |
| var l_document = p_window.document,
| |
| l_init_elements = p_init.HTMLElements,
| |
| l_init_text = p_init.text,
| |
| l_greet = new Greet(l_document, p_init);
| |
| | |
| l_document.getElementById(l_init_elements.headingForm)
| |
| .innerHTML = l_init_text.title;
| |
| l_document.getElementById(l_init_elements.inputNameLabel)
| |
| .innerHTML = l_init_text.query;
| |
| l_document.getElementById(l_init_elements.buttonReset)
| |
| .innerHTML = l_init_text.reset;
| |
| l_document.getElementById(l_init_elements.buttonSubmit)
| |
| .innerHTML = l_init_text.submit;
| |
| l_document.getElementById(l_init_elements.buttonSubmit)
| |
| .addEventListener('click', l_greet.sayHello);
| |
| p_window.addEventListener('keydown', l_greet.sayHelloOnEnter);
| |
| }
| |
| | |
| return init;
| |
| }
| |
| );
| |
| </source>
| |
| | |
| Sobald das Modul „<code>app/greet</code>“ ({{dh}} die Datei „<code>js/app5/greet.js</code>“) vollständig geladen wurde,
| |
| wird die von diesem Modul bereitgestellte Konstruktorfunktion „<code>Greet</code>“ der Callback-Funktion
| |
| im Parameter „<code>Greet</code>“ übergeben. Die Callback-Funktion erstellt daraufhin ein Objekt, welches eine einzige
| |
| Methode enthält: „<code>init</code>“. Die Aufgabe dieser Methode ist es, die Web-Anwendung zu initialiseren, sobald sie
| |
| (von „<code>main5.js</code>“) aufgerufen wird. Sie erstellt insbesondere mit Hilfe der Konstruktorfunktion „<code>Greet</code>“
| |
| ein Objekt „<code>l_greet</code>“, welche speziell angepasste Methoden „<code>sayHello</code>“ und „<code>sayHelloOnEnter</code>“
| |
| zur Verfügung stellt.
| |
| | |
| ====Das Modul „<code>greet</code>“====
| |
| | |
| Das Modul „<code>greet</code>“ benötigt überhaupt keine anderen Module.
| |
| Daher ist die Moduldefinition noch etwas einfacher (die Liste mit benötigten Modulen entfällt ganz). Es ändert sich gegenüber App4 ebenfalls nur der Modulrahmen.
| |
| | |
| <source lang="javascript">
| |
| define
| |
| (function ()
| |
| {"use strict";
| |
| | |
| /**
| |
| * @class
| |
| * @classdesc Contains methods to say hello to the user of the web app.
| |
| *
| |
| * @param p_document A DOM object (usually the global object <code>document</code>)
| |
| * @param p_init A JSON object containing all init data.
| |
| */
| |
| function Greet(p_document, p_init)
| |
| {
| |
| var l_this = this; // this-hack
| |
| | |
| /**
| |
| * Welcomes the user of the web app1 by displaying a welcome message
| |
| * that includes his name. The name is fetched from a text input field.
| |
| */
| |
| l_this.sayHello =
| |
| function ()
| |
| {
| |
| var l_init_elements = p_init.HTMLElements,
| |
| l_init_text = p_init.text,
| |
| l_init_hidden = p_init.css.hidden,
| |
| l_name = p_document.getElementById(l_init_elements.inputName).value;
| |
| | |
| p_document.getElementById(l_init_elements.welcome)
| |
| .innerHTML = l_init_text.welcome;
| |
| p_document.getElementById(l_init_elements.hello)
| |
| .innerHTML = l_init_text.hello.replace('$1', l_name);
| |
| | |
| p_document.getElementById(l_init_elements.sectionHello)
| |
| .classList.remove(l_init_hidden);
| |
| p_document.getElementById(l_init_elements.sectionForm)
| |
| .classList.add(l_init_hidden);
| |
| };
| |
| | |
| /**
| |
| * An keyboard event observer. It tests whether the enter key has been pressed.
| |
| * If so, the greet method is activated. Default reactions of the browser are
| |
| * disabled.
| |
| *
| |
| * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
| |
| * (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
| |
| */
| |
| l_this.sayHelloOnEnter =
| |
| function (p_event)
| |
| {
| |
| if ((p_event.code === 'Enter' || p_event.keyCode === 13) &&
| |
| p_document.activeElement ===
| |
| p_document.getElementById(p_init.HTMLElements.inputName)
| |
| )
| |
| {
| |
| p_event.preventDefault();
| |
| p_event.stopPropagation();
| |
| l_this.sayHello(); // NOT this.sayHello();
| |
| }
| |
| };
| |
| }
| |
| | |
| return Greet; // Return the constructor defined above.
| |
| });
| |
| </source>
| |
|
| |
|
| | {{TBD}} |
| ==Quellen== | | ==Quellen== |
| <references/> | | <references/> |