| 1 | [[PageOutline(2-5)]] |
| 2 | |
| 3 | = !JavaScript = |
| 4 | |
| 5 | == Einbindung in HTML-Seiten == |
| 6 | |
| 7 | Leseempfehlung: [[http://wiki.selfhtml.org/wiki/JavaScript/Tutorials/Einbindung_in_HTML|selfhtml Tutorial Einbindung in HTML]] |
| 8 | |
| 9 | == Sprachelemente == |
| 10 | Leseempfehlung: [[http://wiki.selfhtml.org/wiki/JavaScript/Sprachelemente|selfhtml JavaScript Sprachelemente]] |
| 11 | |
| 12 | == Objekte in !JavaScript == |
| 13 | |
| 14 | In !JavaScript werden keine Klasse benötigt, um Objekte zu erzeugen. Statt dessen können Objekte mit sogenannten Objektliteralen erzeugt werden: |
| 15 | |
| 16 | Tip: Führen Sie die folgenden Anweisungen in der Console von Chrome aus und beobachten Sie die Ausgaben: |
| 17 | |
| 18 | Das leere Objekt ist das einfachste Objekt: |
| 19 | |
| 20 | {{{ |
| 21 | #!javascript |
| 22 | > var o = {} |
| 23 | > o |
| 24 | Object {} |
| 25 | }}} |
| 26 | |
| 27 | Einige Funktionen und Attribute wurden vom Prototypen `Object` übernommen, wie z. B. die Funktion `toString`: |
| 28 | |
| 29 | {{{ |
| 30 | #!javascript |
| 31 | > o.toString // Anzeige des Attributs toString (Funktionen sind Attribute) |
| 32 | function toString() { [native code] } |
| 33 | |
| 34 | > o.toString() // Aufruf der Funktion toString |
| 35 | "[object Object]" |
| 36 | }}} |
| 37 | |
| 38 | Objekte ähneln assoziativen Arrays, die Attribute können als Schlüssel-/Wert-Paare verstanden werden. Objekte können auch direkt mit Attributen angelegt werden: |
| 39 | |
| 40 | {{{ |
| 41 | #!javascript |
| 42 | > var susi = {vorname: "Susi", nachname: "Sonne", alter: 23}; |
| 43 | > susi.nachname |
| 44 | "Sonne" |
| 45 | }}} |
| 46 | |
| 47 | Attribute (auch ererbte) können geändert werden. Dabei kann der Typ sich ändern. Beispiel: das geerbte Attribut `toString` wird mit einer Zahl belegt und danach mit einer Funktion. Innerhalb von Objekten gibt es das Schlüsselwort `this`, mit dem auf die objekteigenen Attribute und Funktionen zugegriffen werden kann. |
| 48 | |
| 49 | {{{ |
| 50 | #!javascript |
| 51 | > susi.toString = 42 // !!! können toString mit irgendwas anderem ersetzen |
| 52 | > susi.toString |
| 53 | 42 |
| 54 | |
| 55 | > susi.toString = function() { |
| 56 | return this.vorname + " " + this.nachname + |
| 57 | " (" + this.alter + ")"; |
| 58 | } |
| 59 | function() { ... } |
| 60 | |
| 61 | > susi.toString() |
| 62 | "Susi Sonne (23)" |
| 63 | }}} |
| 64 | |
| 65 | === Objekte mit gleichen Eigenschaften === |
| 66 | Häufig werden in Informationssystemen mehrere Objekte desselben Typs verwendet. Die Objekte haben denselben Satz an Attributen und dieselben Funktionen. In anderen Sprachen wie z. B. Java spricht man von Klassen. |
| 67 | |
| 68 | Gleichartige Objekte können in Javascript auf verschiedene Weise erzeugt werden. In Java dagegen können Objekte nur erzeugt werden, indem der Konstruktor einer Klasse aufgerufen wird. |
| 69 | |
| 70 | ==== Klonen vorhandener Objekte ==== |
| 71 | Mit der `for-in`-Schleife kann über Attribute von Objekten iteriert werden. Auf die Werte der Attribute kann mittels eckigen Klammern `[]` wie in einem Array zugegriffen werden: |
| 72 | |
| 73 | {{{ |
| 74 | #!javascript |
| 75 | > for (var attr in susi) {console.log(attr + ": " + susi[attr]);} |
| 76 | vorname: Susi |
| 77 | nachname: Sonne |
| 78 | alter: 23 |
| 79 | toString: function () { |
| 80 | return this.vorname + " " + this.nachname + |
| 81 | " (" + this.alter + ")"; |
| 82 | } |
| 83 | }}} |
| 84 | |
| 85 | Diese Möglichkeit lässt sich nutzen, um Objekte rekursiv zu kopieren: |
| 86 | |
| 87 | {{{ |
| 88 | #!javascript |
| 89 | function clone(obj) { |
| 90 | // testen, ob es sich um ein Objekt handelt |
| 91 | // Abbruchbedingung der Rekursion |
| 92 | if ((obj === null) || (typeof obj !== "object")) { |
| 93 | // nein: den Wert einfach zurückgeben |
| 94 | return obj; |
| 95 | } |
| 96 | |
| 97 | // sonst: Klon dieses Objekts anlegen |
| 98 | var newObj = {}; |
| 99 | // Über die Attribute iterieren und diese clonen |
| 100 | // Rekursionsschritt |
| 101 | for (var attr in obj) { |
| 102 | newObj[attr] = clone(obj[attr]); |
| 103 | } |
| 104 | return newObj; |
| 105 | } |
| 106 | }}} |
| 107 | |
| 108 | Zur Demonstration kann das Objekt `susi` um ein weiteres Attribut, `adresse`, das selbst wieder ein Objekt ist, erweitert werden: |
| 109 | |
| 110 | {{{ |
| 111 | #!javascript |
| 112 | > susi.adresse = {strasse: "Friedrich-Heinrich-Allee 25", plz: "47475", ort: "Kamp-Lintfort"} |
| 113 | Object {strasse: "Friedrich-Heinrich-Allee 25", plz: "47475", ort: "Kamp-Lintfort"} |
| 114 | }}} |
| 115 | |
| 116 | Wenn die Funktion `clone` und alle anderen Anweisungen in einem `<script>`-Element einer HTML notiert werden, kann diese Seite in Chrome geladen und die Funktion `clone` im Debugger beobachtet werden. Es zeigt sich, dass die Rekursion bei den primitiven Attributen `vorname`, `nachname` und `alter` in der Abbruchbedingung endet, beim Attribut `adresse` die Schleife jedoch für dessen Attribute `strasse`, `plz` und `ort` erneut ausgeführt wird. Im Ergebnis entsteht ein neues Objekt als echte Kopie des ursprünglichen Objekts `susi`: |
| 117 | |
| 118 | {{{ |
| 119 | #!javascript |
| 120 | > var willy = clone(susi); |
| 121 | > willy |
| 122 | Object {vorname: "Susi", nachname: "Sonne", alter: 23, adresse: Object, toString: function} |
| 123 | }}} |
| 124 | |
| 125 | An dieser Stelle fällt auf, dass das Objekt `willy` eine echte Kopie des Objektes `susi` ist. Zur Erzeugung strukturell gleicher, aber inhaltlich verschiedener Objekte ist das Klonen daher eher unvorteilhaft, da alle Attribute explizit von außen neu gesetzt werden müssen: |
| 126 | |
| 127 | {{{ |
| 128 | #!javascript |
| 129 | > willy.vorname = "Willy" |
| 130 | > willy.nachname = "Winzig" |
| 131 | > willy.alter = 21 |
| 132 | > willy.adresse.strasse = "Marie-Curie-Straße 1" |
| 133 | > willy.adresse.plz = "47533" |
| 134 | > willy.adresse.ort = "Kleve" |
| 135 | > willy |
| 136 | Object {vorname: "Willy", nachname: "Winzig", alter: 21, adresse: Object, toString: function} |
| 137 | }}} |
| 138 | |
| 139 | ==== Prototypen ==== |
| 140 | Ein Objekt lässt sich als Prototyp eines anderen Objektes festlegen. Dazu wird das Attribut `prototype`, über das jedes Objekt verfügt, gesetzt. Hier entsteht im Gegensatz zum Klonen eine Vererbungshierarchie. |
| 141 | |
| 142 | {{{ |
| 143 | #!javascript |
| 144 | function extendObject(obj) { |
| 145 | function C() {} |
| 146 | C.prototype = obj; |
| 147 | return new C(); |
| 148 | } |
| 149 | }}} |
| 150 | |
| 151 | Ein neues Objekt lässt sich nun unter Verwendung eines bestehenden Objektes erzeugen: |
| 152 | |
| 153 | {{{ |
| 154 | #!javascript |
| 155 | > var willy = extendObject(susi) |
| 156 | > willy.toString() |
| 157 | "Susi Sonne (24)" |
| 158 | }}} |
| 159 | |
| 160 | Auch hier lassen sich jedoch keine Attributwerte übergeben, sondern müssen im Nachgang explizit gesetzt werden. Strukturelle Änderungen am originalen Objekt (dem Prototypen) wirken sich direkt auf alle Objekte aus, die auf dem Prototypen basieren: |
| 161 | |
| 162 | {{{ |
| 163 | #!javascript |
| 164 | > willy.vorname = "Willy" |
| 165 | > willy.toString() |
| 166 | "Willy Sonne (24)" |
| 167 | |
| 168 | > susi.increaseAlter = function() {this.alter++;} |
| 169 | > willy.increaseAlter() |
| 170 | > willy.toString() |
| 171 | "Willy Sonne (25)" |
| 172 | }}} |
| 173 | |
| 174 | ==== Erzeugerfunktion ==== |
| 175 | Wird die Erstellung mittels Objektliteral in einer Funktion gekapselt, spricht man von einer Erzeugerfunktion: |
| 176 | |
| 177 | {{{ |
| 178 | #!javascript |
| 179 | function createPerson(vorname, nachname, alter) { |
| 180 | return { |
| 181 | vorname: vorname, |
| 182 | nachname: nachname, |
| 183 | alter: alter, |
| 184 | toString: function() { |
| 185 | return this.vorname + " " + this.nachname + " (" + this.alter + ")"; |
| 186 | } |
| 187 | }; |
| 188 | } |
| 189 | }}} |
| 190 | |
| 191 | Dieser Funktion lassen sich Attributwerte als Parameter übergeben, jedoch entsteht keine Vererbungshierarchie - es sei denn, es wird auch ein Parameter für das Attribut `prototype` übermittelt und in der Funktion gesetzt. |
| 192 | |
| 193 | Das Objekt wird dann mittels Funktionsaufruf erzeugt: |
| 194 | |
| 195 | {{{ |
| 196 | #!javascript |
| 197 | > var susi = createPerson("Susi", "Sonne", 24) |
| 198 | > susi |
| 199 | Object {vorname: "Susi", nachname: "Sonne", alter: 24, toString: function} |
| 200 | |
| 201 | > susi.toString() |
| 202 | "Susi Sonne (24)" |
| 203 | }}} |
| 204 | |
| 205 | ==== Konstruktorfunktion ==== |
| 206 | Konstruktorfunktionen kommen strukturell Java-Klassen mit Konstruktoren am nächsten. Sie werden üblicherweise entsprechend der [[https://google.github.io/styleguide/javascriptguide.xml#Naming|gängigen Namenskonventionen]] in [[https://de.wikipedia.org/wiki/Binnenmajuskel#Programmiersprachen|CamelCase]], beginnend mit einem Großbuchstaben, benannt. Konstruktorfunktionen werden üblicherweise in eigene .js-Dateien ausgelagert; im folgenden Beispiel also Person.js. Der Konstruktor liefert das erzeugte Objekt implizit als Rückgabewert, eine Angabe von `return this;` ist also nicht erforderlich: |
| 207 | |
| 208 | {{{ |
| 209 | #!javascript |
| 210 | function Person(vn, nn, alter) { |
| 211 | // Attribute setzen |
| 212 | this.vorname = vn; |
| 213 | this.nachname = nn; |
| 214 | this.alter = alter; |
| 215 | |
| 216 | // Methoden |
| 217 | this.toString = function() { |
| 218 | return this.vorname + " " + this.nachname + " (" + this.alter + ")"; |
| 219 | }; |
| 220 | } |
| 221 | }}} |
| 222 | |
| 223 | Der Aufruf der Konstruktorfunktion mit dem Schlüsselwort `new` führt dann zur Objekterzeugung: |
| 224 | |
| 225 | {{{ |
| 226 | #!javascript |
| 227 | > var willy = new Person("Willy", "Winzig", 21) |
| 228 | > willy.toString() |
| 229 | "Willy Winzig (21)" |
| 230 | }}} |
| 231 | |
| 232 | ==== Schutz vor fehlerhafter Verwendung ==== |
| 233 | |
| 234 | Bei der Verwendung von Konstruktorfunktionen ist darauf zu achten, dass diese prinzipiell auch ohne `new` aufgerufen werden können. Dann wird das globale Objekt (im Browser: `window`) um die aufgeführten Attribute erweitert, aber kein neues Objekt erzeugt. Es ist ein typischer Fehler, dass `new` vergessen wird: |
| 235 | |
| 236 | {{{ |
| 237 | #!javascript |
| 238 | > var alex = Person("Alex", "Schmidt", 31) |
| 239 | > alex |
| 240 | undefined |
| 241 | |
| 242 | > window.vorname |
| 243 | "Alex" |
| 244 | }}} |
| 245 | |
| 246 | Dies lässt sich durch eine Schutzmaßnahme im Konstruktor selbst verhindern: |
| 247 | |
| 248 | {{{ |
| 249 | #!javascript |
| 250 | function Person(vn, nn, alter) { |
| 251 | // Welchen Typ hat this? |
| 252 | // Schützt den Konstruktor vor Aufruf ohne new |
| 253 | // ggf. erfolgt einmalig rekursiver Aufruf des |
| 254 | // Konstruktors mit new |
| 255 | if (!(this instanceof Person)) { |
| 256 | return new Person(vn, nn, alter); |
| 257 | } |
| 258 | |
| 259 | // Attribute setzen |
| 260 | this.vorname = vn; |
| 261 | this.nachname = nn; |
| 262 | this.alter = alter; |
| 263 | |
| 264 | // Methoden |
| 265 | this.toString = function() { |
| 266 | return this.vorname + " " + this.nachname + " (" + this.alter + ")"; |
| 267 | }; |
| 268 | } |
| 269 | }}} |
| 270 | |
| 271 | Der Aufruf ohne `new` führt nun dazu, dass der Konstruktor sich selbst korrekt mit `new` aufruft und das neu erzeugte Objekt zurückliefert: |
| 272 | |
| 273 | {{{ |
| 274 | #!javascript |
| 275 | > var alex = Person("Alex", "Schmidt", 33) |
| 276 | > alex |
| 277 | Person {vorname: "Alex", nachname: "Schmidt", alter: 33, toString: function} |
| 278 | }}} |
| 279 | |
| 280 | Nichtsdestotrotz sollte beim Programmieren darauf geachtet werden, stets `new` zu verwenden, um Problemen mit Konstruktorfunktionen, die nicht über einen derartigen Schutz verfügen, vorzubeugen. |
| 281 | |
| 282 | ==== Sichtbarkeit von Attributen ==== |
| 283 | |
| 284 | Alle mit `this.` notierten Attribute und Funktionen sind öffentlich zugänglich. In Konstruktorfunktionen lassen sich auch private Attribute und Attribute angeben, indem `this.` weggelassen wird. Statt dessen werden lokale Attribute mit `var` definiert: |
| 285 | |
| 286 | {{{ |
| 287 | #!javascript |
| 288 | function Person(vn, nn, alter) { |
| 289 | |
| 290 | ... |
| 291 | |
| 292 | this.toString = function() { |
| 293 | count(); |
| 294 | return this.vorname + " " + this.nachname + " (" + this.alter + ")"; |
| 295 | }; |
| 296 | |
| 297 | // privater Zähler mit Zählfunktion |
| 298 | var counter = 0; |
| 299 | var count = function() { |
| 300 | counter++; |
| 301 | }; |
| 302 | |
| 303 | // öffentlicher lesender Zugriff |
| 304 | this.getCounter = function() { |
| 305 | return counter; |
| 306 | }; |
| 307 | } |
| 308 | }}} |
| 309 | |
| 310 | === Fazit Objekterzeugung === |
| 311 | Wegen der genannten Probleme bei den anderen Varianten sind Erzeugerfunktionen und Konstruktorfunktionen zu bevorzugen, wobei die Konstruktorfunktion für fachlich komplexe Objekte möglicherweise bessere Übersichtlichkeit bietet. |
| 312 | |
| 313 | == Tip zur Verwendung von Testdaten == |
| 314 | Testdaten lassen sich im Quellcode ganz einfach als Variable definieren: |
| 315 | |
| 316 | {{{ |
| 317 | #!javascript |
| 318 | // Array von Personen |
| 319 | var personen = [ |
| 320 | { |
| 321 | vorname: "Susi", |
| 322 | nachname: "Sonne", |
| 323 | alter: 24, |
| 324 | adresse: { |
| 325 | strasse: "Friedrich-Heinrich-Allee 25", |
| 326 | plz: "47475", |
| 327 | ort: "Kamp-Lintfort" |
| 328 | } |
| 329 | }, |
| 330 | { |
| 331 | vorname: "Willy", |
| 332 | nachname: "Winzig", |
| 333 | alter: 21, |
| 334 | adresse: { |
| 335 | strasse: "Marie-Curie-Straße 1", |
| 336 | plz: "47533", |
| 337 | ort: "Kleve" |
| 338 | } |
| 339 | } |
| 340 | ]; |
| 341 | |
| 342 | > personen[0] |
| 343 | Object {vorname: "Susi", nachname: "Sonne", alter: 24, adresse: Object} |
| 344 | }}} |
| 345 | |
| 346 | == Eventhandling == |
| 347 | |
| 348 | Die im 1. Semester entwickelten Java-Konsolenprogramme haben als Einstiegspunkt eine `main` Methode und laufen ohne größere Unterbrechungen bis zum Ende durch. Dabei wird eine Berechnung o. ä. ausgeführt und das Ergebnis ausgegeben. Das folgende UML-Sequenzdiagramm illustriert diesen Ablauf beispielhaft: |
| 349 | |
| 350 | {{{ |
| 351 | #!html |
| 352 | <style> |
| 353 | .drawing { |
| 354 | stroke: black; |
| 355 | stroke-width: 2px; |
| 356 | } |
| 357 | </style> |
| 358 | <svg width="320px" class="drawing" viewbox="-1 -1 322 282" preserveAspectRatio="xMinYMin"> |
| 359 | |
| 360 | <g id="lifeline"> |
| 361 | <rect fill="none" x="0" y="0" width="100" height="30" /> |
| 362 | <line x1="50" y1="30" x2="50" y2="280" stroke-dasharray="3,3" /> |
| 363 | </g> |
| 364 | |
| 365 | <use x="110" y="0" xlink:href="#lifeline" /> |
| 366 | <use x="220" y="0" xlink:href="#lifeline" /> |
| 367 | |
| 368 | <text x="33" y="20" stroke-width="0.5">main</text> |
| 369 | <text x="127" y="20" stroke-width="0.5">methode1</text> |
| 370 | <text x="237" y="20" stroke-width="0.5">methode2</text> |
| 371 | |
| 372 | <rect x="40" y="30" width="20" height="50" fill="white" /> |
| 373 | <rect x="40" y="240" width="20" height="40" fill="white" /> |
| 374 | |
| 375 | <rect x="150" y="80" width="20" height="30" fill="white" /> |
| 376 | <rect x="150" y="190" width="20" height="50" fill="white" /> |
| 377 | |
| 378 | <rect x="260" y="110" width="20" height="80" fill="white" /> |
| 379 | |
| 380 | <defs> |
| 381 | <g id="arrow"> |
| 382 | <line x1="0" y1="0" x2="90" y2="0" /> |
| 383 | <line x1="80" y1="-5" x2="90" y2="0" /> |
| 384 | <line x1="80" y1="5" x2="90" y2="0" /> |
| 385 | </g> |
| 386 | </defs> |
| 387 | |
| 388 | <use x="60" y="80" xlink:href="#arrow" /> |
| 389 | <use x="170" y="110" xlink:href="#arrow" /> |
| 390 | |
| 391 | <use x="170" y="190" xlink:href="#arrow" transform="scale(180,215,190)" /> |
| 392 | |
| 393 | <use x="60" y="240" xlink:href="#arrow" transform="rotate(180,105,240)" /> |
| 394 | |
| 395 | Ihr Browser beherrscht kein SVG. |
| 396 | </svg> |
| 397 | }}} |
| 398 | |
| 399 | Programme mit grafischen Benutzeroberflächen kombinieren solche fachlich eher kleinen Programmteile zu größeren Applikationen, die mit dem User interagieren. Diese Programme laufen üblicherweise nicht permanent, sondern reagieren auf Benutzereingaben. Damit eine Benutzereingabe die Ausführung von Programmteilen (z. B. eine Suche) auslösen kann, muss es ein Stück Programmcode geben, das ausgeführt wird, sobald die Benutzeraktion eintritt. Das Programm "wartet" gewissermaßen andauernd darauf, dass der Benutzer Ereignisse auslöst. Üblicherweise ist die Runtime der Programmteil, der auf die Benutzeraktionen "wartet". Die Programmteile, die auf die Benutzeraktionen mit einer fachlichen Funktion reagieren nennt man Eventhandler. Ein Eventhandler ist eine Funktion, die von der Runtime (dem Browser) aufgerufen wird, sobald die zugehörige Benutzeraktion (z. B. Mausklick) eingetreten ist. Im folgenden Beispiel sind die Funktionen 1, 2 und 3 Eventhandler, die von der Runtime asynchron aufgerufen werden, sobald ein Ereignis eintritt. Bei einem asynchronen Aufruf wartet der Aufrufer nicht darauf, dass der Aufruf zurückkehrt: |
| 400 | |
| 401 | {{{ |
| 402 | #!html |
| 403 | <style> |
| 404 | .drawing { |
| 405 | stroke: black; |
| 406 | stroke-width: 2px; |
| 407 | } |
| 408 | </style> |
| 409 | <svg width="430px" class="drawing" viewbox="-1 -1 432 282" preserveAspectRatio="xMinYMin"> |
| 410 | |
| 411 | <g id="lifeline"> |
| 412 | <rect fill="none" x="0" y="0" width="100" height="30" /> |
| 413 | <line x1="50" y1="30" x2="50" y2="280" stroke-dasharray="3,3" /> |
| 414 | </g> |
| 415 | |
| 416 | <use x="110" y="0" xlink:href="#lifeline" /> |
| 417 | <use x="220" y="0" xlink:href="#lifeline" /> |
| 418 | <use x="330" y="0" xlink:href="#lifeline" /> |
| 419 | |
| 420 | <text x="22" y="20" stroke-width="0.5">Runtime</text> |
| 421 | <text x="127" y="20" stroke-width="0.5">funktion1</text> |
| 422 | <text x="237" y="20" stroke-width="0.5">funktion2</text> |
| 423 | <text x="347" y="20" stroke-width="0.5">funktion3</text> |
| 424 | |
| 425 | <rect x="40" y="40" width="20" height="220" fill="white" /> |
| 426 | |
| 427 | <rect x="150" y="80" width="20" height="30" fill="white" /> |
| 428 | <rect x="150" y="130" width="20" height="40" fill="white" /> |
| 429 | |
| 430 | <rect x="260" y="50" width="20" height="20" fill="white" /> |
| 431 | <rect x="260" y="110" width="20" height="20" fill="white" /> |
| 432 | |
| 433 | <rect x="370" y="210" width="20" height="30" fill="white" /> |
| 434 | |
| 435 | <defs> |
| 436 | <g id="arrow"> |
| 437 | <line x1="0" y1="0" x2="90" y2="0" /> |
| 438 | <line x1="80" y1="-5" x2="90" y2="0" /> |
| 439 | <line x1="80" y1="5" x2="90" y2="0" /> |
| 440 | </g> |
| 441 | </defs> |
| 442 | |
| 443 | <use x="60" y="50" xlink:href="#dashedarrow" transform="scale(2,1)"/> |
| 444 | <g transform="translate(60,50)"> |
| 445 | <line x1="0" y1="0" x2="200" y2="0" stroke-dasharray="3,3" /> |
| 446 | <line x1="190" y1="-5" x2="200" y2="0" /> |
| 447 | <line x1="190" y1="5" x2="200" y2="0" /> |
| 448 | </g> |
| 449 | <g transform="translate(60,80)"> |
| 450 | <line x1="0" y1="0" x2="90" y2="0" stroke-dasharray="3,3" /> |
| 451 | <line x1="80" y1="-5" x2="90" y2="0" /> |
| 452 | <line x1="80" y1="5" x2="90" y2="0" /> |
| 453 | </g> |
| 454 | <use x="170" y="110" xlink:href="#arrow" /> |
| 455 | <use x="170" y="130" xlink:href="#arrow" transform="rotate(180,215,130)" /> |
| 456 | <g transform="translate(60,210)"> |
| 457 | <line x1="0" y1="0" x2="310" y2="0" stroke-dasharray="3,3" /> |
| 458 | <line x1="300" y1="-5" x2="310" y2="0" /> |
| 459 | <line x1="300" y1="5" x2="310" y2="0" /> |
| 460 | </g> |
| 461 | |
| 462 | Ihr Browser beherrscht kein SVG. |
| 463 | </svg> |
| 464 | }}} |
| 465 | |
| 466 | Damit die Runtime den korrekten Eventhandler aufrufen kann, muss der Eventhandler zuerst bei der Runtime registriert werden. Im Browser kann die Registrierung von Eventhandlern auf verschiedene Arten erfolgen. Für die Registrierung eines Eventhandlers sind diese Informationen erforderlich: |
| 467 | |
| 468 | * Das Ereignis, auf das reagiert werden soll (z. B. Mausklick) |
| 469 | * Das Oberflächenelement, für das das Ereignis überwacht werden soll (das Event-Target) |
| 470 | * Der Eventhandler, eine Funktion, die nach dem Eintritt des Ereignisses ausgeführt wird |
| 471 | |
| 472 | Das Prinzip nennt man ereignisgesteuerte Programmierung. Es ist die konzeptionelle Grundlage nahezu aller interaktiven Programme mit grafischer Benutzeroberfläche. Später werden wir sehen, dass nicht nur Benutzeraktionen Ereignisse auslösen können, die durch Eventhandler behandelt werden. Weitere Beispiele sind Netzwerkverbindungen: Wenn Anfragen (Requests) über das Netzwerk abgesendet werden, wartet das Programm meist nicht auf die Serverantwort, sondern registriert einen Handler, der aufgerufen wird, sobald die Antwort des Servers eintrifft (sog. Callback). |
| 473 | |
| 474 | Das vollständige Beispiel finden Sie in [[source:Web_Quellcode_FP/vorlesung/javascript/eventhandler.html|eventhandler.html]]. |
| 475 | |
| 476 | Registrierung des Eventhandlers HTML-Element über das Attribut on''eventname'' (hier onclick): |
| 477 | |
| 478 | {{{ |
| 479 | #!htm |
| 480 | ... |
| 481 | <div id="d1" onclick="var s='Hallo';alert(s);">Ausgabe Hallo direkt</div> |
| 482 | |
| 483 | <div id="d2" onclick="clickD2();">Ausgabe Hallo Funktion</div> |
| 484 | |
| 485 | <script> |
| 486 | function clickD2() { |
| 487 | var s="Hallo"; |
| 488 | alert(s); |
| 489 | } |
| 490 | </script> |
| 491 | }}} |
| 492 | |
| 493 | Im Attribut onclick des Elements wird !JavaScript-Code notiert. Dabei kann es sich sowohl direkt um den fachlichen Code (siehe d1) als auch um einen Funktionsaufruf (siehe d2) handeln. |
| 494 | |
| 495 | Registrierung mittels DOM-Manipulation über die Funktion `addEventListener`: |
| 496 | |
| 497 | {{{ |
| 498 | #!htm |
| 499 | ... |
| 500 | <div id="d3">Message über benannten Eventhandler</div> |
| 501 | |
| 502 | <div id="d4">Message über anonyme Funktion</div> |
| 503 | |
| 504 | <script> |
| 505 | document.getElementById("d3").addEventListener("click", clickD2); |
| 506 | |
| 507 | document.getElementById("d4").addEventListener("click", function(){ |
| 508 | var s = "Hallo"; |
| 509 | alert(s); |
| 510 | }); |
| 511 | </script> |
| 512 | }}} |
| 513 | |
| 514 | Im ersten Fall wird die Funktion `clickD2` aus dem vorherigen Codebeispiel als Eventhandler für das div d3 registriert. Hinter dem Funktionsnamen dürfen keine runden () Klammern notiert werden, da das Funktionsobjekt selbst übergeben werden soll. Mit runden Klammern würde statt dessen die Funktion `clickD2()` aufgerufen werden und deren Rückgabewert (in diesem Fall `undefined`) als Eventhandler registriert werden. |
| 515 | |
| 516 | Im zweiten Fall (d4) wird eine anonyme Funktion als Eventhandler registriert. Zu beachten sind hier die korrekte Klammerung der in den Aufruf von `addEventListener` eingeschachtelten Funktionsdefinition sowie das Semikolon am Ende des Aufrufs. |
| 517 | |
| 518 | Falls die beim Registrieren eingebundene Funktion selbst kein Eventhandler ist, sondern einen zurückliefert, kann natürlich auch ein Funktionsaufruf erfolgen: |
| 519 | |
| 520 | {{{ |
| 521 | #!javascript |
| 522 | function clickD3() { |
| 523 | alert("Aufruf clickD3"); |
| 524 | return function() { |
| 525 | alert("generierter Eventhandler"); |
| 526 | }; |
| 527 | } |
| 528 | }}} |
| 529 | |
| 530 | Die Registrierung erfolgt dann mit dem Handler, der vom Funktionsaufruf `clickD3()` zurückgeliefert wird (beachte die runden Klammern hinter clickD3): |
| 531 | |
| 532 | {{{ |
| 533 | #!javascript |
| 534 | document.getElementById("d3").addEventListener("click", clickD3()); |
| 535 | }}} |
| 536 | |
| 537 | Der erste Parameter eines Eventhandlers ist standardmäßig das Eventobjekt. Wenn der Eventhandler mit einem Parameter definiert wird, kann darüber auf die Eigenschaften des Events zugegriffen werden. Dies kann z. B. für Spiele oder Grafikeditoren sinnvoll sein: |
| 538 | |
| 539 | {{{ |
| 540 | #!javascript |
| 541 | document.addEventListener("click", function(e){ |
| 542 | alert("x: " + e.clientX + " / y: " + e.clientY); |
| 543 | }); |
| 544 | }}} |
| 545 | |
| 546 | Events lassen sich also auch direkt mit dem gesamten Dokument als Event Target registrieren, nicht nur für einzelne HTML-Elemente. |
| 547 | |
| 548 | Es können auch mehrere Events an demselben Element registriert werden und es können mehrere Eventhandler durch ein und dieselbe Benutzeraktion ausgelöst werden. Beispielsweise wird jeder Klick auf eines der div-Elemente d1 bis d4 dazu führen, dass auch der Eventhandler für den Click am Dokument ausgeführt wird. |
| 549 | |
| 550 | == JSON - !JavaScript Object Notation == |
| 551 | |
| 552 | JSON ist eine als Zeichenkette serialisierte Darstellung eines Objektes, die menschenlesbar ist und zum Datenaustausch verwendet werden kann. Das Format ist interoperabel. Es ist nicht auf !JavaScript beschränkt, es lassen sich beispielsweise auch JSON-Objekte zwischen PHP Skripten austauschen oder zwischen einem !JavaScript und einem PHP-Programm. |
| 553 | |
| 554 | Eine JSON-Zeichenkette lässt sich aus einem Objekt mittels JSON.stringify() erzeugen: |
| 555 | |
| 556 | {{{ |
| 557 | #!javascript |
| 558 | > var bm = {name: "Bohrmaschine", preis: 73.99} |
| 559 | > bm |
| 560 | Object {name: "Bohrmaschine", preis: 73.99} |
| 561 | |
| 562 | > var json = JSON.stringify(bm) |
| 563 | > json |
| 564 | "{"name":"bohrmaschine","preis":73.99}" |
| 565 | }}} |
| 566 | |
| 567 | Mit der Funktion JSON.parse() lässt sich auf dem umgekehrten Weg ein !JavaScript-Objekt aus der JSON-Zeichenkette erezugen: |
| 568 | |
| 569 | {{{ |
| 570 | #!javascript |
| 571 | > var bm2 = JSON.parse(json) |
| 572 | > bm2 |
| 573 | Object {name: "Bohrmaschine", preis: 73.99} |
| 574 | }}} |
| 575 | |
| 576 | JSON-Zeichenketten enthalten nur die datenartigen Attribute der Objekte. Funktionsattribute werden nicht in die Zeichenkette kodiert: |
| 577 | |
| 578 | {{{ |
| 579 | #!javascript |
| 580 | > bm.toString = function() {console.log(this.name + " " + this.preis);} |
| 581 | > bm.toString() |
| 582 | bohrmaschine 73.99 |
| 583 | |
| 584 | > var json = JSON.stringify(bm) |
| 585 | > json |
| 586 | "{"name":"bohrmaschine","preis":73.99}" |
| 587 | |
| 588 | > var bm2 = JSON.parse(json) |
| 589 | > bm2.toString() |
| 590 | "[object Object]" |
| 591 | }}} |
| 592 | |
| 593 | Die Funktion `toString` ist in der JSON-Zeichenkette nicht enthalten. Statt dessen wird die geerbte Funktion `toString` des Objekts Object ausgeführt. |
| 594 | |
| 595 | Attribute, die selbst Objekte sind, und Arrays werden rekursiv in die JSON-Zeichenkette eingebaut: |
| 596 | |
| 597 | {{{ |
| 598 | #!javascript |
| 599 | > var ww = {name: "Wasserwaage", preis: 25.00} |
| 600 | > var bestellung = {datum: "21.05.2017", kunde: "Susi Sonne", artikel: [bm, ww]} |
| 601 | > bestellung |
| 602 | Object {datum: "21.05.2017", kunde: "Susi Sonne", artikel: Array(2)} |
| 603 | |
| 604 | > var json = JSON.stringify(bestellung) |
| 605 | > json |
| 606 | "{"datum":"21.05.2017","kunde":"Susi Sonne","artikel":[{"name":"bohrmaschine","preis":73.99},{"name":"Wasserwaage","preis":25}]}" |
| 607 | }}} |
| 608 | |
| 609 | === Workaround für nicht eingebundene Funktionen === |
| 610 | Falls es erforderlich ist, dass Objekte aus JSON-Zeichenketten zusammen mit den zugehörigen Funktionen wiederhergestellt werden, kann ein Workaround mit einer Erzeugerfunktion implementiert werden. Dieser Workaround erfordert die Kenntnis der ursprünglichen Konstruktorfunktion des Objekts: |
| 611 | |
| 612 | {{{ |
| 613 | #!CodeExample |
| 614 | ## title = JSON Beispiel |
| 615 | ## repo = Web_Quellcode_FP |
| 616 | ## path = /vorlesung/javascript/json.html |
| 617 | #!html |
| 618 | }}} |
| 619 | |
| 620 | Die Funktion `Bestellung.createFromJSON` kann nun verwendet werden, um aus JSON-Zeichenketten, die Bestellung-Objekte enthalten, wieder "echte" Bestellung-Objekte zu erzeugen. Dieses Verfahren funktioniert nur bei überschaubarer Komplexität der betroffenen Objekte und bei Zugang zu den Konstruktorfunktionen. |
| 621 | |
| 622 | Beispiel zur Verwendung (Laden Sie die Datei [[source:/Web_Quellcode_FP/vorlesung/javascript/json.html|json.html]] im Browser und führen Sie die folgenden Anweisungen in der Konsole aus): |
| 623 | |
| 624 | {{{ |
| 625 | #!javascript |
| 626 | > var bm = new Artikel("Bohrmaschine", 73.99) |
| 627 | > bm.toString() |
| 628 | "Bohrmaschine 73.99" |
| 629 | |
| 630 | > var ww = new Artikel("Wasserwaage", 25) |
| 631 | > ww.toString() |
| 632 | "Wasserwaage 25" |
| 633 | |
| 634 | > var b = new Bestellung("21.05.2017", "Willi Wacker") |
| 635 | > b.addArtikel(bm) |
| 636 | > b.addArtikel(ww) |
| 637 | > b |
| 638 | Bestellung {datum: "21.05.2017", kunde: "Willi Wacker", artikel: Array(2), addArtikel: function} |
| 639 | |
| 640 | > var js = JSON.stringify(b) |
| 641 | > js |
| 642 | "{"datum":"21.05.2017","kunde":"Willi Wacker","artikel":[{"name":"Bohrmaschine","preis":73.99},{"name":"Wasserwaage","preis":25}]}" |
| 643 | |
| 644 | > var b2 = JSON.parse(js) |
| 645 | > b2 |
| 646 | Object {datum: "21.05.2017", kunde: "Willi Wacker", artikel: Array(2)} |
| 647 | }}} |
| 648 | |
| 649 | Das aus der JSON-Zeichenkette neu erstellte Objekt enthält keine Funktionen. Der Aufruf von `addArtikel` schlägt fehl: |
| 650 | |
| 651 | {{{ |
| 652 | #!javascript |
| 653 | > b2.addArtikel(ww) |
| 654 | VM203:1 Uncaught TypeError: b2.addArtikel is not a function |
| 655 | at <anonymous>:1:4 |
| 656 | (anonymous) @ VM203:1 |
| 657 | }}} |
| 658 | |
| 659 | Werden dagegen über `Bestellung.createFromJSON()` die ursprünglichen Konstruktorfunktionen verwendet, sind auch die Funktionen vorhanden und können aufgerufen werden: |
| 660 | |
| 661 | {{{ |
| 662 | #!javascript |
| 663 | > var b2 = Bestellung.createFromJSON(js) |
| 664 | > b2.addArtikel(ww) |
| 665 | > b2 |
| 666 | Bestellung {datum: "21.05.2017", kunde: "Willi Wacker", artikel: Array(3), addArtikel: function} |
| 667 | }}} |
| 668 | |
| 669 | == DOM Manipulation mit jQuery == |
| 670 | |
| 671 | [[https://jquery.com|jQuery]] ist ein !JavaScript-Framework, das die DOM-Manipulation, insbesondere die Änderung von Darstellungseigenschaften der Oberflächenelemente stark vereinfacht. |
| 672 | |
| 673 | Mit jQuery lassen sich mehrere Elemente selektieren, die modifiziert werden sollen. Die Beispiele beziehen sich auf den folgenden HTML-Ausschnitt aus [[source:Web_Quellcode_FP/vorlesung/javascript/jquery.html|jquery.html]]: |
| 674 | |
| 675 | {{{ |
| 676 | #!htm |
| 677 | <style> |
| 678 | .rot {color: red;} |
| 679 | .gruen {color: green;} |
| 680 | .blau {color: blue;} |
| 681 | </style> |
| 682 | |
| 683 | ... |
| 684 | |
| 685 | <div id="d1">Bohrmaschine</div> |
| 686 | <div id="d2">Binford Tools</div> |
| 687 | <div id="d3">73.99</div> |
| 688 | |
| 689 | <p id="p1">Wasserwaage</p> |
| 690 | <p id="p2">Wasserversorgung Kamp-Lintfort</p> |
| 691 | <p id="p3">25.00</p> |
| 692 | }}} |
| 693 | |
| 694 | Die Aufrufe des jQuery-Frameworks beginnen meist mit einem Aufruf des Selektors. Der Selektoraufruf folgt der Syntax `$("selektor")` wobei `selektor` ein beliebiger CSS-Selektor ist. Als Ergebnis des reinen Selektoraufrufs wird ein Array mit allen selektierten Elementen zurückgegeben: |
| 695 | |
| 696 | {{{ |
| 697 | #!javascript |
| 698 | > $("#d1") |
| 699 | [div#d1] |
| 700 | |
| 701 | > $("div") |
| 702 | (3) [div#d1, div#d2, div#d3, prevObject: jQuery.fn.init(1)] |
| 703 | }}} |
| 704 | |
| 705 | Auf den Ergebnissen lassen sich direkt weitere Aktionen zur DOM-Manipulation ausführen: |
| 706 | |
| 707 | {{{ |
| 708 | #!javascript |
| 709 | > $("#d1").addClass("rot"); |
| 710 | [div#d1.rot] |
| 711 | |
| 712 | > $("div").addClass("gruen"); |
| 713 | (3) [div#d1.rot.gruen, div#d2.gruen, div#d3.gruen, prevObject: jQuery.fn.init(1)] |
| 714 | }}} |
| 715 | |
| 716 | Neue Elemente lassen sich mit `append`in das DOM einfügen: |
| 717 | |
| 718 | {{{ |
| 719 | #!javascript |
| 720 | > $("#p3").append("<span>Ich bin neu hier</span>") |
| 721 | [p#p3] |
| 722 | |
| 723 | > $("#p3 span") |
| 724 | [span, prevObject: jQuery.fn.init(1)] |
| 725 | |
| 726 | > $("#p3 span").attr("id", "s1") |
| 727 | [span#s1, prevObject: jQuery.fn.init(1)] |
| 728 | |
| 729 | > $("#s1").addClass("blau") |
| 730 | [span#s1.blau] |
| 731 | }}} |