Internettechnologien, LE 4: JavaScript, jQuery

Werbung
Inhaltsverzeichnis
Inhaltsverzeichnis
Vorbemerkungen zu dieser Lerneinheit......................................
3
1
JavaScript..........................................................
4
1.1
Einleitende Beispiele ............................................
5
1.2
Grundsätzliche Syntaxregeln ...................................
9
1.3
Dynamisch und implizite Typisierung .........................
9
1.4
Prototypen ........................................................ 11
1.5
Mathematische Funktionen und Konstanten................ 13
1.6
Kontrollstrukturen ............................................... 14
1.7
Wichtige Standardobjekte ...................................... 16
1.8
Funktionen und Callbacks ...................................... 20
1.9
DOM ............................................................... 26
1.10
XMLHttpRequest und AJAX..................................... 32
1.11
Web Workers: Nebenläufigkeit in JavaScript ................ 39
1.12
DOM Storage ..................................................... 40
1.13
Übungsaufgaben ................................................. 42
2
jQuery.............................................................. 46
2.1
Entstehungsgeschichte, Lizenz, Dokumentation ............ 46
2.2
Einbindung der Bibliothek ...................................... 46
2.3
Syntax .............................................................. 47
2.4
DOM-Manipulation mit jQuery ................................ 48
2.5
CSS-Formatierungen............................................. 50
2.6
AJAX mit jQuery .................................................. 51
2.7
jQuery Mobile .................................................... 53
2.8
Übungsaufgaben ................................................. 56
1
Inhaltsverzeichnis
3
AngularJS .......................................................... 58
3.1
Grundlegende Konzepte ........................................ 58
3.2
Erste Beispiele: HTML wird (fast) eine Programmiersprache ............................................................ 62
3.3
Grundelemente von AngularJS................................. 65
3.4
Erste Datenbankzugriffe mit AngularJS ....................... 66
3.5
Single Page App mit Components und Datenbankzugriffen .................................................................. 68
3.6
Bewertung und Ausblick ........................................ 74
3.7
Übungsaufgaben ................................................. 76
4
Lösungen der Aufgaben ......................................... 77
Literatur ........................................................... 98
2
Inhaltsverzeichnis
Vorbemerkungen zu dieser Lerneinheit
Die vorliegende Lerneinheit bildet den Abschluss des Lehrbriefs über
Internettechnologien und behandelt wichtige Aspekte der clientseitigen Programmierung. Im Zusammenhang mit dem Web bedeutet dies
vor allem Programmierung mit JavaScript und die Verwendung weit
verbreiteter, auf JavaScript basierender Bibliotheken und Frameworks,
insbesondere jQuery und AngularJS.
Die Programmierung mit JavaScript entstand sehr früh mit der massiven Verbreitung des Internets Mitte der 1990er Jahre, wurde aber
im Verlaufe des darauf folgenden Jahrzehnts insbesondere aufgrund sicherheitstechnischer Bedenken zunehmend unbeliebter. Erst mit der
gezielten Förderung durch Google, sowohl durch Marketing als auch
durch technische Weiterentwicklung, gewann JavaScript wieder an Bedeutung. Zur Trendwende hat daneben auch ganz sicher das Buch von
Douglas Crockford, JavaScript: The Good Parts [1], beigetragen, das eine „saubere“ Programmierung mit der Skriptsprache propagiert. Das
Buch hat wesentlich die Entstehung dieser Lerneinheit beeinflusst und
dessen Geist geprägt.
Hagen,
im Dezember 2016
Andreas de Vries
3
1
JavaScript
1
JavaScript
JavaScript ist eine wichtige Programmiersprache, da es die Sprache der
Browser ist. Nach C, mit dem alle wichtigen Betriebssystemkerne programmiert sind, ist JavaScript damit die wohl am weitesten verbreitete
höhere Programmiersprache der Welt. JavaScript ist, ähnlich wie PHP,
eine Skriptsprache, deren Anweisungen der Interpreter direkt in den
Arbeitsspeicher liest und ausführt. Üblicherweise versteht man unter
dem Begriff JavaScript eine clientseitige Sprache, deren Interpreter ein
Modul des Browsers ist.1 JavaScript ermöglicht es, durch Zugriff auf bestimmte Funktionen des Browsers, also „lokal“ auf dem Rechner des
Webclients, eine HTML-Seite dynamisch und interaktiv zu gestalten. Im
wesentlichen lassen sich mit JavaScript Formulareingaben lokal (also
noch vor Abschicken der Daten an den Server) überprüfen, Bilder animieren, Mausereignisse verarbeiten, Pop-Up-Fenster öffnen, oder (insbesondere im Rahmen von AJAX) asynchrone HTML-Requests für einzelne HTML-Elemente durchführen.
JavaScript wurde 1995 von Brendan Eich bei Netscape Communications entwickelt. Der Name suggeriert eine Verwandtschaft mit der Programmiersprache Java, was aber bis auf gewisse Syntaxähnlichkeiten
überhaupt nicht der Fall ist. Als Reaktion auf den Erfolg von JavaScript
entwickelte Microsoft kurz darauf eine sehr ähnliche Sprache unter
dem Namen JScript. Die europäische Industrievereinigung ECMA (European Computer Manufacturers Association) mit Sitz in Genf erarbeitete 1997 den Standard ECMA-Script (ECMA-262, [JS]). ECMA-262 ist
seit 1998 internationaler Standard (ISO/IEC 16262). Aktuelles findet
man unter [JS] insbesondere zu den verschiedenen Browserimplementierungen auf der Unterseite About_JavaScript.
Insgesamt ist JavaScript eine im Kern sehr schöne und mächtige Programmiersprache, die allerdings auch einige üble Eigenschaften hat.
Zudem existieren im Web unzählige unsäglich schlechte Programmbeispiele von Programmierlaien und – ja, auch Idioten. Glauben Sie niemals blind irgendeinem Beispiel aus dem Netz, prüfen Sie stets die Programme und Ausschnitte auf Nachvollziehbarkeit! Douglas Crockford,
einer der Kämpfer für die Nutzung der guten Möglichkeiten von JavaScript [1], hat die Webseite JSLint [JSL] eingerichtet, auf der JavaScriptQuelltext auf seine Qualität geprüft werden kann. In einer umfangreichen Optionsliste kann man dabei die Toleranz des Prüfprogramms einstellen. Die Quelltexte dieses Skriptes sind mit den Einstellungen in 1.1
geprüft (bis auf die Methode document.write).
1
Seit November 2009 existiert die vielbeachtete auf JavaScript basierende eigenständige Laufzeitumgebung Node.js, die Netzwerk- und Dateisystemfunktionen besitzt
und serverseitig laufen kann [9].
4
1.1
Einleitende Beispiele
Abb. 1.1: Optionen der Toleranz von JSLint [JL]
1.1
Einleitende Beispiele
Nach alter Tradition soll unser erstes Programm eine kleine Begrüßungsformel ausgeben. Als Vorbemerkung halten wir zunächst fest,
dass ein JavaScript-Programm stets in einem HTML-Dokument eingebettet ist. Anders als bei PHP ist also der Quelltext von JavaScript in
einer Textdatei mit der Endung .html oder .htm.
Listing 1: Hello World mit document.write
1
<!DOCTYPE html>
2
<html>
3
<head><meta charset="UTF-8"/></head>
4
<body>
<script>
5
document.write("<h2>Grüße Dich, Welt!</h2>");
6
</script>
7
</body>
8
9
</html>
Achtet man auf die korrekte Textcodierung des HTML-Dokuments, so
kann man problemlos Umlaute oder Sonderzeichen ausgeben.
1.1.1
Das <script>-Tag
Gehen wir das obige Programm kurz durch. Zunächst bemerken wir,
dass das Programm ein HTML-Dokument ist: Die ersten vier Zeilen sind
Standard-HTML. In der fünften Zeile beginnt mit dem Tag <script> die
Deklaration des JavaScript-Programms. Das HTML-Tag
<script> ... </script>
sagt dem Browser, dass nun JavaScript-Code folgt. Man kann JavaScriptQuellext auch in eine eigene Datei auslagern, was mit Hilfe des Attributs
5
1
JavaScript
src geschieht:
<script src="./quelltext.js"></script>
Das Auslagern von Quelltext ist für kleine Programme zwar etwas aufwendiger, dafür aber bei größeren Projekten übersichtlicher. Zudem
kann so einmal erstellter JavaScript-Code in anderen Webseiten verwendet werden.
Wo kann ein <script> erscheinen?
In unserem ersten Programm wurde der Quelltext im body-Tag des
HTML deklariert. Muss das immer so sein? Prinzipiell kann JavaScript
durch ein script-Tag an jeder Stelle des HTML-Dokuments – also im
head-Element oder im body-Element – eingebunden werden, solange die
HTML-Syntax nicht verletzt wird. Insbesondere kann man beliebig viele script-Tags verwenden. Der Browser interpretiert sie alle der Reihe
nach durch.
Als Faustregel sollte man jedoch beachten, dass Deklarationen von
Funktionen (s.u.) eher im head vorgenommen werden, das ist eine übliche und sinnvolle Konvention. JavaScript-Anweisungen, die vor oder
während des Aufbaus der HTML-Seite geschehen sollen, müssen sogar
im head deklariert werden.
Aus Performanzgründen allerdings wird für große Programme oft geraten, die <script>-Elemente am Ende des <body>-Elements zu laden,
da somit das Laden des JavaScript-Quelltextes und der HTML-Elemente
sich nicht gegenseitig blockieren können.2 Sicher sollte man diesen
Hinweis bei Ladezeitproblemen großer Programme auch beachten, jedoch werden in diesem Lehrbrief die <script>-Elemente bevorzugt im
<head> erscheinen.
1.1.2
Anweisungen
Jede Anweisung sollte mit einem ; abgeschlossen werden. (Solange jede
Anweisung in einer eigenen Zeile steht, ist dies zwar nicht notwendig,
aber es kann zu Mehrdeutigkeiten und somit zu unvorhergesehenen Effekten führen.)
1.1.3
Ausgaben
Ausgabe mit document.write()
Der JavaScript-Befehl document.write übergibt direkt Text an den Browser, den dieser gemäß HTML interpretiert und im Fenster darstellt.
Der Browser empfängt zum Beispiel im Programm des Listings 1 einfach <h2>Hello World</h2> innerhalb des body-Tags. Eine Ausgabe mit
2
6
https://docs.angularjs.org/guide/bootstrap
1.1
sollte also stets im
auch der Text erscheinen soll.
document.write
body-Tag
Einleitende Beispiele
an der Stelle stehen, wo
Ausgabe in HTML-Elementen mit ID
Eine weitere Möglichkeit zur Ausgabe in JavaScript zeigt das folgende Beispiel. Es ist ein wenig komplizierter, verwendet aber bereits eine sehr bedeutsame Möglichkeit von JavaScript, auf einzelne Elemente
eines HTML-Dokuments zuzugreifen, wenn sie jeweils dokumentweit
eindeutige ID’s erhalten haben. Dazu wird das Attribut id verwendet,
das bereits im Zusammenhang mit CSS angesprochen wurde. Mit der
Funktion getElementById kann dann aus JavaScript mit dieser ID auf das
Element referenziert werden. Das so erhaltene Element ist ein Objekt
in JavaScript, das als ein Attribut innerHTML besitzt, das wiederum auf
den Textinhalt verweist. (Beachten Sie dabei die Groß- und Kleinschreibung!) Betrachten wir dazu das Beispielprogramm 1.2, in dem eine Ausgabe nach Anklicken eines Buttons geschieht.
Abb. 1.2: Ausgabe per Element-ID
Hier hat das <p>-Element die ID "ausgabe" und wird so mit der Anweisung in Zeile 7 mit einem Text beschrieben. Zu beachten ist, dass die
Funktion getElementById zum Zeitpunkt ihrer Ausführung das Element
kennen muss. Da sie in unserem Programm im <head>-Element deklariert ist, das <p>-Element in Zeile 13 also noch nicht bekannt ist, wird
hier ein in JavaScript üblicher Mechanismus verwendet, der die Ausführung erst nach Anklicken des Buttons ausführt; zu diesem Zeitpunkt
ist das <p>-Element also schon bekannt. (Diesen Mechanismus werden wir unter dem Stichwort Ereignisbehandlung bzw. Event Handling
näher kennen lernen.): Mit dem Attribut onclick wird festgelegt, was
bei Anklicken der Schaltfläche passieren soll. Hier wird die Funktion
ausgeben ausgeführt, die den Inhalt innerHTML des <p>-Elements überschreibt. Die Deklaration der Funktion ab Zeile 6 mit dem Schlüsselwort
function ist JavaScript typisch und wird weiter unten erklärt.
7
1
JavaScript
Debugging: Ausgabe auf der JavaScript-Konsole
Bei der Entwicklung von JavaScript-Programmen kann man zum Debuggen seiner Programme auch Text auf der JavaScript-Konsole ausgeben, und zwar mit dem Befehl
console.log("Hallo Welt!");
Die Konsole ist bei den Browsern Firefox oder Chrome sehr einfach einzuschalten.
1.1.4
Eingabe von Daten in JavaScript
Eine Erweiterung des Programmbeispiels 1.2 ist das folgende Programm mit der Möglichkeit, Daten in ein JavaScript-Programm einzulesen. Bei dieser Erweiterung definiert man im <head>-Element eine
Funktion einlesen, die beim Klicken des OK-Schaltknopfes aufgerufen
wird und den eingegebenen Text verarbeitet. Hierzu müssen also zwei
HTML-Elemente mit einer ID versehen werden, so dass sie aus JavaScript angesprochen werden können, ein <input>-Element für die Eingabe und ein <span>-Element für die Ausgabe:
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<script>
6
var einlesen = function() {
7
var text = document.getElementById("eingabe").value;
8
document.getElementById("ausgabe").innerHTML = text;
9
10
}
</script>
11
</head>
12
<body>
13
<input type="text" id="eingabe"/>
14
<input type="button" value="OK" onclick="einlesen();"/>
15
<p>Sie haben gerade <span id="ausgabe"></span> eingegeben.</p
>
16
</body>
17
</html>
Um auf den eingegeben Wert des Eingabefeldes eingabe zuzugreifen,
muss man sein value-Attribut ansprechen, das auf aktuell den eingegegeben Inhalt verweist.
8
1.2
1.2
Grundsätzliche Syntaxregeln
Grundsätzliche Syntaxregeln
JavaScript ist schreibungssensitiv (case sensitive), d.h. es wird strikt unterschieden zwischen Groß- und Kleinschreibung. Das reservierte Wort
null ist etwas völlig anderes als Null.
Wie in Java beginnen Kommentare entweder mit einem // und enden
mit dem Zeilenende, oder sind eingeschlossen zwischen /* und */.
Eine Anweisung wird mit einem Semikolon beendet. Zwar besitzt JavaScript einen Mechanismus, der fehlende Semikolons einfügt, d.h. man
muss sie nicht notwendig selber setzen. Allerdings sollte man sich auf
diesen Mechanismus nicht verlassen, er kann zu logischen Programmfehlern führen [1, S. 109f, 127f].
Anweisungen werden durch Umschließen von geschweiften Klammern
{...} zu Blöcken zusammengefasst. Empfohlen wird in JavaScript der
1TBS-Stil,3 bei dem die öffnende geschweifte Klammer immer am Ende
einer Zeile steht, nie am Anfang [1, S. 103].
1.3
Dynamisch und implizite Typisierung
Eine Variable in JavaScript kann während ihrer Laufzeit verschiedene Datentypen annehmen. Programmiersprachen, die das ermöglichen,
heißen dynamisch typisiert. Demgegenüber heißt eine Programmiersprache statisch typisert, wenn jede Variable einen Datentyp hat, den
sie lebenslänglich behält. Konsequenterweise ist in JavaScript eine Variable stets implizit definiert, d.h. der Datentyp wird bei jeder Zuweisung während der Laufzeit automatisch bestimmt. Java ist demgegenüber statisch und explizit typisiert. In JavaScript kann eine Variable die
folgenden 7 möglichen Datentypen annehmen oder eine Funktion oder
ein Objekt sein.
3
4
•
string:
eine in Anführungszeichen "..." oder Apostrophs’...’
eingerahmte Zeichenkette (mit Unicode-Zeichen), z.B. "¡Howdy!"
oder ’Allô’; um Unicode zu ermöglichen, sollte das einbettende HTML-Dokument die Zeichenkodierung UTF-8 im <meta>-Tag
vorsehen werden, wie in den obigen einleitenden Beispielen.
•
number:
eine Zahl gemäß IEEE754 (double in Java oder C), also 42
oder 3.14159. Spezielle Werte sind Infinity für den Wert Unendlich (∞) und NaN für „not a number“. Einen expliziten Datentyp für
ganze Zahlen (wie int in Java) kennt JavaScript nicht. Eine Zahl in
JavaScript hat eine Objektmethode4 toString(radix) mit radix ∈
{2, 3, . . . , 36}, die sie bezüglich der Basis radix darstellt, also beispielsweise
http://de.wikipedia.org/wiki/Einrückungsstil [2016-07-17]
https://developer.mozilla.org/en/JavaScript/Reference/Global_
Objects/Number/toString [2016-07-17]
9
1
JavaScript
1
document.write((71).toString(36));
2
document.write(Math.PI.toString(7)); // =>
// => 1z
3.066365143203613411
Eine weitere nützliche Standardmethode einer Zahl in JavaScript
ist die Methode toFixed, die als Parameter die Anzahl n der darzustellenden Dezimalstellen erwartet, wobei 0 5 n 5 20.5
1
•
document.write((123.4567).toFixed(3));
// => 123.457
boolean: einer der beiden logischen Werte true oder false. Wich-
tige Boole’sche Operatoren sind === und !==, hierbei ergibt x ===
y den Wert true genau dann, wenn Datentyp und Wert der beiden
Operanden x und y gleich sind, und umgekehrt liefert x !== y den
Wert true genau dann, wenn Typ oder Wert verschieden sind.6
Um den Unterschied zwischen === und == zu illustrieren, sei auf
die Webseite
http://haegar.fh-swf.de/Webtechnologie/equalities-js.html
verwiesen. Hier erkennt man, dass === die erwartete Gleichheit
zeigt (nur die Diagonale ist besetzt). Auch die „falsy“ Werte, also
Werte == false, sind einigermaßen übersichtlich (0, "0", NULL, leere Arrays oder Arrays mit Eintrag 0); Ähnliches gilt für „truthy“,
also == true, nur mit 1 statt 0. (Vergleiche die Situation in PHP).
•
function: Der Datentyp für eine Funktion.
•
object:
Der Datentyp für ein Objekt. Für den Operator typeof ist
auch ein Array vom Typ object [1, S. 18, 66].
•
null: ein spezieller Wert für das Null-Objekt;
•
undefined: ein spezieller Wert für „undefiniert“.
Der Datentyp eines gegebenen Wertes kann mit dem Operator typeof
ermittelt werden. Vorsicht ist dabei nur geboten, wenn man auf das
Nullobjekt prüfen möchte, denn typeof null ergibt object.
In JavaScript wird mit dem Schlüsselwort var eine Variable deklariert.
1
var x = 1;
2
var a = 2, b, c = "Hallo?";
3
document.write("a=" + a + ", b=" + b+ ", c=" + c); // a=2,
b=undefined, c=Hallo?
5
Reference#toFixed
6
http://www.hunlock.com/blogs/The_Complete_Javascript_Number_
Die auch in JavaScript vorhandenen Operatoren == und != sind nicht zu empfehlen, sie erzwingen bei Typenungleichheit unerwartete Ergebnisse; insbesondere ist ==
nicht transitiv [1, S. 117]: ""==0: true, 0=="0": true, ""=="0": false.
10
1.4
Prototypen
Dabei können in einer var-Anweisung auch mehrere durch Kommas getrennte Variablen deklariert werden. Auch wenn eine Variablendeklaration in JavaScript nicht notwendig und prinzipiell immer eine einfache Wertzuweisung wie x = 1 für eine neue Variable x möglich ist, wird
sie aus Gründen der Softwarequalität dringend empfohlen [1, S. 125,
130], [JSL].
Zwar besitzt JavaScript eine „C-Syntax“ (wie Java), durch die insbesondere ein Programm durch geschweifte Klammern in Blöcke {...} unterteilt wird. Anders als in Java oder C impliziert dies aber nicht automatisch, dass ein Block den Geltungsbereich (Scope) aller in ihm deklarierten Variablen begrenzt. JavaScript nutzt nicht Blöcke als Geltungsbereich für Variablen, sondern Funktionen [1, S. 40, 109].
1.4
Prototypen
Im Unterschied zu einer klassenbasierten objektorientierten Sprache
wie Java oder C++ gibt es in JavaScript keine Klassen, sondern nur Prototypen. Klassenbasierte Objektorientierung unterscheidet zwischen
Klassen, die die Struktur seiner Attribute und Methoden festlegen, und
den Objekten oder Instanzen, die daraus gebildet werden. Ein Objekt
kann niemals die Struktur seiner eigenen Klasse ändern.
In einer prototypenbasierten Programmiersprache wie JavaScript gibt
es keine Klassen, sondern nur Objekte. Sie sind prototypisch in dem
Sinne, dass ihre Struktur von anderen Objekten übernommen und vererbt werden kann und sie ihre eigene Struktur dynamisch erweitern
können. Ein Objekt in JavaScript ist eine Menge von Attribut-Wert-Paaren, ganz ähnlich wie HashMaps in Java oder assoziative Arrays in PHP.
Die Attribute heißen in JavaScript auch Properties. JavaScript eröffnet
grundsätzlich zwei Möglichkeiten, ein Objekt zu erzeugen, eine Deklaration in einer pseudoklassische Notation [1, S. 52] einerseits, und als
Objektliteral andererseits. Ist man an eine klassenbasierte Programmiersprache wie Java gewöhnt, ist die pseudoklassische Notation zwar
einsichtig, allerdings sind Objektliterale eleganter und werden allgemein empfohlen [1, §3]. Bei einem Objektliteral werden mit einem Doppelpunkt jeweils ein Attribut-Wert-Paar bestimmt und diese verschiedenen Paare durch Kommas getrennt:
1
var gemüse = {
2
"name": "Möhre",
3
"details": {
"color": "orange",
4
"size": 12
5
6
},
7
"toString": function() {return this.name + ", " + this.
details.color;}
8
}
11
1
JavaScript
9
document.write(gemüse.name + ", " + gemüse.details.color);
//
=> Möhre, orange
10
document.write("<br>" + gemüse["name"] + ", " + gemüse["details
"]["color"]);
11
document.write("<br>" + gemüse); // => Möhre, orange
Diese Syntax liegt dem wichtigen Format JSON zugrunde, das mittlerweile zu einem De-Facto-Standard der Datenübertragung im Web geworden ist.7 In den write-Funktionen erkennt man die verschiedenen
Abrufmöglichkeiten der Objekteigenschaften, nämlich mit der Punktnotation objekt.property (wie in Java oder C++), mit der Array-Notation mit eckigen Klammern, oder über definierte Objektmethoden wie
toString. Die toString-Methode braucht dabei (als einzige Methode)
nicht explizit genannt zu werden.
Die zweite Möglichkeit der Objekterzeugung ist wie in Java mit dem
Schlüsselwort new; dazu muss ein Konstruktor als Funktion definiert
werden, die mit der Selbstreferenz this die Eigenschaften (properties)
des Objekts festlegt, also dessen Attribute und Funktionen (Methoden):
1
var Angestellter = function(name, abteilung) {
2
this.name = name;
3
this.abteilung = abteilung;
4
this.toString = function() {return name + " in " + abteilung
;}
5
}
6
emil = new Angestellter("Emil", "IT");
7
document.write(emil.name + ", " + emil.abteilung); // => Emil,
// Objekterzeugung
IT
Der Konstruktor übernimmt somit in gewisser Hinsicht die Rolle der
Klasse in einer klassenbasierten Sprache wie Java.
Für Objekte gibt es den Operator in mit der Syntax "xyz" in obj , der
true zurückgibt, wenn das Objekt obj die Eigenschaft xyz besitzt, ansonsten false:
1
"details" in emil;
2
"details" in gemüse; // => true
// => false
Der in-Operator kann auch für Arrays (s.u.) verwendet werden, im Falle
eines numerischen Indizes müssen dabei allerdings die Anführungszeichen weggelassen werden.
Notiz. JavaScript ermöglicht auch das Konzept der „prototypischen
Vererbung“, eine Möglichkeit zur dynamischen Veränderung und
Erweiterung der Property prototype bestehender Objekte. Allerdings
ist das Konzept aus meiner Sicht unklar und nur begrenzt brauchbar
(es ermöglicht z.B. kein Überschreiben bestehender Properties),
daher wird es in diesem Skript, im Gegensatz zu früheren Versionen,
7
12
Für „lupenreines“ JSON kann es als Wert allerdings keine Funktionen geben.
1.5
Mathematische Funktionen und Konstanten
nicht erläutert. Auch die ansonsten ja sehr guten Quellen
https:
//developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_
oder
http://www.w3schools.com/js/js_
brachten mir nicht viel. (Gerne nehme ich das
Konzept wieder auf, wenn mich jemand vom Gegenteil überzeugt!)
Objects/Object/prototype
object_prototype.asp
1.5
Mathematische Funktionen und Konstanten
Das für mathematische Funktionen und Operationen zentrale Standardobjekt in JavaScript ist Math [JS, §15.8]. Es besitzt als Attribute die Konstanten
e=
∞
X
1
≈ 2,718281828 . . .
k!
Math.E
// Euler’sche Zahl
Math.LN2
// Natürlicher Logarithmus von 2:
k=0
ln 2 ≈ 0,6931471 . . .
Math.LN10
// Natürlicher Logarithmus von 10:
ln 10 ≈ 2,302585092994 . . .
Math.LOG2E // log2 e ≈ 1,44269504 . . .
Math.LOG10E // log10 e ≈ 0,4342944819 . . .
Math.PI
// Kreiszahl π =
∞
X 1
4
2
1
1
–
–
–
≈ 3,14159 . . .
16k 8k + 1 8k + 4 8k + 5 8k + 6
k=0
√
Math.SQRT2 //
2 ≈ 1,41421356237 . . .
√
_
Math.SQRT1 2// 1/ 2 ≈ 0,707106781 . . .
und als Methoden die mathematischen Funktionen
Math.abs(x)
//
Math.acos(x)
//
Math.asin(x)
//
Math.atan(x)
//
Math.atan2(x,y) //
Math.ceil(x)
//
Math.cos(x)
//
Math.exp(x)
//
Math.floor(x)
//
Math.log(x)
//
Math.max(x,y)
//
|x|
arccos x
arcsin x
arctan x
arctan(x/y)
dxe
cos x
ex (e hoch x)
bxc
ln x (natürlicher Logarithmus)
Maximum von x und y; kann auch
mehrere
Argumente bekommen
Math.min(x,y)
// Minimum von
x
und
y;
kann auch mehrere
Argumente bekommen
xy (x
y)
Math.pow(x,y)
//
Math.random()
// Pseudozufallszahl
Math.round(x)
// kaufmännische Rundung
hoch
z
0≤z<1
von x auf die
mit
nächste
ganze Zahl
Math.sin(x)
//
sin x
13
1
JavaScript
Math.sqrt(x)
//
Math.tan(x)
//
1.6
√
x
tan x
Kontrollstrukturen
1.6.1
Die if-Anweisung
JavaScript hat dieselbe bedingten Verzweigung wie Java:
if(ausdruck) {
code
} [else {
code2
}]
Hierbei bezeichnet ausdruck einen Boole’schen Wert und code eine Anweisung oder einen Block von Anweisungen, der else-Zweig ist optional (und daher in eckigen Klammern). code wird genau dann ausgeführt, wenn der Ausdruck nicht die Werte true annimmt, ansonsten
wird code2 ausgeführt, oder eben nichts, wenn der else-Zweig nicht
existiert.
Fallunterscheidungen mit switch-case
JavaScript verwendet zur Unterscheidung mehrerer alternativer Fälle
den Befehl switch. Die Syntax und Funktionsweise ähnelt der switchAnweisung in Java.
1
compute = function (operator, x, y) {
2
var z;
3
switch (operator) {
4
case "+": z = x + y;
5
case "-": z = x - y;
break;
6
case "*": z = x * y;
break;
7
case "/" | ":": x / y; break;
8
case "%": z = x % y;
9
default: z = NaN;
10
}
11
return z;
break;
break;
12
};
13
document.write(compute("+", 4, 3));
14
document.write("<br/>" + compute("%", 4, Math.PI));
// => 7
// =>
0.8584073464102069
1.6.2
Schleifen
Schleifen gehören als iterative Kontrollstrukturen in die Welt der imperativen Sprachen, rein funktionale Sprachen ermöglichen die Wiederholungen ausschließlich über Rekursionsaufrufe. JavaScript ist jedoch nicht rein funktional, es hat die vier Schleifenkonstrukte while,
do/while, for und for-in wie in Java. Die Syntax ist bei den ersten drei
14
1.6
Kontrollstrukturen
Schleifenkonstrukten identisch zu der in Java. Bei der for-Schleife ist
zu beachten, dass die Zählvariable mit var deklariert wird, so wie im
folgenden Beispiel, dass die Euler’sche Zahl iterativ berechnet:
1
var n = 5, y = 0;
2
for (var k = 0; k <= n; k++) {
3
y += 1/factorial(k);
4
document.write("<br/>" + k + ". Iteration für e: " + y);
5
}
Hier bezeichnet factorial die Fakultätsfunktion auf S. 22. Die Ausgaben lauten dabei sukzessive 2, 2.5, 2.66666. . . , 2.70833. . . , 2.71666. . . .
Mit der for-in-Schleife kann man auf die Eigenschaften eines Objekts
zugreifen, ähnlich wie die foreach-Schleife in PHP. Ihre Syntax lautet:
key in obj) {
obj[key] ...;
for(var
...
}
Hierbei sind for, var und in Schlüsselworte, während key die durch die
Eigenschaften (properties) des Objekts obj laufende Variable bezeichnet. Auf beide Werte von key und obj kann dann in der Schleife zugegriffen werden. Das Beispielprogramm
1
var gemüse = {
2
name: "Möhre",
3
for: "Max",
4
details: {
// funktioniert nicht im IE 8! (aber in IE 9)
5
color: "orange",
6
size: 12
7
},
8
toString: function() {return this.name + ", " + this.details
.color;}
9
10
}
for(var schlüssel in gemüse) {
document.write("<br/> gemüse[" + schlüssel + "] = " + gemüse
11
[schlüssel]);
12
}
ergibt die Ausgabe
gemüse[name] = Möhre
gemüse[for] = Max
gemüse[details] = [object Object]
gemüse[toString] = function () {return this.name + ", " + this.
details.color;}
Man sieht, dass Werte von Attributen mit einfachem Datentyp angezeigt
werden, Objekte als Attribut jedoch nicht. Bei einer Funktion wird der
Queltext angezeigt, d.h. im Programmablauf kann man auf den definierenden Quelltext von Objektfunktionen lesend zugreifen.
15
1
JavaScript
1.7
Wichtige Standardobjekte
Es gibt einige vorgegebene Objekte in JavaScript, so die drei Objekte
String, Number, Boolean zu den entsprechenden Datentypen, die Objekt
Math, Date und RegExp, das Function-Objekt und einige Error-Objekte, sowie die Datenstrukturen Array und JSON [JS, §15].
1.7.1
Arrays
Die einzige Datenstruktur oder Kollektion in JavaScript ist das Array.
Ein Array ist in JavaScript ein Objekt mit dem Konstruktor Array und
kann daher mit dem new-Operator erzeugt werden. Empfohlen ist jedoch stattdessen die Erzeugung mit [] (dem Literalausdruck):
1
var a = [];
2
a[0] = 10;
3
a[1] = 20;
4
a[5] = "Unterschied"; // Array mit unterschiedlichen Datentypen
5
document.write("Länge von a: " + a.length + "<br/>");
6
document.write("a = "+a+"<br/>");
// => 6
// => 10,20,,,,Unterschied
Arrays sind, wie in Skriptsprachen allgemein, dynamisch in ihrer Größe. Daher muss die Größe nicht bei der Erzeugung angegeben werden
wie beispielsweise in Java. Standardmäßig sind Arrays in JavaScript numerisch. Der Array-Index wird von 0 an hochgezählt. Die Elemente eines Array müssen nicht vom gleichen Typ sein. Das Attribut length gibt
die Länge des Arrays zurück, die sich bei numerischen Arrays aus dem
größten belegten Indexwert plus Eins ergibt. Ferner muss man das Array nicht sukzessive auffüllen, sondern kann durchaus nach Index 1 den
Index 3 belegen; der Eintrag mit Index 2 bleibt dann entsprechend undefiniert.
Ein Array kann ohne new und den Konstruktor mit einer einzigen Anweisung erzeugt werden, indem die Einträge in eckigen Klammern eingeschlossen und durch Kommas getrennt dem Arraynamen zugewiesen
werden:
1
var b = [5,7,12];
2
document.write("Länge von b: " + b.length + "<br/>"); // => 3
3
document.write("b = " + b + "<br/>");
// => 5,7,12
Damit ist das Array automatisch indiziert. (Eine Einzelbelegung der Arrayplätze ohne einen Index mit leeren eckigen Klammern, wie in PHP,
funktioniert in JavaScript jedoch nicht.)
Assoziative Arrays.
In JavaScript sind auch assoziative Arrays möglich. Als Schlüssel (keys)
können dabei Strings verwendet werden, aber auch beliebige Objekte
wie ein Array.
16
1.7
1
var tel = new Array();
2
tel["Meier"] = "9330-723";
3
tel["Müller"] = "0815/4711";
4
tel[a] = 42;
5
document.write("<br/>Länge: " + tel.length + "<br/>"); //
Wichtige Standardobjekte
// Arrays können assoziativ sein
// a ist das Array von oben!
lenght=0!
6
for(var key in tel) {
document.write("tel[" + key + "] = " + tel[key] + "<br/>");
7
8
} // tel[Meier] = 9330-723, tel[Müller] = 0815/4711,
tel[10,20„„Unterschied] = 42
Allerdings kann man bei einem assoziativen Array nicht mehr das Attribut length verwenden, es hat stets den Wert 0. Im Grunde sind Objekte
in JavaScript nichts anderes als assoziative Arrays, bis auf die Tatsache,
dass für ein Objekt im allgemeinen das Attribut length undefiniert ist,
während es für ein assoziatives den Wert 0 hat.
Eine wichtige Funktion eines Arrays ist forEach,8 die als Argument eine Funktion callback erhält, die sie auf jedes Array-Element anwendet.
callback muss dabei drei Argumente (value, index, array) vorsehen,
wobei value der beim Durchlaufen aktuell bearbeitete Elementwert ist,
index der entsprechende Array-Index, und array das aktuelle Array.
1
var callback = function(value, index, array) {
document.write("a[" + index + "] = " + value + ", ");
2
3
}
4
[2, 3, 5].forEach(callback); // => a[0] = 2, a[1] = 3, a[2] =
5,
Falls der dritte Parameter array nicht benötigt wird (wie in diesem Beispiel), kann er auch einfach weggelassen werden.
1.7.2
JSON
Die Abkürzung JSON steht für JavaScript Object Notation und bezeichnet zunächst ein weitverbreitetes Format zum Datenaustausch, also
insbesondere eine allgemeine Syntax zur Serialisierung (Speicherung)
und Übermittlung von Werten oder Objekten der sieben Datentypen
auf S. 9, bis auf die Funktionen und undefined. Dabei ist zu beachten,
dass in JSON nur Objekte erlaubt sind, deren Attribute Strings sind;
es muss also heißen "name"="Müller" statt name="Müller". In JavaScript
ist JSON ein spezielles globales Objekt, das die beiden entgegengesetzt
wirkenden („komplementären“) Methoden parse und stringify bereitstellt. Mit dem Befehl JSON.parse("...") wird der JSON-String in ein Objekt oder einen Wert in JavaScript umgewandelt. Ist der String nicht im
JSON-Format, so wird das Programm mit einem Syntaxfehler beendet.
8
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_
Objects/Array/forEach
17
1
JavaScript
Mit JSON.stringify(...) wird ein Objekt oder ein Wert in einen String
in JSON-Format umgewandelt.
1
var gemüse = {
2
name: "Möhre",
3
details: {
color: "orange",
4
size: 12
5
6
},
7
toString: function() {return this.name + ", " + this.details
.color;}
8
}
9
document.write("<br>" + gemüse);
// Möhre, orange
10
//var obj = JSON.parse(gemüse); // => Uncaught SyntaxError:
11
var jsn = JSON.stringify(gemüse);
12
document.write("<br>" + jsn); //
Unexpected token M
"name":"Möhre","details":"color":örange",ßize":12
13
var obj = JSON.parse(jsn);
14
document.write("<br>" + obj.name); // Möhre
15
var a = [2,3,5,7,11,13,17]; // Array von Primzahlen
16
jsn = JSON.stringify(a)
17
document.write("<br>" + jsn); // [2,3,5,7,11,13,17]
18
var b = JSON.parse("[2,3,5,7,11,13,17]");
19
document.write("<br>" + b); // 2,3,5,7,11,13,17
20
var c = JSON.parse("2357111317");
21
document.write("<br>" + c); // 2357111317
Man erkennt an dem Quelltextbeispiel, dass stringify alle Attribute in
Strings verwandelt und Funktionen (wie hier die toString-Methode)
einfach ignoriert. Weitere Details, insbesondere zur Syntax von JSON,
findet man im MDN.9
1.7.3
Ereignisbehandlung (Events und Event-Handler)
JavaScript ermöglicht die Ausführung von Anweisungen, die durch Eingabeereignisse, sogenannte Events, ausgelöst werden und in speziellen Methoden, den zuständigen Event-Handlern, implementiert sind.
Events und Event-Handler stellen auf diese Weise ein wesentliches
Bindeglied zwischen dem HTML-Dokument und JavaScript-Funktionen
dar. Grundsätzlich funkioniert die Ereignisbehandlung dabei wie folgt.
Jede Anwenderaktion, also eine Mausbewegung, ein Mausklick, ein Tastendruck, löst ein Ereignis oder Event aus. Ist für das Element, in dem
das Ereignis erzeugt wird, also beispielsweise eine Schaltfläche oder
ein Textfeld, ein Ereignisbehandler registriert, so ruft dieser diejenige,
9
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_
Objects/JSON; weitere Infos zu JSON: https://developer.mozilla.org/en-US/docs/JSON
18
1.7
Wichtige Standardobjekte
auf die er referenziert, und führt sie aus.
Das Grundkonzept der Ereignisbehandlung in JavaScript ist klar und
einfach: Für ein HTML-Element kann ein Event-Handler als ein Attribut festgelegt werden, das auf die JavaScript-Funktion oder die Anweisungfolge verweist, die bei Auslösen des entsprechenden Ereignisses
auszuführen ist. Die Attribute der Ereignisbehandler sind daran zu erkennen, dass sie mit on... beginnen. Beispielsweise ist der Ereignisbehandler für einen Mausklick onclick oder für das Loslassen einer Taste
onkeyup. Im allgemeinen kann nicht jeder Ereignisbehandler in jedem
HTML-Element registriert werden, eine Auflistung der Ereignisbehandler und der HTML-Elemente, für die sie definiert sind, findet man auf
selfhtml.org10 , eine umfangreiche Liste von Ereignissen in der Mozilla
Event Reference11 . Das folgende Beispielprogramm zeigt einige Ereignisbehandler für Maus- und Touchscreen-Ereignisse:
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<script>
6
var machma = function(event) {
alert(event + " ausgelöst!");
7
8
};
</script>
9
10
</head>
11
<body>
<p onclick="machma(’Klick’);">
12
Wer hier klickt, kann was erleben!
13
14
</p>
15
<p ondblclick="machma(’Doppelklick’);">
Wer hier doppelklickt, auch!
16
17
</p>
18
<p onwheel="machma(’Mausrad’);">
Hier müssen Sie das Mausrad drehen!
19
20
</p>
21
<p ontouchmove="machma(’Touchmove’);">
Hier müssen Sie wischen!
22
</p>
23
24
</body>
25
</html>
Das
Programm
ist
für
Ihr
Endgerät
abrufbar
//haegar.fh-swf.de/JavaScript/event-handler.html.
über
10
http://de.selfhtml.org/javascript/sprache/eventhandler.htm
11
https://developer.mozilla.org/en-US/docs/Mozilla_event_reference
http:
19
1
JavaScript
1.7.4
Date
Das Objekt Date regelt in JavaScript alle Berechnungen mit Datum und
Zeit. Grundlage ist die Unix-Zeit in Millisekunden mit dem Nullpunkt
1.1.1970, 0:00 Uhr UTC (Universal Coordinated Time). Es gibt mehrere
Konstruktoren für ein Date-Objekt
1
var today = new Date();
2
var birthday = new Date("December 17, 1985 03:24:00");
3
var geburtstag = new Date(1985,11,17);
4
var anniversaire = new Date(1985,11,17,3,24,0);
// liefert das aktuelle Datum und
Uhrzeit
Daneben gibt es noch einen Konstruktor, der als Argument die Unixzeit
in Millisekunden erwartet. Zwar gibt es eine Reihe von Methoden für
das Objekt, insbesondere zur Ausgabe von Datum und Uhrzeit, jedoch
sind die Ausgabeformate nur wenig spezifiziert und daher in ihrer Darstellung browserabhängig.12 Mit den folgenden Methoden kommt man
in der Regel jedoch hin:
1
var tag = ["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag
","Freitag","Samstag"];
2
var jetzt = new Date();
3
document.write("Heute ist " + tag[jetzt.getDay()] + ", der ");
4
document.write(jetzt.getDate() + "." + (jetzt.getMonth()+1) + "
." + jetzt.getFullYear());
5
var minuten = Math.floor(jetzt.getMinutes() / 10) + "" + jetzt.
6
document.write(", " + jetzt.getHours() + "." + minuten + " Uhr"
getMinutes() % 10;
);
7
document.write(", oder: " + jetzt.toISOString());
Eine Liste der möglichen Methoden für ein Date-Objekt findet man beispielsweise auf selfhtml.de.13
1.8
Funktionen und Callbacks
JavaScript ermöglicht funktionale Programmierung. In einer funktionalen Programmiersprache können Funktionen definiert werden und es
gibt keinen Unterschied zwischen Daten und Funktionen, Funktionen
können also auch in Variablen gespeichert und als Parameter übergeben werden [8, S. 6]. Die funktionale Programmierung steht damit im
Gegensatz zur imperativen Programmierung, denn die Trennung von
Daten und Programmlogik wird aufgehoben und jede Variable kann nur
einmal einen Wert bekommen (final in Java). Rein funktionale Spra12
Eine der Methoden, getYear(), ist sogar deutlich schlecht programmiert, sie gibt
die Differenz der Jahreszahl mit 1900 zurück!
13
http://de.selfhtml.org/javascript/objekte/date.htm [2016-07-17]
20
1.8
Funktionen und Callbacks
chen haben sogar gar keine Variablen oder Schleifen, denn ein Algorithmus wird hier durch eine Abfolge von Funktionsaufrufen ausgeführt;
für Wiederholungen werden also keine Schleifen verwandt, sondern
ausschließlich Rekursionen. So werden mathematisch unsinnige Ausdrücke der imperativen Programmierung wie x=x+1 vermieden. Andererseits widerspricht die funktionale Programmierung dem objektorientierten Paradigma, gemäß dem Objekte im Laufe ihres Lebenszyklus
verschiedene Zustände annehmen können, die Attribute also Veränderliche sein müssen. JavaScript ist ein Kompromiss aus beiden Ansätzen,
indem es beide Programmierparadigmen ermöglicht.
Funktionen gehören zu einem der elementaren Datentypen von JavaScript und bilden die modularen Einheiten der Sprache [1, S. 29]. Eine Funktion wird definiert durch das Schlüsselwort function, ihren Namen, eine von runden Klammern eingeschlossene Liste von durch Kommas getrennten Parametern, und den durch geschweifte Klammern
umschlossenenen Funktionsrumpf, der die Anweisungen der Funktion
enthält. Der Rückgabewert der Funktion wird mit dem Schlüsselwort
return ausgegeben:
1
function f(x,y) {
return x*x - y*y;
2
3
}
4
document.write("f(5,3) = " + f(5,3)); // => 16
Als funktionale Programmiersprache ermöglicht JavaScript die Definition anonymer Funktionen, auch „Lambda-Ausdrücke“ genannt:
1
var g = function(x,y) {return x*x + y*y}
2
document.write("<br>g(5,3) = " + g(5,3)); // => 34
Damit kann eine Funktion in einer Variablen gespeichert werden;
oder genauer gesagt als Referenz auf eine anonyme Funktion. Solche Funktionsreferenzen werden Rückruffunktionen oder Callbacks genannt, wenn sie in andere Funktionen als Argument eingesetzt und von
diesen aufgerufen werden.
Rückruffunktionen sind ein wichtiges Programmierkonzept der funktionalen Programmierung. Die Programmierung mit Rückruffunktionen folgt dem Entwurfsmuster der Kontrollflussumkehr (Inversion of
Control, IoC), denn in ein bereits vorher definiertes Programmmodul
(die aufrufende Funktion) bzw. eine gegebene Infrastruktur (z.B. die
API) kann „nachträglich“ auszuführender Quelltext definiert werden.
Viele JavaScript-Bibliotheken und Frameworks basieren auf diesem
Prinzip. Als ein einfaches Beispiel einer Rückruffunktion soll das folgende kurze Programm dienen:
var callback = function(x) {return x*x;};
var quadrate = [1, 2 , 3, 4, 5].map(callback);
document.write(quadrate); // => 1,4,9,16,25
21
1
JavaScript
Hier wird die Funktion callback(x) = x2 von der API-Funktion map eines Arrays aufgerufen und ausgeführt. Ihr „Rückruf“ ergibt am Ende
Abb.
1.3:
Aufrufstruktur
einer
Rückruffunktion.
(Modifiziert
nach
https:
//en.wikipedia.org/wiki/File:Callback-notitle.svg)
ein Array, das in jedem Eintrag den Quadratwert des originalen Arrays
enthält. Die Aufrufstruktur ist in Abbildung 1.3 skizziert.
Es gibt zwei Arten von Rückruffunktionen. Eine Rückruffunktion heißt
blockierend, oder auch synchron, wenn sie mit ihrer Referenz aufgerufen wird und ihr Ausführungsergebnis abgewartet wird, um weiterverarbeitet zu werden. Das obige Programmierbeispiel ist eine solche synchrone Rückruffunktion.
Bei einer verzögerten (deferred) oder asynchronen Rückruffunktion
wird die Rückruffunktion in einem anderen Prozess oder Thread ausgeführt, das Hauptprogramm wartet aber nicht auf das Ergebnis der
Rückruffunktion. Stattdessen wird das Ergebnis der Rückruffunktion
„möglichst schnell“ verarbeitet, wenn es eingetroffen ist. Typische Anwendungsfälle solcher verzögerten Rückruffunktionen sind Ereignisbehandler, die nach einem gefeuerten Ereignis die Rückruffunktion
ausführen, oder Aufrufe serverseitiger Skripte im Web.
In der nebenläufigen Programmierung spricht man bei asynchronen
Rückruffunktionen auch oft von Promises.14
Rekursionen.
In rein funktionalen Sprachen können Wiederholungen nicht durch
Schleifen, sondern nur durch Rekursionen durchgeführt werden, also
durch Selbstaufrufe von Funktionen. Als ein einfaches Beispiel sei die
Berechnung der Fakultät angegeben:
1
var factorial = function (n) {
if (n < 0) return NaN;
2
3
if (n === 0) return 1;
4
return n * factorial(n-1);
5
}
6
document.write("<br>factorial(5) = " + factorial(5)); // => 120
Hierbei ist NaN in JavaScript ein globales Objekt und wird in der Regel als
Ergebnis von im Reellen nicht erlaubten mathematischen Operationen
14
22
https://docs.angularjs.org/api/ng/service/$q#the-promise-api
1.8
Funktionen und Callbacks
√
wie Division durch 0 oder –1 zurückgegeben.15
In JavaScript wird häufig eine Rekursion mit einer definierten Verzögerung aufgerufen. Dazu muss die Funktion setTimeout verwendet werden,16 deren erstes Argument die aufzurufende Funktion und deren
zweites die Verzögerungszeit in Millisekunden ist:
1
var rekurs = function(n) {
2
if (n <= 0) return;
3
setTimeout("rekurs("+(n-1)+")",1000);
4
console.log("Aufruf rekurs("+n+")");
5
}
6
rekurs(5);
Um eine Funktion mit Argumenten aufzurufen, muss sie als String mit
dem gewünschten Argumentwert aufbereitet werden.17 Man erkennt
beim Ausführen des Skripts insbesondere in der Konsole des Firefox, in
der die Uhrzeiten der Ausgaben protokolliert werden, dass setTimeout
zwar den Aufruf der angegebenen Funktion verzögert, den weiteren
Ablauf der aufrufenden Funktion jedoch nicht aufhält: Die Ausgabe erscheint stets direkt nach Aufruf der Funktion, aber eine Sekunde vor
dem Rekursionsaufruf der nächsten Funktion.
Funktionen höherer Ordnung.
Eine Funktion höherer Ordnung ist eine Funktion, die als Argument eine
Funktion erwartet oder deren Ergebnis eine Funktion ist. Beispielsweise kann die Kepler’sche Fassregel (oder auch Simpson-Regel)
b–a
If (a, b) ≈
f (a) + 4f a+b
+
f
(b)
(1.1)
2
6
Rb
als Näherungsformel für das Integral If (a, b) = a f (x) dx einer integrierbaren Funktion f : [a, b] → R mit a, b ∈ R in JavaScript wie folgt
implementiert werden:
1
var I = function (f, a, b) {
return (b-a)/6 * (f(a) + 4*f((a+b)/2) + f(b));
2
3
}
4
5
var f = function(x,y) {return 3*x*x;}
6
var g = function(x,y) {return 1/x;}
7
var h = function(x,y) {return Math.sqrt(1 - x*x);}
8
9
10
document.write("I(f,0,1) = " + I(f, 0, 1)); // => 1
document.write("<br/>I(g,1,e) = " + I(g, 1, Math.E)); // =>
1.0078899408946134
15
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_
Objects/NaN [2016-07-17]
16
http://developer.mozilla.org/en/DOM/window.setTimeout [2016-07-17]
17
http://javascript.about.com/library/blrecursive.htm [2016-07-17]
23
1
JavaScript
11
document.write("<br/>I(h,0,1) = " + I(h, 0, 1)); // =>
0.7440169358562924
Die tatsächlichen Werte sind
Z 1
1
3 x2 dx = x3 = 1,
0
0
Z e
e
dx
= ln x = 1
x
1
1
(1.2)
und [12, S. 163]
√
Z 1p
arcsin
x
+
x
1 – x2
1 – x2 dx =
2
0
1
π
≈ 0,78539816339745.
=
4
0
(1.3)
(Für die Herleitung dieser Gleichung mit Integration
durch Substitution
√
2
siehe z.B. [3, §19.15]; der Graph der Funktion 1 – x beschreibt in dem
Intervall [0, 1] einen Viertelkreis mit Radius 1.)
Closures.
Im Gegensatz zu vielen Programmiersprachen wie Java können in JavaScript Funktionen auch innerhalb von Funktionen definiert werden. Eine solche innere Funktion hat wie jede Funktion Zugriff auf ihre eigenen
Variablen, aber auch auf die Variablen der Funktion, in der sie definiert
ist. Eine innere Funktion heißt Closure.18 Mit Closures kann man Funktionenscharen oder „Funktionenfabriken“ programmieren, also Funktionen, die parameterabhängig Funktionen zurückgeben:
1
var fabrik = function(name) {
return function(instrument) {
2
return name + " spielt " + instrument;
3
}
4
5
};
6
var a = fabrik("Anna");
7
var b = fabrik("Bert");
8
9
document.write(a("Geige") + ", " + b("Bass")); // Anna spielt
Geige, Bert spielt Bass
Die äußere Funktion definiert hier eine Variable name, auf die die innere anonyme Funktion auch Zugriff hat. Bei jedem Aufruf der äußeren
Funktion wird der jeweilige Wert der Variablen in der inneren Funktion „eingefroren“ und mit ihr zurückgegeben. Daher gibt die Funktion
anna() einen anderen Wert zurück als die Funktion bert().
Wofür braucht man Closures? Ein wichtiger Anwendungsfall für Closures ist das Verbergen von Hilfs- oder Arbeitsfunktionen, die nach außen vor dem Anwender gekapselt werden sollen. (In Java bis Version
7 war das nur mit private Objektmethoden möglich.) Als Beispiel sei
die folgende effiziente Berechnung der Fibonacci-Zahlen f0 = 0, f1 = 1,
18
24
http://jibbering.com/faq/notes/closures/#clClose [2016-07-17]
1.8
Funktionen und Callbacks
f2 = 1, f3 = 2, . . . , fn = fn–1 + fn–2 durch eine Rekursion innerhalb der
aufzurufenden Funktion angegeben:
1
var f = function (n) {
var c = function (f1, f2, i) {
2
// innere
"Arbeitsfunktion" (Closure)
if (i == n) return f2;
3
return c(f2, f1+f2, i+1);
4
5
}
6
if (n.toFixed(0) != n) return NaN;
7
if (n <= 0) return 0;
8
return c(0,1,1);
// nur für ganze Zahlen
definiert!
9
}
Ruft man nun die Funktion f mit einer natürlichen Zahl n auf, so wird
die innere Arbeitsfunktion c in Zeile 8 mit den Startwerten f0 = 0 unf
f1 = 1 und dem Zähler i = 0 aufgerufen, die sich wiederum in Zeile 6
selbst aufruft, falls keine der Abbruchkriterien erfüllt ist. Bei jedem Aufruf enthält der zweite Parameter von c die Summe der letzten beiden
Fibonacci-Zahlen, die
Zudem finden Closures Einsatz bei dem Funktionalkalkül, also der Algebra mit Funktionen. Man kann beispielsweise die Summe f + g zweier
Funktionen f , g : R → R definieren, indem man (f + g)(x) = f (x) + g(x)
definiert. In JavaScript könnte der Additionsoperator für zwei Funktionen beispielsweise so aussehen:
1
var add = function(f, g) {
return function(x) {return f(x) + g(x);};
2
3
};
4
5
var f = function(x) {return x*x - 1;};
6
var g = function(x) {return x*x + 1;};
7
var x = 2;
8
document.write(add(f,g)(x) + ", " + max(f,g)(x)); // => 8, 5
IIFE.
Ein von JavaScript-Entwicklern oft verwendetes Programmiermuster
ist ein „sofort aufgerufener Funktionsausdruck“ (immediately invoked
function expression, IIFE) meist mit Closures. Hierbei wird eine anonyme Funktion deklariert und sofort aufgerufen [11]:
Listing 2: Ein einfaches IIFE
1
2
(function() {
var tuwas = function() {
document.write("a="+ a);
3
4
}
25
1
JavaScript
5
6
var a = 1;
7
tuwas();
8
})();
// => ä=1"
Dieser Ausdruck tut zunächst nichts Besonderes, die Funktion tuwas
wird ausgeführt und verarbeitet die Variable a. Funktionsdeklaration
und Anweisungen sind in eine anonyme Funktion (Zeile 1) gekapselt,
die sofort ausgeführt wird (letzte Zeile), also schematisch dargestellt:
(function() {...}) ();
Die Funktion tuwas ist hier also eine Closure, die auf die Variable a zugreifen kann, auch wenn die äußere anonyme Funktion bereits abgelaufen ist. Das Programm würde aber dasselbe ausgeben, wenn man die
erste und letzte Zeile einfach wegließe. Nur wären dann tuwas und die
Variable a global definiert, und genau das ist das Problem: Globale Variablen sollten in JavaScript in komplexen Systemen möglichst vermieden werden. Durch IIFEs lässt sich deren Anzahl deutlich reduzieren.
Denn in Listing 2 beispielsweise sind die Variable a und die Funktion
tuwas außerhalb der anonymen Funktion unbekannt.
1.9
DOM
Das DOM (Document Object Model), ist eine Schnittstellenbeschreibung
für API’s, die Parser oder Browser zur Darstellung von HTML- oder
XML-Dokumenten verwenden können. Für JavaScript (aber auch für jede andere Programmiersprache) ist im DOM festgelegt, welche HTMLElemente als Objekte in zur Verfügung stehen und wie man dynamisch
neue Elemente hinzufügen oder löschen kann, aber auch, welche EventHandler für eine clientseitige Interaktion implementiert werden können. Obwohl das Grundkonzept des DOM als eine Baumstruktur von
Knoten einfach ist, sind die Bezeichnungen und Dokumentationen zunächst etwas verwirrend. Neben der Standardreferenz sei daher die
DOM-API von Gecko, dem HTML-Interpreter („Rendering Machine“)
der Mozilla-Programme,
https://developer.mozilla.org/en-US/docs/Gecko_DOM_Reference
empfohlen. Bevor wir das DOM in seinen für uns wesentlichen Details
untersuchen sei noch seine Historie erwähnt. Das DOM erschien sukzessive in mehreren Versionen, bis zur Version 3 Level genannt. Level 1
wurde im Oktober 1998 veröffentlicht, Level 2 im November 2000 und
Level 3 im April 2004 .
Das DOM trägt seinen Namen „Objektmodell“ vor allem, da es HTMLoder XML-Elemente als Objekte darstellt und in gegenseitige Beziehungen setzt, nämlich in eine Baumhierarchie wie in Abbildung 1.4 skizziert. In jedem Browser ist eine Objekthierarchie mit dem Wurzelobjekt window vorhanden (Abbildung 1.4). Eines seiner Kindelemente ist
26
1.9
DOM
Abb. 1.4: Objekt-Hierarchie des Browsers mit wichtigen Objekten, insbesondere das
document-Objekt.
und bestimmt den im Browserfenster sichtbaren Inhalt. Das
Objekt navigator liefert Informationen über den aktuellen verwendeten Browser, und history über die im aktuellen Tab oder Frame bisher
aufgerufenen Webseiten.19
document
1.9.1
Grundlegende Objekte des DOM: document und element
Das zentrale Interface des DOM ist Node,20 das einen allgemeinen
Knoten des DOM-Baumes beschreibt. Ein Node im DOM hat u.a.
stets einen parentNode und eine Liste von childNodes als Attribute und Methoden zum Hinzufügen oder Löschen von Kindknoten
(appendChild, removeChild). Die wichtigsten Implementierungen von
Node sind document und element. Ein element21 ist ein allgemeines Element eines HTML- oder XML-Dokuments.
Das document-Objekt22 repräsentiert den Inhalt des darzustellenden
Dokuments, also den Inhalt des Browser-Fensters. Mit der Methode
write() des document-Objekts können wir mit JavaScript in das BrowserFenster schreiben. Eine weitere wichtige Methode des documentObjektes ist getElementById("ID"), die ein Element des Dokuments als
Ergebnis liefert, dessen Attribut "id" als Wert den übergebenen String
hat. Beide Methoden haben wir bereits als einführende Beispiele für
JavaScript-Ausgaben im Abschnitt 1.1 kennen gelernt.
CSS-Anweisungen mit der Eigenschaft style
Ein element-Objekt hat unter anderem die Eigenschaft style, mit
der CSS-Deklarationen über JavaScript angewiesen werden können.21
Hierbei kann zu jedem HTML-Element dessen style-Attribut per JavaScript verändert werden, indem das zu jeweilige CSS-Attribut mit
einem neuen Wert belegt wird. Dabei ist zu beachten, dass ein CSSAttribut mit einem Bindestrich in JavaScript mit der BinnenmajuskelNotation (CamelCase) übernommen wird, da dort der Bindestrich als
Minuszeichen eine arithmetische Rechenoperation darstellen würde.
In dem folgenden Beispielprogramm wird die Hintergrundfarbe des
19
20
https://developer.mozilla.org/en-US/docs/Web/API/Window
https://developer.mozilla.org/en-US/docs/DOM/Node,
http:
//de.selfhtml.org/javascript/objekte/node.htm [2016-01-18]
21
https://developer.mozilla.org/en-US/docs/DOM/Element [2016-01-18]
22
https://developer.mozilla.org/en-US/docs/DOM/Document [2016-01-18]
27
1
JavaScript
Textes in einem span-Element per Schaltfläche verändert, indem die
Funktion aendereFarbe mit der gewünschten Hintergrundfarbe aufgerufen wird.
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<title>CSS-Anweisungen über JavaScript und DOM</title>
6
<script>
var aendereFarbe = function(farbe) {
7
document.getElementById("text").style.backgroundColor =
8
farbe;
}
9
10
</script>
11
</head>
12
<body>
13
Hintergrundfarbe ändern:
14
<button onclick="aendereFarbe(’green’);">Grün</button>
15
<button onclick="aendereFarbe(’red’);">Rot </button>
16
<span id="text">Die Zeichen an der Wand.</span>
17
</body>
18
</html>
Die entscheidende Anweisung dazu ist in Zeile 8, wo das CSS-Attribut
background-color in Binnenmajuskelnotation in JavaScript als Attribut
backgroundColor des Elementattributs style festlegt.
Das folgende auf Crockford [1, S. 42] zurückgehende Programm beispielsweise verändert mit einer Closure langsam die Hintergrundfarbe des Browserfensters (das Element document.body) von gelb (#ffff00)
auf weiß (#ffffff):
1
var fade = function(element, delay) {
2
var level = 0;
3
var step = function() {
4
var hex = level.toString(16);
5
element.style.backgroundColor = "#ffff" + hex + hex;
6
if (level < 15) {
7
level += 1;
// Rekursion nach delay [ms]
}
9
10
};
11
step();
12
// Variable der äußeren Funktion!
setTimeout(step, delay);
8
// level in hexadezimal
// erster Aufruf von step
};
13
14
fade(document.body, 100);
Hier rufen wir in Zeile 14 die Funktion fade auf und übergeben ihr das
28
1.9
DOM
DOM-Objekt document.body, also den Knoten des HTML-Dokuments, der
durch das Tag <body> erzeugt wurde, und die Verzögerungszeit delay in
Millisekunden. Die Methode fade setzt zunächst die Variable level auf
0 und definiert als Closure eine rekursive Funktion step, in der level
bei jedem hochgezählt wird und der Rekursionsaufruf mit Hilfe von
setTimeout um delay verzögert wird. In Zeile 11 wird dann step erstmals aufgerufen und die Rekursion gestartet. Bei jedem weiteren Aufruf
von step wird der Wert der Variablen level erhöht und als neuer Wert
in step verwendet. Die Variablen element, delay und level sind hier aus
Sicht der Closure also eine „globale“ Variable. Eleganter wäre sicherlich
eine Rekursion, die ohne solche grlobalen Variablen auskommt, z.B. indem sie sie als Parameter übergibt:
var step = function(element, delay, level) {
...
if (level < 15) {
step(element, delay, level + 1);
}
};
Leider ermöglicht setTimeout jedoch nur den verzögerten Aufruf von
Funktionen ohne Parametern.
Hinzufügen und Löschen von Elementen
In dem folgenden Beispielprogramm wird mit Hilfe der Standardmethoden des DOM createElement in Zeile 9 und appendChild in Zeile 10
durch JavaScript dynamisch ein <li>-Element an das <ol>-Element mit
der id angehängt.
Listing 3: DOM-Manipulation
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<title>DOM-Elemente</title>
6
<script>
7
var hinzufuegen = function() {
var wurzel = document.getElementById(’liste’);
8
var neu = document.createElement(’li’);
9
var neuerText = document.createTextNode("Die Zeichen an
10
der Wand.");
neu.appendChild(neuerText);
11
wurzel.appendChild(neu);
12
13
}
14
var loeschen = function() {
15
var element = document.getElementById(’liste’);
16
if (element.hasChildNodes()) {
29
1
JavaScript
element.removeChild(element.lastChild);
17
}
18
}
19
20
</script>
21
</head>
22
<body>
23
<button onclick="hinzufuegen();">Neuer Text</button>
24
<button onclick="loeschen();">Löschen</button>
25
<ol id="liste"></ol>
26
</body>
27
28
</html>
Entsprechend wird in Zeile 18 das letzte Kindelement des <ol>Elements mit der id mit der Methode removeChild gelöscht. Damit
es nicht zu einem Laufzeitfehler kommt, wenn kein Kindelement des
<ol>-Elements mehr vorhanden ist, wird das Löschen nur durchgeführt, wenn die Prüfung auf Existenz von Kindknoten mit der Methode hasChildNodes in Zeile 17 positiv ausgeht. Der DOM-Baum des Doku-
Abb. 1.5: Der DOM-Baum des HTML-Dokuments aus Listing 3.
ments ist in Abbildung 1.5 skizziert. Die gestrichelt gezeichneten Knoten werden durch das JavaScript-Programm je nach Eingabe hinzugefügt oder gelöscht.
1.9.2
Das window-Objekt
Das Browser-Fenster, in dem die HTML-Seite, die den JavaScript-Code
enthält, dargestellt wird, wird durch ein window-Objekt repräsentiert.
Das window-Objekt verfügt über Methoden wie prompt() alert() oder
confirm(), mit denen eigene Fenster zur Ein- und Ausgabe geöffnet werden können.
30
1.9
DOM
Darüberhinaus hat das window-Objekt Attribute, die sich von JavaScript
aus steuern lassen. Dazu gehört die Statuszeile (Attribut status), der
URL, der gerade dargestellt wird (location) oder die URL’s der Seiten,
die bis jetzt dargestellt wurden (history).
Tatsächlich sind sind sogar alle anderen JavaScript-HTML-Objekte entweder selbst Attribute des window-Objekts oder Attribute von Attributen des window-Objekts; so auch das document-Objekt, welches wir bereits öfter zum Schreiben in das Browser-Fenster benutzt haben. Das
window-Objekt ist also die Wurzel der Objekthierarchie in JavaScript.
Das window-Objekt verfügt über drei Methoden, mit denen Benutzerinteraktion möglich ist. Die erste (prompt()) haben wir bereits kennengelernt. prompt() öffnet ein eigenes Fenster oberhalb des BrowserFensters. Das Fenster enthält zwei Bereiche: im ersten wird eine Meldung dargestellt (erster Parameter von prompt()), im zweiten kann der
Benutzer Eingaben tätigen (der zweite Parameter von prompt() ist eine
Vorgabe im Eingabefeld).
confirm() erwartet vom Benutzer eine Bestätigung. Der einzige Parameter von confirm() ist eine Meldung, die im von confirm() aufgespannten Fenster dargestellt wird. Der Benutzer kann mit einem OKButton bestätigen (confirm() liefert dann true zurück) oder mit einem
Abbruch-Button abbrechen (confirm() liefert false zurück). Betrachten
wir dazu folgendes Programm:
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<script>
6
var factorial = function (n) {
7
if (n < 0) return NaN;
8
if (n === 0) return 1;
return n * factorial(n-1);
9
10
}
11
12
var einlesen = function () {
13
var n = document.getElementById("eingabe").value;
14
var wirklich = true;
15
if (n > 150) {
wirklich = confirm("Wirklich "+n+"! berechnen? Das kann
16
lange dauern . . .");
17
}
18
if (wirklich) {
19
document.getElementById("label").innerHTML = n + "! = " +
factorial(n);
20
21
} else {
document.getElementById("label").innerHTML = "Berechnung
von "+n+"! abgebrochen.";
31
1
JavaScript
}
22
23
}
24
</script>
25
</head>
26
<body>
27
<input type="text" id="eingabe"/>
28
<input type="button" value="Rechnen" onclick="einlesen();"/>
<p>Ergebnis: <span id="label"></span>.</p>
29
30
</body>
31
</html>
Hier muss der Anwender die Frage bestätigen, ob die Zahl, deren Fakultät berechnet werden soll, größer als 150 sein darf. In diesem Fall wird
ein confirm-Fenster aufgebaut. Wählt der Benutzer „OK“, gibt confirm
einfach true zurück. Der Boole’schen Variablen wirklich wird damit der
Wert true zugewiesen und die Fakultäten werden berechnet. Klickt der
Benutzer hingegen Abbrechen, wird von confirm false zurückgegeben,
wirklich wird damit false zugewiesen, und die Berechnung findet nicht
statt.
1.10
XMLHttpRequest und AJAX
Ein weiteres wichtiges Objekt in JavaScript ist XMLHttpRequest. Es ermöglicht, über einen aus JavaScript ausgelösten Request, also eine
Netzanfrage, Daten von einem beliebigen URL zu laden. Es spielt eine Schlüsselrolle bei AJAX-Anwendungen. Anders als der Name suggeriert, kann XMLHttpRequest nicht nur XML-Daten laden, sondern beliebige Formate, also auch Binärdateien wie Bilder und Streams. Ebenso kann es nicht ausschließlich HTTP-Requests auslösen, sondern auch
andere Anfragen über andere Protokolle wie FTP oder FILE.
Als Objekt besitzt es als wichtigste Attribute readystate, response und
status, als wichtigste Methoden open und send sowie den Event-Handler
onreadystatechange:23
Um
einen
HTTP-Request
auszulösen,
muss
zunächst
ein
XMLHttpRequest-Objekt mit new instanziiert werden:
var request = new XMLHttpRequest();
Mit der Methode open wird ein Request initialisiert. Sie benötigt dafür
mindestens zwei Parameter vom Typ String, sowie drei optionale Parameter:
23
32
https://developer.mozilla.org/en/DOM/XMLHttpRequest
1.10
XMLHttpRequest und AJAX
void open(String method, String url, [boolean async, String user,
String password])
Hierbei bezeichnet method die HTTP(S)-Methode, die einen der Werte "GET", "POST", "PUT" oder "DELETE" annehmen kann und url der angefragte URL, also der Pfad zur angefragten Datei. Der optionale Boole’sche Parameter async bestimmt, ob die Anfrage asynchron ablaufen
oder das Programm die Antwort abwarten soll; er ist im Default auf true
gesetzt. Die beiden optionalen Parameter user und password schließlich ermöglichen eine etwaig notwendige Authentifizierung für Zugang
zu dem URL. Mit der Methode send wird die Anfrage abgeschickt. Dazu
muss sie vorher mit open initialisiert sein. Ist sie asynchron initialisiert,
so läuft das Programm weiter, andernfalls wird das Ende der Methode abgewartet. Die Methode kann optional mit zu übertragenden Daten als Parameter aufgerufen werden, z.B. Formulardaten. Die Funktion
onreadystatechange ist ein Event Handler, der auf Änderungen des Attributs readyState reagiert, das wiederum einen der 5 möglichen Werte 0,
1, 2, 3, 4 annehmen kann:
readyState
Zustand
0
1
2
UNSENT
3
4
LOADING
OPENED
HEADERS_RECEIVED
DONE
Bedeutung
open ist noch nicht aufgerufen
send ist noch nicht aufgerufen
send ist aufgerufen und Headers und Status des
Requests sind verfügbar
Daten werden empfangen
Die Anfrage ist erledigt
Die beiden ersten Zustände dienen der Vorbereitung der Kommunikation, der Request wird erst durch die send()-Methode ausgelöst. Diese Kommunikation läuft übrigens asnchron ab, d.h. die send()-Methode
blockiert oder beeinflusst nicht andere JavaScriptprogramme der Seite.
Diese Möglichkeit, asynchrone Anfragen an den Server zu stellen, ohne
dass die dargestellte Webseite blockiert wird oder gar neu geladen werden muss, bildet die technische Grundlage für AJAX-Anwendungen, die
wir in Abschnitt 1.10.2 behandeln werden.
Der Event Handler ist zunächst nur eine „leere“ Funktion und muss
implementiert werden, so wie wir es ja auch von den Event-Handlern
des DOM bereits kennen. Üblicherweise wird hier die Antwort der
Anfrage verarbeitet, die wiederum in dem Attribut response gespeichert ist. Im Überblick ergibt sich damit das Zustandsdiagramm eines XMLHttpRequest-Objekts nach Abbildung 1.6. Die Details sind im
technischen Report unter http://w3.org/TR/XMLHttpRequest des W3Konsortiums festgelegt.
Beginnen wir mit einem einfachen Beispiel, das die vier Schritte Erzeugung eines Requests, Implementierung des Event Handlers, Öffnen und
Senden eines Requests zeigt:
33
1
JavaScript
Abb. 1.6: Zustandsdiagramm eines XMLHttpRequest-Objekts. Vgl. [6, S. 26].
1
var request = new XMLHttpRequest();
2
request.onreadystatechange = function() { // Event Handler
if (request.readyState === 4) {
3
// Request ist erledigt
document.write(request.response);
4
}
5
6
}
7
request.open("GET", "http://localhost/");
8
request.send();
Die Reihenfolge der Anweisungen bei der Ausführung des Programms
entspricht dabei nicht ganz der Reihenfolge ihrer Implementierung:
Der Event-Handler onreadystatechange wird für das Request-Objekt
zwar zu Beginn des Objektes implementiert, aber erst nach Beenden
der Anfrage verarbeitet, also nach dem send. Er sollte aber natürlich bereits implementiert sein, wenn der Request gesendet wird.
Zu beachten ist, dass bei den Standardeinstellungen des Webservers
eine Anfrage in JavaScript nur für denselben Server möglich ist, auf
dem sich auch das JavaScript-Programm befindet. Möchte man über
XMLHttpRequest einen anderen Server aufrufen, so müssen die Einstellungen dieses Servers entsprechend angepasst sein. Man spricht in diesem Falle von Cross Origin Resource Sharing (CORS).24 Damit JavaScript
einen XMLHttpRequest weiter verarbeitet, muss der Server nämlich die
Antwort mit dem HTTP-Header
Access-Control-Allow-Origin: *
senden. Möglich ist auch die Variante Access-Control-Allow-Origin:
http://foo.bar, wobei foo.bar die Domain ist, von der die Anfrage, also
das JavaScript-Programm, kommt. Ein PHP-Skript kann diesen Header
individuell senden, wenn es mit der Zeile
header("Access-Control-Allow-Origin: *");
1
beginnt.
24
34
http://www.w3.org/wiki/CORS_Enabled
1.10
XMLHttpRequest und AJAX
Mit dem Standardobjekt FormData kann man dynamisch erstellte Formulardaten versenden.25 Ein einfaches Beispiel übermittelt die Formulardaten x=2 an ein PHP-Skript:
1
var request = new XMLHttpRequest();
2
request.onreadystatechange = function() { // Event Handler
if (request.readyState === 4) {
3
document.write(request.response);
4
}
5
6
};
7
request.open("POST", "http://haegar.fh-swf.de/AJAX/spiegel.php"
8
var data = new FormData();
9
data.append("x",2);
10
request.send(data);
);
Mit der folgenden Änderung der Zeile 9 erhält man sogar die Übertragung eines dynamisch in JavaScript erzeugten Arrays prim:
1
data.append("prim[]",2); data.append("prim[]",3); data.append("
prim[]",5);
1.10.1
Aktionen nach dem Request: onreadystatechange
Bislang haben wir nur Beispiele von XMLHttpRequest-Objekten kennengelernt, deren Event-Handler onreadystatechange einfach nur die
empfangene Antwort des Servers, d.h. den String request.response,
ausgibt. Häufig werden allerdings komplexere Daten übermittelt, die
in dem Event-Handler weiter verarbeitet werden sollten. Grundsätzlich können diese Daten als beliebiger Text vorliegen, üblich sind aber
Strings in den drei folgenden Formaten HTML, JSON und XML. Die Verarbeitung
• HTML-Strings werden dabei in der Regel nicht weiterverarbeitet und unverändert dargestellt, also so wie in den obigen
XMLHttpRequest-Beispielen mit request.response.
• Ein JSON-String kann mit der Standardmethode JSON.parse() direkt in ein JSON-Objekt umgewandelt werden, also beispielsweise
var objekt = JSON.parse(request.respnse);
Dieses Objekt kann nun in JavaScript beliebig weiterverarbeitet
werden.
25
https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/FormData/ [2016-
07-17]; siehe auch:
https://developer.mozilla.org/en/DOM/XMLHttpRequest/FormData/Using_FormData_
Objects
35
1
JavaScript
• Ein XML-String kann manuell geparst werden. Ist die Serverantwort beispielsweise ein SVG-String, so kann die Grafik direkt im
Browser dargestelllt werden.
Betrachten wir als Beispiel das folgende Programm, das die Daten
des aktuellen Sondernangebots im JSON-Format erhält und im EventHandler verarbeitet:
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<script>
6
var ladenJSON = function() {
7
var request = new XMLHttpRequest();
8
request.onreadystatechange = function() {
if (request.readyState === 4 && request.status === 200)
9
{
10
var artikel = JSON.parse(request.response);
11
var element = document.getElementById("angebot");
12
var ausgabe = "Kaufen Sie unseren " +
artikel.name + " mit der Nummer " +
13
artikel.id + " für " + artikel.preis + " €!";
14
element.innerHTML = ausgabe;
15
}
16
17
};
18
request.open("GET", "http://haegar.fh-swf.de/json-artikel
.php");
request.send();
19
20
21
}
</script>
22
</head>
23
<body>
24
<h1>Unser Preisknaller!</h1>
25
<p id="angebot">Klicken Sie, um unser heutiges Sonderangebot
zu sehen:</p>
26
<input type="button" value="JSON Eingabe" onclick="ladenJSON
();";>
27
</body>
28
</html>
Bei Aufruf der HTML-Datei erscheint unter der Überschrift zunächst
der Text „Klicken Sie, um unser heutiges Sonderangebot zu sehen:“.
Klickt man den Button, so wird sein Event-Handler onclick aufgerufen,
der wiederum die Funktion ladenJSON aufruft. In ihr wird ein XMLHttpRequest request erzeugt, das ein PHP-Skript auf dem Hägar-Server
aufruft und einen JSON-String empfängt, der in dem Event-Handler
onreadystatechange zu einem vollständigen Text „Kaufen Sie unseren
36
1.10
XMLHttpRequest und AJAX
Prozessor mit der Nummer 123 für 99.99 €!“ ausformuliert wird und
damit den vorherigen Text im <p>-Element überschreibt, allerdings nur
unter der Bedingung, dass die HTTP-Antwort eingetroffen und der
Status-Code OK lautet. (Der Status-Code 200 besagt, dass die HTTPAnfrage erfolgreich verlief.) Das PHP-Skript auf Hägar ist übrigens nur
ganz kurz und gibt einen immer gleichen String aus:
1
<?php
2
header("Access-Control-Allow-Origin: *");
3
echo ’{
4
"id"
5
"name" : "Prozessor",
"preis": 99.99
6
}’;
7
8
: "123",
?>
Wir können uns jedoch leicht vorstellen, dass das PHP-Programm stattdessen einen Datenbankzugriff ausführen könnte und so das Tagesangebot aktuell aus der Datenbank selektiert.
1.10.2
AJAX
Viele moderne Webanwendungen basieren auf AJAX (Asynchronous JavaScript and XML), das wiederum auf dem XMLHttpRequest-Objekt beruht. Ziel vieler AJAX-Anwendungen ist es, das starre Architekturmuster des Request-Response von HTTP zu überwinden, bei dem Anfragen nur vom Client ausgehen und der Server nur reagieren kann. Haben sich jedoch zentrale Daten auf dem Server aktualisiert, so kann der
Server die Clients nicht darüber informieren. Bei einer Internetauktion
beispielsweise würde ein Bieter nicht erfahren, wenn ein neues Gebot
eines anderen vorläge, wenn er nicht selber immer wieder nachfragt.
Ebenso würde bei einem Chatserver beispielsweise ein angemeldeter
User nicht erfahren, wenn eine neue Nachricht eines Anderen eingetroffen wäre. Zwei Verfahren, den Client mit Hilfe eines asynchronen
XMLHttpRequest-Objekts automatisiert von einer Datenaktualisierung
in Kenntnis zu setzen, sind das Polling und das Long Polling, siehe Abbildung 1.7. Beim Polling wird in einem regelmäßigen Zeitabstand ∆t
eine Anfrage an den Server gestellt, auf die er unverzüglich antwortet.
Beim Long Polling dagegen stellt der Client eine asynchrone Anfrage,
auf die der Server jedoch erst nach einer Aktualisierung seiner Daten
antwortet. Empfängt der Client eine Antwort, stellt er sofort eine neue
Anfrage, auf die er wiederum eine Antwort erst nach erfolgter Datenaktulisierung auf dem Server erhält.
Das Polling ist ein in JavaScript clientseitig relativ einfach zu implementierendes Verfahren, siehe Listing 4. Allerdings hat es den großen Nachteil, dass eine große Anzahl unnützer Anfragen gestellt werden, wenn
die Anfragefrequenz höher als die typische Änderungsfrequenz der Da-
37
1
JavaScript
Abb. 1.7: Polling und Long Polling als Methoden zur Synchronisation eines zentralen
Datenbestandes. Vgl. [6, S. 27f].
ten ist. Vor allem Server mit sehr vielen Usern können dadurch unnötig
belastet werden. Das Long-Polling dagegen lastet den Server optimal
aus, da er nur noch zu den notwendigen Zeitpunkten an die betroffenen Clients Nachrichten sendet.
Listing 4: Polling durch Rekursion
1
<!DOCTYPE html>
2
<html>
<head>
3
4
<meta charset="UTF-8"/>
5
<script>
6
var periode = 2000; // Polling-Periode in ms
7
var data;
8
var request = new XMLHttpRequest();
9
10
request.onreadystatechange = function() { // Event Handler
if (request.readyState === 4) {
11
document.getElementById("ausgabe").innerHTML = request.
12
response;
}
13
14
}
15
16
var poll = function(x) {
17
if (x > 5) return;
18
request.open("POST", "http://haegar.fh-swf.de/AJAX/spiegel.
// Basisfall: beende Rekursion
php");
38
19
data = new FormData();
20
data.append("x", x);
21
data.append("y", x*x); // y-Werte
// x-Werte
1.11
Web Workers: Nebenläufigkeit in JavaScript
22
request.send(data);
23
setTimeout("poll("+(x+1)+")", periode); // Rekursionsaufruf
24
}
25
26
poll(0); // Start der Rekursion
</script>
27
</head>
28
29
<body>
30
31
<div id="ausgabe"></div>
32
<p>(<i>y</i> = <i>x</i><sup>2</sup>)</p>
</body>
33
34
</html>
1.11
Web Workers: Nebenläufigkeit in JavaScript
Im Gegensatz zu Java oder C++ bietet JavaScript keine Möglichkeit,
einzelne Threads zu programmieren, ein JavaScript-Programm wird
grundsätzlich in nur einem einzigen Thread ausgeführt [7]. Bei rechenintensiven Algorithmen bewirkt diese Eigenart, dass das Programm solange blockiert und keine weiteren Anweisungen ausführt oder auf Benutzereingaben reagiert, bis die Berechnungen beendet sind.
Allerdings bietet JavaScript zur nebenläufigen Programmierung das
Konzept der Workers oder Web Workers an.26 Ein Worker in JavaScript
ist ein Standardobjekt, das als Teilprozess im Hintergrund läuft, also
nachrangig zum Hauptprozess des eigentlichen JavaScript-Programms,
und isoliert für sich ausgeführt wird, insbesondere unabhängig von
weiteren Benutzereingaben. Interaktionen sind ausschließlich mit dem
Hauptprozess über die Methoden postMessage und terminate sowie den
Event-Handler onmessage möglich. Um ein Worker-Objekt zu erzeugen,
muss Quelltext des Workerprogramms in einer externen JavaScriptDatei ausgelagert vorliegen. Dies sei beispielsweise worker.js; dann
wird im Hauptdokument das Worker-Objekt wie folgt erzeugt:
var worker = new Worker("worker.js");
Die Kommunikation zwischen dem Hauptprozess und dem Workerprozess verläuft nun über den Austausch von Nachrichten. Wie in Abbil-
Abb. 1.8: Kommunikation zwischen Hauptprozess und Worker.
dung 1.8 dargestellt reagiert der Event-Handler des einen Prozesses
26
http://dev.w3.org/html5/workers/http://dev.w3.org/html5/workers/,
https:
//developer.mozilla.org/en-US/docs/DOM/Worker [2016-07-17]
39
1
JavaScript
stets auf die von postMessage gesendeten Nachricht des jeweils anderen
Prozesses. Ein einfaches Beispiel ist das folgende HTML-Dokument, das
einen bidirektionalen Nachrichtenaustausch bewirkt:
1
<!DOCTYPE HTML>
2
<html>
3
<head><meta charset="UTF-8"/><title>Worker example: Send
Messages</title></head>
4
<body>
5
<p>
6
<script>
7
var worker = new Worker("worker.js");
8
worker.onmessage = function (event) {
document.getElementById("result").innerHTML = event.data;
9
};
10
</script>
11
12
<input type="text" id="message" value="Hallo Welt!"/>
13
<input type="button" value="Send"
onclick="worker.postMessage(document.getElementById(’
14
message’).value);"/>
15
</p>
16
<p>Message: <output id="result"></output></p>
17
</body>
18
</html>
Die zugehörige Worker-Datei lautet:
1
var count = 0;
2
self.onmessage = function (event) {
3
count++;
4
self.postMessage(event.data + " (No " + count + ")");
5
};
1.12
DOM Storage
DOM Storage, oder Web Storage nach W3C,27 ist eine Web-Technik, mit
der über einen Browser Daten von mehreren MB Größe auf einem Rechner gespeichert werden können. DOM Storage ermöglicht damit eine
persistente Datenspeicherung ähnlich wie ein Cookie, allerdings mit
der entscheidenden Einschränkung, dass Datenzugriffe nicht über das
HTTP-Protokoll geschehen, sondern vollständig vom Browser gesteuert und durch clientseitige Skripte (in der Regel JavaScript) durchgeführt werden.
DOM Storage speichert Daten in einem assoziativen Array, in dem die
Schlüssel und Werte Strings sind. Sie können also in JavaScript einfach
27
Die Namensgebung ist einigermaßen kompliziert, nach W3C heißt es Web Storage,
aber auch DOM Storage ist möglich, allerdings setzt es nicht die Existenz eines tatsächlichen Dokument-Objekts voraus http://dev.w3.org/html5/webstorage/#terminology.
40
1.12
DOM Storage
mit JSON.parse als Objekt erzeugt und mit JSON.stringify in DOM Storage gespeichert werden.
1.12.1
Lokale und sessionspezifische Speicherung
Daten, die lokal gespeichert werden, die sogenannten Local Shared Objects (LSO), sind mit einer Domain verknüpft und bleiben auch nach
Beenden des Browsers bestehen. Alle Skripte einer Domain, von der
aus die Daten gespeichert wurden, können auf die Daten zugreifen. Bei
Mozilla Firefox werden die Daten in der Datenbankdatei webappsstore.sqlite gespeichert.
Session-spezifisch gespeicherte Daten sind mit dem erzeugenden
Browser-Fenster verknüpft und auf dieses beschränkt. Gespeicherte
Daten werden beim Schließen des Browser-Fensters gelöscht. Diese
Technik bietet die Möglichkeit, mehrere Instanzen derselben Anwendung in verschiedenen Fenstern laufen zu lassen, ohne dass es zu einer
gegenseitigen Beeinflussung kommt, was von Cookies nicht unterstützt
wird.
In JavaScript werden beide Speicherarten durch die Standardobjekte
localStorage und sessionStorage realisiert und bieten die Methoden
setItem und getItem zum Speichern und Lesen der Daten:
// Speichert ein Key-Value-Paar im localStorage
localStorage.setItem(’schluessel’, ’wert’);
// Liest den eben gespeicherten Wert aus und zeigt ihn an
alert(localStorage.getItem(’schluessel’)); // => wert
// Speichert ein Key-Value-Paar im sessionStorage
sessionStorage.setItem(’key’, ’value’);
// Liest den eben gespeicherten Wert aus und zeigt ihn an
alert(sessionStorage.getItem(’key’)); // => value
Für weitere Informationen zu DOM Storage mit JavaScript siehe http:
//www.jstorage.info/
41
1
JavaScript
1.13
Übungsaufgaben
1.13.1
Grundlagen JavaScript
Aufgabe 1.1 (Ein- und Ausgabe in JavaScript) (a) Erstellen Sie ein Programm in JavaScript, das einen Euro-Betrag in Dollar zu einem einzugebenden Kurs umrechnet.
Die Umrechnung soll dabei nach Drücken des = -Buttons geschehen.
(b) Modifizieren Sie Ihr Programm so, dass die Ein- und Ausgabe mit Dezimalkommas möglich ist. Finden Sie die dazu
nötigen Funktionen mit Hilfe der Objektreferenz auf http:
//de.selfhtml.org/javascript/objekte/, heraus, dort unter einem
passenden der vordefinierten Objekte.
Aufgabe 1.2 (Eingabe und Ereignisbehandlung) Progammieren Sie eine
Webseite zur Datenerfassung für den Browser, in dem für ein Unternehmen die Eigenkapitalquote und die Fremdkapitalquote einzugeben
ist:
Geben Sie die folgenden Daten Ihres Unternehmens ein:
Eigenkapitalquote:
Fremdkapitalquote:
%
%
Dabei soll nach jedem Tastendruck in einem der Eingabefelder der Differenzwert zu 100 im jeweiligen anderen Eingabefeld erscheinen. D.h.
tippt man in das untere Feld 4 ein, so soll sofort 96 im oberen erscheinen, und umgekehrt.
Hinweise: (i) Den notwendigen Ereignisbehandler finden Sie z.B. unter
https://wiki.selfhtml.org/wiki/JavaScript/Event-Handler. (ii) So wie
Sie in JavaSript aus einem Eingabefeld lesen können, können Sie auch
etwas hineinschreiben, d.h. das geeignete Attribut verändern!
Aufgabe 1.3 (Funktionale Programmierung) Nach der Regel von Weddle,
auch Newton-Cotes-Formel vom Grad n = 6 genannt, wird das Integral
Rb
If (a, b) = a f (x) dx einer integrierbaren Funktion f : [a, b] → R mit a,
b ∈ R durch
b–a 4a+2b + 272f 3a+3b
If (a, b) ≈
41f (a) + 216f 5a+b
+
27f
6
6
6
840
+ 216f a+5b
+ 41f (b)
+ 27f 2a+4b
6
6
42
1.13
Übungsaufgaben
angenähert. Programmieren Sie diese Formel in JavaScript als eine
Funktion, die neben den Zahlen a und b die Funktion f als Parameter
erwartet. Wenden Sie damit die Näherungsformel auf die Integrale
Z 10
0
7x6 – 8x3 + 2x – 1 dx,
Z 1
0
sin x dx
und
Z 10
0
sin x dx
und vergleichen Sie die Ergebnisse mit den analytisch berechneten
Werten. Die ersten beiden Integrale sollten dabei recht genau angenähert werden, das dritte dagegen hat einen Fehler von fast 20% des tatsächlichen Wertes. Haben Sie eine Erklärung dafür?
Aufgabe 1.4 (Funktionsauswertung mit map) Erstellen Sie in JavaScript
eine Funktion map(f,a), die eine Funktion f und ein Array a von Zahlen
als Parameter erwartet und ein Array zurück gibt, das aus den Funktionswerten f(a[i]) der Werte von a besteht. Beispielsweise soll für die
Funktion f (x) = x3 gelten:
map (x3 , [1, 2, 3, 4]) = [1, 8, 27, 64]
1.13.2
JavaScript, DOM und AJAX
Aufgabe 1.5 (DOM und Ereignisbehandlung in JavaScript) (a) Erstellen Sie
ein JavaScript-Programm, das zwei Buttons Grün und Rot anzeigt,
die beim Anklicken entsprechend die Schriftfarbe in einem <p>-Element
ändert. Dabei soll zum Verändern der Farbe nur eine Funktion verwendet werden.
a)
b)
(b) Schreiben Sie ein JavaScript Programm, das durch das Anklicken
des Buttons Stern erzeugen einen neuen Stern unter dem Button einfügt, indem ein neues <img>-Element erzeugt wird. Entsprechend soll
beim Anklicken der Schaltfläche Stern löschen der letzte Stern gelöscht werden. Die Bilddatei für den Stern finden Sie auf dem HägarServer.
Aufgabe 1.6 (Laden von JSON-Elementen mit AJAX) In dem Verzeichnis http://haegar.fh-swf.de/Webtechnologie/Aufgaben/Woche_1-15/ befinden sich der Quelltext einer HTML-Seite und mehrere JSON-Dateien
mit Informationen zu verschiedenen Städten. Die HTML-Seite liefert
das Gerüst, mit dem die Städteinformationen HTML5-konform angezeigt werden sollen. (Insbesondere sollte sie auch auf mobilen Endgeräten ansprechend dargestellt werden, testen Sie es!)
43
1
JavaScript
Erweitern Sie den Quelltext um JavaScript-Anweisungen, so dass beim
Anklicken der Buttons die jeweilige JSON-Datei geladen wird und deren Informationen in den Elementen titel und inhalt angezeigt werden. Zum Beispiel soll nach dem Laden der Datei Istanbul.json mit den
Daten
{
"name": "Istanbul",
"state": "Turkey",
"capital": false,
"residents": "14 million",
"remark": "Istanbul is the most populous city of Turkey and
of Europe."
}
der folgende Text erscheinen:
Istanbul
Istanbul is a city in Turkey with a metropolitan area of
14 million residents. Istanbul is the most populous city
of Turkey and of Europe.
Aufgabe 1.7 (Google Visualization API) (a) Erstellen Sie mit Hilfe der Google Visualization API und der Dokumentation
https://developers.google.com/chart/interactive/docs/gallery
eine Webseite mit den folgenden drei Diagrammen,
44
1.13
Übungsaufgaben
die die Studierendenzahlen der FH in 2012 wiedergeben:
Standort
Hagen
Iserlohn
Meschede
Soest
Erstsemester
790
859
504
803
Studierende
1689
1640
1127
2007
Erstellen Sie dabei zunächst jedes der Diagramme in einer eigenen Datei, bevor Sie sie auf eine gemeinsame Seite zusammenfassen.
(b) Erstellen Sie mit Hilfe der Google Visualization API die folgenden
Funktionsgraphen im Browserfenster:
45
2
jQuery
2
jQuery
JavaScript spielt in der professionellen Webprogrammierung eine
ganz zentrale Rolle, insbesondere aufgrund seiner Fähigkeiten, DOMManipulationen und asynchrone HTTP-Requests dynamisch auszulösen. Da allerdings die dazu notwendigen Anweisungen in JavaScript
recht aufwendig sind — mit Funktionen wie getElementById zur DOMManipulation oder dem im wesentlichen immer gleichen Dreischritt zur Implementierung eines asynchronen HTTP-Requests — wurden immer wieder spezielle JavaScript-Bibliotheken entwickelt, um
die Programmierung zu vereinfachen. Eine der wichtigsten und verbreitesten dieser Bibliotheken ist jQuery, das zu einem De-FactoStandard der Webprogrammierung geworden ist: Nach W3Techs (http:
//w3techs.com/) wird jQuery auf fast 70 % aller Webauftritte eingesetzt.
2.1
Entstehungsgeschichte, Lizenz, Dokumentation
jQuery wurde 2006 von John Resig veröffentlicht und ist seit April 2013
in der Version 2 verfügbar. Da diese Version nur noch mit modernen
Browserversionen kompatibel ist (also insbesondere nicht mit dem Internet Explorer bis einschließlich Version 8), wird die Version 1 parallel weitergeführt. jQuery ist als freie Software unter der MIT-Lizenz
(https://tldrlegal.com/license/mit-license) verfügbar, darf also auch
für kommerzielle Zwecke verwendet werden, wobei jede Kopie ihr Copyright beinhalten muss.
Die API-Dokumentation von jQuery ist unter
http://api.jquery.com/
verfügbar,
ein
sehr
gutes
Tutorial
unter
http:
//www.w3schools.com/jquery/.
2.2
Einbindung der Bibliothek
Da jQuery eine JavaScript-Bibliothek ist, benötigt man zur dessen Verwendung keine besondere Installation, sondern bindet es per Hyperlink in das HTML-Dokument ein. Die aktuellste Variante findet man unter
http://code.jquery.com/
Dort ist jede Version in einer „unkomprimierten“ und einer „minifizierten“ Fassung verfügbar. Die erste ist kommentiert und formatiert und
somit gut lesbar, die andere ist unformatiert und ohne Kommentare,
aber mit deutlich weniger Speicherbedarf. Die minifizierte Fassung eignet sich also zur reinen Nutzung besser aals die unkomprimierte. Man
kann nun die gewünschte Version der Bibliothek einfach auf den eigenen Server herunterladen und sie dann per Link einbinden, man kann
46
2.3
Syntax
aber auch die Originalbibliothek einfach verlinken. Die minifizierte Version 2.2.0 beispielsweise kann man im Head des HTML-Dokuments mit
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></
script>
einbinden.
2.3
Syntax
Basis einer jQuery-Anweisung ist der Aufruf der Funktion $, oder synonym jQuery, mit einem Selektor und einer Aktion:
oder synonym
jQuery(Selektor).Aktion(...);
$(Selektor).Aktion(...);
(Gebräuchlicher ist die Verwendung von $.) Der Selektor ist hierbei ein
CSS-Selektor zur Auswahl eines oder mehrerer HTML-Elemente, die Aktion ist eine auszuführende Funktion aus der jQuery-Bibliothek. Für
den Selektor gibt es im Wesentlichen vier Varianten, beispielhaft mit
der Aktion hide() aufgelistet, die die ausgewählten HTML-Elemente per
CSS verstecken lässt:
$("#test").hide()
$(".test").hide()
$("p").hide()
$(this).hide()
versteckt alle HTML-Elemente mit der ID "test".
versteckt alle HTML-Elemente der Klasse "test".
versteckt alle <p>-Elemente.
versteckt das aktuelle Element (funktioniert normalerweise nur innerhalb eines selektierten Elements);
Diese Anweisungen müssen in eine anonyme Funktion (auch „CallbackFunktion“ oder „Lambda-Ausdruck“) eingepackt werden:
$(function(){
...
$(document).ready(function(){
oder äquivalent
}
...
}
und sollten im <head> des HTML-Dokuments programmiert (bzw. als
externe JS-Datei dort eingelesen) werden. Auf diese Weise wird garantiert, dass das gesamte Dokument vollständig geladen ist, bevor jQueryCode ausgeführt wird.
Neben diesen „Selektor-Aktion“-Anweisungen kann man in jQuery auch
direkt vordefinierte Nutzfunktionen durch
$.Nutzfunktion(...);
oder synonym
jQuery.Nutzfunktion(...);
aufrufen. In der API-Dokumention (http://api.jquery.com/) sind die
vorhandenen Nutzfunktionen mit
jQuery.Nutzfunktion (...)
47
2
jQuery
aufgeführt. Die auf selektierte Elemente möglichen Aktionen sind dagegen einfach mit
.Aktion (...)
beschrieben.
2.4
DOM-Manipulation mit jQuery
Als erstes Programmbeispiel mit jQuery betrachten wir eine Version
des Programms aus Kapitel 16 (Listing 16.2), das nach Klicken der
Schaltfläche ein neues <li>-Element an die geordnete Liste <ol> hängt:
Listing 5: DOM Manipulation mit jQuery
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></
6
<script>
script>
7
8
$(function() {
$("button").click(function(){ // Event-Handler für alle
Buttons
if ($(this).val() === "hinzufuegen") {
9
$("#liste")
10
.append($("<li>Die Zeichen an der Wand</li>"));
11
} else if ($(this).val() === "loeschen") {
12
$("#liste")
13
14
.children()
15
.last()
16
.remove();
}
17
18
19
20
});
});
</script>
21
</head>
22
<body>
23
<button value="hinzufuegen">Neuer Text</button>
24
<button value="loeschen">Löschen</button>
25
<ol id="liste"></ol>
26
</body>
27
</html>
Ein direkter Vergleich der jQuery-Anweisungen mit einem äquivalenten Programm in „reinen“ JavaScript zeigt die Philosophie und die funktional orientierte Sicht von jQuery. Das jQuery Programm
48
2.4
DOM-Manipulation mit jQuery
$(function() {
$("button").click(function(){ // Event-Handler für alle
Buttons
console.log("$(this).val(): " + $(this).val());
if ($(this).val() === "hinzufuegen") {
$("#liste")
.append($("<li>Die Zeichen an der Wand</li>"));
} else if ($(this).val() === "loeschen") {
$("#liste")
.children()
.last()
.remove();
}
});
});
würde in JavaScript (fast) äquivalent wie folgt lauten:
var modifizieren = function(aktion) {
var wurzel = document.getElementById(’liste’);
if (aktion === "hinzufuegen") {
var neu = document.createElement(’li’);
wurzel.appendChild(neu);
var neuerText = document.createTextNode("Die Zeichen an
der Wand.");
neu.appendChild(neuerText);
} else if (aktion === "loeschen") {
if (wurzel.hasChildNodes()) {
wurzel.removeChild(wurzel.lastChild);
}
}
};
mit den Ereignisbehandlern
<button onclick="modifizieren(’hinzufuegen’);">Neuer Text</
button>
<button onclick="modifizieren(’loeschen’);">Löschen</button>
Im jQuery-Quelltext wird die objektorientierte API des DOM derart
gekapselt, dass sie eine „fluente“ („fließende“) Programmierung hintereinandergeschalteter Funktionen erlaubt, wie in Zeile 15 bis 17 in
Listing 5. Außerdem brauchen die Ereignisbehandler nicht mehr im
HTML-Quelltext in den betreffenden Elementen implementiert zu werden, sondern über die Selektoren ausschließlich in JavaScript. Damit ist
eine saubere Trennung zwischen der Ablaufdynamik und den HTMLInhalten möglich.
49
2
jQuery
2.5
CSS-Formatierungen
Etwas einfacher und auch durchweg gebräuchlicher als direkte DOMManipulationen, wie im vorigen Abschhnitt besprochen, sind dynamische Veränderungen von Formatierungen über CSS. Hier stehen in
jQuery mit hide, show und toggle (umschalten) drei wirkmächtige Funktionen zur Verfügung, die hier stellvertretend für dynamische Effekte aufgelistet seien. Die Funktionen modifizieren dabei gar nicht die
Struktur des DOM, sondern ändern lediglich die style-Attribute der
HTML-Elemente. Als Beispiel sei das folgende jQuery-Programm gelistet, das durch Klicken der Schaltfläche „Umschalten“ bewirkt, dass alle
Abätze des HTML-Dokuments angezeigt werden oder nicht:
Listing 6: Zeigen und Verbergen von HTML-Elementen mit jQuery
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></
script>
6
7
8
<script>
$(function() {
$("button").click(function(){
$("p").toggle();
9
10
11
12
});
});
</script>
13
</head>
14
<body>
15
<button>Umschalten</button>
16
<p>Dies ist ein erster Absatz.</p>
17
<p>Dies ist ein anderer kleiner Absatz.</p>
18
</body>
19
</html>
Untersucht man die HTML-Elemente mit den Entwicklertools des
Browsers, so erkennt man, dass lediglich das style-Attribut aller <p>Elemente verändert wird, entweder von <p style="display: block;"
(sichtbar) auf <p style="display: none;" (unsichtbar), oder umgekehrt.
Mit jQuery stehen aber auch Funktionen für Effekte mit CSS zur Verfügung, die lange und komplexe JavaScript-Anweisungen bündeln. Im
folgenden Beispielprogramm wird ein angeklickter Listeneintrag zunächst rot gefärbt und verblasst dann langsam, bevor er nach 3 Sekunden (scheinbar) verschwindet.
Listing 7: Animationsartiger CSS-Effekt mit jQuery
50
2.6
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></
AJAX mit jQuery
script>
<script>
6
$(function() {
7
$("li").click(function() {
8
$(this).css("color", "red").fadeOut(3000);
9
});
10
});
11
</script>
12
13
</head>
14
<body>
15
<ul>
16
<li>Clyde</li>
17
<li>Fred</li>
<li>Bonny</li>
18
19
</ul>
20
</body>
21
</html>
Lassen Sie das Programm ablaufen und beobachten Sie dabei im
Entwicklertool des Firefox („Inspector“) oder des Chrome Browsers
(„Elements“) die sich dynamisch verändernden Einstellungen der CSSAnweisungen!
2.6
AJAX mit jQuery
Die einfachste Möglichkeit zur Implementierung von AJAX-Ereignissen
ist über die Nutzfunktion $.ajax (bzw. jQuery.ajax). Es gibt zwei Versionen (siehe http://api.jquery.com/jQuery.ajax/), die gebräuchlichere erwartet als Arguent ein JSON-Objekt setting mit den Einstellungen
der asynchronen HTML-Anfrage:
•
type: Die Methode der HTML-Anfrage, also "GET" oder "POST"
•
url: Der URL der HTML-Anfrage
•
data: Die zu übermittelnden Daten der HTML-Anfrage,
•
success: Der Ereignisbehandler bei Erfolg der HTML-Anfrage.
Beispielhaft sei hier ein einfacher asynchroner Aufruf des Spiegelskripts auf Hägar beschrieben:
Listing 8: AJAX mit jQuery
1
<!DOCTYPE html>
51
2
jQuery
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></
6
<script>
script>
7
$.ajax({
8
type: "POST",
9
url: "http://haegar.fh-swf.de/AJAX/spiegel.php",
10
data: "y=x^2-1&x=1",
11
success: function(response){
$("#ausgabe").html("<h2>Serverantwort</h2>" + response);
12
}
13
14
15
});
</script>
16
</head>
17
<body>
18
<p id="ausgabe"></p>
19
</body>
20
</html>
Während also mit jQuery ein einziger Funktionsaufruf zur Erzeugung
der asynchronen HTTP-Anfrage genügt,
$.ajax({
type: "POST",
url: "http://haegar.fh-swf.de/AJAX/spiegel.php",
data: "y=x^2-1&x=1",
success: function(response){
$("#ausgabe").html("<h2>Serverantwort</h2>" + response);
}
});
muss man in JavaScript für denselben Zweck 7 Anweisungen programmieren:
var request = new XMLHttpRequest();
request.onreadystatechange = function() { // Event Handler
if (request.readyState === 4 && request.status === 200) {
document.getElementById("ausgabe").innerHTML =
"<h2>Serverantwort</h2>" + request.response;
}
};
request.open("POST", "http://haegar.fh-swf.de/AJAX/spiegel.php"
);
var data = new FormData();
data.append("y","x^2 - 1");
data.append("x",1);
request.send(data);
52
2.7
jQuery Mobile
Zu beachten ist dabei, dass in jQuery die AJAX-Anweisungen aufgrund
der verzögerten Ausführung im <head> des Dokuments programmiert
werden können, während die reinen JavaScript-Anweisungen nach dem
<p>-Element geschrieben werden sollten, also wenn es zum Ausführungszeitpunkt bekannt ist, oder als Funktionsaufruf des Ereignisbehandlers onload im <body>-Element.
Eine genauere Beschreibung der für die Funktion $.ajax möglichen Parameter sind in der API-Dokumentation unter
http://api.jquery.com/jquery.ajax/
beschrieben. Die wichtigsten Parameter sind bereits in obigem Listing 8
aufgeführt. Bemerkenswert ist außerdem der Parameter dataType, der
als Standard per Intelligent Guess eines der Formate xml, json, script
(für JavaScript-Quelltext) oder html auswählt.
2.7
jQuery Mobile
jQuery Mobile ist eine für Wischgesten optimierte JavaScriptBibliothek, die auf jQuery und auf eine spezifische CSS-Bibliothek
aufbaut. Sie ist kompatibel mit den gängigen Smartphones und Tablets. Sie erlaubt es durch gekapseltes JavaScript, vorwiegend mit
HTML-Elementen und geeigneten Attributen dynamische Apps zu
programmieren. Als ein einführendes Beispiel sei die folgende App, die
zwei miteinander verlinkte Seiten mit einfachen Effekten darstellt:
Listing 9: Eine zweiseitige App für mobile Endgeräte
1 <!DOCTYPE html>
2 <html>
3 <head>
4
<meta charset="utf-8"/>
5
<title>jQuery Mobile Example</title>
6
<!-- Include meta tag to ensure proper rendering and touch zooming: -->
7
<meta name="viewport" content="width=device-width, initial-scale=1">
8
<!-- Include jQuery Mobile stylesheets: -->
9
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.
min.css"/>
10
<!-- Include the jQuery library: -->
11
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
12
<!-- Include the jQuery Mobile library: -->
13
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
14 </head>
15 <body>
16 <div data-role="page" id="pageone">
17
18
19
<div data-role="header">
<h1>Welcome To My Homepage</h1>
</div>
20
21
<div data-role="main" class="ui-content">
22
<h1>Welcome!</h1>
23
<p>I am glad to be a mobile app developer.</p>
24
<p>If you click on the link below, it will take you to Page Two.</p>
25
26
<a href="#pagetwo">Go to Page Two</a>
</div>
27
28
<div data-role="footer">
53
2
jQuery
29
30
<h1>Footer Text</h1>
</div>
31 </div>
32
33 <div data-role="page" id="pagetwo">
34
35
36
<div data-role="header">
<h1>Welcome To My Homepage</h1>
</div>
37
38
39
<div data-role="main" class="ui-content">
<p>Now you are on Page Two.</p>
40
41
42
43
44
<div data-role="collapsible" data-collapsed="true">
<h1>Click me - I am collapsible!</h1>
<p>I am not expanded by default.</p>
</div>
45
46
47
48
<p>If you click on the link below, it will take you to Page Two.</p>
<a href="#pageone">Go to Page One</a>
</div>
49
50
51
52
<div data-role="footer">
<h1>Footer Text</h1>
</div>
53 </div>
54 </body>
55 </html>
Die App ist verfügbar unter
http://haegar.fh-swf.de/Webtechnologie/jQuery-mobile.html
Testen Sie sie mit Ihrem mobilen Endgerät oder mit den Entwicklertools des Google Chrome!
Im <head> des Dokuments werden die notwendigen CSS- und JavaScript-Bibliotheken geladen, bevor mit <div>-Elementen über das Attribut data-role die Struktur einer Seite festgelegt wird:
<div data-role="page" id="pageone">
<div data-role="header">
...
</div>
<div data-role="main" class="ui-content">
...
</div>
<div data-role="footer">
...
</div>
</div>
Jedes <div>-Element mit der data-role="page" sollte dabei eine eigene
ID bekommen. Die Rollen von "header", "main" und "footer" sind (hoffentlich) selbsterklärend und können bei Betrachtung der App nötigenfalls erschlossen werden.
Weitere Informationen: Die Dokumentation zur API befindet sich unter
54
2.7
jQuery Mobile
http://api.jquerymobile.com/,
ein gutes Tutorial unter http://www.w3schools.com/jquerymobile/.
55
2
jQuery
2.8
Übungsaufgaben
Aufgabe 2.1 (CSS-Selektoren) (a) Verändern Sie mit jQuery die CSSEinstellungen, so dass die Hintergrundfarbe aller <tr>-Elemente hellblau erscheinen.
(b) Modifizieren Sie das Dokument, so dass nur noch jede zweite Zeile
mit der Hintergrundfarbe erscheint.
(Hinweis: http://www.w3schools.com/jquery/jquery_selectors.asp.)
Aufgabe 2.2 (Animationseffekte) Programmieren Sie folgende mit jQuery folgende Animationseffekte:
Aufgabe 2.3 (Suchbegriffvervollständigung) (a) Programmieren Sie eine
Suchanfrage, die mittels der autocomplete-Funktion aus einer vordefinierten Liste von Programmiersprachen nach jedem Tastendruck diejenigen Begriffe anzeigt, die mit den bisher im Suchfeld eingegebenen
Buchstaben übereinstimmen. Verwenden Sie dazu die bereitgestellten
Frameworks neben der Standardbibliothek von jQuery:
http://code.jquery.com/ui/1.11.4/jquery-ui.js,
http://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css
(letztere ist kein JavaScript!) und beachten Sie die Hinweise unter http:
//api.jqueryui.com/autocomplete/.
(b) Modifizieren Sie Ihr Programm, so dass die gefilterten Suchbegriffe dynamisch aus einer Datenbank selektiert und im Suchfeld anzeigt
werden.
Aufgabe 2.4 (AJAX mit jQuery) (Vgl. Aufgabe 1.6.) In dem Verzeichnis http://haegar.fh-swf.de/Webtechnologie/Aufgaben/Woche_1-15/ befinden sich der Quelltext einer HTML-Seite und mehrere JSON-Dateien
mit Informationen zu verschiedenen Städten. Die HTML-Seite liefert
das Gerüst, mit dem die Städteinformationen HTML5-konform angezeigt werden sollen. (Insbesondere sollte sie auch auf mobilen Endgeräten ansprechend dargestellt werden, testen Sie es!)
56
2.8
Übungsaufgaben
Laden Sie die Dateien in ein Unterverzeichnis Ihres Webservers und erweitern Sie den Quelltext der HTML-Datei um jQuery-Anweisungen, so
dass beim Anklicken der Buttons die jeweilige JSON-Datei geladen wird
und deren Informationen in den Elementen titel und inhalt angezeigt
werden. Zum Beispiel soll nach dem Laden der Datei Istanbul.json mit
den Daten
{
"name": "Istanbul",
"state": "Turkey",
"capital": false,
"residents": "14 million",
"remark": "Istanbul is the most populous city of Turkey and
of Europe."
}
der folgende Text erscheinen:
Istanbul
Istanbul is a city in Turkey with a metropolitan area of
14 million residents. Istanbul is the most populous city
of Turkey and of Europe.
Beachten Sie, dass standardmäßig jQuery als empfangenen Datentyp
per „Intelligent Guess“ in JavaScript umformt.
57
3
AngularJS
3
AngularJS
AngularJS ist eine clientseitige JavaScript-Bibliothek zur Erstellung von
Einzelseiten-Webanwendungen nach dem Entwurfsmuster Model View
ViewModel (MVVM). AngularJS wird seit 2009 von Google als quelloffenes Projekt entwickelt. Wichtigste Eigenschaften von AngularJS sind
die Zwei-Wege-Datenbindung zum Abgleich von Darstellung und Daten,
und Dependency Injection zur Auflösung von Abhängigkeiten zwischen
verschiedenen Komponenten. Sie stellen jede für sich sehr moderne
Konzepte der Programmierung und des Software Engineering dar und
werden daher in einem ersten Abschnitt kurz theoretisch behandelt.
AngularJS ist flexibel erweiterbar durch sogenannte Services zur Erstellung eigener Routinen und Algorithmen und durch Direktiven zur Erstellung wiederverwendbarer HTML-Elemente.
3.1
3.1.1
Grundlegende Konzepte
Einzelseiten-Webanwendungen
Eine Einzelseiten- oder Single-Page-Webanwendung (englisch Singlepage Web Application, SPA) ist eine Webanwendung, die aus einem einzigen HTML-Dokument besteht und deren Inhalte dynamisch nachgeladen werden. Diese Art von Web-Architektur steht im Gegensatz zu klassischen Webanwendungen, welche aus mehreren, untereinander verlinkten HTML-Dokumenten bestehen. Eine EinzelseitenWebanwendung ermöglicht eine Reduzierung der Serverlast, gibt dem
Anwender das Gefühl einer klassischen Desktopanwendung und erlauben die Umsetzung von selbständigen Webclients, die auch temporär
offline verwendbar sind. Insbesondere aus letzterem Grund eignen sie
sich für den Einsatz in störanfälligen und oft unterbrochenen Verbindungen mobiler Endgeräte.
Die Informationen dieses Kapitels basieren vor allem auf dem Buch [10]
sowie auf der Website http://www.w3schools.com/angular/.
3.1.2
Model-View-ViewModel (MVVM)
Eines der wichtigsten Architekturmuster des Software Engineering ist
das Model-View-Controller (MVC). Es zählt zu den Mustern der interaktiven Systeme, die allgemein Mensch-Maschine-Interaktion strukturieren. In den 1980er Jahren von Trygve Reenskaug für die Programmiersprache Smalltalk entwickelt, trennt es die Verantwortlichkeiten
von Datenhaltung (Model), Darstellung (View) und Ablauflogik (Controller) und lagert sie in separate Schichten aus. Siehe dazu Abbildung
3.9 (rechts).
In einer dynamischen Webanwendung kann die strikte Trennung von
Ablauflogik (Controller) und Darstellung (View) allerdings nicht auf-
58
3.1
Grundlegende Konzepte
recht erhalten werden, da der Controller HTML-Code generieren muss,
der grundsätzlich Teil der View-Komponente ist. Damit trägt aber am
Ende der Server die Hauptlast der Webanwendung, worunter die Interaktivität und das Antwortverhalten bei hoher Nutzung leiden kann,
während der Browser vergleichsweise wenig zu tun hat. Er ist in diesem Falle nur noch ein sogenannter Thin Client.
Mit dem Muster Model-View-ViewModel (MVVM) wird der Controller
durch eine Proxy-Schicht auf dem Client (!) ersetzt, die ViewModel genannt wird. Dorthin werden lediglich diejenigen Daten geliefert, die tat-
Abb. 3.9: Das MVVM-Muster (links) für eine Webanwendung, im Vergleich dazu das
MVC-Muster (rechts). Da bei MVC die Hauptlast der Webanwendung auf dem Server läuft,
ist der Browser in diesem Fall ein Thin Client. Bei MVVM dagegen hat er mit der PoxySchicht MVVM einen großen Teil der Arbeitslast und ist daher in diesem Fall ein Fat Client.
sächlich für die Anzeige gebraucht werden. Oft müssen die Daten auch
auf bestimmte Art und Weise transformiert werden, bevor sie zur Anzeige kommen; auch hierfür ist das ViewModel zuständig. Außerdem
definiert es die innerhalb der Anzeige benötigte Funktionalität: Dort
müsste beispielsweise ein Ereignisbehandler definiert werden, der auf
einen Button-Klick reagiert.
So plausibel das MVVM-Muster auch auf den ersten Blick erscheint,
wirft es sofort ein großes Problem auf: die Datenredundanz zwischen
Model, ViewModel und View. Wie kann dieses Problem gelöst werden?
Betrachten wir dazu im nächsten Abschnitt einen Ansatz, der sich als
großenteils erfolgreich realisierbar herausgestellt hat.
3.1.3
Datenbindung (data binding)
Aufgrund der engen Verzahnung von Model, ViewModel und Model und
dadurch implizierten grundsätzlichen Datenredundanz ergibt sich sofort das Problem der Synchronisation der Schichten. D.h. jede Datenaktualisierung, sei sie im Model durch die Datenbank oder im View
durch eine Anwendereingabe, muss in den jeweils anderen Schichten
59
3
AngularJS
nachvollzogen werden. Ansonsten kann es sehr schnell zu Inkonsistenzen der Daten kommen, beispielsweise weil mittlerweile ein Anwender
einen Dateninhalt geändert hat, den etwas später ein anderer verändern will: Hat zum Beispiel ein Anwender den letzten vorhanden Artikel im Bestand gekauft, so muss das ViewModel jedes anderen Anwenders sofort angepasst werden, sonst wird dieser Artikel möglicherweise mehrfach verkauft.
Als ein Konzept zur Lösung dieses Synchronisationsproblems des
MVVM-Musters hat sich die Datenbindung, vor allem die „ZweiWege-Datenbindung“, etabliert. Wir werden dieses gleich im nächsten
Programmierbeispiel betrachten. Die Zwei-Wege-Datenbindung ist im
MVVM ein Mechanismus zur automatischen Synchronisation von Viewmodell und View bei Änderung von Daten, sei es durch den Nutzer in
der View oder durch andere Nutzer oder Prozesse im mit dem Model
synchronisierten Viewmodell.
Normalerweise muss man eine solche Datenbindung zwischen den
Schichten mühsam programmieren. Beispielsweise müssen für alle eingaberelevanten DOM-Elemente jeweils extra ein Ereignisbehandler registriert werden, der die betroffenen Variablen des Models ändert. Entsprechend muss auch die Logik des umgekehrten Weges programmiert
werden, also die Aktualisierung der DOM-Elemente nach einer Änderung des Daten des Models. Auf diese Weise entsteht bereits für relativ einfache Anwendungen jede Menge sogenannter „Boilerplate-Code“
(etwa „Textbaustein-“ oder „Klebe-Code“), der die eigentliche Anwendungslogik verschmutzt.
3.1.4
Dependency Injection
Ein grundlegendes Problem komponentenbasierter Softwaresysteme,
die wartbar, testbar und komponentenweise austauschbar sein sollen,
ist die Auflösung von Abhängigkeiten der Komponenten untereinander.
Eine einfache Abhängigkeit ist bereits gegeben, wenn eine Komponente
eine andere für ihren Ablauf benötigt. In einer objektorientierten Programmiersprache beispielsweise wäre dies der Fall, wenn ein Objekt
zu seiner Erzeugung ein anderes verwendet. Durch solche Abhängigkeiten kann ganz schnell ein verschlungenes Netz von Abhängigkeiten
entstehen, das eine Modifikation des Softwaresystems oder den Austausch von Komponenten praktisch unmöglich macht.
Im Software Engineering hat sich daher in den 2000er Jahren das auf
Martin Fowler [4] zurück gehende Entwurfsmuster der Dependency Injection (DI) etabliert. Ihr liegt das abstrakte Prinzip der Umkehrung des
Kontrollflusses (Inversion of Control (IoC)) zugrunde, also zum Beispiel
eine Umkehrung der Aufrufe von Funktionen oder der Ablaufsteuerung
eines Programms. (Getreu dem Hollywood-Prinzip, “Don’t call us, we’ll
call you!” [5]) Eine typische Realisierung dieses Prinzips sind beispielsweise Event Handler, da sie Funktionen implementieren, deren Aufruf
60
3.1
Grundlegende Konzepte
nicht in der Anwendung selbst geschieht, sondern aus einer der Programme der API-Bibliothek erfolgt. Ermöglicht wird eine solche Umkehrung des Kontrollflusses beispielsweise durch Interfaces, Plug-Ins
oder Callback-Funktionen. Ein anderes Beispiel für eine Kontrollflussumkehrung ist eine serverseitig programmierte Webanwendung, wie
etwa mit PHP, da die Ausführungsverantwortung beim Server liegt [10,
§2.1.3].
Das Entwurfsmuster der Dependency Injection oder der automatischen
Übergabe von Abhängigkeiten nun ist eine spezielle Form der Kontrollflussumkehrung und besagt, dass eine Komponente ihre Abhängigkeiten von anderen Komponenten nicht selbst erzeugt, sondern von einer
zentralen Komponente des Systems, dem „Assembler“, übergeben bekommt [4]. Dadurch können Abhängigkeiten zwischen Komponenten
also ausschließlich durch die zentrale Komponente erzeugt und damit
zentral aufgelöst werden.
Listing 10: Einfache Implementierung einer Dependency Injection in Java
1
interface IAbhängiges {
public Abhängiges(Abhängigkeit abhängigkeit);
2
3
}
4
5
class Abhängiges implements IAbhängiges {
private Abhängigkeit abhängigkeit;
6
7
public Abhängiges(Abhängigkeit abhängigkeit)
8
this.abhängigkeit = abhängigkeit;
9
}
10
11
{
}
12
13
class Injizierer {
void methode() {
14
15
Abhängigkeit abhängigkeit = ... ;
16
IAbhängiges abhängiges = new Abhängiges(abhängigkeit);
}
17
18
}
Mit der automatischen Übergabe von Abhängigkeiten weist ein Softwaresystem eine lose Kopplung der Komponenten auf. Lose Kopplung
führt dazu, dass Änderungen in einer Komponente nur dann Änderungen in einer anderen Komponente bedingen, wenn die Änderungen die
Schnittstelle betreffen. Lose Kopplung ist daher eine hinreichende Bedingung für maximale Wartbarkeit und Testbarkeit eines Systems gegebener Komplexität.
61
3
AngularJS
3.2
Erste Beispiele: HTML wird (fast) eine Programmiersprache
Genug der Theorie, wie kann man denn nun mit AngularJS programmieren? In diesem Abschnitt werden wir einführende Programmbeispiele vorstellen, die die Datenbindung verwenden, aber auch ein Schleifenkonstrukt und eine (Art) Selektionsstruktur kennenlernen. Dadurch
wird HTML scheinbar zu einer vollständigen Programmiersprache, in
Wirklichkeit ist es natürlich JavaScript, aber aufgerufen durch erweiterte HTML-Attribute, die sogenannten Direktiven.
Betrachten wir zunächst ein Programm mit AngularJS, das das Prinzip
der Datenbindung (data binding) illustriert.
Listing 11: Beispiel einer Datenbindung in AngularJS
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<script src="https://ajax.googleapis.com/ajax/libs/angularjs
/1.5.0/angular.min.js"></script>
6
</head>
7
<body>
8
<p>Gib einen Text ein:</p>
9
10
<div ng-app="">
11
<input type="text" ng-model="eingabe"/>
12
<p><tt>{{ eingabe }}</tt></p>
13
</div>
14
15
</body>
16
</html>
Zunächst sehen wir, dass die Bibliothek im <head> in Zeile 5 eingebunden wird. Der Kern des Programms besteht aus den Zeilen 10 bis 13:
<div ng-app="">
<input type="text" ng-model="eingabe"/>
<p><tt>{{ eingabe }}</tt></p>
</div>
Mit der Direktive ng-app, also ein „künstliches“ HTML-Attribut, wird
eine Applikation in AngularJS initialisiert, hier ist es die als Standard
vorhandene namenlose Applikation. Eine Applikation muss stets bereits mit ihrem Namen definiert sein, um sie über diese Direktive damit aufrufen zu können. Mit der Direktive ng-model wird eine Variable
in AngularJS festgelegt, hier ist es die Variable eingabe, die den Wert des
<input>-Elements erhält. In dem Ausdruck (expression)
{{ eingabe }}
62
3.2
Erste Beispiele: HTML wird (fast) eine Programmiersprache
schließlich wird der aktuelle Wert der Variable ausgegeben. Hier ist es
eingepackt in ein <p>- und ein tt-Element zur Ausgabe in Schreibmaschinenschrift.
Was geschieht in diesem Programm? Gibt der Anwender in dem Eingabefeld einen Text ein, so wird nach jedem Tastendruck der aktualisierte
Wert der Variable angezeigt. Man hat also in dem HTML-Dokument zu
jedem Zeitpunkt in zwei verschiedenen Elementen denselben Wert stehen, es handelt sich um eine (hier noch einfache) Datenbindung.
Die Datenbindung ist eine mächtige Technik und ermöglicht mit relativ
wenig Quelltext bemerkenswerte Effekte. So wird im nächsten Beispielprogramm ein Farbenwähler erstellt, der durch Schiebereglern festgestellte Farben nach dem RGBA darstellt.28
Listing 12: 2-Wege-Datenbindung in AngularJS: Ein Farbenwähler (nach
[10, §1.1])
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<script src="https://ajax.googleapis.com/ajax/libs/angularjs
/1.5.0/angular.min.js"></script>
6
</head>
7
<body ng-app="" ng-init="r=255; g=0; b=123; a=0.7;">
R: <input type="range" name="rot"
8
min="0" max="255"
step="
G: <input type="range" name="gruen" min="0" max="255"
step="
1" ng-model="r"/><br/>
9
1" ng-model="g"/><br/>
B: <input type="range" name="blau"
10
min="0" max="255"
step="
1" ng-model="b"/><br/>
A: <input type="range" name="alpha" min="0" max="1" step="
11
0.01" ng-model="a"/>
12
<div style="width: 300px; height: 100px; background-color:
13
rgba({{r}},{{g}},{{b}},{{a}});">
</div>
14
15
</body>
16
</html>
Hier wird in Zeile 7 die Angularapplikation für das gesamte <body>Element definiert. Hier werden auch mit der Direktive ng-init die Werte der verwendeten Variablen r, g, b und a initialisiert. Die entsprechenden Schiebereglern (Zeilen 8 bis 11) werden über die Direktive ng-model
mit diesen Werten voreingestellt und im <div>-Element mit Hilfe der
28
RGBA erweitert das RGB-Farbmodell mit den Werten für Rot, Grün und Blau jeweils ganzzahlig zwischen 0 und 255 um den Alphawert α zwischen 0 und 1, der die
Transparenz der Farbe festlegt (0: transparent, also unsichtbar, 1: nicht transparent,
also opak).
63
3
AngularJS
CSS-Funktion rgba als Farbe dargestellt.
Im letzten Beispielprogramm dieses Abschnitts wird über die Direktive
ng-repeat eine Schleife zum Durchlaufen eines Arrays erzeugt, die alle
Einträge ausgibt, sowie eine zweite Schleife, die bestimmte Elemente
des Arrays filtert, also selektiert. Damit bietet AngularJS, zumindest auf
den ersten Blick, alle Kontrollstrukturen einer vollständigen Programmiersprache in HTML-Syntax. Aber es gibt Einschränkungen. Aber zunächst das Programm:
Listing 13: Beispiel einer Schleife in AngularJS
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<script src="https://ajax.googleapis.com/ajax/libs/angularjs
/1.5.0/angular.min.js"></script>
5
</head>
6
<body ng-app="" ng-init="woerter=[’Hallo ’,’Welt, ’, ’hallo ’,
7
<h1><span ng-repeat="wort in woerter">{{ wort }}</span></h1>
8
<p> <span ng-repeat="wort in woerter | filter: ’gul’">{{ wort
’Angular!’]">
}}</span></p>
9
</body>
10
</html>
Es gibt als <h1>-Überschrift „Hallo Welt, hallo Angular!“ aus, und in dem
das gefilterte Wort „Angular!“. Das ergibt sich, da in Zeile 6 die Angularapplikation mit dem Array woerter und vier Einträgen
initialisiert wird und die Direktive ng-repeat als eine foreach-Schleife
durch das Array woerter läuft. In Zeile 8 wird dieselbe Schleife mit
| filter: ... durchlaufen, also einem Filter. Ein Filter übernimmt in
der funktionalen Programmierung die Rolle der Selektion, also der ifAnweisung der imperativen Programmierung.
Ein Problem ergibt sich bei gleichen Einträgen des Arrays, AngularJS
bricht dann die Verarbeitung ab und ... gibt nichts aus, was die Fehlersuche nicht gerade vereinfacht. Man muss in diesem Falle bei der Direktive ng-repeat zusätzlich die Option track by $index angeben:
<p>-Element
<body ng-app="" ng-init="woerter=[’Da’,’steh’,’ich’,’nun,’,’ich
’,’armer’,’Tor’]">
<p> <span ng-repeat="wort in woerter track by $index">{{wort
}} </span></p>
</body>
Der doppelte Eintrag ist hier das Wort „ich“. In diesem Beispiel wird
überdies die Direktive ng-bind benutzt, die eine ähnliche Wirkung wie
ein {{}}-Ausdruck hat. Obwohl etwas umständlicher in der Schreibweise, sollte sie bei komplexeren Webanwendungen bevorzugt werden, da
sie als ein Elementattribut die Datenbindung erst anzeigt, wenn die Sei-
64
3.3
Grundelemente von AngularJS
te geladen ist. In extremen Fällen kann es dagegen bei einem {{xyz}}Ausdruck passieren, dass er vor Vollendung des Ladens unausgewertet
angezeigt wird, also wörtlich als „{{xyz}}“.
Weitere
Hinweise
zur
Direktive
ng-repeat
findet
man
in
der
Dokumentation
der
API
https:
//docs.angularjs.org/api/ng/directive/ngRepeat,
zur
Direktive
ng-bind siehe https://docs.angularjs.org/api/ng/directive/ngBind.
Ist also HTML mit AngularJS eine vollständige Programmiersprache
wie Java, JavaScript oder PHP? Immerhin ermöglicht die HTML-Syntax
Schleifen und Selektionen. Die Antwort ist Nein, da es, vereinfacht
gesagt, keine while-Schleife gibt: Eine Programmiersprache muss die
Mächtigkeit haben, Algorithmen auszuführen, die potenziell unendlich
lang laufen können.29 Das ist mit AngularJS nicht möglich.
3.3
Grundelemente von AngularJS
Zunächst sei als Standardreferenz die Dokumentation der API von AngularJS erwähnt:
https://docs.angularjs.org/api/
AngularJS basiert auf den folgenden Grundelementen, siehe Abbildung
3.10. Das übergreifende Strukturelement in AngularJS bilden Module.
Sie kapseln zusammenhängende Anwendungskomponenten und dienen als Container für Controller, Routen, Services, Direktiven und Filter.
Modulen in AngularJS können im Sinne der Dependency Injection be-
Abb. 3.10: Struktur der Grundelemente von AngularJS.
quem andere Module als Abhängigkeiten einbinden. Sofern diese Module zuvor in einem <script>-Element in der index.html eingebunden
worden sind, übernimmt AngularJS die Abhängigkeitsauflösung automatisch.
Eine Angularanwendung besteht aus mindestens einem Startmodul,
das durch die Direktive ng-app festgelegt und durch AngularJS automatisch initialisiert und nach dem Laden der Anwendung ausgeführt wird.
29
Eine Programmiersprache muss eine universelle Turingmaschine realisieren können, die auch einen Algorithmus zur Lösung des (übrigens unentscheidbaren) Entscheidungsproblems umfasst [2].
65
3
AngularJS
Einfache Anwendungen basieren auf genau einem Modul, in großen
Projekten mit mehreren Teams dagegen können nur mehrere Module
für eine lose Kopplung des Systems sorgen.
3.4
Erste Datenbankzugriffe mit AngularJS
An Programmbeispielen haben wir bislang AngularJS lediglich im Bereich des Clients gesehen, also der View und der ModelView mit der
Zwei-Wege-Datenbindung. Ohne Zweifel ist das eine der Stärken von
AngularJS und ermöglicht beeindruckende Effekte. Aber den serverseitigen Anteil, also die Datensynchronisation mit dem Model in Abbildung 3.9 (links) haben wir noch nicht betrachtet.
Wir werden jedoch gleich sehen, dass dies eine weitere Stärke des Ansatzes von AngularJS ist. Um das Prinzip dieses clientseitigen Ansatzes
zu verstehen, betrachten wir zunächst eine einfache Webapplikation,
die die Adressdatenbank aus Abschnitt 12.2 mit AngularJS darstellt. In
der HTML-Datei wird dazu der folgende Quelltext erstellt:
Listing 14: Datenbankzugriff mit AngularJS
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<script src="https://ajax.googleapis.com/ajax/libs/angularjs
/1.5.0/angular.min.js"></script>
6
<script>
7
var addressApp = angular.module(’addressApp’, []);
8
addressApp.controller(’listCtrl’, function($scope, $http) {
9
$http({
10
method: "GET",
11
url: "http://localhost/programme/angular/datenbank/
address-db.php?action=list"
12
}).then(function (response) {
$scope.response = response.data;
13
14
}, function error(response) {
$scope.response = response.statusText;
15
16
17
18
19
});
});
</script>
</head>
20
21
<table ng-controller="listCtrl">
23
<tr><th>Vorname</th><th>Name</th><th>URI</th></tr>
24
<tr ng-repeat="eintrag in response">
25
66
<body ng-app="addressApp">
22
<td>{{ eintrag.vorname }}</td>
3.4
Erste Datenbankzugriffe mit AngularJS
26
<td>{{ eintrag.name }}</td>
27
<td><a href="http://{{ eintrag.web }}">{{ eintrag.web }}</a
></td>
</tr>
28
</table>
29
30
</body>
31
</html>
Listing 15: Das serverseitige PHP-Skript, das in Listing 14 aufgerufen
wird.
1
<?php
2
header("Access-Control-Allow-Origin: *"); //für JS-Zugriffe von
anderen Hosts
3
4
if (isset($_GET[’action’]) && $_GET[’action’] == "list") {
$mysqli = new mysqli("localhost", "user", "c5dDC8VJemscKPQS"
5
, "AdressDB");
6
$mysqli->query("SET CHARACTER SET ’utf8’");
7
$mysqli->query("SET NAMES ’utf8’");
8
$sql = "SELECT * FROM adressen";
9
$result = $mysqli->query("SELECT * FROM adressen");
10
11
12
$array = array();
13
while ($adresse = $result->fetch_assoc()) {
$array[] = $adresse;
14
15
}
16
echo json_encode($array);
17
}
18
?>
Hier wird in den Zeilen 7 bis 17 das Modul addressApp erzeugt und ihr
sofort der Controller lstCtrl hinzugefügt:
var addressApp = angular.module(’addressApp’, []);
addressApp.controller(’listCtrl’, function($scope, $http) {
...
});
Der Controller wiederum besteht aus einer Funktion,
die den Angularservice $http aufruft. Er ist in https:
//docs.angularjs.org/api/ng/service/$http dokumentiert und erwartet ein JSON-Objekt mit den Attributen method und url:
$http({
method: "GET",
url: "http://..."
}).then(function success(response) {
67
3
AngularJS
$scope.response = response.data;
}, function error(response) {
$scope.response = response.statusText;
});
});
Die Funktion ist ein Promise30 , also eine asynchron aufgerufene Funktion, auf deren Resultat eine then-Funktion mit zwei Callbackfunktionen reagiert, eine für den Erfolgsfall und eine für den Fehlerfall. Die
Konstruktion mit der then-Funktion ist also die Entsprechung der Ausnahmebehandlung mit try-catch in der synchronen objektorientierten
Programmierung.
3.5
Single Page App mit Components und Datenbankzugriffen
In diesem Abschnitt erstellen wir eine kleine Single-Page App (SPA) mit
Components. Components sind in AngularJS als Teil eines Moduls implementierbar und lehnen sich an die Web-Components an, das sind selbst
definierbare HTML-Tags. In AngularJS hat jede Component einen Namen und bezieht sich damit auf HTML-Tags, die jeden Großbuchstaben
durch einen Bindestrich und den entsprechenden Kleinbuchstaben ersetzt. Die Component "adressenListe" beispielsweise wird mit den Tags
<adressen-liste> ... </adressen-liste>
verwendet. Sie wird in AngularJS mit der Funktion component eines Moduls definiert und neben dem Namen mit einem Objekt
{
templateUrl: "...",
controller: function(...) {...},
bindings: {...}
}
versehen, das die Adresse der Templatedatei, den Controller und optional die Bindung zu importierenden oder exportierenden Daten benennt. In AngularJS zeichnet sich eine Component grundsätzlich dadurch aus, dass alle Daten nur innerhalb ihres Controllers gültig sind,
ein- und ausgehende Datenflüsse sowie von der Component gefeuerte
Ereignisse müssen über das Bindings-Objekt definiert werden. Als Konsequenz ist die $scope-Variable von AngularJS als öffentliches Objekt
in einer Component wirkungslos. Stattdessen muss im entsprechenden
Template mit $ctrl auf den Controller mit seinen Properties zugegriffen werden (Abbildung 3.11). Hierbei ist innerhalb einer Component
auch gewährleistet, dass immer nur auf das passende Controllerobjekt
$ctrl zugegriffen wird.
30
68
https://docs.angularjs.org/api/ng/service/$q#the-promise-api
3.5
Single Page App mit Components und Datenbankzugriffen
Abb. 3.11: Verantwortlichkeitsbereiche (Scopes) zweier verschachtelter Components
und der Datenbindungen zwischen ihren Controllern ($ctrl).
Für weitere Details zu Components siehe den Developer Guide von AngularJS, https://docs.angularjs.org/guide/component.
3.5.1
Bindings
Üblicherweise können in AngularJS Daten an irgendeiner Stelle verändert werden und sind mit der 2-Wege-Bindung auch überall verändert.
In Components wird mit diesem allgemeinen Grundsatz gebrochen, eine Component soll nur diejenigen Daten ihres lokalen Zuständigkeitsbereichs ändern können. Nur so kann in einer komplexen Anwendung
nachverfoĺgt werden, wer welche Daten wann verändert hat. Für Components in AngularJS gelten daher folgende Konventionen:
• Eingaben in eine Component sollten mit den Binding < für aus
der Elternkomponente eingehende Daten (Einwegbindung) versehen werden, beispielsweise für ein Objekt adresse:
bindings: {
adresse: ’<’
}
• Ausgaben an die Elternkomponente werden mit den Binding & definiert, typischerweise als in der Component gefeuerte Ereignisse. Beispielsweise würden die in den Funktionen onUpdate oder
onDelete des Controllers der <inner>-Komponente gefeuerten Ereignisse
bindings: {
onUpdate: ’&’,
onDelete: ’&’
}
in der Elternkomponente <outer> die Ereignisbehandler
on-update oder on-delete aufrufen, wenn sie dort mit
<outer on-update="..." on-delete="...">
<inner> ... </inner>
</outer>
angegeben werden. Hierbei sind die drei Punkte in Anführungszeichen jeweils die zuständigen Ereignisbehandler im Controller
der <outer>-Komponente.
69
3
AngularJS
3.5.2
Beispielprogramm
Mit Components lässt sich nun eine webbasierte Single-Page App programmieren, indem einerseits gleich mit dem Startaufruf alle Components aufgebaut werden, allerdings mit den Direktiven ng-hide bzw.
ng-show abhängig von dem aktuellen Wert von Schaltervariablen des
zuständigen Controllers angezeigt bzw. verborgen werden. Abhängig
von spezifischen Ereignisbehandlern, beispielsweise über die Direktive
ng-click können diese Schaltervariablen verändert werden. Eine Beispielanwendung ist mit den folgenden Listings gegeben. Zunächst de
Datei index.html, die das Viewmodel implementiert:
Listing 16: Die Index-Seite der App
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<script src="https://ajax.googleapis.com/ajax/libs/angularjs
/1.5.0/angular.min.js"></script>
6
7
<script>
var modelUri = "http://localhost/programme/angular/datenbank/
address-db-2.php";
8
var addressApp = angular.module(’addressApp’, []);
9
10
addressApp.component(’adressenListe’, {
11
templateUrl: ’templates/adressenListe.html’,
12
controller: function($http) {
13
var ctrl = this; // reference necessary for closure in
$http!
14
ctrl.liste;
15
$http({
16
method: "GET",
17
url: modelUri + "?action=list"
18
19
20
21
22
}).then(function (response) {
ctrl.liste = response.data;
}, function error(response) {
ctrl.response = response.statusText;
});
23
24
25
ctrl.update = function(adresse) {
$http({
26
method: "GET",
27
url: modelUri + "?action=update&json=" + JSON.
stringify(adresse)
70
28
}).then(function (response) {
29
}, function error(response) {
3.5
ctrl.response = response.statusText;
30
});
31
}
32
33
34
Single Page App mit Components und Datenbankzugriffen
}
});
35
36
addressApp.component(’adressenDetail’, {
37
templateUrl: ’templates/adressenDetail.html’,
38
controller: function () {
39
this.editMode = false;
40
41
this.switchEditMode = function() {
42
this.editMode = !this.editMode;
}
43
44
this.update = function() {
45
46
this.switchEditMode();
47
this.onUpdate({adresse: this.adresse});
// <- Event
feuern
};
48
49
},
50
bindings: {
51
adresse: ’<’,
52
onUpdate: ’&’
53
54
55
56
}
});
</script>
</head>
57
58
<body ng-app="addressApp">
59
<adressen-liste>
60
</adressen-liste>
61
</body>
62
</html>
Hier wird das Modul sowie seine beiden Components definiert. Die
Component adressenListe wird ab Zeile 10 definiert, Die erste ist zur
Darstellung der gesamten Adressenliste zuständig und enthält den
Controller zur Steuerung der Datenbindung zum Model. Die Component adressenDetail ab Zeile 36 ist zur Anzeige und Editierung einer
einzelnen Adresse verantwortlich und hat einen Controller, der die Anzeige über einen Schalter editMode zur Editierung ein- oder ausschaltet.
Über die Bindungen (Zeile 50 bis 53) wird ein Objekt adresse von außen
importiert und ein Ereignis onUpdate nach außen gefeuert.
Aus diesem Viewmodel ergibt sich noch nicht unbedingt die Hierarchie der Components, sie wird durch den HTML-Teil der Indexdatei und
durch die beiden Templates im Unterverzeichnis templates festgelegt.
71
3
AngularJS
Betrachten wir die Templates zunächst im Detail:
Listing 17: Template zur Ansicht der Adressliste
1
<h2>Adressen</h2>
2
<table>
3
<tr><th>Vorname</th><th>Nachname</th><th>URI</th><th>E-Mail</
th><th></th></tr>
4
<tr ng-repeat="adresse in $ctrl.liste">
5
<td>{{ adresse.vorname }}</td>
6
<td>{{ adresse.name }}</td>
7
<td><a href="http://{{ adresse.web }}">{{ adresse.web }}</a
></td>
<td><a href="mailto:{{ adresse.email }}">{{ adresse.email
8
}}</a></td>
<td>
9
<adressen-detail
10
11
adresse="adresse"
12
on-update="$ctrl.update(adresse)">
</adressen-detail>
13
</td>
14
15
16
</tr>
</table>
Und:
Listing 18: Template zum Editieren einer Adresse
1
<button ng-hide="$ctrl.editMode" ng-click="$ctrl.switchEditMode
()">Editieren</button>
2
<div ng-show="$ctrl.editMode">
3
<input type="text" ng-model="$ctrl.adresse.vorname"><br/>
4
<input type="text" ng-model="$ctrl.adresse.name"><br/>
5
<input type="text" ng-model="$ctrl.adresse.web"><br/>
6
<input type="text" ng-model="$ctrl.adresse.email"><br/>
7
<button ng-click="$ctrl.update()">Speichern</button>
8
<button ng-click="$ctrl.switchEditMode()">Abbrechen</button>
9
</div>
Das erste Template ist zur Darstellung der vollständigen Adressliste
vorgesehen, das andere zur editierbaren Darstellung einer einzelnen
Adresse. Zusammen mit dem HTML-Teil der index.html ergibt sich dadurch die folgende Grobstruktur der Components in der View:
<body ng-app="addressApp">
<adressen-liste>
<h2>Adressen</h2>
<table>
...
<adressen-detail adresse="adresse" on-update="..."></
adressen-detail>
72
3.5
Single Page App mit Components und Datenbankzugriffen
...
</table>
</adressen-liste>
</body>
Der Datenbankkonnektor auf Serverseite schließlich ist in Listing 19
angegeben.
Listing 19: Das serverseitige PHP-Skript, das in Listing 16 aufgerufen
wird.
1
<?php
2
if (!isset($_GET[’action’])) {
die ("Kein Feld \"action\" in der GET-Method! Was soll ich
3
tun?");
4
}
5
6
$db = "AdressDB";
7
$table = "adressen";
8
9
$mysqli = new mysqli("localhost", "user", "c5dDC8VJemscKPQS",
$db);
10
$mysqli->query("SET CHARACTER SET ’utf8’");
11
$mysqli->query("SET NAMES ’utf8’");
12
13
if ($_GET[’action’] === "list") {
14
$sql = "SELECT * FROM $table";
15
$result = $mysqli->query($sql);
16
$array = array();
17
while ($entry = $result->fetch_assoc()) {
$array[] = $entry;
18
19
}
20
echo json_encode($array);
21
} else if ($_GET[’action’] === "update") {
22
$entry = json_decode($_GET[’json’]);
23
$sql
= "UPDATE $table SET ";
24
25
foreach ($entry as $key => $value) {
26
if ($key === "\$\$hashKey") continue;
27
if ($key === "id") {
28
$where = " WHERE id=$value";
29
continue;
}
30
$sql .= " $key=’$value’,";
31
32
}
33
// Delete last comma:
34
$sql = substr($sql, 0, strlen($sql) - 1);
35
73
3
AngularJS
36
$sql .= $where;
37
$mysqli->query($sql);
38
}
39
?>
Bekommt er per $_GET als Aufrufparameter action=list, so selektiert er
alle Adressen der Datenbank und gibt sie als JSON-String an die Applikation aus, während mit action=update ein Parameter json mit der zu
ändernden Adresse als JSON-Objekt erwartet wird.
3.6
Bewertung und Ausblick
Es ist immer ein gewisses Risiko, ein neues Programmierkonzept oder
eine aktuelle Softwarebibliothek in ein Lehrwerk aufzunehmen. Insbesondere trifft dies zu, wenn es sich wie bei AngularJS um ein Framework handelt, das nicht standardisiert oder auf andere Art und Weise
übergeordnet legitimiert ist. So ist die Gefahr nicht vollkommen ausgeschlossen, dass zu dem Zeitpunkt, zu dem Sie diese Zeilen lesen, AngularJS bereits als veraltet gilt und ganz andere Konzepte zu empfehlen
sind. Für ein Lehrwerk, das auf die praktischen Probleme der Zukunft
vorbereiten und das dafür notwendige Handwerkszeug bereit stellen
möchte, wäre das Aufzeigen einer fachlichen Sackgasse oder eines inhaltlichen Irrweges nicht zielführend.
Doch es sprechen aus meiner Sicht mehrere Gründe dafür, dass AngularJS auch im Verlaufe des nächsten Jahrzehnts eine bedeutende Rolle
spielen wird. Zum Ersten ist es seit mehr als einem halben Jahrzehnt
ein Projekt des zur Zeit größten und mächtigsten Innovationstreibers
der digitalen Ökonomie, der Google Inc. Ein Ende des Projekts seitens
Google ist nicht erkennbar und wäre in den nächsten Jahren mindestens überraschend.
Ein weiteres Argument zur Behandlung von AngularJS ist die Tatsache,
dass dort moderne und aktuelle Konzepte des Software Engineerings
angewandt werden, so Kontrollflussumkehr IoC, Dependency Injection, Datenbindung und lose Kopplung. Selbst wenn AngularJS als System vollständig in Vergessenheit geraten sein sollte, wäre es allein als
Anwendungsbeispiel dieser wichtigen Konzepte didaktisch wertvoll.
Zum Dritten bewirkt AngularJS einen sehr beachtenswerten Paradigmenwechsel der Softwareentwicklung im Web, die effiziente Aufgabenverlagerung weg vom Server hin zum Client. Waren in den ersten Jahren des Internet, also Ende der 1990er Jahre, mit JavaScript, Java Applets und Flash Webanwendungen vorherrschend, die dem Client fast
die gesamte Arbeit überließen, so war es in den 2000er und beginnenden 2010er Jahren der Server, auf den aufgrund zentral zu verwaltender Daten mit PHP, Java EE oder ASP konzeptionell die Hauptlast der
Webanwendungen verlagert wurde; die serverseitigen Skripte und Programme waren zuständig sowohl für Darstellung als auch Ablauflogik
74
3.6
Bewertung und Ausblick
eines Webauftritts. Selbst modernere Ansätze wie Ruby on Rails, die
mit einer strikteren Architektur die Webanwendungen nach dem MVCMuster strukturieren, änderten nichts an der zentralen Rolle des Servers. Erst durch das MVVM-Muster mit einem clientseitigen ViewModel werden große Teile der Ausführung einer Webanwendung dem Client übertragen. Die dadurch erst mögliche lose Kopplung hat mehrere
wichtige Vorteile, vor allem die konsequente Trennung der zentralen
Funktionen des Models und der Datenspeicherung von den Funktionen der Darstellung und der Bedieneroberfläche auf dem Client. Dies
wiederum ermöglicht die weitgehend getrennte Programmierung von
GUI und Datenspeicherung, d.h. View und Model. Es liegt auf der Hand,
dass auf diese Weise zentrale Datenspeicherungen von unterschiedlichen Clientsystemen dargestellt und verarbeitet werden können, insbesondere von mobilen Endgeräten. Grundsätzlich könnte AngularJS oder
ein darauf aufbauender Ansatz sich zu einer Schlüsseltechnologie für
die mobile ubiquitäre digitale Ökonomie entwickeln.
75
3
AngularJS
3.7
Übungsaufgaben
Aufgabe 3.1 (Direktiven und Datenbindung) (a) Schreiben Sie eine Anwendung, die zwei Textfelder „Menge“ und „Preis“ anzeigt und schon
bei der Eingabe den Gesamtbetrag Menge mal Preis ausgibt.
(b) Schreiben Sie eine Webseite, in der mit einem Slider ein dargestelltes Bild ausgeblendet werden kann. Benutzen Sie dazu das CSS-Attribut
opacity für Ihr Bild.
(c) Schreiben Sie eine Webanwendung, die einen Text und eine Checkbox anzeigt, wobei der Text sich ändert, wenn die Checkbox selektiert
ist:
Verwenden Sie die Direktiven ng-hide und ng-show und die
API
https://docs.angularjs.org/api/ng/directive/.
(Sollte dies nicht helfen, verwenden Sie die Hinweise auf http:
//www.w3schools.com/angular/tryit.asp?filename=try_ng_ng-hide.)
Aufgabe 3.2 (Single-Page App (SPA) mit Datenbankanbindung) Erweitern
Sie die SPA unter
https:
//docs.angularjs.org/guide/component#example-of-a-component-tree,
so dass damit eine Datenbank als Model geladen und nach Änderung
der Location in der SPA auch in der Datenbank geändert wird.
76
4
Lösungen der Aufgaben
Aufgabe 1.1
1
<!doctype html>
2
<html>
3
<head>
<meta http-equiv="content-type" content="text/html; charset
4
=utf-8" />
5
<title>Währungsumrechner</title>
6
<script
7
type="text/javascript">
var berechnen = function() {
var euro = document.getElementById("euro").value.replace("
8
,",".");
9
var kurs = document.getElementById("kurs").value.replace("
10
document.getElementById(’dollar’).innerHTML = (euro*kurs).
,",".");
toFixed(2).replace(".",",");
11
12
}
</script>
13
</head>
14
<body>
15
<h2>Währungsumrechner</h2>
16
<article>
17
<table>
18
19
<tr>
<td>Kurs:</td><td><input id="kurs" value="1,29"/> $/ e </
td>
20
</tr>
21
<tr>
22
<td>Betrag:</td>
23
<td width="300px">
e
24
<input type="text" id="euro" value="230,78"/>
25
<input type="button" value=" = " onclick="berechnen();"
/>
<span id="dollar">?</span> $
26
27
28
</td>
</tr>
29
</table>
30
</article>
31
32
</body>
</html>
Aufgabe 1.2
1
<!doctype html>
2
<html>
3
<head>
77
4
Lösungen der Aufgaben
4
<meta charset="UTF-8" />
5
<title>Dateneingabe</title>
6
<script
type="text/javascript">
var get = function(id) {
7
return document.getElementById(id);
8
}
9
10
var berechnen = function(n) {
11
get((n+1)%2).value = (100 - get(n).value); //
12
.toFixed(2).replace(".",",");
}
13
</script>
14
15
</head>
16
<body>
17
<h2>Daten eingeben:</h2>
18
Eigenkapitalquote: <input id="0" onkeyup="berechnen(0);"/> %
19
<br/>
20
Fremdkapitalquote: <input type="text" id="1" onkeyup="
21
</body>
berechnen(1);"/> %
22
</html>
Aufgabe 1.3
1
<!DOCTYPE html>
2
<html>
3
<head> <meta charset="UTF-8"/> </head>
4
<body>
<script type="text/javascript">
5
6
/* Hintergrund: http://de.wikipedia.org/wiki/Newton-CotesFormeln */
7
// n=1, Sehnentrapezformel:
8
document.write("Sehnentrapezformel:<br/>");
9
var I = function (f, a, b) {
return (b-a)/2 * (f(a) + f(b));
10
11
}
12
13
var f = function(x,y) {return 7*x*x*x*x*x*x - 8*x*x*x + 2*x -
14
var g = function(x,y) {return 1/x;}
15
var h = function(x,y) {return Math.sin(x);}
1;}
16
17
document.write("I(f,0,10) = " + I(f, 0, 10)); // => 1
18
document.write("<br/>I(g,1,e) = " + I(g, 1, Math.E)); // =>
1.1752011936438014
78
19
document.write("<br/>I(h,0,1) = " + I(h, 0, 1)); // => 0.5
20
document.write("<br/>I(h,0,10) = " + I(h, 0, 10)); // => 0.5
21
22
// n=2, Kepler’sche Fassregel oder Simpsonformel:
23
document.write("<br/><br/>Kepler’sche Fassregel:<br/>");
24
var I = function (f, a, b) {
return (b-a)/6 * (f(a) + 4*f((a+b)/2) + f(b));
25
26
}
27
document.write("I(f,0,10) = " + I(f, 0, 10)); // => 1
28
document.write("<br/>I(g,1,e) = " + I(g, 1, Math.E)); // =>
1.0078899408946134
29
document.write("<br/>I(h,0,1) = " + I(h, 0, 1)); // => 0.5
30
document.write("<br/>I(h,0,10) = " + I(h, 0, 10)); // => 0.5
31
32
// n=3, Newton’sche 3/8-Regel:
33
document.write("<br/><br/>Newton’sche 3/8-Regel:<br/>");
34
var I = function (f, a, b) {
return (b-a)/8 * (f(a) + 3*f((2*a+b)/3) + 3*f((a+2*b)/3) + f
35
(b));
36
}
37
document.write("I(f,0,10) = " + I(f, 0, 10)); // => 1
38
document.write("<br/>I(g,1,e) = " + I(g, 1, Math.E)); // =>
1.003823485552161
39
document.write("<br/>I(h,0,1) = " + I(h, 0, 1)); // => 0.5
40
document.write("<br/>I(h,0,10) = " + I(h, 0, 10)); // => 0.5
41
42
// n=4, Boole-Regel:
43
document.write("<br/><br/>Milne-Regel:<br/>");
44
var I = function (f, a, b) {
return (b-a)/90 * (7*f(a) + 32*f((3*a+b)/4) + 12*f((2*a+2*b)
45
/4) + 32*f((a+3*b)/4) + 7*f(b));
46
}
47
document.write("I(f,0,10) = " + I(f, 0, 10)); // => 1
48
document.write("<br/>I(g,1,e) = " + I(g, 1, Math.E)); // =>
1.0003409768323182
49
document.write("<br/>I(h,0,1) = " + I(h, 0, 1)); // => 0.5
50
document.write("<br/>I(h,0,10) = " + I(h, 0, 10)); // => 0.5
51
52
// n=6, Weddle-Regel:
53
document.write("<br/><br/>Weddle-Regel:<br/>");
54
var I = function (f, a, b) {
return (b-a)/840 * (
55
41*f(a) + 216*f((5*a+b)/6) + 27*f((4*a+2*b)/6) + 272*f((3*
56
a+3*b)/6) +
27*f((2*a+4*b)/6) + 216*f((a+5*b)/6) + 41*f(b)
57
);
58
59
}
60
document.write("I(f,0,10) = " + I(f, 0, 10)); // => 1
79
4
Lösungen der Aufgaben
61
document.write("<br/>I(g,1,e) = " + I(g, 1, Math.E)); // =>
1.0000220551768157
62
document.write("<br/>I(h,0,1) = " + I(h, 0, 1)); // => 0.5
63
document.write("<br/>I(h,0,10) = " + I(h, 0, 10)); // => 0.5
</script>
64
</body>
65
66
</html>
Aufgabe 1.4
1
<!DOCTYPE html>
2
<html>
3
<head> <meta charset="UTF-8"/> </head>
4
<body>
<script type="text/javascript">
5
6
var map = function(f,a) {
7
var result = []; // Create a new Array
8
for (var i = 0; i < a.length; i++) {
result[i] = f(a[i]);
9
}
10
return result;
11
12
};
13
var square = function(x) {return x*x;};
14
var cube
15
document.write("map(x^2, [1,2,3,4,5]) = " + map(square,
= function(x) {return x*x*x;};
[1,2,3,4,5]));
16
document.write("<br/>map(x^3, [1,2,3,4,5]) = " + map(cube,
[1,2,3,4,5]));
17
18
var a = [1,2,3,4,5];
19
document.write("<hr/><table border=\"1\"><tr><td><i>x</i></td>"
20
for (var i = 0; i < a.length; i++) {
);
document.write("<td style=\"text-align:right;\">" + a[i] + "
21
</td>");
22
}
23
document.write("</tr><tr><td><i>x</i><sup>2</sup></td>");
24
var m = map(square,a);
25
for (var i = 0; i < a.length; i++) {
document.write("<td style=\"text-align:right;\">" + m[i] + "
26
</td>");
27
}
28
document.write("</tr><tr><td><i>x</i><sup>3</sup></td>");
29
m = map(cube,a);
30
for (var i = 0; i < a.length; i++) {
31
document.write("<td style=\"text-align:right;\">" + m[i] + "
</td>");
80
32
}
33
document.write("</tr></table>");
</script>
34
</body>
35
36
</html>
Aufgabe 1.5 (a) & (b)
1
<!doctype html>
2
<html>
3
<head>
4
<meta charset="utf-8" />
5
<title>JavaScript - DOM</title>
6
<script type="text/javascript">
7
var wechsleFarbe = function(farbe) {
document.getElementById("Text").style.color = farbe;
8
9
}
10
11
var erstelleStern = function() {
12
var stern = document.createElement("img");
13
stern.setAttribute("src", "./stern.png");
14
stern.setAttribute("width", "30px");
15
document.getElementById("stern").appendChild(stern);
16
}
17
18
var loescheStern = function() {
document.getElementById("stern").removeChild(document.
19
getElementById("stern").lastChild);
20
21
}
</script>
22
</head>
23
<body>
24
<!-- main_document -->
25
<article>
26
<header>
27
28
<h1>Übungsblatt 3, Aufgabe 1</h1>
</header>
29
30
31
32
<header>
<h2>a)</h2>
</header>
33
34
<section>
35
<p id="Text" style="font-weight:bold;">Ändere meine Farbe:</p
>
36
<button onclick="wechsleFarbe(’#009900’)">Grün</button>
37
<button onclick="wechsleFarbe(’#e60000’)">Rot</button>
81
4
Lösungen der Aufgaben
38
</section>
39
40
41
42
<header>
<h2>b)</h2>
</header>
43
44
<section>
45
<button id="erstelleStern" onclick="erstelleStern()">Stern
erstellen</button>
46
<button id="loescheStern" onclick="loescheStern()">Stern l&
ouml;schen</button>
47
<div id="stern"></div>
48
</section>
49
50
</article>
51
</body>
52
</html>
Aufgabe 1.6
1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
<meta charset="utf-8">
5
<title>FH Südwestfalen Städteinfo</title>
6
<style>
7
body {font-family: Verdana, sans-serif; font-size:0.8em;}
8
header,nav, section,article,footer
9
{border:1px solid grey; margin:5px; padding:8px;}
10
nav ul {margin:0; padding:0;}
11
nav ul li {display:inline; margin:5px;}
12
</style>
13
<script type="text/javascript">
14
var laden = function(stadt) {
15
var request = new XMLHttpRequest();
16
request.onreadystatechange = function() {
17
if (request.readyState === 4) {
18
var info = JSON.parse(request.response);
19
document.getElementById("titel").innerHTML = info.
name;
20
var el = document.getElementById("inhalt");
21
var ausgabe = info.name;
22
if (info.capital) {
ausgabe += " is the capital of ";
23
24
} else {
ausgabe += " is a city in ";
25
26
82
}
ausgabe += info.state + " with a metropolitan area
27
of " + info.residents + " residents. ";
28
ausgabe += info.remark;
29
el.innerHTML = ausgabe;
}
30
31
};
32
request.open("GET", "./" + stadt + ".json");
request.send();
33
34
35
36
};
</script>
</head>
37
38
<body>
39
<header>
40
41
<h1>Famous Cities</h1>
</header>
42
43
<nav>
44
<ul>
45
<li><input type="button" value="Berlin" onclick="laden(’
Berlin’);"/></li>
46
<li><input type="button" value="Istanbul" onclick="laden(’
47
<li><input type="button" value="Paris" onclick="laden(’Paris
Istanbul’);"/></li>
’);"/></li>
48
<li><input type="button" value="London" onclick="laden(’
49
<li><input type="button" value="Tokyo" onclick="laden(’Tokyo
London’);"/></li>
’);"/></li>
50
</ul>
51
</nav>
52
53
<section>
54
55
<h1>Famous Cities</h1>
56
57
<article>
58
<h2 id="titel">London</h2>
59
<p id="inhalt">
60
London is the capital city of England
61
with a metropolitan area of over 13 million inhabitants.
62
It is the most populous city in the United Kingdom.
63
</p>
64
</article>
65
66
</section>
83
4
Lösungen der Aufgaben
67
68
<footer>
69
<p>© 2016 FH Südwestfalen.</p>
70
</footer>
71
72
</body>
Aufgabe 1.7 (a)
1
<!doctype html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<!--Load the AJAX API-->
6
<script type="text/javascript" src="http://www.google.com/
jsapi"></script>
7
8
<script type="text/javascript">
google.load(’visualization’, ’1’, {’packages’: [’geochart’]})
;
9
var datatable_2012 = [
10
[’Standort’, ’Erstsemester’, ’Studierende’],
11
[’Hagen’,
12
[’Iserlohn’, 859, 1640],
13
[’Meschede’, 504, 1127],
14
[’Soest’,
15
790, 1689],
803, 2007],
];
16
17
google.setOnLoadCallback(drawMarkersMap);
18
19
20
function drawMarkersMap() {
var data = google.visualization.arrayToDataTable(
datatable_2012);
21
var options = {
22
//width: ’600’,
23
region: ’DE’,
24
resolution: ’provinces’,
25
displayMode: ’markers’,
26
colorAxis: {colors: [’red’, ’purple’]}
27
};
28
29
var chart = new google.visualization.GeoChart(document.
getElementById(’karte’));
30
31
chart.draw(data, options);
};
32
84
33
google.load("visualization", "1", {packages:["corechart"]});
34
google.setOnLoadCallback(drawChart);
35
function drawChart() {
36
var data = google.visualization.arrayToDataTable(
37
datatable_2012);
var options = {
38
39
//title: ’Company Performance’,
40
//hAxis: title: ’Year’, titleTextStyle: color: ’red’,
//width: ’600’,
41
};
42
43
44
var chart = new google.visualization.ColumnChart(document.
45
chart.draw(data, options);
46
//chart.draw(data);
getElementById(’säulen’));
47
var pieChart = new google.visualization.PieChart(document.
48
getElementById(’torten’));
var options = {
49
50
title: ’Einschreibungen 2012’,
51
//hAxis: title: ’Year’, titleTextStyle: color: ’red’,
52
//width: ’600’,
};
53
pieChart.draw(data, options);
54
}
55
</script>
56
</head>
57
58
<body>
59
60
<h2>Studierendenzahlen der FH Südwestfalen 2012</h2>
61
<div>(Haupthörer in Regelstudienzeit)</div>
62
<table border="0">
<tr>
63
64
<td id="säulen" style="width: 700px; height: 180px;"></
65
<td rowspan="2" id="karte" style="width: 800px; height:
td>
400px;"></td>
66
</tr>
67
<tr>
<td id="torten" style="width: 400px; height: 220px;"></
68
td>
</tr>
69
</table>
70
</body>
71
72
</html>
(b)
85
4
Lösungen der Aufgaben
1
<!doctype html>
2
<html>
3
<head>
4
<meta charset="UTF-8"/>
5
<!--Load the AJAX API-->
6
<script type="text/javascript" src="http://www.google.com/
jsapi"></script>
7
8
<script type="text/javascript">
function tanh(x) {
return 1 - 2/(Math.exp(2*x) + 1);
9
10
}
11
12
// Load the Visualization API and the standard diagram
package.
13
google.load(’visualization’, ’1’, {’packages’:[’corechart
’]});
14
15
// Die möglichen Diagrammtypen:
16
//
https://developers.google.com/chart/interactive/docs/gallery
17
18
// Set a callback to run when the Google Visualization
19
google.setOnLoadCallback(drawChart);
API is loaded.
20
21
// Callback that creates and populates a data table,
22
// instantiates the diagram chart, passes in the data and
draws it.
23
function drawChart() {
24
// Create the data table.
25
var data = new google.visualization.DataTable();
26
data.addColumn(’string’, ’x’);
27
data.addColumn(’number’, ’2/π arctan x’);
28
data.addColumn(’number’, ’sin x’);
29
data.addColumn(’number’, ’tanh x’);
30
86
31
var min
= -2;
32
var max
=
33
// either ... --------
34
var n = 100; var delta = (max - min) / n;
35
// ... or ...
36
//var delta = 0.04; var n = (max - min) / delta;
37
// ---------------
38
var x, t, precision = 2;
39
data.addRows(n+1);
40
for (i = 0; i <= n; i++) {
2;
41
t = min + i*delta;
42
x = Math.PI * t;
43
if (t.toFixed(precision) == Math.round(t)) {
44
if (t == -1) data.setValue(i, 0, "-π");
45
else if (t == 0) data.setValue(i, 0, "0");
46
else if (t == 1) data.setValue(i, 0, "π");
47
else data.setValue(i, 0, t + "π");
48
}
49
data.setValue(i, 1, 2/Math.PI * Math.atan(x));
50
data.setValue(i, 2, Math.sin(x));
51
data.setValue(i, 3, tanh(x));
}
52
53
// Instantiate and draw our chart, passing in some
54
options.
var chart = new google.visualization.LineChart(document
55
.getElementById(’diagram’));
chart.draw(data, {
56
57
width:640, height: 240,
58
fontSize: 10,
59
legend: ’top’, legendTextStyle: {fontSize: 12},
60
hAxis: {title: ’x’, slantedText: true,
slantedTextAngle: 90, titleTextStyle: {fontSize:
14}},
vAxis: {title: ’f(x)’, titleTextStyle: {fontSize:
61
14}},
62
//curveType: ’function’,
63
pointSize: 0,
});
64
}
65
</script>
66
</head>
67
68
<body>
69
<div id="diagram"></div>
70
</body>
71
72
</html>
Aufgabe 2.1 (a)
(b)
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<title>Companies</title>
87
4
Lösungen der Aufgaben
6
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></
script>
7
8
<script>
$(function(){
$("tr:even").css("background-color", "lightblue");
9
10
11
});
</script>
12
</head>
13
<body>
14
15
<h1>Welcome to My Web Page</h1>
16
17
18
19
20
<table>
<tr>
<th>Company</th>
<th>Country</th>
21
</tr>
22
<tr>
23
<td>Alfreds Futterkiste</td>
24
<td>Germany</td>
25
</tr>
26
<tr>
27
<td>Berglunds snabbköp</td>
28
<td>Sweden</td>
29
</tr>
30
<tr>
31
<td>Centro comercial Moctezuma</td>
32
<td>Mexico</td>
33
</tr>
34
<tr>
35
<td>Ernst Handel</td>
36
<td>Austria</td>
37
</tr>
38
<tr>
39
<td>Island Trading</td>
40
<td>UK</td>
41
42
</tr>
</table>
43
44
</body>
45
</html>
Aufgabe 2.2
88
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<title>Effekte</title>
6
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></
script>
7
<script src="http://code.jquery.com/ui/1.11.4/jquery-ui.js"
></script>
8
9
10
<script>
$(function(){
$("#explode").click(function() {
$(this).hide("explode", {pieces: 16}, 2000);
11
12
13
14
});
});
</script>
15
</head>
16
<body>
17
<h1>Effekte</h1>
18
<p>
19
<img src="http://haegar.fh-swf.de/grafik/logo.gif" id="
20
<p>
21
</body>
22
</html>
explode" alt="Logo"/>
Aufgabe 2.3 (a)
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<title>Programmiersprachen</title>
6
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></
7
<script src="http://code.jquery.com/ui/1.11.4/jquery-ui.js"
script>
></script>
8
<link rel="stylesheet" href="http://code.jquery.com/ui
/1.11.4/themes/smoothness/jquery-ui.css">
9
10
11
<script>
$(function() {
var availableTags = [
12
"ActionScript",
13
"AppleScript",
14
"Asp",
15
"BASIC",
16
"C",
17
"C++",
18
"Clojure",
19
"COBOL",
89
4
Lösungen der Aufgaben
20
"ColdFusion",
21
"Erlang",
22
"Fortran",
23
"Groovy",
24
"Haskell",
25
"Java",
26
"JavaScript",
27
"Lisp",
28
"Perl",
29
"PHP",
30
"Python",
31
"Ruby",
32
"Scala",
33
"Scheme"
34
];
35
$("#tags").autocomplete({
36
source: availableTags
37
});
38
$("#autocomplete").autocomplete({
source: function(request, response) {
39
var matcher = new RegExp("^" + $.ui.autocomplete.
40
escapeRegex(request.term), "i");
response($.grep(availableTags, function(item) {
41
return matcher.test(item);
42
}));
43
}
44
45
});
46
});
47
</script>
48
</head>
49
<body>
50
51
<div class="ui-widget">
52
Wähle eine Programmiersprache aus:
53
<label for="tags">Tags: </label>
54
<input id="tags">
55
<label for="autocomplete">Autocomplete: </label>
56
57
<input id="autocomplete">
</div>
58
59
</body>
60
</html>
Aufgabe 2.4
90
1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
<meta charset="utf-8">
5
<title>FH Südwestfalen Städteinfo</title>
6
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></
7
<script>
script>
8
var laden = function(stadt) {
$.ajax({
9
10
type: "GET",
11
url: "./"+stadt+".json",
12
success: function(response) {
13
var info = response;
14
$("#title").html(info.name);
15
var ausgabe = info.name;
16
if (info.capital) {
ausgabe += " is the capital of ";
17
} else {
18
ausgabe += " is a city in ";
19
20
}
21
ausgabe += info.state + " with a metropolitan area of
" + info.residents + " residents. ";
ausgabe += info.remark;
22
$("#inhalt").html(ausgabe);
23
}
24
});
25
26
}
27
</script>
28
<style>
29
body {font-family: Verdana, sans-serif; font-size:0.8em;}
30
header,nav, section,article,footer
31
{border:1px solid grey; margin:5px; padding:8px;}
32
nav ul {margin:0; padding:0;}
33
nav ul li {display:inline; margin:5px;}
34
35
</style>
</head>
36
37
<body>
38
<header>
39
40
<h1>Famous Cities</h1>
</header>
41
42
<nav>
43
<ul>
44
<li><input type="button" value="Berlin" onclick="laden(’
45
<li><input type="button" value="Istanbul" onclick="laden(’
Berlin’);"/></li>
91
4
Lösungen der Aufgaben
Istanbul’);"/></li>
46
<li><input type="button" value="Paris" onclick="laden(’Paris
’);"/></li>
47
<li><input type="button" value="London" onclick="laden(’
48
<li><input type="button" value="Tokyo" onclick="laden(’Tokyo
London’);"/></li>
’);"/></li>
49
</ul>
50
</nav>
51
52
<section>
53
54
<h1>Famous Cities</h1>
55
56
<article>
57
<h2 id="titel">London</h2>
58
<p id="inhalt">
59
London is the capital city of England
60
with a metropolitan area of over 13 million inhabitants.
61
It is the most populous city in the United Kingdom.
62
</p>
63
</article>
64
65
</section>
66
67
<footer>
68
<p>© 2016 FH Südwestfalen.</p>
69
</footer>
70
71
</body>
Aufgabe 3.1 (a)
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<script src="https://ajax.googleapis.com/ajax/libs/angularjs
/1.5.0/angular.min.js"></script>
6
</head>
7
<body ng-app="">
8
Menge: <input type="text" ng-model="menge"/> <br/>
9
Preis: <input type="text" ng-model="preis"/> <br/>
10
92
Gesamter Betrag: {{ menge*preis }}
11
</div>
12
</body>
13
</html>
(b)
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<script src="https://ajax.googleapis.com/ajax/libs/angularjs
/1.5.0/angular.min.js"></script>
6
</head>
7
<body ng-app="" ng-init="alpha=0.7;">
8
Fade out: <input type="range" name="alpha" min="0" max="1"
9
<img style="opacity:{{alpha}};" src="Merkel.jpg" width="200"
step="0.01" ng-model="alpha"/>
alt="Die Kanzlerin"/>
10
</body>
11
</html>
(c)
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta charset="utf-8"/>
5
<script src="https://ajax.googleapis.com/ajax/libs/angularjs
/1.5.0/angular.min.js"></script>
6
</head>
7
<body ng-app="">
8
<input type="checkbox" ng-model="flag"/>
9
<span ng-show="flag">Text anzeigen</span>
10
<span ng-hide="flag">Text verstecken</span>
11
</div>
12
</body>
13
</html>
Aufgabe 3.2 Die Seite index.html:
1 <!DOCTYPE html>
2 <html>
3 <head>
4
<meta charset="utf-8"/>
5
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></
6
<script>
script>
7 var heroApp = angular.module(’heroApp’, []);
8 var modelUri = "http://localhost/programme/angular/datenbank/heroes/heroes.php";
9
10 heroApp.component(’heroList’, {
11
templateUrl: ’heroList.html’,
12
controller: function($scope, $element, $attrs, $http) {
13
var ctrl = this; // reference necessary for closure in $http!
14
ctrl.list;
15
$http({
16
method: "GET",
93
4
Lösungen der Aufgaben
17
url: "http://localhost/programme/angular/datenbank/heroes/heroes.php?action=
list"
18
}).then(function (response) {
19
ctrl.list = response.data; // <- defines the list
20
}, function error(response) {
21
$scope.response = response.statusText;
22
});
23
24
ctrl.updateHero = function(hero, prop, value) {
25
hero[prop] = value; // <- no "deep data binding"!!
26
var rest = modelUri + "?action=update&id=" + hero.id + "&"+prop+"="+value;
27
$http({
28
method: "POST",
29
url: rest
30
}).then(function success(response) {
31
}, function error(response) {
32
$scope.response = response.statusText;
33
});
34
};
35
36
ctrl.deleteHero = function(hero) {
37
var idx = ctrl.list.indexOf(hero);
38
if (idx >= 0) {
39
ctrl.list.splice(idx, 1);
40
}
41
var rest = modelUri + "?action=delete&id=" + hero.id;
42
$http({
43
method: "POST",
44
url: rest
45
}).then(function success(response) {
46
}, function error(response) {
47
$scope.response = response.statusText;
48
});
49
};
50
51
ctrl.insertHero = function(hero) {
52
var rest = modelUri + "?action=insert";
53
for (var field in hero) {
54
rest += "&" + field + "=" + hero[field];
55
}
56
$http({
57
method: "POST",
58
url: rest
59
}).then(function success(response) {
60
}, function error(response) {
61
$scope.response = response.statusText;
62
});
63
ctrl.list.push(hero);
64
65
};
}
66 });
67
68 heroApp.component(’heroDetail’, {
69
templateUrl: ’heroDetail.html’,
70
controller: function ($scope, $element, $attrs) {
71
this.update = function(prop, value) {
72
this.onUpdate({hero: this.hero, prop: prop, value: value});
73
};
74
},
75
bindings: {
76
hero: ’<’,
77
onDelete: ’&’,
78
79
onUpdate: ’&’
}
80 });
94
81
82 heroApp.component(’editableField’, {
83
templateUrl: ’editableField.html’,
84
controller: function ($scope, $element, $attrs) {
85
this.editMode = false; // property used to switch button view
86
87
this.handleModeChange = function() {
88
if (this.editMode) {
89
this.onUpdate({value: this.fieldValue});
90
this.fieldValueCopy = this.fieldValue;
91
}
92
this.editMode = !this.editMode;
93
};
94
95
this.reset = function() {
96
this.fieldValue = this.fieldValueCopy;
97
};
98
99
this.$onInit = function() {
100
// Make a copy of the initial value to be able to reset it later
101
this.fieldValueCopy = this.fieldValue;
102
103
// Set a default fieldType
104
if (!this.fieldType) {
105
this.fieldType = ’text’;
106
}
107
};
108
},
109
bindings: {
110
fieldValue: ’<’,
111
fieldType: ’@?’,
112
113
onUpdate: ’&’
}
114 });
115
116 heroApp.component(’heroInsert’, {
117
templateUrl: ’heroInsert.html’,
118
controller: function ($scope, $http) {
119
var ctrl = this; // reference necessary for closure in $http!
120
ctrl.hero;
121
ctrl.editMode = false;
122
ctrl.properties = [];
123
ctrl.switchEditMode = function() {
124
ctrl.editMode = !ctrl.editMode;
125
};
126
127
ctrl.save = function() {
128
ctrl.switchEditMode();
129
ctrl.onSave({hero: ctrl.hero}); // <- fire event
130
};
131
},
132
bindings: {
133
134
onSave: ’&’
}
135 });
136
</script>
137 </head>
138 <body ng-app="heroApp">
139
<hero-list></hero-list>
140 </body>
141 </html>
Das Template heroList.html:
1 <b>Heroes</b>
2 <br/>
95
4
Lösungen der Aufgaben
3 <hero-detail
4
ng-repeat="hero in $ctrl.list"
5
hero="hero"
6
on-delete="$ctrl.deleteHero(hero)"
7
on-update="$ctrl.updateHero(hero, prop, value)">
8 </hero-detail>
9 <br/>
10 <hero-insert on-save="$ctrl.insertHero(hero)">
11
<button ng-click="onInsert()">Insert a new
hero</button>
12 </hero-insert>
Das Template heroList.html:
Das Template heroDetail.html:
1 <hr>
2 <div>
3
Name: {{$ctrl.hero.name}}
4
<br>
5
Location:
6
<editable-field
7
field-value="$ctrl.hero.location"
8
field-type="text"
9
10
on-update="$ctrl.update(’location’, value)">
</editable-field>
11
<br>
12
Feat:
13
<editable-field
14
field-value="$ctrl.hero.feat"
15
field-type="text"
16
on-update="$ctrl.update(’feat’, value)">
17
</editable-field>
18
<br>
19
<button ng-click="$ctrl.onDelete({hero: $ctrl.hero})">Delete</button>
20 </div>
Das Template editableField.html:
1 <span ng-switch="$ctrl.editMode">
2
<input ng-switch-when="true" type="{{$ctrl.fieldType}}" ng-model="$ctrl.fieldValue">
3
<span ng-switch-default>{{$ctrl.fieldValue}}</span>
4 </span>
5 <button ng-click="$ctrl.handleModeChange()">{{$ctrl.editMode ? ’Save’ : ’Edit’}}</button
>
6 <button ng-if="$ctrl.editMode" ng-click="$ctrl.reset()">Reset</button>
Das Template heroInsert.html:
1 <hr>
2 <button ng-hide="$ctrl.editMode" ng-click="$ctrl.switchEditMode()">Create new hero</
button>
3
4 <form name="insertForm" ng-show="$ctrl.editMode" novalidate>
5 <div>
6
<input type="text" name="Name" placeholder="Name" ng-model="$ctrl.hero.name"/><br/>
7
<input type="text" name="Location" placeholder="Location" ng-model="$ctrl.hero.
8
<textarea rows="5" name="Feat" placeholder="The hero’s feat" ng-model="$ctrl.hero.feat
location"/><br/>
"/></textarea>
9 </div>
10 <button ng-show="$ctrl.editMode" ng-click="$ctrl.save();">Save</button>
11 </form>
Das PHP-Skript heroes.php:
1 <?php
2 if (!isset($_GET[’action’])) {
3
4 }
96
die ("No field \"action\" in GET method! What shall I do?");
5
6 $db = "halloffame";
7 $table = "heroes";
8
9 $mysqli = new mysqli("localhost", "user", "c5dDC8VJemscKPQS", $db);
10 $mysqli->query("SET CHARACTER SET ’utf8’");
11 $mysqli->query("SET NAMES ’utf8’");
12
13 if ($_GET[’action’] == "list") {
14
$sql = "SELECT * FROM $table";
15
$result = $mysqli->query($sql);
16
$array = array();
17
while ($entry = $result->fetch_assoc()) {
18
$array[] = $entry;
19
}
20
echo json_encode($array);
21 } else if ($_GET[’action’] == "insert") {
22
$keys = array(); $values = array();
23
24
foreach ($_GET as $key => $value) {
25
if ($key === "action") continue;
26
$keys[] = $key;
27
28
$values[] = "’$value’";
}
29
30
$sql
31
$sql .= "(" . implode(",", $keys) . ")";
32
$sql .= " VALUES ";
33
$sql .= "(" . implode(",", $values) . ")";
34
$mysqli->query($sql);
= "INSERT INTO $table ";
35 } else if ($_GET[’action’] == "update") {
36
$sql
= "UPDATE $table SET ";
37
38
foreach ($_GET as $key => $value) {
39
if ($key === "action") continue;
40
$sql .= " $key=’$value’,";
41
}
42
// Delete last comma:
43
$sql = substr($sql, 0, strlen($sql) - 1);
44
45
$sql .= " WHERE id=$_GET[id]";
46
47
$mysqli->query($sql);
48 } else if ($_GET[’action’] == "select") {
49
$sql = "SELECT * FROM $table WHERE id=$_GET[id] LIMIT 1";
50
$result = $mysqli->query($sql);
51
$json = "{}";
52
while ($entry = $result->fetch_assoc()) {
$json = json_encode($entry);
53
54
}
55
echo $json;
56 } else if ($_GET[’action’] == "delete") {
57
$sql = "DELETE FROM $table WHERE id=$_GET[id]";
58
$result = $mysqli->query($sql);
59 } else if ($_GET[’action’] == "structure") {
60
$sql = "SHOW COLUMNS FROM $table";
61
$result = $mysqli->query($sql);
62
$fields = array();
63
while ($entry = $result->fetch_assoc()) {
64
$fields[] = $entry;
65
}
66
echo json_encode($fields);
67 }
68 ?>
97
Literatur
Literatur
[1]
Crockford, D. : Das Beste an JavaScript. Köln : O’Reilly, 2008
[2]
de Vries, A. : Hagener Berichte der Wirtschaftsinformatik. Bd. 1: P
6= NP? Norderstedt : Books on Demand, 2012. – 71–102 S.
[3]
Forster, O. : Analysis 1. 9. Wiesbaden : Vieweg, 2008
[4]
Fowler, M. :
Inversion of Control Containers
and the Dependency Injection pattern.
http:
//martinfowler.com/articles/injection.html, Jan 2004
[5]
Fowler,
M.
:
InversionOfControl.
http:
//martinfowler.com/bliki/InversionOfControl.html, Jun 2005
[6]
Gorski, P. L. ; Iacono, L. L. ; Nguyen, H. V.: WebSockets. Moderne
HTML5-Echtzeitanwendungen entwickeln. Müchen : Hanser, 2015
[7]
Lau, O. : ‘Verteiltes Rechnen mit JavaScript’. In: c’t kompakt Programmieren 3 (2012). – http://www.ct.de/cs1207014
[8]
Piepmeyer, L. : Grundkurs Funktionale Programmierung mit
Scala. München Wien : Carl Hanser Verlag, 2010. – http:
//www.grundkurs-funktionale-programmierung.de/
[9]
Schuhfuß, M. : ‘Node-Ausrüstung’. In: JavaScript Spezial (2012),
S. 34–38. – http://www.javascript-spezial.de
[10] Tarasiewicz, P. ; Böhm, R. : AngularJS. Eine praktische Einführung
in das JavaScript-Framework. Heidelberg : dpunkt.verlag, 2014
[11] Wenz, C. : ‘Die 10 Gebote. Zehn Dinge, die man über JavaScript
wissen muss’. In: Entwickler Magazin Spezial 5 (2015), S. 10–16.
– https://entwickler.de/entwickler-magazin/
[12] Zeidler, E. (Hrsg.): Teubner Taschenbuch der Mathematik. Teil 1.
Leipzig : B. G. Teubner, 1996
Internetquellen
[DOM]
[JS]
https://w3.org/DOM – Spezifikation des W3C des DOM.
https://developer.mozilla.org/en-US/docs/Web/JavaScript
–
JavaScript-Dokumentation des Mozilla Developer Network.
[JSL]
http://www.jslint.com/ – JSLint, Programm zur Überprüfung der
Qualität von JavaScript Quelltext.
98
Literatur
[W3C]
– World Wide Web Consortium, Gremium
zur Standardisierung der Web-Techniken; eine deutsche Übersetzung wichtiger Teile dieses Webauftritts findet man unter http:
//www.edition-w3c.de/, auf die auch das Deutsch-Österreichische
Büro W3C.DE/AT http://www.w3c.de/ bzw. http://www.w3c.at/
verlinkt.
http://www.w3c.org/
99
Abbildungsverzeichnis
Abbildungsverzeichnis
1.1 Optionen der Toleranz von JSLint [JL] . . . . . . . . . . . . . . . . . . .
1.2 Ausgabe per Element-ID . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Aufrufstruktur
einer
Rückruffunktion.
(Modifiziert
nach
https://en.wikipedia.org/wiki/File:Callback-notitle.svg) . . . . . .
1.4 Objekt-Hierarchie des Browsers mit wichtigen Objekten, insbesondere
das document-Objekt. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.5 Der DOM-Baum des HTML-Dokuments aus Listing 3. . . . . . . . . . .
1.6 Zustandsdiagramm eines XMLHttpRequest-Objekts. Vgl. [6, S. 26]. . .
1.7 Polling und Long Polling als Methoden zur Synchronisation eines zentralen Datenbestandes. Vgl. [6, S. 27f]. . . . . . . . . . . . . . . . . . . .
1.8 Kommunikation zwischen Hauptprozess und Worker. . . . . . . . . .
3.9 Das MVVM-Muster (links) für eine Webanwendung, im Vergleich dazu
das MVC-Muster (rechts). Da bei MVC die Hauptlast der Webanwendung
auf dem Server läuft, ist der Browser in diesem Fall ein Thin Client. Bei
MVVM dagegen hat er mit der Poxy-Schicht MVVM einen großen Teil
der Arbeitslast und ist daher in diesem Fall ein Fat Client. . . . . . . .
3.10 Struktur der Grundelemente von AngularJS. . . . . . . . . . . . . . . .
3.11 Verantwortlichkeitsbereiche (Scopes) zweier verschachtelter Components und der Datenbindungen zwischen ihren Controllern ($ctrl).
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
100
5
7
22
27
30
34
38
39
59
65
69
Herunterladen