HTML5-Tutorium: JavaScript: Hello World 04

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg

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: index1.html, index2.html, index3.html, index3b.html, index4.html, index5.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.

Bitte beachten Sie, dass ich einige HTML-Elemente und Funktionen des dritten Tutoriums umbenannt habe. Das heißt, Ihre Lösungen des dritten Teil des Tutoriums sind vermutlich nicht mehr ganz konsistent mit der [1]. Daher ist es evtl. sinnvoll, wenn Sie den vierten Teil aus Basis der Musterlösung des dritten Teils bearbeiten.

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 speichern Sie die CSS-Dateien Ihrer Web-Anwendung.
  • web/js
  • web/js/app: Hier speichern Sie die von Ihnen geschriebenen JavaScript-Dateien Ihrer Web-Anwendung.
  • web/js/lib: Hier speichern Sie die JavaScript-Bibliotheken, die andere JavaScript-Entwickler erstellt haben und die Sie in Ihrer Anwendung einsetzen..

Da in diesem Teil des Tutoriums fünf Web-Anwendungen mit jeweils unterschiedlichem Modularisierungsgrad erstellt werden, werden hier 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/app4: 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.

App 1

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 diese 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 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

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

   ...
  <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 „<code<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. 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.

App 2

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 KonstruktorfunktionGreet“ 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. Ein Konstruktor verhält sich wie eine Prozedur und nicht wie eine Funktion. Sie erstellt – als Seiteneffekt – ein neues Objekt und initialisiert es. Allerdings wird diese Objekt nirgendwo gespeichert, sondern – automatisch – als Ergebnis des Befehls new Greet() zurückgeliefert. Anschließend wird es üblicherweise irgendwo gespeichert, in einer Variablen, in einem Array, in einem Objekt oder in einem Funktionsparameter.

Im Funktionsrumpf selbst kann über die Spezialvariable „this“ auf das neu erstellte Objekt zugegriffen werden. Dem Objekt können mittels „this.sayHello = ...“, „this.sayHelloOnEnter = ...“ etc. Attribute zugewiesen werden. Sie speichern in dem neu erstellten Objekt wieder dieselben Methoden, wie im Objekt „greet“ der Anwendung „app1“.

Beachten Sie, dass in der Definition 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 immer den Aufrufer eine Methode, und nicht das Objekt, das die Methode beinhaltet. Aber im Aufrufer der Methode ist „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, auf die die Methode „sayHelloOnEnter“ auch dann Zugriff hat, wenn Sie von einem anderen Objekt aufgerufen wird.

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 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.

App 3

Erstellen Sie in Ihrem WebStorm-Projekt 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.

Modularisierung

Eine Anwendung, wie z. B. ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgabe. 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 enzelnen Komponenten an neue Endgeräte oder Betreibssysteme angepasst werden. Beispielsweise muss eine Tastatursteuerung für eine Mobilversion des Spiels durch eine Touch- oder Gestensteuerung ersett werden.

Das alles ist nicht machbar, wenn man die Anwendung selbst nicht 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 Namenskolilsionen kommt. Daher gilt der Grundsatz: Verwende so wenig globale größen wie möglich.

TO BE DONE

Quellen

  1. Google (Web): Make the Web Faster; Organisation: Google Developers; https://developers.google.com/speed/; Quellengüte: 3 (Web), Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery
  2. Google (Web): Make the Web Faster; Organisation: Google Developers; https://developers.google.com/speed/; Quellengüte: 3 (Web), Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/BlockingJS
  1. Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)