HTML5-Tutorium: JavaScript: Hello World 03

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Die druckbare Version wird nicht mehr unterstützt und kann Darstellungsfehler aufweisen. Bitte aktualisieren Sie Ihre Browser-Lesezeichen und verwenden Sie stattdessen die Standard-Druckfunktion des Browsers.

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 Musterlösung: index.html, index2.html (SVN-Repository)

Anwendungsfälle (Use Cases)

Gegenüber dem zweiten Teil des Tutoriums ändern sich die die Anwendungsfälle deutlich. Es soll nun nicht mehr die ganze Welt begrüßt werden, sondern der Benutzer, der die Web-Anwendung gestartet hat. Dazu muss er zunächst nach seinem Namen gefragt werden. Anschließend wird das HTML-Dokument mit Hilfe von JavaScript umgestaltet: Das Eingabeformular wird ausgeblendet und stattdessen wird die Begrüßungsformel angezeigt.

Erstellen eines neuen Projektes

Erstellen Sie ein neues Projekt „HelloWorld03“ und legen Sie dieses in Ihrem Repository ab. Kopieren Sie anschließend die Dateien „index.html“ und „main.css“ aus dem zweiten Teil des Tutoriums, passen Sie den Titel in der HTML-Datei an und committen Sie abermals.

Single-Page-Web-Anwendung

Die Anwendung wird als Single-Page-Web-Anwendung (Onepager) realisiert. Das HTML-Dokument „index.html“ enthält zwei Abschnitte (sections), eines mit einem Formular zur Eingabe des Namens und ein zweites zur Begrüßung des Benutzers, nachdem er seinen Namen eingegeben hat.

Für jede Seite fügen wir in „index.html“ einen HTML-Abschnitt „<section id="..."> ...</section>“ ein. Das Section-Element gruppiert einen logischen Abschnitt oder ein Kapitel eines HTML-Dokuments. Es soll laut Spezifikation eine Überschrift enthalten, die das Thema des Abschnitts beschreibt. Man hätte auch ein Div-Element „<div id="..."> ...</div>“ anstelle des Section-Elements verwenden können. Ein Div-Element hat keinerlei Semantik (Bedeutung), es dient lediglich der Strukturierung einer HTML-Datei. Die (spezifikationsgemäße) Verwendung von Section- und Article-Elementen, die in HTML5 eingeführt wurden, hat den Vorteil, dass Browser deren Bedeutung kennen und daher geeignete Defaultstyles verwenden können, falls der Entwickler keine entsprechenden Styles angibt. Ein Beispiel sind Browser für Blinde. Derartige Browser lesen die Inhalte entweder vor oder geben sie textuell über eine sogenannte Braillezeile aus. Für derartige Browser werden meist keine CSS-Layout-Vorgaben gemacht (obwohl dies problemlos möglich wäre) und daher ist es wichtig, Strukturelementen eine Semantik zuzuordnen. Ein Blindenbrowser könnte beim Vorlesen eines Dokuments beispielsweise stets das Wort „Kapitel“ vor eine H1-Überschrift einfügen, die als erstes Element innerhalb eines Section-Elements steht.

<body>
  <section id="section_form">
    <h1>Hello, Stranger!</h1>
  </section>
  <section id="section_hello" class="hidden">
    <h1 id="heading_hello">Hello, ...!</h1>
    <p>Welcome to Multimedia Programming!</p>
  </section>
</body>

Wenn Sie diese Datei ausführen, stellen Sie fest, dass sich nicht viel geändert hat. Anstelle einer Überschrift werden nun zwei angezeigt.

Allerdings wurden drei id-Attribute in das Dokument eingefügt: „id="section_form"“, „id="section_hello"“ und „id="heading_hello"“. Jedes öffnende HTML-Element darf mit einem derartigen Attribut versehen werden. Allerdings darf es in einer HTML-Datei keine zwei id-Attribute mit demselben Namen geben.

Die Vergabe von id-Attributen bringt zwei Vorteile mit sich: Zum einen können so bestimmte HTML-Element gezielt mittels CSS gestylt werden, und zum anderen können bestimmte HTML-Element gezielt mittels JavaScript modifiziert werden.

Im Browser soll zunächst nur der erste Abschnitt mit dem Formular angezeigt werden (id="section_form"). Um das zu erreichen, fügen Sie zunächst folgenden Code in die CSS-Datei ein:

.hidden
{
  display: none;
}

Fügen Sie nun in das öffnende Tag des Section-Elements mit dem Identifikator „section_hello“ das Attribut-Wert-Paar „class="hidden"“ ein. Ein class-Attribut darf im Gegensatz zu einem id-Attribut beliebig vielen Elementen zugeordnet werden, ohne dass sich der zugehörige Wert unterscheiden muss. id-Attribute werden verwendet, um HTML-Elemente eindeutig zu kennzeichnen, class-Attribute werden verwendet, um diversen HTML-Elementen gleiche CSS-Eigenschaften zuzuordnen. In einem Onepager sind üblicherweise die meisten Seiten unsichtbar. Daher ist hier ein class-Attribut angebracht.

In der CSS-Datei werden Identifikator-Attributewerte mit einer Raute „#“ gekennzeichnet (z. B. #section_form) und Klassen-Attributwerte mit einem Punkt „.“ (z. B. .hidden):

Führen Sie diese Änderungen durch und testen Sie die Web-Anwendung erneut. Nun sollte nur noch die Überschrift der Formular-Section zu sehen sein.

Definition eines HTML5-Formulars

Als nächstes muss das Formular erstellt werden, mittels dem der Name des Besuchers erfragt wird. Es soll folgende Elemente haben:

  • ein Label, das beschreibt, welche Information vom Benutzer eingegeben werden soll
  • ein Text-Feld, in das der Benutzer seinen Namen eingeben kann
  • einen Reset-Button, um den Inhalt des Namensfeldes zu löschen
  • einen Submit-Button, um dem Browser mitzuteilen, dass der Name vollständig eingegeben wurde

Der zugehörige HTML-Code sieht folgendermaßen aus:

<form>
  <div>
    <label for="input_name">What's your name?</label>
    <input id="input_name"/>
  </div>
  <div>
    <button                    type="reset">Reset</button>
    <button id="button_submit" type="button">Send</button>
  </div>
</form>

Er wird in die erste Section hinter die zugehörige Überschrift eingefügt.

In diesem Formular („<form> ... </form>“) sind die vier zuvor genannten Elemente enthalten. Je zwei davon sind mittels eines Div-Elements zu einer Gruppe zusammengefasst. Damit ist die Struktur des Formulars vorgegeben: Jedes Div-Element steht in einer eigenen Zeile, die darin enthaltenen Elemente stehen jeweils hintereinander in einer Zeile. Mittels CSS können die Elemente nun besser angeordnet werden: mehr Abstand von der Überschrift, mehr vertikaler Abstand zwischen den Elementen, einheitliche Breite der Elemente etc. Wie sagt Captain Picard ganz richtig: „Machen Sie es so.“

Beachten Sie, dass zwei weitere id-Attribute eingeführt wurden: „id="input_name"“ und „id="button_submit"“. Diese werden für den Zugriff von JavaScript aus auf das Dokument benötigt. Über den Identifikator „input_name“ kann auf den Inhalt des Textfeldes, d. h. auf den Namen, den der Benutzer eingegeben hat, zugegriffen werden. Außerdem kann mit Hilfe dieses Identifikators das Label-Element über das Attribut „for“ an das Textfeld gekoppelt werden. Damit weiß der Browser, auf welches Input-Element sich das Label-Element bezieht. Auch dies ist wieder besonders wichtig, wenn der Zusammenhang nicht optisch (per CSS) hergestellt werden kann bzw. hergestellt wird. Damit lassen sich aber auch Checkboxes realisieren, die durch einen Klick auf den zugehörigen Label aktiviert und wieder deaktiviert werden können.

Der Identifikator für den Submit-Button wird benötigt, um in JavaScript die Klick-Aktion des Benutzers abfangen zu können. Normalerweise im Form-Element in einem Attribut namens „action“ ein URI angegeben. Dieser verweist auf eine Serveradresse, an den die vom Benutzer erfassten Daten bei einem Klick auf einen echten Submit-Button (type="submit") übermittelt werden. In dieser Web-Anwendung werden keine Daten an einen Server übermittelt. Alle Benutzereingaben werden direkt im Browser (per JavaScript) verarbeitet. Deshalb wird das Action-Attribut nicht benötigt und als Submit-Button wird ein einfacher Button (type="button") eingesetzt.

Wenn alles zu Ihrer Zufriedenheit ausgefallen ist, sollten Sie den aktuellen Stand in Ihr Repository einspielen.

Interaktion mittels JavaScript

Erzeugen Sie eine leere JavaScript-Datei:

  • Rechtsklick auf die Projektwurzel „HelloWorld03“ im Dateibrowser → NewJavaScript file
  • Name: „main.js“ → OK

Fügen Sie folgenden JavaScript-Code in diese Datei ein

/**
 * Welcomes the user of the web app by displaying a welcome message
 * that includes his name. The name is fetched from a text input field.
 */
function sayHello()
{
  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');
}

/**
 * Initializes the web app.
 * Is to be called when the web app has been loaded completely.
 */
function init()
{
  document.getElementById('button_submit')
          .addEventListener('click', sayHello);
}

// So call init, when you are ready with loading.
window.addEventListener('load', init);

Im diesem Stückchen JavaScript-Code sind mehrere interessante Dinge zu entdecken.

  1. Alle Befehle innerhalb einer JavaScript-Datei werden der Reihe nach abgearbeitet, sobald sie geladen wird. In der Datei „main.js“ gibt es insgesamt drei Befehle: Zwei Funktionsdefinitionen und eine Wertzuweisung.
  2. Achtung: Die beiden Funktionen „sayHello“ und „init“ werden nur definiert, aber nicht sofort ausgeführt! Bei diesen Funktionen handelt es sich um sogenannte Observer-Funktionen. Derartige Funktionen werden immer dann aktiviert, wenn ein bestimmtes Ereignis eintritt. Mittels nachfolgender JavaScript-Befehle wird festgelegt, dass die Funktion „sayHello“ ausgeführt wird, sobald der Submit-Button gedrückt wird, und die Init-Funktion ausgeführt wird, sobald die Web-Anwendung vollständig geladen ist.
  3. Beide Funktionen machen regen Gebrauch vom JavaScript-Objekt „document“, das das Document Object Model, d. h. die interne Darstellung des HTML-Dokuments als sogenannten DOM-Baum beinhaltet (siehe MDN-API-Dokumentation). Mit Hilfe der Methode „getElementById“ kann man besonders elegant auf bestimmte Elemente des DOM-Baus zugreifen, sofern man zuvor im HTML-Dokument für die entsprechenden Elemente id-Attribute definiert hat (was wir gemacht haben).
  4. Für jedes Element des DOM-Baums gibt es diverse elementspezifische Attribute. So kann man mittels „innerHTML“ auf den HTML-Code eines Elements wie „p“ (Paragraph), „h1“ (Hauptüberschrift), „h2“ (Überschrift der 2. Stufe) etc. lesend und schreibend zugreifen. Das Attribut „value“ ermöglicht einen lesenden und schreibenden Zugriff auf den Inhalt von Formularfeldern. Über das Attribut „classList“ hat man Zugriff auf class-Attribute, die einem HTML-Element zugeordnet sind. Man kann jedem Element neue class-Attribut-Werte zuordnen und bestehende Werte entfernen. Wir nutzen das aus, indem wir die Formularabschnitt „section_form“ unsichtbar und dafür den Begrüßungsabschnitt „section_hello“ sichtbar machen, sobald die Funktion „sayHello“ ausgeführt wird.
  5. Der dritte Befehl „window.addEventListener('load', init);“ übergibt die Funktioninit“ (und nicht etwa den Funktionsaufruf init()) dem JavaScript-Objekt „window“ mit der Bitte, diese Methode auszuführen, sobald das load-Ereignis eintritt. Das hat zur Folge, dass die Funktion init nicht sofort aufgerufen wird, sondern erst – durch das Observer-Objekt „window“ – sobald das Ereignis „der Inhalt des aktuelle Browserfensters wurde vollständig geladen“ eintritt (siehe MDN-API-Dokumentation).
  6. Auch für den Submitbutton wird ein Eventlistener registriert. Die Funktion „sayHello“ soll ausgeführt werden, sobald für diesen Button das click-Ereignis eintritt. Diese Zuordnung kann allerdings erst passieren, wenn sich der Submit-Button auch im DOM-Baum befindet. Das ist jedoch sicher erst der Fall, wenn das gesamte Dokument geladen wurde. Daher wird diese Zuordnung nicht sofort, sondern in der Init-Funktion durchgeführt. (Das window-Objekt existiert dagegen von Anfang an, d. h. auch wenn der DOM-Baum noch nicht geladen wurde.)
  7. Alle Funktionen wurden mit Kommentaren im JSDoc-Format versehen.[1] Machen Sie das auch immer. Andere Entwickler und auch Sie selbst werden das schätzen, wenn sie bzw. Sie den Code zu einem späteren Zeitpunkt lesen und verstehen müssen. Das JSDoc-Format bring den Vorteil mit sich, dass Sie automatisch eine Schnittstellen-Dokumentation für Ihre Anwendung erstellen können

Wenn Sie jetzt Ihre Anwendung testen, hat sich nichts geändert. Der Grund ist wie bei der Datei „main.css“ auch, dass die Datei „index.html“ nichts davon weiß, das ihr dieser JavaScript-Code zugeordnet ist. Das muss ihr erst bekannt gegeben werden. Fügen Sie folgende Zeile in der Head-Bereich der index.html ein:

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

Nun wird nicht nur die CSS-Datei, sondern auch die JavaScript-Datei geladen, bevor der Body-Bereich der index.html eingelesen wird. Beachten Sie, dass folgender Code zwar XML-konform, aber in Standard-HTML5-Dokumenten nicht erlaubt ist:

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

Die tieferen Gründe dafür werden sehr schön auf Stack Overflow beschrieben.

Testen Sie Ihre Anwendung nochmals. Sie sollten jetzt Ihren Namen in das Textfeld eingeben und dann „Say hello“ klicken können. Das Ergebnis sollte sein, dass Sie von Ihrer Anwendung persönlich begrüßt werden. Wenn alles funktioniert, sollten Sie wieder committen.

Barrierefreiheit

Gemäß den „Richtlinien für barrierefreie Webinhalte“[2], Abschnitt 2.1 soll eine Web-Entwickler dafür Sorge tragen, „dass alle Funktionalitäten per Tastatur zugänglich sind“. Das ist bei unserer Web-Anwendung nicht in voller Schönheit der Fall. Der Cursor befindet sich wegen des Attributes „autofocus="autofocus"“ im Text-Input-Feld des HTML-Dokument bei Start der Anwendung automatisch an der richtigen Position. Man muss also den Cursor nicht erst mit Hilfe der Maus platzieren. Auch die Weiterschaltung mittels Tab-Taste funktioniert. Man kann zunächst seinen Namen eingeben, dann zweimal die Tab-Taste betätigen, um den Submit-Button zu aktivieren und dann Return drücken, um ein Klick-Event für diesen Button auszulösen.

Schöner wäre es jedoch, wenn man nach Eingabe des Namens gleich die Returntaste nutzen könnte, um die Eingabe abschließen zu können. Zurzeit passiert nichts, wenn Sie Ihren Namen eingeben und dann die Returntaste betätigen. Versuchen Sie es.

Damit begeben wir uns in die Untiefen der Benutzerinteraktion mit dem Browser...

Um diese Funktionalität zu realisieren, brauchen wir einen weiteren Eventlistener. Die Observer-Methode heiße „sayHelloOnEnter“. Diese muss wieder registriert werden. Die Tastaturereignisse werden vom window-Objekt gemeldet (und nicht etwa vom dokument-Objekt, da das Dokument nichts mit Tastatureingaben zu schaffen hat). Die Registrierung des Listeners erfolgt wie üblich mit Hilfe der addEventListener-Methode

window.addEventListener('keydown', sayHelloOnEnter);

Dieser Befehl kann sowohl innerhalb der init-Funktion, als auch außerhalb stehen (da das window-Objekt von Anfang an existiert). Man sollte es in den Rumpf der init-Funktion einfügen. Die Anzahl der globalen Befehle sollte so gering wie möglich gehalten werden. (Dieser Punkt wird später noch wesentlich genauer behandelt.)

Die (in diesem Teil des Tutoriums noch globale) Funktion „sayHelloOnEnter“ wird folgendermaßen definiert:

/**
 * 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
 * disabled.
 * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
 *   (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
 */
function sayHelloOnEnter(p_event)
{
  if (p_event.code === 'Enter' &&
      document.activeElement === document.getElementById('input_name')  // focus is on 'input_name'
     )
  {
    p_event.preventDefault();
    p_event.stopPropagation();
    sayHello();
  }
}

Vier Dinge fallen bei dieser Definition auf:

  1. Die Funktion hat einen Parameter namens „p_event“ (Parameternamen können beliebig gewählt werden. Ich beginne Parameternamen stets mit einem „p_“, um rein optisch klar zu machen, dass es sich um einen Parameter handelt; siehe Multimedia-Programmierung: Style Guide). In diesem Parameter übermittelt das window-Objekt dem Eventhandler nähere Informationen zum aktuellen Tastaturereignis: welche Taste gedrückt wurde, ob die Schift-Taste dabei gehalten wurde etc. Details finden sich in der ausgezeichneten MDN-JavaScript-Dokumentation.
  2. Im Funktionsrumpf wird getestet, ob die Returntaste gedrückt wurde. Falls das Ergebnis des Test „false“ lautetet, macht sie nichts (da das Zeichen einfach ins Textfeld eingefügt werden soll), anderenfalls ruft sie die Funktion „sayHello“ auf, um die Eingabe abzuschließen.
  3. Die Gleichheit wird mit „===“ anstelle von „==“ getestet. Bei dem ersten Test handelt es sich um einen strikten Gleichheitstest. Der zweite Test liefert dagegen manchmal ziemlich eigenwillige Ereignisse. So wird beispielsweise bei den Tests „0==''“ „0=='0'“ jeweils „true“ als Ereignis ausgegeben. Der strikte Gleichheitstest liefert dagegen das erwartete Ergebnis „false“.
  4. Man muss höllisch aufpassen, wenn man Tastatur-Events abfängt. Wenn der Fokus nicht auf dem Textfeld, sondern auf dem Reset-Button liegt, darf man die Funktion der Enter-Taste nicht ändern. Der Reset-Button kann mittels der Tab-Taste selektiert und mittels Enter-Taste aktiviert werden (barrierefreie Formulareingabe ohne Maus). Daher darf die Bedeutung der Enter-Taste nur verändert werden, wenn der Fokus auf dem Texteingabefeld liegt.


So weit so gut. Aber wenn man sayHelloOnEnter so definiert, dass sie bei einem Druck der Returntaste lediglich die Funktion „sayHello“ aufruft, stellt man schnell fest, dass ein Betätigen der Returntaste nicht wie gewünscht funktioniert. Das Problem ist, dass der Browser Tastatur-Ereignisse bereits anderweitig verarbeitet. Je nach Tastendruck werden Textfelder befüllt, Formularelemente aktiviert oder irgendwelche Spezialfunktionen ausgeführt. Man muss den JavaScript-Interpreter klar machen, dass er dies im Falle der Returntaste nicht machen soll. Das ist Aufgabe der beiden Befehle „p_event.preventDefault();“ und „p_event.stopPropagation();“. Der erste verlangt, dass bei Drücken dieser Taste keine Default-Aktionen ausgeführt werden sollen und der zweite bewirkt, dass das Ereignis nicht auch noch von irgendwelchen anderen Eventhandlern behandelt wird.

Damit sollte alle funktionieren wie geplant. Denkste! In (zumindest manchen) Browsern (zumindest mancher) mobiler Devices bewirkt ein Betätigen der Eingabetaste im Tastaturfeld ein Neuladen der Anwendung und nicht eine Begrüßung. Hier tut sich ein prinzipielles Problemgebiet auf. In mobilen Browsern sind viele Standardaktionen fest in bestimmte Browser integriert. Dies gilt insbesondere für die Behandlung von Touchevents. Wenn man ein Browserspiel programmiert, das mit Touchgesten bedient wird, bemerkt man sehr oft, dass das Spiel mit bestimmten Geräten und/oder Browsern nicht gespielt werden kann, da irgendwelche Default-Eventhandler Gesten anders interpretieren als geplant. Hier ist Testen, Googeln, Testen, Googeln und nochmals Testen angesagt. Oft hilft es nur noch, eine native App zu erstellen, die nichts weiter macht, als JavaScript-Code in einem Browser auszuführen. In derartigen Apps sind die Defaultaktionen des Browsers deaktiviert (oder können zumindest leicht deaktiviert werden).

Die Lösung des Problems ist hier allerdings einfacher. Man muss

  if (p_event.code === 'Enter'  || p_event.keyCode === 13)

anstelle von

  if (p_event.code === 'Enter' )

schreiben. Die Verwendung von „p_event.keyCode“ gilt eigentlich als veraltet. Sie ist als “deprecated” („veraltet“, „überholt“, „missbilligt“) gekennzeichnet. Man soll stattdessen „p_event.code“ verwenden. Dieses Attribut wird allerdings von vielen aktuellen Browsern noch nicht richtig interpretiert... (Hier tauchen wieder die Browser-Inkompatibilitäten auf, die man mit dem Erscheinen von HTML5 und EcmaScript 5 eigentlich schon als überwunden glaubte.)

Fortsetzung des Tutoriums

Sie sollten nun Teil 4 des Tutoriums bearbeiten.

Vergessen Sie nicht, vorher Ihr aktuelles Projekt im Repository zu speichern.

Quellen

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