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 + " &euro;!"; 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>&copy; 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>&copy; 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