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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
(71 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
{{In Bearbeitung}}
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}
'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld03/index.html <code>index.html</code>]
 
([https://glossar.hs-augsburg.de/beispiel/tutorium/es5/hello_world/WK_HelloWorld03/ SVN-Repository])
'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/2018/helloworld/WK_HelloWorld03/web/index.html <code>index.html</code>],
[https://glossar.hs-augsburg.de/beispiel/tutorium/2018/helloworld/WK_HelloWorld03/web/index2.html <code>index2.html</code>]
([{{Git-Server}}/kowa/WK_HelloWorld03 Git-Repository])
==Anwendungsfälle (Use Cases)==
==Anwendungsfälle (Use Cases)==
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_03|zweiten Teil des Tutoriums]]
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_02|zweiten Teil des Tutoriums]]
ändern sich die die Anwendungsfälle deutlich. Es soll nun nicht mehr die ganze
ä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.
Welt begrüßt werden, sondern der Benutzer, der die Web-Anwendung gestartet hat.
Dazu muss er zunächst nach seinem Namen gefragt werden.
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
Anschließend wird das HTML-Dokumnt mit Hilfe von [[JavaScript]] umgestaltet: Das Eingabeformular wird ausgeblendet
und stattdessen wird die Begrüßungsformel angezeigt.
und stattdessen wird die Begrüßungsformel angezeigt.


==Erstellen eines neuen Projektes==
==Erstellen eines neuen Projektes==


Erstellen Sie ein neues Projekt  <code>HelloWorld03</code>“ und legen Sie dieses in Ihrem Repository ab.
Erstellen Sie ein neues Projekt  <code>HelloWorld03</code>.
Kopieren Sie anschließend die Dateien <code>HelloWorld02</code>und <code>HelloWorld02</code>
 
aus dem zweiten Teil des Tutoriums, passen Sie den Titel in der HTML-Datei an und committen Sie abermals.
Kopieren Sie anschließend die Projektdateien (insbesondere den Ordner <code>web</code> mit den Dateien <code>index.html</code> und <code>main.css</code> sowie die Datei <code>.gitignore</code>) aus dem zweiten Teil des Tutoriums und passen Sie den Titel in der HTML-Datei an.
 
Sofern sich die Datei <code>.gitignore</code> noch nicht in Ihrem HelloWorld02-Projekt befindet,
sollten Sie die Datei https://glossar.hs-augsburg.de/beispiel/tutorium/2018/.gitignore in das Wurzelverzeichnis Ihres Web-Projektes kopieren. Speichern Sie sie unbedingt unter dem Namen .gitignore (mit einem Punkt als erstes Zeichen). Diese Datei enthält die Namen und Endungen zahlreicher Dateien und Ordner, die üblicherweise nicht auf einem Git-Server gespeichert werden sollten.
 
Erstellen Sie für Ihr Projekt ein Git-Repository, committen Sie und pushen Sie es dann auf den Git-Server.


==Single-Page-Web-Anwendung==
==Single-Page-Web-Anwendung==


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


Für jede Seite fügen wir in <code>index.html</code>
Für jede Seite fügen wir in <code>index.html</code>
einen HTML-Abschnitt „<code>&lt;section id="..."&gt; ...&lt;/section&gt;</code>“
einen HTML-Abschnitt „<code>&lt;section id="..."&gt; ...&lt;/section&gt;</code>“
ein. Das Section-Element gruppiert einen logischen Abschnitt oder ein Kapitel  
ein. Das Section-Element gruppiert einen logischen Abschnitt oder ein Kapitel  
eines HTML-Dokuments. Es soll laut [https://www.w3.org/TR/html5/sections.html#the-section-element Spezifikation]
eines HTML-Dokuments. Es soll laut [https://www.w3.org/TR/html5/sections.html#the-section-element Spezifikation]
eine Überschrift enthalten, die das Thema des Abschnitts beschreibt. Man hätte auch ein Div-Element „<code>&lt;divn id="..."&gt; ...&lt;/div&gt;</code>“
eine Überschrift enthalten, die das Thema des Abschnitts beschreibt. Man hätte auch ein Div-Element „<code>&lt;div id="..."&gt; ...&lt;/div&gt;</code>“
anstelle des Section-Elements verwenden können. Ein Div-Element hat keinerlei Semantik (Bedeutung), es dient lediglich der Strukturierung einer
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,
HTML-Datei. Die (spezifikationsgemäße) Verwendung von Section- und Article-Elementen, die in HTML5 eingeführt wurden, hat den Vorteil,
Zeile 41: Zeile 47:
   <section id="section_form">
   <section id="section_form">
     <h1>Hello, Stranger!</h1>
     <h1>Hello, Stranger!</h1>
   
   </section>
   </section>
   <section id="section_welcome">
   <section id="section_hello" class="hidden">
     <h1 id="text_hello">Hello, ...!</h1>
     <h1 id="heading_hello">Hello, ...!</h1>
     <p>Welcome to Multimedia Programming!</p>
     <p>Welcome to Multimedia Programming!</p>
   </section>
   </section>
Zeile 53: Zeile 58:


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


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


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


<source lang="css">
<source lang="css">
#section_welcome
.hidden
{
{ display: none; }
  display: none;
}
</source>
</source>


Machen Sie dies und testen Sie die Web-Anwendung erneut.
Fügen Sie nun in das öffnende Tag des Section-Elements mit dem Identifikator <code>section_hello</code>
das Attribut-Wert-Paar „<code>class="hidden"</code>“ ein. Ein <code>class</code>-Attribut darf im Gegensatz zu einem
<code>id</code>-Attribut beliebig vielen Elementen zugeordnet werden, ohne dass sich der zugehörige Wert unterscheiden muss.
<code>id</code>-Attribute werden verwendet, um HTML-Elemente eindeutig zu kennzeichnen, <code>class</code>-Attribute werden verwendet, um
diversen HTML-Elementen gleiche CSS-Eigenschaften zuzuordnen. In einem Onepager sind üblicherweise die meisten Seiten unsichtbar.
Daher ist hier ein <code>class</code>-Attribut angebracht.
 
In der CSS-Datei werden Identifikator-Attributewerte mit einer Raute „<code>#</code>“ gekennzeichnet ({{zB}} <code>#section_form</code>) und
Klassen-Attributwerte mit einem Punkt „<code>.</code>“ ({{zB}} <code>.hidden</code>):
 
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==
==Definition eines HTML5-Formulars==
Zeile 78: Zeile 91:
Es soll folgende Elemente haben:
Es soll folgende Elemente haben:


* ein Label, das besschreibt, welche Information vom Benutzer enigegeben werden soll
* ein Label, das beschreibt, welche Information vom Benutzer eingegeben werden soll
* ein Text-Feld, in das der Benutzer seinen Namen eingeben kann
* ein Text-Feld, in das der Benutzer seinen Namen eingeben kann
* einen Reset-Button, um den Inhalt des Namenfeldes zu löschen
* 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
* einen Submit-Button, um dem Browser mitzuteilen, dass der Name vollständig eingegeben wurde


Zeile 89: Zeile 102:
   <div>
   <div>
     <label for="input_name">What's your name?</label>
     <label for="input_name">What's your name?</label>
     <input id="input_name" type="text" autofocus="autofocus"/>
     <input id="input_name" autofocus="autofocus"/>
   </div>
   </div>
   <div>
   <div>
     <button                    type="reset">Reset</button>
     <input id="button_reset"  type="reset" value="Reset"/>
     <button id="button_submit" type="button">Send</button>
     <input id="button_submit" type="button" value="Say hello"/>
   </div>
   </div>
</form>
</form>
Zeile 107: Zeile 120:


Beachten Sie, dass zwei weitere <code>id</code>-Attribute eingeführt wurden: „<code>id="input_name"</code>“ und „<code>id="button_submit"</code>“.
Beachten Sie, dass zwei weitere <code>id</code>-Attribute eingeführt wurden: „<code>id="input_name"</code>“ und „<code>id="button_submit"</code>“.
Diese werden für den Zugriff von JavaScript aus auf das Dokument benötigt. Über den Identifikator <code>input_name</code>kann auf
Diese werden für den Zugriff von JavaScript aus auf das Dokument benötigt. Über den Identifikator <code>input_name</code> kann auf
den Inhalt des Textfeldes, {{dh}} auf den Namen, den der Benutzer eingegeben hat, zugegriffen werden. Außerdem kann  
den Inhalt des Textfeldes, {{dh}} auf den Namen, den der Benutzer eingegeben hat, zugegriffen werden. Außerdem kann  
mit Hilfe dieses Identifikators das Label-Element über das Attribut <code>for</code>an das Textfeld gekoppelt werden.
mit Hilfe dieses Identifikators das Label-Element über das Attribut <code>for</code> 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
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 [[Checkbox]]es
Zusammenhang nicht optisch (per CSS) hergestellt werden kann bzw. hergestellt wird. Damit lassen sich aber auch [[Checkbox]]es
Zeile 115: Zeile 128:


Der Identifikator für den Submit-Button wird benötigt, um in JavaScript die Klick-Aktion des Benutzers abfangen zu 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 <code>action</code>ein URI angegeben. Dieser verweist auf eine  
Normalerweise im Form-Element in einem Attribut namens <code>action</code> ein URI angegeben. Dieser verweist auf eine  
Serveradresse, an den die vom Benutzer erfassten Daten bei einem Klick auf einen echten Submit-Button (<code>type="submit"</code>) übermittelt werden.
Serveradresse, an den die vom Benutzer erfassten Daten bei einem Klick auf einen echten Submit-Button (<code>type="submit"</code>) übermittelt werden.
In dieser Web-Anwendung werden keine Daten an einen Server übermittelt. Alle Benutzereingaben werden direkt
In dieser Web-Anwendung werden keine Daten an einen Server übermittelt. Alle Benutzereingaben werden direkt
Zeile 127: Zeile 140:
Erzeugen Sie eine leere JavaScript-Datei:
Erzeugen Sie eine leere JavaScript-Datei:


* Rechtklick auf die Projektwurzel <code>HelloWorld03</code>im Dateibrowser → <code>New</code> → <code>JavaScript file</code>
* Rechtsklick auf die Projektwurzel <code>HelloWorld03</code> im Dateibrowser → <code>New</code> → <code>JavaScript file</code>
* Name: <code>main.js</code>→ <code>OK</code>
* Name: <code>main.js</code> → <code>OK</code>


Fügen Sie folgenden JavaScript-Code in diese Datei ein
Fügen Sie folgenden JavaScript-Code in diese Datei ein
Zeile 137: Zeile 150:
  * that includes his name. The name is fetched from a text input field.
  * that includes his name. The name is fetched from a text input field.
  */
  */
function greet()
function sayHello()
{
{ document.getElementById('heading_hello').innerHTML =
  document.getElementById('text_hello' ).innerHTML
     'Hello, ' + document.getElementById("input_name").value + '!';
     = 'Hello, ' + document.getElementById("input_name").value + '!';


   document.getElementById('section_form')   .style.display = 'none';
   document.getElementById('section_form') .classList.add  ('hidden');
   document.getElementById('section_welcome').style.display = 'block';
   document.getElementById('section_hello').classList.remove('hidden');
}
}


Zeile 151: Zeile 163:
  */
  */
function init()
function init()
{
{ document.getElementById('button_submit')
  document.getElementById('button_submit')
           .addEventListener('click', sayHello);
           .addEventListener('click', greet);
}
}


Zeile 160: Zeile 171:
</source>
</source>


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


# Alle Befehle innerhalb einer JavaScript-Datei werden der Reihe nach abgearbeitet, sobaldsie geladen wird. In der Datei <code>main.js</code>gibt es insgesamt drei Befehle: Zwei Funktionsdefinitionen und eine Wertzuweisung.
# Alle Befehle innerhalb einer JavaScript-Datei werden der Reihe nach abgearbeitet, sobald sie geladen wird. In der Datei <code>main.js</code> gibt es insgesamt drei Befehle: Zwei Funktionsdefinitionen und eine Wertzuweisung.
# Bei den Funktionen „<code>greet</code>und <code>init</code>handelt es sich um sogenannte [[Observer]]-Funktionen. Derartige Funktionen werden immer dann aktiviert, wenn ein bestimmtes [[Ereignis (OOP)|Ereignis]] eintritt. Die Funktion <code>greet</code>wird aufgerufen, sobald der Submit-Button gedrückt wird, die Init-Funktion wird aufgerufen, sobald die Web-Anwendung vollständig geladen ist.
# Achtung: Die beiden Funktionen <code>sayHello</code> und <code>init</code> werden nur definiert, aber nicht sofort ausgeführt! Damit sie eine Wirkung entfalten, müssen sie aufgerufen werden.
# Beide Funktionen machen regen Gebrauch vom JavaScript-Objekt <code>document</code>, das das [[Document Object Model]], {{dh}} die interne Darstellung des HTML-Dokuments als sogenannten DOM-Baum beinhaltet (siehe [https://developer.mozilla.org/en-US/docs/Web/API/Document MDN-API-Dokumentation]). Mit Hilfe der  Methode <code>getElementById</code>kann man besonders elegant auf bestimmte Elemente des DOM-Baus zugreifen, sofern man zuvor im HTML-Dokument für die entsprechenden Elemente <code>id</code>-Attribute definert hat (was wir gemacht haben).
#Bei <code>sayHello</code> und <code>init</code> handelt es sich um sogenannte [[Observer]]-Funktionen. Derartige Funktionen werden immer dann aktiviert, wenn ein bestimmtes [[Ereignis (OOP)|Ereignis]] eintritt.  
#Für jedes Element des DOM-Baums gibt es diverse elementspezifische [[Attribut]]e. So kann man mittels <code>innerHTML</code>auf den HTML-Code eines Elements wie  <code>p</code>(Paragraph), <code>h1</code>(Hauptüberschrift), <code>h2</code>(Überschrift der 2. Stufe) etc. lesend und schreibend zugreifen. Das Attribut <code>value</code>ermöglicht einen lesenden und schreibenden Zugriff auf den Inhalt von Formularfeldern.  Über das Attribut <code>style</code>hat man Zugriff auf den CSS-Style eines jeden HTML-Elements, das vom Browser gerendert wird. Man kann damit nicht nur jeden CSS-Wert eines Elements lesen, sondern auch verändern. Wir nutzen das aus, indem wir die Formularabschnitt  <code>section_form</code>unsichtbar und dafür den Begrüßungsabschnitt  <code>section_welcome</code>sichtbar machen, sobald die Funktion  <code>greet</code>ausgeführt wird. Es werden also die Werte, die in der CSS-Datei spezifiziert wurden, nachträglich dynamisch geändert.
#Mittels des letzten JavaScript-Befehls, wird festgelegt, dass  die Init-Funktion ausgeführt wird, sobald die Web-Anwendung vollständig geladen ist, {{dh}}, sobald der Browser-Fenster <code>window</code> das Ereignis <code>'load'</code> signalisiert.
# Der dritte Befehl „<code>window.addEventListener('load', init);</code>“ übergibt die [[Funktion]] <code>init</code>(und nicht etwa den [[Funktionsaufruf]] <code>init()</code>) dem JavaScript-Objekt <code>window</code>mit der Bitte, diese Methode auszuführen, sobald das <code>load</code>-Ereignis eintritt. Das hat zur Folge, dass die Funktion  <code>init</code> nicht sofort aufgerufen wird, sondern erst – durch den Observer – sobald das Ereignis „der Inhalt des aktuelle Browserfensters wurde vollständig geladen“ eintritt (siehe [https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload MDN-API-Dokumentation]).
# Sobald Init-Funktion aufgerufen wird, wird festgelegt, dass die Funktion <code>sayHello</code> ausgeführt wird, wenn der Benutzer den Submit-Button drückt. Das heißt, solange die Init-Funktion nicht ausgeführt wurde, {{dh}}, solange das Dokument und alle seine assoziierten Dokumente nicht vollständig geladen wurden, hat ein Klick des Benutzers auf den Submit-Button keinerlei Auswirkungen.  
# Auch für den Submitbutton wird ein [[Eventlistener]] registriert. Die Funktion <code>greet</code>soll ausgeführt werden, sobald für diesen Button das  <code>click</code>-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  <code>window</code>-Objekt existiert dagegen von Anfang an, {{dh}} auch wenn der DOM-Baum noch nicht geladen wurde.)
# Beide Funktionen machen regen Gebrauch vom JavaScript-Objekt <code>document</code>, das das [[Document Object Model]], {{dh}} die interne Darstellung des HTML-Dokuments als sogenannten DOM-Baum beinhaltet (siehe [https://developer.mozilla.org/en-US/docs/Web/API/Document MDN-API-Dokumentation]).  
# Alle Funktionen wurden mit kommentaren im [[JSDoc (GitHub)JSDoc]]-Format versehen.<ref>{{Quelle|JSDoc (GitHub)}}</ref> 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  
# Mit Hilfe der  Methode <code>getElementById</code> kann man besonders elegant auf bestimmte Elemente des DOM-Baus zugreifen, sofern man zuvor im HTML-Dokument für die entsprechenden Elemente <code>id</code>-Attribute definiert hat (was wir gemacht haben).
#Für jedes Element des DOM-Baums gibt es diverse elementspezifische [[Attribut]]e:
#*So kann man mittels <code>innerHTML</code> auf den HTML-Code eines Elements wie  <code>p</code> (Paragraph), <code>h1</code> (Hauptüberschrift), <code>h2</code> (Überschrift der 2. Stufe) etc. lesend und schreibend zugreifen.  
#*Das Attribut <code>value</code> ermöglicht einen lesenden und schreibenden Zugriff auf den Inhalt von Formularfeldern.   
#*Über das Attribut <code>classList</code> hat man Zugriff auf <code>class</code>-Attribute, die einem HTML-Element zugeordnet sind. Man kann jedem Element neue <code>class</code>-Attribut-Werte zuordnen und bestehende Werte entfernen.  
#*Wir nutzen das aus, indem wir die Formularabschnitt  <code>section_form</code> unsichtbar und dafür den Begrüßungsabschnitt  <code>section_hello</code> sichtbar machen, sobald die Funktion  <code>sayHello</code> ausgeführt wird.
# Der dritte Befehl „<code>window.addEventListener('load', init);</code>“ übergibt die [[Funktion]] <code>init</code> (und nicht etwa den [[Funktionsaufruf]] <code>init()</code>) dem JavaScript-Objekt <code>window</code> mit der Bitte, diese Methode auszuführen, sobald das <code>load</code>-Ereignis eintritt. Das hat zur Folge, dass die Funktion  <code>init</code> nicht sofort aufgerufen wird, sondern erst – durch das Observer-Objekt <code>window</code>  – sobald das Ereignis „der Inhalt des aktuelle Browserfensters wurde vollständig geladen“ eintritt (siehe [https://developer.mozilla.org/en-US/docs/Web/Events/load MDN-API-Dokumentation]).
# Auch für den Submitbutton wird ein [[Eventlistener]] registriert. Die Funktion <code>sayHello</code> soll ausgeführt werden, sobald für diesen Button das  <code>click</code>-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  <code>window</code>-Objekt existiert dagegen von Anfang an, {{dh}} auch wenn der DOM-Baum noch nicht geladen wurde.)
# Alle Funktionen wurden mit Kommentaren im [[JSDoc (GitHub)|JSDoc]]-Format versehen.<ref>{{Quelle|JSDoc (GitHub)}}</ref> 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.
Wenn Sie jetzt Ihre Anwendung testen, hat sich nichts geändert.
Der Grund ist wie bei der Datei <code>main.css</code>auch,
Der Grund ist wie bei der Datei <code>main.css</code> auch,
dass die  Datei <code>index.html</code>nichts davon weiß,
dass die  Datei <code>index.html</code> nichts davon weiß,
das ihr dieser JavaScript-Code zugeordnet ist. Das muss ihr erst bekannt gegeben werden.
das ihr dieser JavaScript-Code zugeordnet ist. Das muss ihr erst bekannt gegeben werden.
Fügen Sie folgende Zeile in der Head-Bereich der <code>index.html</code> ein:
Fügen Sie folgende Zeile in der Head-Bereich der <code>index.html</code> ein:


<source lang="html5">
<source lang="html5">
<script type="text/javascript" src="main.js"></script>
<script src="main.js" async="async"></script>
</source>
</source>


Nun wird nicht nur die CSS-Datei, sondern auch die JavaScript-Datei geladen,
Nun wird nicht nur die CSS-Datei, sondern auch die JavaScript-Datei geladen,
bevor der Body-Bereich der  <code>index.html</code> eingelesen wird.
bevor der Body-Bereich der  <code>index.html</code> eingelesen wird.
Beachten Sie, dass folgender Code zwar XML-konform, aber in Standard-HTML5-Dokumenten nicht erlaubt ist:
Die Angabe von <code>async</code> bewirkt Folgendes: Das Rendering
der Seite wird durch das Laden des Skripts '''nicht''' unterbrochen. Das heißt, wenn ein langes Skript ohne
Angabe von  <code>async</code> geladen wird, kann sich der Seitenaufbau merklich verzögern.
Mit Angabe dieser Option ist dies nicht der Fall.
 
Sobald das Skript geladen wurde, wird es ausgeführt.
Zu diesem Zeitpunkt ist der Rendervorgang möglicherweise noch nicht abgeschlossen. Das
heißt, man muss darauf achten, dass man von Skript aus nicht zu früh auf die Elemente des
HTML-Dokuments zugreift. Aus diesem Grund wird die <code>init</code>-Funktion
am Ende der Datei <code>main.css</code> nicht direkt mittels <code>init()</code>
gestartet, sondern mittels eines Eventlisteners <code>window.addEventListener('load', init)</code>.
 
Beachten Sie, dass folgender Code zwar SGML/XML-konform, aber in Standard-HTML5-Dokumenten nicht erlaubt ist:
<source lang="html5">
<source lang="html5">
<script type="text/javascript" src="main.js"/>
<script src="main.js" async="async"/>
</source>
</source>


Die tieferen Gründe dafür werden sehr schön auf
Die tieferen Gründe dafür werden sehr schön auf
[http://stackoverflow.com/questions/69913/why-dont-self-closing-script-tags-work Stack Overflow] beschrieben.
[https://stackoverflow.com/questions/69913/why-dont-self-closing-script-tags-work Stack Overflow] beschrieben.
(Ich verstehe es trotzdem nicht.)


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


==Barrierefreiheit==
==Barrierefreiheit==
Zeile 199: Zeile 231:
„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
„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 „<code>autofocus="autofocus"</code>“ im Text-Input-Feld des HTML-Dokument bei Start der Anwendung automatisch an der richtigen Position.
des Attributes „<code>autofocus="autofocus"</code>“ 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 sinen
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 Returm drücken, um ein Klick-Event für diesen Button
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.
auszulösen.


Zeile 206: Zeile 238:
Zurzeit passiert nichts, wenn Sie Ihren Namen eingeben und dann die Returntaste betätigen. Versuchen Sie es.
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 Benutzerinteraktin mit dem Browser...
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 „greet_on_enter“.
Um diese Funktionalität zu realisieren, brauchen wir einen weiteren Eventlistener. Die Observer-Methode heiße <code>sayHelloOnEnter</code>.
Diese muss wieder registriert werden. Die Tastaturereignisse werden vom <code>window</code>-Objekt gemeldet
Diese muss wieder registriert werden. Die Tastaturereignisse werden vom <code>window</code>-Objekt gemeldet
(und nicht etwa vom <code>dokument</code>-Objekt, da das Dokument nichts mit Tastatureingaben zu schaffen hat).
(und nicht etwa vom <code>dokument</code>-Objekt, da das Dokument nichts mit Tastatureingaben zu schaffen hat).
Zeile 214: Zeile 246:


<source lang="javascript">
<source lang="javascript">
window.addEventListener('keydown', greet_on_enter);
window.addEventListener('keydown', sayHelloOnEnter);
</source>
</source>


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


Die (in diesem Teil des Tutoriums noch globale) Funktion  <code>greet_on_enter</code>wird folgendermaßen definiert:
Die (in diesem Teil des Tutoriums noch globale) Funktion  <code>sayHelloOnEnter</code> wird folgendermaßen definiert:


<source lang="javascript">
<source lang="javascript">
/**
/**
  * An keyboard event observer. It tests whether the enter key has been pressed.
  * A 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
  * If so, the sayHello method is activated. Default reactions of the browser are
  * disabled.
  * disabled.
  * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
  * @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
  *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
  *  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
  */
  */
function greet_on_enter(p_event)
function sayHelloOnEnter(p_event)
{
{ if (p_event.key === 'Enter') // The enter key has been pressed.
  if (p_event.code === 'Enter')
   { // If the input text field or the submit button is active say hello.
   {
     if (document.activeElement === document.getElementById('input_name') ||
     p_event.preventDefault();
        document.activeElement === document.getElementById('button_submit')
     p_event.stopPropagation();
      )
     greet();
    { sayHello(); }
     // If the reset button is active clear the input text field.
    else if (document.activeElement === document.getElementById('button_reset'))
     { document.getElementById('input_name').value = ''; }
   }
   }
}
}
</source>
</source>


Zwei Dinge fallen bei dieser Definition auf:  
Fünf Dinge fallen bei dieser Definition auf:
 
# Die Funktion hat einen Parameter namens <code>p_event</code> (Parameternamen können beliebig gewählt werden. Ich beginne Parameternamen stets mit einem <code>p_</code>, um rein optisch klar zu machen, dass es sich um einen Parameter handelt; siehe [[Multimedia-Programmierung: Style Guide]]). In diesem Parameter übermittelt das <code>window</code>-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 [https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent MDN-JavaScript-Dokumentation].
#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 <code>sayHello</code> auf, um die Eingabe abzuschließen.
#Die Gleichheit wird mit „<code>==&#61;</code>“  anstelle von „<code>==</code>“  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  „<code>0==&#39;&#39;</code>“ „<code>0=='0'</code>“ jeweils <code>true</code> als Ereignis ausgegeben. Der strikte Gleichheitstest liefert dagegen das erwartete Ergebnis <code>false</code>.
# Die Bedeutung der Enter-Taste anhängig muss bhängig vom Fokus verändert werden, da dieser mittels der Tab-Taste geändert werden kann. Wenn der Fokus auf dem Texteingabefeld oder dem Submit-Button liegt, soll der Benutzer gegrüßt werden. Liegt er dagegen auf der Reset-Taste, wird das Eingabefeld geleert.
# Im Code wird <code>p_event.key === 'Enter'</code> anstelle von <code>p_event.keyCode ===13</code> verwendet. Die letztere Methode gilt als deprecated, da sie die Position der gedrückten Taste auf der Tastatur ausgibt. Diese Position ist jedoch sprachabhängig. Beispielsweise hat die Z-Taste auf einer amerkikansischen Tastatur eine andere Position als auf einer deutschen. Mit der folgenden [https://jsfiddle.net/kowarschick/3e0L2cna/ JSFiddle-Anwendung] können Sie die Unterschiede zwischen <code>p_event.key</code>, <code>p_event.code</code> und <code>p_event.keyCode</code> untersuchen. Öffen Sie die Anwendung in verschiedenen Brwosern und drücken Sie unterschiedliche Tasten ({{zB}} auch im Nummernblock.
 
So weit so gut. Aber wenn man <code>sayHelloOnEnter</code> so definiert, dass sie bei einem Druck der Returntaste lediglich die Funktion
<code>sayHello</code> aufruft oder das Eingabefeld löscht,
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 folgenden Befehle:


# Die Funktion hat einen Parameter namens „<code>p_event</code>“ (Parameternamen können beliebig gewälht werden. Ich beginne Parameternamen stets mit einem „<code>p_</code>“, um rein optisch klar zu machen, dass es sich um einen Parameter handelt; siehe [[Multimedia-Programmierung: Style Guide]]). In diesem Parameter übermittelt das <code>window</code>-Objekt dem Eventhandler nähere Informationen zum aktuellen Tastaturereignis: welche Taste gedrückt wurde, ob die Schifttaste dabei gehalten wurde etc. Details finden sich in der ausgezeichneten [https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent MDN-JavaScript-Dokumentation].
<source lang="javascript">
#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 „<code>greet</code>“ auf, um die Eingabe abzuschließen.
p_event.preventDefault();
#Die Gleichheit wird mit „<code>==&#61;</code>“  anstelle von „<code>==</code>“  getestet. Bei dem ersten Test handelt es sich um einen strikten Gleichheitstest.  Der zweite Test liefert dagegen manchmal ziemlich eingewillige Ergenisse. So wir beispielsweise bei den Tests  „<code>0==&#39;&#39;</code>“ „<code>0=='0'</code>“ jeweils „<code>true</code>“ als Ergnis ausgegeben. Der strikte Gleichheitstest liefert dagegen das erwartete Ergebnis „<code>false</code>“.
p_event.stopPropagation();
</source>  


So weit so gut. Aber wenn man <code>greet_on_enter</code> so definiert, dass sie bei einem Druck der Returntaste lediglich die Funktion „<code>greet</code>“ aufruft,
Der erste verlangt, dass bei Drücken dieser Taste keine Default-Aktionen ausgeführt werden sollen und der zweite bewirkt, dass  
stellt man schnell fest, dass ein Betätigen der Returntaste nicht wie gewünscht funktioniert. Das Problem ist, dass der Browser Tastatur-Ereignisse bereits anwerweitig 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  „<code>p_event.preventDefault();</code>“ und „<code>p_event.stopPropagation();</code>“. Der erste verlangt, dass bei Drücken dieser Tase keine Default-Aktionen ausgeführt werden sollen und der zweite bewirkt, dass  
das Ereignis nicht auch noch von irgendwelchen anderen Eventhandlern behandelt wird.
das Ereignis nicht auch noch von irgendwelchen anderen Eventhandlern behandelt wird.
Fügen Sie also die beiden Befehle noch an den Anfang des Rumpfes der Funktion <code>sayHelloOnEnter</code> ein.<!--


Damit sollte alle funktionieren wie geplant. Denkste! In (zumindest manchen) Browsern (zumindest mancher) mobiler Devices bewirkt ein Betätigen der
Damit sollte alle funktionieren wie geplant. Denkste! In (zumindest manchen) Browsern (zumindest mancher) mobiler Devices bewirkt ein Betätigen der
Zeile 257: Zeile 303:
Browsern sind viele Standardaktionen fest in bestimmte Browser integriert. Dies gilt insbesondere für die Behandlung von Touchevents.
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
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  
nicht gespielt werden kann, da irgendwelche Default-Eventhandler Gesten anders interpretieren als geplant. Hier ist Testen, Googeln, Testen, Googeln und  
nochmals testen angesagt. Oft hilt es nur noch, eine native App zu erstellen, die nichts weiter macht, als JavaScript-Code in einem Browser auszuführen.
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 leich deaktiviert werden).
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  
Die Lösung des Problems ist hier allerdings recht einfach. Man muss  
<source lang="javascript">
<source lang="javascript">
  if (p_event.code === 'Enter'  || p_event.keyCode === 13)
if (p_event.key === 'Enter'  || p_event.keyCode === 13)
</source>
</source>
anstelle von  
anstelle von  
<source lang="javascript">
<source lang="javascript">
  if (p_event.code === 'Enter' )
if (p_event.key === 'Enter' )
</source>
</source>
schreiben. Die Verwendung von  „<code>p_event.keyCode</code>“ gilt eigentlich als veraltet. Sie ist als “deprecated” („veraltet“, „überholt“, „missbiligt“) gekennzeichnet.
schreiben. Die Verwendung von  „<code>p_event.keyCode</code>“ gilt eigentlich als veraltet. Sie ist als [https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode “deprecated” („veraltet“, „überholt“, „missbilligt“) gekennzeichnet].
Man soll stattdessen „<code>p_event.code</code>“. Dieses Attribut wird allerdings von vielen aktuellen Browsern noch nicht richtig interpretiert...
Man soll stattdessen „<code>p_event.key</code>“  oder <code>p_event.code</code>“ (Unterschied: siehe [https://codepen.io/denilsonsa/pen/epmoma codepen.io]) verwenden. Diese Attribute sind allerdings in einigen aktuellen Browsern wie Microsoft Edge noch nicht richtig implementiert worden. Versuchen Sie es, indem Sie folgende URL in diversen Browsern öffnen.
(Hier tauchen wieder die Browser-Inkombatibilitäten auf, die man mit dem Erscheinen von HTML5 und EcmaScript 5 eingentlich schon als überwunden glaubte.)
  https://jsfiddle.net/kowarschick/3e0L2cna/
 
(Hier tauchen wieder die Browser-Inkompatibilitäten auf, die man mit dem Erscheinen von HTML5 und EcmaScript 5 eigentlich schon als überwunden glaubte.)
==Quellen==
-->


<references/>
Vergessen Sie nicht, Ihr aktuelles Projekt auf dem Git-Server zu speichern, sobald alles funktioniert.
<!--
<!--
→ <code></code> „<code></code>“
-->
==Fortsetzung des Tutoriums==
==Fortsetzung des Tutoriums==


Sie sollten nun [[HTML5-Tutorium: JavaScript: Hello World 04|Teil 4 des Tutoriums]] bearbeiten.
Sie sollten nun [[HTML5-Tutorium: JavaScript: Hello World 04|Teil 4 des Tutoriums]] bearbeiten.
Vergessen Sie nicht, vorher Ihr aktuelles Projekt im Repository zu speichern.
-->


==Quellen==
==Quellen==
<references/>
<references/>
<noinclude>[[Kategorie: HTML5-Tutorium: JavaScript: Hello World]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]</noinclude>
<ol>
<li value="3">{{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
</ol>
[[Kategorie: HTML5-Tutorium: JavaScript: Hello World]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]

Version vom 8. November 2018, 18:28 Uhr

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 (Git-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-Dokumnt 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.

Kopieren Sie anschließend die Projektdateien (insbesondere den Ordner web mit den Dateien index.html und main.css sowie die Datei .gitignore) aus dem zweiten Teil des Tutoriums und passen Sie den Titel in der HTML-Datei an.

Sofern sich die Datei .gitignore noch nicht in Ihrem HelloWorld02-Projekt befindet, sollten Sie die Datei https://glossar.hs-augsburg.de/beispiel/tutorium/2018/.gitignore in das Wurzelverzeichnis Ihres Web-Projektes kopieren. Speichern Sie sie unbedingt unter dem Namen .gitignore (mit einem Punkt als erstes Zeichen). Diese Datei enthält die Namen und Endungen zahlreicher Dateien und Ordner, die üblicherweise nicht auf einem Git-Server gespeichert werden sollten.

Erstellen Sie für Ihr Projekt ein Git-Repository, committen Sie und pushen Sie es dann auf den Git-Server.

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" autofocus="autofocus"/>
  </div>
  <div>
    <input id="button_reset"  type="reset"  value="Reset"/>
    <input id="button_submit" type="button" value="Say hello"/>
  </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.jsOK

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! Damit sie eine Wirkung entfalten, müssen sie aufgerufen werden.
  3. Bei sayHello und init handelt es sich um sogenannte Observer-Funktionen. Derartige Funktionen werden immer dann aktiviert, wenn ein bestimmtes Ereignis eintritt.
  4. Mittels des letzten JavaScript-Befehls, wird festgelegt, dass die Init-Funktion ausgeführt wird, sobald die Web-Anwendung vollständig geladen ist, d. h., sobald der Browser-Fenster window das Ereignis 'load' signalisiert.
  5. Sobald Init-Funktion aufgerufen wird, wird festgelegt, dass die Funktion sayHello ausgeführt wird, wenn der Benutzer den Submit-Button drückt. Das heißt, solange die Init-Funktion nicht ausgeführt wurde, d. h., solange das Dokument und alle seine assoziierten Dokumente nicht vollständig geladen wurden, hat ein Klick des Benutzers auf den Submit-Button keinerlei Auswirkungen.
  6. 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).
  7. 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).
  8. 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.
  9. Der dritte Befehl „window.addEventListener('load', init);“ übergibt die Funktion init (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).
  10. 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.)
  11. 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 src="main.js" async="async"></script>

Nun wird nicht nur die CSS-Datei, sondern auch die JavaScript-Datei geladen, bevor der Body-Bereich der index.html eingelesen wird. Die Angabe von async bewirkt Folgendes: Das Rendering der Seite wird durch das Laden des Skripts nicht unterbrochen. Das heißt, wenn ein langes Skript ohne Angabe von async geladen wird, kann sich der Seitenaufbau merklich verzögern. Mit Angabe dieser Option ist dies nicht der Fall.

Sobald das Skript geladen wurde, wird es ausgeführt. Zu diesem Zeitpunkt ist der Rendervorgang möglicherweise noch nicht abgeschlossen. Das heißt, man muss darauf achten, dass man von Skript aus nicht zu früh auf die Elemente des HTML-Dokuments zugreift. Aus diesem Grund wird die init-Funktion am Ende der Datei main.css nicht direkt mittels init() gestartet, sondern mittels eines Eventlisteners window.addEventListener('load', init).

Beachten Sie, dass folgender Code zwar SGML/XML-konform, aber in Standard-HTML5-Dokumenten nicht erlaubt ist:

<script src="main.js" async="async"/>

Die tieferen Gründe dafür werden sehr schön auf Stack Overflow beschrieben. (Ich verstehe es trotzdem nicht.)

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:

/**
 * A 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.key === 'Enter')  // The enter key has been pressed.
  { // If the input text field or the submit button is active say hello.
    if (document.activeElement === document.getElementById('input_name') ||
        document.activeElement === document.getElementById('button_submit')
       )
    { sayHello(); }
    // If the reset button is active clear the input text field.
    else if (document.activeElement === document.getElementById('button_reset'))
    { document.getElementById('input_name').value = ''; }
  }
}

Fünf 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. Die Bedeutung der Enter-Taste anhängig muss bhängig vom Fokus verändert werden, da dieser mittels der Tab-Taste geändert werden kann. Wenn der Fokus auf dem Texteingabefeld oder dem Submit-Button liegt, soll der Benutzer gegrüßt werden. Liegt er dagegen auf der Reset-Taste, wird das Eingabefeld geleert.
  5. Im Code wird p_event.key === 'Enter' anstelle von p_event.keyCode ===13 verwendet. Die letztere Methode gilt als deprecated, da sie die Position der gedrückten Taste auf der Tastatur ausgibt. Diese Position ist jedoch sprachabhängig. Beispielsweise hat die Z-Taste auf einer amerkikansischen Tastatur eine andere Position als auf einer deutschen. Mit der folgenden JSFiddle-Anwendung können Sie die Unterschiede zwischen p_event.key, p_event.code und p_event.keyCode untersuchen. Öffen Sie die Anwendung in verschiedenen Brwosern und drücken Sie unterschiedliche Tasten (z. B. auch im Nummernblock.

So weit so gut. Aber wenn man sayHelloOnEnter so definiert, dass sie bei einem Druck der Returntaste lediglich die Funktion sayHello aufruft oder das Eingabefeld löscht, 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 folgenden Befehle:

p_event.preventDefault();
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. Fügen Sie also die beiden Befehle noch an den Anfang des Rumpfes der Funktion sayHelloOnEnter ein.

Vergessen Sie nicht, Ihr aktuelles Projekt auf dem Git-Server zu speichern, sobald alles funktioniert.

Quellen

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