Eine kurze Einführung/eine kurze Übersicht über JavaScript für erfahrene Programmierer.
michael.eichberg@dhbw.de, Raum 149B
2.1.2
Seit 2016 gibt es jährliche Updates (ECMAScript 2016, 2017, 2018, 2019, 2020, 2021, 2022, ...)
Objektorientiert
Protoypische Vererbung
Objekte erben von anderen Objekten
Objekte als allgemeine Container
(Im Grunde eine Vereinheitlichung von Objekten und Hashtabellen.)
seit ES6 werden auch Klassen unterstützt; diese sind aber nur syntaktischer Zucker
Skriptsprache
Loose Typing/Dynamische Typisierung
Load and go-delivery (Lieferung als Text/Quellcode)
Garbage Collected
Single-Threaded
Funktionen sind Objekte erster Klasse
(im Wesentlichen) ein (globaler) Namespace
Syntaktisch eine Sprache der "C"-Familie (viele Ähnlichkeiten zu Java)
Standardisiert durch die ECMA (ECMAScript)
Verwendet ganz insbesondere in Browsern, aber auch Serverseitig (z. B. Node.js) oder in Desktop-Anwendungen (z. B. Electron)
Genutzte Schlüsselworte
function, async, await, return, yield, void
break, continue, case, default, do, else, for, if, instanceof, of, typeof, switch, while
throw, try, finally, catch
class, delete, extends, in, new, static, super, this
const, let, var
export, import
(Sehr vergleichbar mit Java.)
Buchstaben (Unicode), Ziffern, Unterstriche, Dollarzeichen
Ein Identifier darf nicht mit einer Ziffer beginnen
Nameskonventionen:
Klassen beginnen mit einem Großbuchstaben (UpperCamelCase)
Variablen und Funktionen beginnen mit einem Kleinbuchstaben (lowerCamelCase)
Konstanten sind komplett in Großbuchstaben
console
Number, Boolean, Date, BigInt, Math, ...
window
document (bzw. window.document)
alert
navigator
location
module
exports
require
process
crypto
const und let)1// Der "Scope" ist auf den umgebenden Block begrenzt.2// Eine Änderung des Wertes ist möglich.3let y = "yyy";45// Der "Scope" ist auf den umgebenden Block begrenzt.6// Eine Änderung des Wertes ist nicht möglich.7const z = "zzz";89log("y, z:", y, z);1011function doIt() {12const y = "---";13log("y, z:", y, z);14return "";15}1617ilog('"doIt done"', doIt());18log("y, z:", y, z);
Um diesen und den Code auf den folgenden Folien ggf. mit Hilfe von Node.js auszuführen, muss am Anfang der Datei:
import { ilog, log, done } from "./log.mjs";
und am Ende der Datei:
done();
hinzugefügt werden.
Den entsprechenden Code der Module (log.mjs und später Queue.mjs) finden Sie auf:
https://github.com/Delors/delors.github.io/tree/main/web-javascript/code
1let y = "yyy"; // wie zuvor2const z = "zzz";34// Der Gültigkeitsbereich von var ist die umgebende Funktion oder der5// globale Gültigkeitsbereich.6// Die Definition ist hochgezogen (eng. "hoisted") (initialisiert mit undefined);7var x = "xxx";89function sumIfDefined(a, b) {10// ⚠️ Der folgende Code ist NICHT empfehlenswert!11// Er dient der Visualisierung des Verhaltens von var.12if (parseInt(a)) {13var result = parseInt(a);14} else {15result = 0;16}17const bVal = parseFloat(b);18if (bVal) {19result += bVal;20}21return result;22}2324ilog("sumIfDefined()", sumIfDefined()); // 025ilog("sumIfDefined(1)", sumIfDefined(1)); // 126ilog("sumIfDefined(1, 2)", sumIfDefined(1, 2)); // 327ilog('sumIfDefined(1, "2")', sumIfDefined(1, "2")); // 328ilog("undefined + 2", undefined + 2);29ilog('sumIfDefined(undefined, "2")', sumIfDefined(undefined, "2")); // 23031function global_x() {32ilog("global_x():", x, y, z);33}3435function local_var_x() {36ilog("local_var_x(): erste Zeile (x)", x);3738var x = 1; // the declaration of var is hoisted, but not the initialization39let y = 2;40const z = 3;4142ilog("local_var_x(): letzte Zeile (x, y, z)", x, y, z); // 1 2 343}4445global_x();46local_var_x();4748ilog("nach global_x() und local_var_x() - x, y, z:", x, y, z);495051// Hier, ist nur die Variablendeklaration (helloExpr) "hoisted", aber nicht52// die Definition. Daher kann die Funktion nicht vorher im Code aufgerufen53// werden!54try {55helloExpr();56} catch ({error, message}) {57log("calling helloExpr() failed:", error, "; message: ", message);58}59var helloExpr = function () {60log("expr: Hello World!");61};62// ab jetzt funktioniert es63helloExpr();
1console.log("\nUndefined ----------------------------------------------------");2let u = undefined;3console.log("u", u);45console.log("\nNumber -------------------------------------------------------");6let i = 1; // double-precision 64-bit binary IEEE 754 value7let f = 1.0; // double-precision 64-bit binary IEEE 754 value8let l = 10_000;9let binary = 0b1010;10console.log("0b1010", binary);11let octal = 0o12;12console.log("0o12", octal);13let hex = 0xa;14console.log("0xA", hex);15console.log(16Number.MIN_VALUE,17Number.MIN_SAFE_INTEGER,18Number.MAX_SAFE_INTEGER,19Number.MAX_VALUE,20);21let x = NaN;22let y = Infinity;23let z = -Infinity;2425// Standard Operatoren: +, - , *, /, %, ++, --, **26// Bitwise Operatoren: &, |, ^, ~, <<, >>, >>>27// (operieren immer auf dem Ganzzahlwert der Bits)28console.log("i =", i, "; i++ ", i++); // 1 oder 2?29console.log("i =", i, "; ++i ", ++i); // 2 oder 3?30console.log("2 ** 4 === 0 ", 2 ** 4);31console.log("7 % 3 === ", 7 % 3);32console.log("1 / 0 === ", 1 / 0);3334console.log("\nBigInt -------------------------------------------------------");35let ib = 1n;36console.log(100n === BigInt(100));37console.log(Number.MAX_SAFE_INTEGER + 2102); // 900719925474309238console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2102n); // 9007199254743093n3940console.log("\nBoolean ------------------------------------------------------");41let b = true; // oder false42console.log("Boolean(undefined)", Boolean(undefined)); // true oder false?43console.log(null == true ? "true" : "false"); // true oder false?4445console.log("\n(Quasi-)Logische Operatoren ----------------------------------");46console.log('1 && "1": ', 1 && "1");47console.log('null && "1": ', null && "1");48console.log("null && true: ", null && true);49console.log("true && null: ", true && null);50console.log("null && false: ", null && false);51console.log("{} && true: ", {} && true);5253// Neben den Standardoperatoren: ``&&``, ``||``, ``!`` gibt es auch noch ``??``54// Der ``??``-Operator gibt den rechten Operanden zurück, wenn der linke Operand55// ``null`` oder ``undefined`` ist. Andernfalls gibt er den linken Operanden56// zurück.57// ``??`` ist der *nullish coalescing operator (??)*58// Falls der linke Wert null oder undefined ist, dann ist er vergleichbar zu ||59console.log('1 ?? "1": ', 1 ?? "1");60console.log('null ?? "1": ', null ?? "1");61console.log("null ?? true: ", null ?? true);62console.log("true ?? null: ", true ?? null);63console.log("null ?? false: ", null ?? false);64console.log("{} ?? true: ", {} ?? true);6566console.log('undefined ?? "1": ', undefined ?? "1");67console.log('undefined ?? "1": ', undefined ?? "1");68console.log("undefined ?? true: ", undefined ?? true);69console.log("true ?? undefined: ", true ?? undefined);70console.log("undefined ?? false: ", undefined ?? false);71console.log("undefined ?? undefined: ", undefined ?? undefined);7273console.log("\nStrings ------------------------------------------------------");74let _s = "42";75console.log("Die Antwort ist " + _s + "."); // String concatenation76console.log(`Die Antwort ist ${_s}.`); // Template literals (Template strings)77// multiline Strings78console.log(`79Die Antwort mag ${_s} sein,80aber was ist die Frage?`);8182console.log(String(42)); // "42"8384// ACHTUNG Objekte und Errors am Besten direkt an log übergeben,85// damit die Objekteigenschaften ausgeben werden.86console.log("State: " + { a: "abc" }, { a: "abc" });8788console.log("\nObjekte ------------------------------------------------------");89let emptyObject = null;90let anonymousObj = {91i: 1,92u: { j: 2, v: { k: 3 } },93toString: function () {94return "anonymousObj";95},96"?": "question mark",97};98// Zugriff auf die Eigenschaften eines Objekts99anonymousObj.j = 2; // mittels Bezeichner ("j") (eng. Identifier)100anonymousObj["j"] = 4; // mittels String ("j")101anonymousObj["k"] = 3;102console.log("anonymousObj: ", anonymousObj);103console.log("anonymousObj.toString(): ", anonymousObj.toString());104105// delete dient dem Löschen von Eigenschaften:106delete anonymousObj["?"];107delete anonymousObj.toString;108console.log("anonymousObj.toString() [original]", anonymousObj.toString());109110// Der Chain-Operator "?." kann verwendet werden, um auf Eigenschaften111// (Properties) von Objekten zuzugreifen, ohne dass eine Fehlermeldung112// ausgegeben wird, wenn eine (höher-liegende) Eigenschaft nicht definiert ist.113// Besonders nützlich beim Verarbeiten von komplexen JSON-Daten.114console.log("anonymousObj.u?.v.k", anonymousObj.u?.v.k);115console.log("anonymousObj.u.v?.k", anonymousObj.u.v?.k);116console.log("anonymousObj.u.v?.z", anonymousObj.u.v?.z);117console.log("anonymousObj.u.q?.k", anonymousObj.u.q?.k);118console.log("anonymousObj.p?.v.k", anonymousObj.p?.v.k);119120// Nützliche Zuweisungen, um den Fall undefined und null gemeinsam zu behandeln:121anonymousObj.name ||= "Max Mustermann";122123console.log("\nDate ---------------------------------------------------------");124let date = new Date("8.6.2024"); // ACHTUNG: Locale-Settings125console.log(date);126127console.log("\nFunktionen sind auch Objekte ---------------------------------");128let func = function () {129return "Hello World";130};131console.log(func, func());132133console.log("\nArrays -------------------------------------------------------");134let temp = undefined;135let $a = [1];136console.log("let $a = [1]; $a, $a.length", $a, $a.length);137$a.push(2); // append138console.log("$a.push(2); $a", $a);139temp = $a.unshift(0); // "prepend" -> return new length140console.log("temp = $a.unshift(0); temp, $a", temp, $a);141temp = $a.shift(); // remove first element -> return removed element142console.log("temp = $a.shift(); temp, $a", temp, $a);143// Um zu prüfen ob eine Datenstruktur ein Array ist:144console.log("Array.isArray($a)", Array.isArray($a));145console.log("Array.isArray({})", Array.isArray({}));146console.log("Array.isArray(1)", Array.isArray(1));147148console.log("\nSymbols ------------------------------------------------------");149let sym1 = Symbol("1"); // a unique and immutable primitive value150let sym2 = Symbol("1");151let obj1Values = { sym1: "value1", sym2: "value2" };152console.log(obj1Values);153console.log(`sym1 in ${JSON.stringify(obj1Values)}: `, sym1 in obj1Values);154let obj2Values = { [sym1]: "value1", [sym2]: "value2" };155console.log(obj2Values);156console.log(`sym1 in ${JSON.stringify(obj2Values)}: `, sym1 in obj2Values);157console.log(obj1Values, " vs. ", obj2Values);158159console.log({ sym1: "this", sym1: "that" }); // ??? { sym1: "that" }160console.log("sym1 == sym2", sym1 == sym2);
1// Die Funktionsdeklaration der Funktion "hello" ist "hochgezogen" (🇺🇸 hoisted)2// und kann hier verwendet werden.3hello("Michael");45function hello(person = "World" /* argument with default value */) {6log(`fun: Hello ${person}!`);7}8hello();910waitOnInput();1112const helloExpr = function () {13// Anonymer Funktionsausdruck14log("expr: Hello World!");15};1617// Arrow Functions18const times3 = (x) => x * 3;19log("times3(5)", times3(5)); // 152021const helloArrow = () => log("arrow: Hello World!");22const helloBigArrow = () => {23const s = "Hello World!";24log("arrow: " + s);25return s;26};27helloExpr();28helloArrow();2930var helloXXX = function helloYYY() {31// benannter Funktionsausdruck32// "helloYYY" ist _nur_ innerhalb der Funktion sichtbar und verwendbar33// "arguments" ist ein Arrays-vergleichbares Objekt34// und enthält alle Argumente der Funktion35log(`Hello: `, ...arguments); // "..." ist der "Spread Operator"36};37helloXXX("Michael", "John", "Jane");3839waitOnInput();4041log("\nFunction Arguments ---------------------------------------------------");4243function sum(...args) {44// rest parameter: ...45log("typeof args: " + typeof args + "; isArray: " + Array.isArray(args));46log("args: " + args);47log("args:", ...args); // die Arraywerte werden als einzelne Args. übergeben48return args.reduce((a, b) => a + b, 0); // function nesting49}50log("sum(1, 2, 3, 4, 5)", sum(1, 2, 3, 4, 5)); // 1551log("sum()", sum());5253log("\nGenerator Functions --------------------------------------------------");54/* Generator Functions */55function* fib() {56// generator57let a = 0,58b = 1;59while (true) {60yield a;61[a, b] = [b, a + b];62}63}64const fibGen = fib();65log(fibGen.next().value); // 066log(fibGen.next().value); // 167log(fibGen.next().value); // 168log(fibGen.next().value); // 269/* Will cause an infinite loop:70for (const i of fib()) console.log(i);71// 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 ... */
Voraussetzung: Installieren Sie Node.js (http://nodejs.org/).
Hello World in Node.js
Starten Sie die Konsole/Terminal und schreiben Sie ein einfaches JavaScript Programm, das "Hello World" ausgibt.
Hello World auf der JavaScript Console
Starten Sie einen Browser und aktivieren Sie die JavaScript Console in den Entwicklerwerkzeugen. Schreiben Sie ein einfaches JavaScript Programm, das "Hello World" ausgibt.
Prototyping mit der JavaScript Konsole
Schreiben Sie ein kurzes JavaScript Snippet (in der Konsole des Browsers), das programmatisch zum Ende des Dokuments scrollt.
1// Gleichheit == // mit Typumwandlung (auch bei <, >, <=, >=)23// strikt gleich === // ohne Typumwandlung4// strike Ungleichheit !== // ohne Typumwandlung56log('1 == "1": ', 1 == "1");7log('1 === "1": ', 1 === "1");8log("1.0 == 1: ", 1 == 1.0);9log("1.0 === 1: ", 1 === 1.0);10log("1 === 1n: ", 1 === 1n); // 1n ist ein bigint mit den Wert 111log("1 == 1n: ", 1 == 1n);12log('1 < "1"', 1 < "1");13log('0 < "1"', 0 < "1");14log('0 <= "0"', 0 <= "0");15log('"abc" <= "d"', "abc" <= "d");1617log('"asdf" === "as" + "df"', "asdf" === "as" + "df"); // unlike Java!1819log("NaN === NaN: ", NaN === NaN);20log("NaN == NaN: ", NaN == NaN);21log("null === NaN: ", null === NaN);22log("null == NaN: ", null == NaN);23log("null === null: ", null === null);24log("null == null: ", null == null);25log("undefined === undefined: ", undefined === undefined);26log("undefined == undefined: ", undefined == undefined);27log("null === undefined: ", null === undefined);28log("null == undefined: ", (null == undefined) + "!");2930const a1 = [1, 2, 3];31const a2 = [1, 2, 3];32log("const a1 = [1, 2, 3]; a1 == [1, 2, 3]: ", a1 == [1, 2, 3]);33log("const a1 = [1, 2, 3]; a1 == a1: ", a1 == a1);34log("const a1 = [1, 2, 3]; a1 === a1: ", a1 === a1);35log("const a1 = [1, 2, 3]; const a2 = [1, 2, 3]; a1 === a2: ", a1 === a2);36log("const a1 = [1, 2, 3]; const a2 = [1, 2, 3]; a1 == a2: ", a1 == a2);37log(38"flatEquals(a1,a2):",39a1.length == a2.length && a1.every((v, i) => v === a2[i]),40);4142let firstJohn = { person: "John" };43show('let firstJohn = { person: "John" };');44let secondJohn = { person: "John" };45show('let secondJohn = { person: "John" };');46let basedOnFirstJohn = Object.create(firstJohn);47show("let basedOnFirstJohn = Object.create(firstJohn)");48log("firstJohn == firstJohn: ", firstJohn == firstJohn);49log("firstJohn === secondJohn: ", firstJohn === secondJohn);50log("firstJohn == secondJohn: ", firstJohn == secondJohn);51log("firstJohn === basedOnFirstJohn: ", firstJohn === basedOnFirstJohn);52log("firstJohn == basedOnFirstJohn: ", firstJohn == basedOnFirstJohn);5354{55const obj = {56name: "John",57age: 30,58city: "Berlin",59};60show("Typtests und Feststellung des Typs:");61log("typeof obj", typeof obj);62log("obj instanceof Object", obj instanceof Object);63log("obj instanceof Array", obj instanceof Array);6465// ⚠️ Das Array Objekt ist Kontextabhängig:66//67// iframe.contentWindow.Array !== window.Array;68//69// D.h. ein instanceof Check liefert ggf. unerwartete Resultate70}71{72const obj = { a: "lkj" };73const obj2 = Object.create(obj);74log("obj2 instanceof obj.constructor", obj2 instanceof obj.constructor);75}7677log("\n?-Operator/if condition and Truthy and Falsy Values:");78log('""', "" ? "is truthy" : "is falsy");79log("f()", (() => {}) ? "is truthy" : "is falsy");80log("Array ", Array ? "is truthy" : "is falsy");81log("obj ", {} ? "is truthy" : "is falsy");82log("undefined ", undefined ? "is truthy" : "is falsy");83log("null ", null ? "is truthy" : "is falsy");84log("0", 0 ? "is truthy" : "is falsy");85log("1", 1 ? "is truthy" : "is falsy");
NaN (Not a Number) repräsentiert das Ergebnis einer Operation die keinen sinnvollen Wert hat. Ein Vergleich mit NaN ist immer false. Um zu überprüfen, ob ein Wert NaN ist muss isNaN(<Value>) verwendet werden.
1const arr = [1, 3, 4, 7, 11, 18, 29];23log("if-else_if-else:");4if (arr.length == 7) {5ilog("arr.length == 7");6} else if (arr.length < 7) {7ilog("arr.length < 7");8} else {9ilog("arr.length > 7");10}1112log("\nswitch (integer value):");13switch (arr.length) {14case 7:15ilog("arr.length == 7");16break;17case 6:18ilog("arr.length == 6");19break;20default:21ilog("arr.length != 6 and != 7");22}2324log("\nswitch (string value):");25switch ("foo") {26case "bar":27ilog("it's bar");28break;29case "foo":30ilog("it's foo");31break;32default:33ilog("not foo, not bar");34}3536log("\nswitch (integer - no type conversion):");37switch (381 // Vergleich auf strikte Gleichheit (===)39) {40case "1":41ilog("string(1)");42break;43case 1:44ilog("number(1)");45break;46}4748ilog("\nfor-continue:");49for (let i = 0; i < arr.length; i++) {50const v = arr[i];51if (v % 2 == 0) continue;52log(v);53}5455ilog("\n(for)-break with label:");56outer: for (let i = 0; i < arr.length; i++) {57for (let j = 0; j < i; j++) {58if (j == 3) break outer;59log(arr[i], arr[j]);60}61}6263ilog("\nin (properties of Arrays; i.e. the indexes):");64for (const key in arr) {65log(key, arr[key]);66}6768ilog("\nof (values of Arrays):");69for (const value of arr) {70log(value);71}7273ilog("\nArray and Objects - instanceof:");74log("arr instanceof Object", arr instanceof Object);75log("arr instanceof Array", arr instanceof Array);7677const obj = {78name: "John",79age: 30,80city: "Berlin",81};8283ilog("\nin (properties of Objects):");84for (const key in obj) {85log(key, obj[key]);86}8788/* TypeError: obj is not iterable8990for (const value of obj) {91log(value);92}93*/9495{96ilog("\nIteration über Iterables (here: Map):");97const m = new Map();98m.set("name", "Elisabeth");99m.set("alter", 50);100log("Properties of m: ");101for (const key in m) {102log(key, m[key]);103}104log("Key-Values of m: ");105for (const [key, value] of m) {106log(key, value);107}108}109110{111ilog("\nWhile Loop: ");112let c = 0;113while (c < arr.length) {114const v = arr[c];115if (v > 10) break;116log(v);117c++;118}119}120121{122ilog("\nDo-While Loop: ");123let c = 0;124do {125log(arr[c]);126c++;127} while (c < arr.length);128}
Die Tatsache, dass insbesondere null als auch undefined falsy sind, wird of in Bedingungen ausgenutzt (z. B., if (!x)...).
1console.log("try-catch-finally - Grundlagen ---------------------------------");23try {4let i = 1 / 0; // Berechnungen erzeugen nie eine Exception5console.log("i", i);6} catch {7console.error("console.log failed");8} finally {9console.log("computation finished");10}1112console.log("Programmierfehler behandeln ------------------------------------");13try {14const obj = {};15obj = { a: 1 };16} catch ({ name, message }) {17console.error(message);18} finally {19console.log("object access finished");20}2122console.log("Handling of a specific error -----------------------------------");23try {24throw new RangeError("out of range");25} catch (error) {26if (error instanceof RangeError) {27const { name, message } = error;28console.error("a RangeError:", name, message);29} else {30throw error;31}32} finally {33console.log("error handling finished");34}
In JavaScript können während der Laufzeit Fehler auftreten, die (z. B.) in Java während des kompilierens erkannt werden.
removeNthElement
Implementieren Sie eine Funktion, die ein Array übergeben bekommt und ein neues Array zurückgibt in dem jedes n-te Element nicht vorkommt.
Beispiel: removeNthElement([1,2,3,4,5,6,7], 2) \(\Rightarrow\) [1,3,5,7]
Schreiben Sie Ihren Code in eine JavaScript Datei und führen Sie diese mit Hilfe von Node.js aus.
Testen Sie Ihre Funktion mit verschiedenen Eingaben und lassen Sie sich das Ergebnis ausgeben (z. B. console.log(removeNthElement([1,2,3,4,5,6,7],2)))!
removeNthElement mit Fehlerbehandlung
Erweitern Sie die Implementierung von removeNthElement so, dass die Funktion einen Fehler wirft, wenn das übergebene Array kein Array ist oder wenn der zweite Parameter kein positiver Integer ist.
Testen Sie alle Fehlerzustände und fangen Sie die entsprechenden Fehler ab (catch) und geben Sie die Nachrichten aus.
Einfacher RPN Calculator
Implementieren Sie einen einfachen RPN (Reverse Polish Notation) Calculator, der eine Liste von Zahlen und Operatoren (+, -, *, /) als Array entgegennimmt und das Ergebnis berechnet.
Nutzen Sie keine if oder switch Anweisung, um die Operatoren zu unterscheiden. Nutzen Sie stattdessen ein Objekt. Sollte der Operator unbekannt sein, dann geben Sie eine entsprechende Fehlermeldung aus.
Nutzen Sie node.js als Test-/Ausführungsumgebung.
Beispiel: eval([2,3,"+",4,"*"]) \(\Rightarrow\) 20
1log("Array Destructuring:");23let [val1, val2] = [1, 2, 3, 4];4ilog("[val1, val2] = [1, 2, 3, 4]:", "val1:", val1, ", val2:", val2); // 156log("Object Destructuring:");78let { a, b } = { a: "aaa", b: "bbb" };9ilog('let { a, b } = { a: "aaa", b: "bbb" }: ', "a:", a, ", b:", b); // 11011{12let { a: x, b: y } = { a: "aaa", b: "bbb" };13ilog('let { a: x, b: y } = { a: "aaa", b: "bbb" }: ', "x:", x, ", y:", y); // 114}15{16let { a: x, c: y } = { a: "aaa", b: "bbb" };17ilog('let { a: x, c: y } = { a: "aaa", b: "bbb" }: ', "x:", x, ", y:", y); // 118}1920let { a: u, b: v, ...w } = { a: "+", b: "-", c: "*", d: "/" };21ilog(22'let { a: u, b: v, ...w } = { a: "+", b: "-", c: "*", d: "/" }:',23"u:",24u,25", v:",26v,27", w:",28JSON.stringify(w), // just for better readability/comprehension29);3031let { k1, k2 } = { a: "a", b: "b" };32ilog('let { k1, k2 } = { a: "a", b: "b" }:', "k1:", k1, ", k2:", k2);33// "undefined undefined", weder k1 noch k2 sind definiert
1const someJSON = `{2"name": "John",3"age": 30,4"cars": {5"American": ["Ford"],6"German": ["BMW", "Mercedes", "Audi"],7"Italian": ["Fiat","Alfa Romeo", "Ferrari"]8}9}`;1011// JSON.parse(...) JSON String => JavaScript Object12const someObject = JSON.parse(someJSON);1314someObject.age = 31;15someObject.cars.German.push("Porsche");16someObject.cars.Italian.pop();17console.log(someObject);1819// JSON.stringify(...) JavaScript Object => JSON String20console.log(JSON.stringify(someObject, null, 2));
JSON requires that keys must be strings and strings must be enclosed in double quotes.
Eingebaute Unterstützung basierend auf entsprechenden Literalen (Strings in "/") und einer API
inspiriert von der Perl Syntax
Methoden auf regulären RegExps: test (e.g., <RegExp>.test(String)).
Methoden auf Strings, die reguläre Ausdrücke verarbeiten: search, match, replace, split, ...
1{2const p = /.*[1-9]+H/; // a regexp3console.log(p.test("ad13H"));4console.log(p.test("ad13"));5console.log(p.test("13H"));6}7{8const p = /[1-9]+H/g;9const s = "1H, 2H, 3P, 4C";10console.log(s.match(p));11console.log(s.replace(p, "XX"));12}
1class Figure {2calcArea() {3throw new Error("calcArea is not implemented");4}5}6class Rectangle extends Figure {7height;8width;910constructor(height, width) {11super();12this.height = height;13this.width = width;14}1516calcArea() {17return this.height * this.width;18}1920get area() {21return this.calcArea();22}2324set area(value) {25throw new Error("Area is read-only");26}27}2829const r = new Rectangle(10, 20);30console.log("r instanceof Figure", r instanceof Figure); // true31console.log(r.width);32console.log(r.height);33console.log(r.area); // 2003435try {36r.area = 300; // Error: Area is read-only37} catch (e) {38console.error(e.message);39}
this ist ein "zusätzlicher" Parameter, dessen Wert von der aufrufenden Form abhängt
this ermöglicht den Methoden den Zugriff auf ihr Objekt
this wird zum Zeitpunkt des Aufrufs gebunden (außer bei Arrow-Funktionen, da erfolgt die Bindung zum Zeitpunkt der Definition und es wird das this aus dem umgebenden Context geerbt.)
1//"use strict";23function counter() {4// console.log(this === globalThis); // true5if (this.count)6// this is the global object if we don't use strict mode7this.count++;8else {9this.count = 1;10}1112return this.count;13}1415const counterExpr = function () {16if (this.count) this.count++;17else {18this.count = 1;19}2021return this.count;22};2324const counterArrow = () => {25console.log(this);26console.log(this === globalThis);27this.count = this.count ? this.count + 1 : 1;28return this.count;29};3031console.log("\nCounter");32console.log(counter()); // 133console.log(counter()); // 234console.log(`globalThis.count (${globalThis.count})`);3536console.log("\nCounterExpression");37console.log(counterExpr()); // 338console.log(counterExpr()); // 43940console.log("\nCounter");41const obj = {}; // empty42console.log(counter.apply(obj)); // 1 - we pass in obj as "this"!43console.log(counterExpr.apply(obj)); // 24445console.log(`\nCounterArrow (${this.count})`);46console.log(counterArrow.apply(obj)); // 147console.log(counterArrow.apply(undefined)); // 248console.log(counterArrow.apply()); // 349console.log(counterArrow.apply(obj)); // 450console.log(counterArrow.apply({})); // 55152console.log("\nCounter (global)");53console.log(counter());54console.log(counterExpr());
1function add(x, y) {2return x + y;3}45// Partial function application:6const add2 = add.bind(null, 2); // "null" is the value of "this"7console.log(add2(3));8910function addToValue(b) {11return this.x + b;12}13console.log(addToValue.call({x : 0}, -101));
Verwendung von Object.create zur Initialisierung der Prototype Chain:
1const p = { s : "p" };2const c = Object.create(p);3const gc = Object.create(c);
Verwendung der Eigenschaften von Prototypen:
1const p = { s : "p" };2const c = Object.create(p);3const gc = Object.create(c);4gc.t = "q";
5gc.s = "gc"6console.log(gc.s); // gc7delete gc.s;8console.log(gc.s); // p
Pseudoclassical Inheritance
1// constructor for Person objects:2function Person(name, title){ this.name = name; this.title = title; }3Person.prototype.formOfAddress = function (){4const foa = "Dear ";5if(this.title){ foa += this.title+" "; }6return foa + this.name;7}8function Student(name, title, id, email) {9Person.call(this, name, title); // super constructor call10this.id = id;11this.email = email;12}13Student.prototype = Object.create(Person.prototype);14Student.prototype.constructor = Student;1516const aStudent = new Student("Emily Xi", "Mrs.", 12441, 'emily@xi.de');
Objektabhängigkeiten
1function Person(name, title){ … }2Person.prototype.formOfAddress = function (){ … }34function Student(name, title, id, email) { … }5Student.prototype = Object.create(Person.prototype);6Student.prototype.constructor = Student;78const p = new Person(…);9const s = new Student(…);
Die Eigenschaft prototype einer Funktion (F) verweist auf das Objekt, dass als Prototype (__proto__) verwendet wird, wenn die Funktion als Konstruktor verwendet wird. D. h. im Falle einer Instantiierung von F (d. h. const newF = new F()) wird das Objekt, das durch F.prototype referenziert wird, als Prototype (newF.__proto__) des neu erstellten Objekts (newF) verwendet.
1// Prototypen2console.log("{}.__proto__: ",{}.__proto__);3console.log("Array.prototype: ",Array.prototype);4console.log("Array.prototype.__proto__: ",Array.prototype.__proto__);5console.log("Object.prototype: ",Object.prototype);6console.log("Object.__proto__: ",Object.__proto__);78let o = { created: "long ago" };9var p = Object.create(o);10console.log("Object.getPrototypeOf(o): " + Object.getPrototypeOf(o));11console.log("o.isPrototypeOf(p):" + o.isPrototypeOf(p));12console.log("Object.prototype.isPrototypeOf(p): " + Object.prototype.isPrototypeOf(p));
1let a = [1, 10, 100, 1000];2try { console.log(a.fold()); } catch (error) {3console.log("error: ", error.message);4}56// - ATTENTION! ----------------------------------------------------------------7// ADDING FUNCTIONS TO Array.prototpye IS NOT RECOMMENDED! IF ECMAScript8// EVENTUALLY ADDS THIS METHOD (I.E. fold) TO THE PROTOTYPE OF ARRAY OBJECTS,9// IT MAY CAUSE HAVOC.10Array.prototype.fold = function (f) {11if (this.length === 0) {12throw new Error("array is empty");13} else if (this.length === 1) {14return this[0];15} else {16let result = this[0];17for (let i = 1; i < this.length; i++) {18result = f(result, this[i]);19}20return result;21}22};2324console.log(a.fold((u, v) => u + v));
Queue.mjs exportiert die Klasse Queue
1/* Modul für den Datentyp Warteschlange (Queue). */2export class Queue {3#last = null; // private field4#first = null;5constructor() {} // "default constructor"6enqueue(elem) {7if (this.#first === null) {8const c = { e: elem, next: null };9this.#first = c;10this.#last = c;11} else {12const c = { e: elem, next: null };13this.#last.next = c;14this.#last = c;15}16}17dequeue() {18if (this.#first === null) {19return null;20} else {21const c = this.#first;22this.#first = c.next;23return c.e;24}25}26head() {27if (this.#first === null) {28throw new Error("Queue is empty");29} else {30return this.#first.e;31}32}33last() {34if (this.#first === null) {35throw new Error("Queue is empty");36} else {37return this.#last.e;38}39}40isEmpty() {41return this.#first === null;42}43}
log.mjs verwendet (import) die Klasse Queue und exportiert Funktionen zum Loggen
1import { Queue } from "./Queue.mjs"; // import des Moduls "Queue.mjs"23const messages = new Queue();45export function log(...message) {6if (messages.isEmpty()) {7messages.enqueue(message);8} else {9message.unshift("\n");10messages.last().push(...message);11}12}
ECMAScript Module verwenden immer den strict mode.
Import Statements erlauben das selektierte importieren als auch das Umbenennen von importierten Elementen (z. B., import { Queue as Q } from "./Queue.mjs";).
1<html lang="en">2<head>3<meta charset="utf-8" />4<meta name="viewport" content="width=device-width, initial-scale=1.0" />5<title>DOM Manipulation with JavaScript</title>6<script>7function makeScriptsEditable() {8const scripts = document.getElementsByTagName("script");9for (const scriptElement of scripts) {10scriptElement.contentEditable = false;11const style = scriptElement.style;12style.display = "block";13style.fontFamily = "menlo, monospaced";14style.whiteSpace = "preserve";15style.padding = "0.25em";16style.backgroundColor = "lightyellow";17}18}19</script>20</head>21<body>22<h1>DOM Manipulation with JavaScript</h1>23<p id="demo">This is a paragraph.</p>24<button25type="button"26onclick="27document.getElementById('demo').style.color = 'red';28makeScriptsEditable();29document.querySelector('button').style.display = 'none';"30>31Magic!32</button>3334<script>35const demoElement = document.getElementById("demo");36const style = demoElement.style;37demoElement.addEventListener("mouseover", () => (style.color = "green"));38demoElement.addEventListener("mouseout", () => (style.color = "unset"));39</script>4041<p>Position der Mouse: <span id="position"></span></p>42<script>43window.addEventListener("mousemove", () => {44document.getElementById("position").innerHTML =45`(${event.clientX}, ${event.clientY})`;46});47</script>48</body>49</html>
1// "express" and "cors" are CommonJS modules, which requires us to use the2// "default import" syntax.3import express from "express";45// Cross-Origin Resource Sharing (CORS); This is required to allow the browser6// using a different domain to load the HTML to make requests to this server.7// I. e., we can use the HTML file from the "web-javascript" project to make8// requests to this server.9import cors from "cors";10const APP_PORT = 5080;1112const app = express();1314app.get("/users", cors(), function (req, res) {15res.set("Content-Type", "application/json");16res.end(`{17"user1" : {18"name" : "dingo",19"password" : "1234",20"profession" : "chef",21"id": 122},2324"user2" : {25"name" : "ringo",26"password" : "asdf",27"profession" : "boss",28"id": 329}30}`);31});3233app.listen(APP_PORT, function () {34console.log(`Users App @ http://127.0.0.1:${APP_PORT}`);35});
Express ist ein minimalistisches Web-Framework für Node.js, das die Entwicklung von Webanwendungen vereinfacht. Die Installation kann über einen Packagemanager erfolgen.
Installieren Sie (z. B.) pnpm (https://pnpm.io/) und nutzen Sie danach pnpm, um die benötigten Module zu installieren:
$ pnpm init
$ pnpm install express
Danach starten Sie Ihren Server mit:
node --watch UsersServer.mjs
1<html lang="en">2<head>3<meta charset="utf-8" />4<meta name="viewport" content="width=device-width, initial-scale=1.0" />5<title>Eventhandling</title>6</head>7<body>8<script>9/* Using Promises:10function getUsers() {11fetch('http://127.0.0.1:4080/users')12.then(response => response.json())13.then(users => {14const usersElement = document.getElementById('users');15usersElement.innerText = JSON.stringify(users);16});17}18*/1920/* Using async/await: */21async function getUsers() {22let response = await fetch("http://127.0.0.1:5080/users");23let users = await response.json();24const usersElement = document.getElementById("users");25usersElement.innerText = JSON.stringify(users);26}27</script>2829<div id="users"></div>30<button onclick="getUsers()">Get Users</button>31</body>32</html>
Im Folgenden verwenden wir zur Client-/Server-Kommunikation insbesondere Websockets.
Server
1const express = require('express');2const app = express();34const expressWs = require('express-ws')(app);56let clients = 0;7let playerWSs = [];89let adminWS = null;10let answersCount = 0;11let correctAnswersCount = 0;1213app.use(express.static('.')); // required to serve static files141516function sendCurrentPlayers() {17if (adminWS && playerWSs.length > 0) {18allPlayers = playerWSs19.filter(player => player.name)20.map(player => { return { "id": player.id, "name": player.name } })21console.log("Sending current players: " + JSON.stringify(allPlayers));22adminWS.send(JSON.stringify({ "type": "players", "players": allPlayers }));23}24}2526function sendNextQuestion() {27answersCount = 0;28correctAnswersCount = 0;29const question = "What is the capital of France?";30const answers = ["Paris", "London", "Berlin", "Madrid"];31const correct = "Paris";3233const nextQuestion = JSON.stringify({34"type": "question",35"question": question,36"answers": ["Paris", "London", "Berlin", "Madrid"]37})38playerWSs.forEach(player => player.ws.send(nextQuestion));39adminWS.send(JSON.stringify({40"type": "question",41"question": question,42"answers": answers,43"correct": correct44}));45}4647function sendResults() {48const results = playerWSs.map(player => {49return { "id": player.id, "name": player.name, "wins": player.wins }50});51const sortedResults = results.sort((a, b) => b.wins - a.wins);52const resultsMsg = JSON.stringify({53"type": "results",54"results": sortedResults55});56playerWSs.forEach(player => player.ws.send(resultsMsg));57adminWS.send(resultsMsg);5859}606162function handleAnswer(clientId, answer) {63const correct = answer.answer === "Paris";64const player = playerWSs.find(player => player.id === clientId);65if (correct) {66if (correctAnswersCount === 0) {67player.wins++;68}69correctAnswersCount++;70}71answersCount++;72if (answersCount === playerWSs.length) {73// sendNextQuestion();74sendResults();75} else {76adminWS.send(JSON.stringify({77"type": "answers",78"count": answersCount,79"correctAnswersCount": correctAnswersCount80}));81}82}838485app.ws('/player', function (ws, request) {86const clientId = clients++;87const playerData = { "ws": ws, "id": clientId, "wins": 0 }88playerWSs.push(playerData);89ws.onmessage = function (event) {90message = JSON.parse(event.data);91switch (message.type) {92case "registration":93const name = message.name;94console.log("Registration: " + clientId + "/" + name);95playerData.name = name;96sendCurrentPlayers();97break;9899case "answer":100const answer = message;101handleAnswer(clientId, answer);102break;103104default:105console.log("Unknown message: " + message);106break;107}108};109ws.onclose = function () {110console.log("Player disconnected: " + clientId);111playerWSs = playerWSs.filter(player => player.id !== clientId);112sendCurrentPlayers();113};114ws.onerror = function () {115console.log("Player error: " + clientId);116playerWSs = playerWSs.filter(player => player.id !== clientId);117sendCurrentPlayers();118};119});120121app.ws('/admin', function (ws, req) {122adminWS = ws;123sendCurrentPlayers(); // when admin registers her/himself, send current players124ws.onmessage = function (event) {125message = JSON.parse(event.data);126switch (message.type) {127case "start":128console.log("Start game");129sendNextQuestion();130break;131default:132console.log("Unknown message: " + message);133break;134}135};136137ws.onclose = (event) => {138console.log("Admin disconnected");139adminWS = null;140sendCurrentPlayers();141};142143ws.onerror = (event) => {144console.log("Admin error: " + event);145sendCurrentPlayers();146};147148});149150151const PORT = process.env.QUIZZY_PORT || 8800;152153var server = app.listen(PORT, function () {154console.log(`Quizzy running at http://127.0.0.1:${PORT}/`);155})
Client - Players
1<!DOCTYPE html>2<html lang="en">34<head>5<script>6let ws = undefined7try {8ws = new WebSocket("ws://localhost:5557/player");9} catch (e) {10alert("WebSocket connection failed: " + e);11document.getElementById("main").innerText = "WebSocket connection failed. Please check the server.";12}13ws.onmessage = (event) => {14const data = JSON.parse(event.data);15switch (data.type) {16case "question":17console.log("Question: " + data.question);18showQuestion(data);19break;20case "results":21const main = document.getElementById("main")22main.innerText = "Results: " + event.data;23break;24default:25console.log("Unknown message: " + data);26break;27}28};29ws.onclose = (event) => {30console.log("Connection closed: " + event);31}32ws.onerror = (event) => {33console.error("Error: " + event);34}3536function showQuestion(data) {37const main = document.getElementById("main")38main.innerHTML = `<h1>Question</h1><p>${data.question}</p>`;3940function createAnswerButton(answer) {41const button = document.createElement("button");42button.innerText = answer;43button.onclick = submitAnswer(answer);44return button;45}4647for (answer of data.answers) {48main.appendChild(createAnswerButton(answer));49}50}5152function submitAnswer(answer) {53return () => {54ws.send(JSON.stringify({55"type": "answer",56"answer": answer57}));58doWait();59}60}6162function submitUsername() {63const name = document.getElementById("username").value;64ws.send(JSON.stringify({65"type": "registration",66"name": name67}));6869doWait();70}7172function doWait() {73const main = document.getElementById("main");74main.innerHTML = "Waiting for other players...";75}76</script>7778<body>7980<main id="main">81<form>82<input type="text" id="username" placeholder="Username">83<button type="button" onclick="submitUsername();">Submit</button>84</form>85</main>86</body>8788</html>
Client - Admin
1<!DOCTYPE html>2<html lang="en">34<head>5<script>6const ws = new WebSocket("ws://localhost:5557/admin");78ws.onmessage = (event) => {9const data = JSON.parse(event.data);10console.log("Received: " + event.data);11switch (data.type) {12case "players":13const players = document.getElementById("players")14players.innerText =15"["+data.players.length + " players] " +16data.players17.map(player => player.id + ": " + player.name)18.join(", ");19break;20case "question":21showQuestion(data);22break;23case "results":24const main = document.getElementById("main")25main.innerText = "Result: " + event.data;26break;27default:28console.log("unknown: " + event.data);29break;30}31};3233ws.onclose = (event) => {34console.log("Connection closed: " + event);35const main = document.getElementById("main")36main.innerText = "Connection closed - you need to restart.";37};38ws.onerror = (event) => {39console.log("Connection error: " + event);40};4142function startGame() {43ws.send(JSON.stringify({"type": "start"}));44}4546function showQuestion(data) {47document.getElementById("main").innerText = `48question: ${data.question}; correct answer: ${data.correct}49`50}51</script>52</head>5354<body>55<main id="main">56<h1>Players</h1>57<p id="players"></p>58<button type="button" onclick="startGame();">Start Game</button>59</main>60</body>6162</html>
Die Implementierung dient nur dazu die grundlegenden Konzepte zu verdeutlichen. Es fehlen viele Aspekte wie z. B., Sicherheit.
Im Folgenden wird primär die Verwendung eines JWTs zur Authentifizierung von Benutzern demonstriert.
Die initiale Authentifizierung, die im folgenden Beispiel über ein per get-Request übermittelten Benutzernamen und Passwort erfolgt, ist nicht sicher. In einer realen Anwendung sollte für die initiale Authentifizierung ein sicherer Mechanismus verwendet werden. Eine Möglichkeit wäre z. B. die Verwendung von DIGEST Authentication (nicht empfohlen bzw. nur für einfachste Fälle). Sinnvoll wäre Basic Authentication in Verbindung mit HTTPS oder zum Beispiel der Einsatz von OAuth.
Server
1import express from "express";2import fs from "fs";3import path from "node:path";4import { fileURLToPath } from "url";5import jwt from "jsonwebtoken";6import crypto from "crypto";7import bodyParser from "body-parser";89const app = express();1011const SERVER_SECRET = crypto.randomBytes(64).toString("hex");12const users = JSON.parse(13fs.readFileSync(14path.resolve(path.dirname(fileURLToPath(import.meta.url)), "users.json"),15"utf8",16),17);18console.log("Users: " + JSON.stringify(users));1920app.use(express.static("."));21app.use(express.json());22app.use(bodyParser.text());2324const verifyToken = (req, res, next) => {25console.log("Headers: " + JSON.stringify(req.headers));2627const token = req.headers["authorization"].split(" ")[1];28if (!token) {29return res.status(401).json({ error: "Unauthorized" });30}3132jwt.verify(token, SERVER_SECRET, (err, decoded) => {33console.log("Decoded: " + JSON.stringify(decoded));34if (err) {35return res.status(401).json({ error: "Unauthorized" });36}37req.userIndex = decoded.userIndex;38next();39});40};4142app.get("/admin/login", function (req, res) {43const name = req.query.name;44const password = req.query.password; // in a real app use hashed passwords!4546if (!name || !password) {47res.status(400).send("Missing name or password");48return;49}5051let userIndex = -1;52for (let i = 0; i < users.length; i++) {53if (users[i].name === name && users[i].password === password) {54userIndex = i;55break;56}57}58if (userIndex === -1) {59res.status(401).send("Credentials invalid.");60return;61}62console.log(63"Authenticated: " + users[userIndex].name + " " + users[userIndex].password,64);6566// Here, we can use the userIndex to identify the user;67// but his only works as long as the user list is fixed.68// In a real app use, e.g., a users's email.69const token = jwt.sign({ userIndex: userIndex }, SERVER_SECRET, {70expiresIn: "2h",71});72res.status(200).json({ token });73});7475app.post("/admin/question", verifyToken, function (req, res) {76const userIndex = req.userIndex;77const question = req.body;78console.log("Received question: " + question + " from user: " + users[userIndex].name);7980res.status(200).send("Question stored. Preliminary answer: 42.");81});8283// Attention: a port like 6666 will not work on (most?) browsers84const port = 8080;85var server = app.listen(port, function () {86console.log(`Running at http://127.0.0.1:${port}/`);87});
Client (JavaScript)
1/*2Initializes the login interface.3*/4document5.getElementsByTagName("main")[0]6.replaceChildren(document.getElementById("log-in").content.cloneNode(true));7document.getElementById("login-dialog").showModal();8document.getElementById("login-button").addEventListener("click", login);910let jwt = null; // JSON Web Token for authentication1112async function login() {13const name = document.getElementById("administrator").value;14const password = document.getElementById("password").value;15const urlEncodedName = encodeURIComponent(name);16const urlEncodedPassword = encodeURIComponent(password);17const response = await fetch(18"http://" +19location.host +20"/admin/login?name=" +21urlEncodedName +22"&password=" +23urlEncodedPassword,24);25if (response.status !== 200) {26console.error("Login failed: " + response.status);27return;28}29const responseJSON = await response.json();30jwt = responseJSON.token;31console.log("Received JWT: " + jwt);3233document.getElementById("login-dialog").close();3435document36.getElementsByTagName("main")[0]37.replaceChildren(document.getElementById("logged-in").content.cloneNode(true));38document.getElementById("enter-question-dialog").showModal();39document.getElementById("send-question").addEventListener("click", sendQuestion);40}4142async function sendQuestion() {43const question = document.getElementById("question").value;4445const response = await fetch("http://" + location.host + "/admin/question", {46method: "POST",47headers: {48"Content-Type": "text/plain",49Authorization: `Bearer ${jwt}`,50},51body: question,52});53const text = await response.text();54showAnswer(text);55}5657function showAnswer(text) {58document.getElementById("answer-dialog").showModal(false);59document.getElementById("answer-paragraph").textContent = text;60}
Work in progress!
Bei der Quizzy Komponenten handelt es sich um eine (ganz) einfache Client-Server basierte Komponente für Quizzes. Die Komponente besteht aus der Definition der Web Komponente und einem Server. Die Komponente und der Server kommunizieren über Web Sockets. Der Server führt in Hinblick auf Cross-origin requests keine besonderen Prüfungen durch!
1const express = require('express');2const app = express();34const expressWs = require('express-ws')(app);56let clients = 0;7let playerWSs = [];89let adminWS = null;10let answersCount = 0;11let correctAnswersCount = 0;1213app.use(express.static('.')); // required to serve static files1415function sendCurrentPlayers() {16if (adminWS && playerWSs.length > 0) {17allPlayers = playerWSs18.filter(player => player.name)19.map(player => { return { "id": player.id, "name": player.name } })20console.log("Sending current players: " + JSON.stringify(allPlayers));21adminWS.send(JSON.stringify({ "type": "players", "players": allPlayers }));22}23}2425function sendNextQuestion() {26answersCount = 0;27correctAnswersCount = 0;28const question = "What is the capital of France?";29const answers = ["Paris", "London", "Berlin", "Madrid"];30const correct = "Paris";3132const nextQuestion = JSON.stringify({33"type": "question",34"question": question,35"answers": ["Paris", "London", "Berlin", "Madrid"]36})37playerWSs.forEach(player => player.ws.send(nextQuestion));38adminWS.send(JSON.stringify({39"type": "question",40"question": question,41"answers": answers,42"correct": correct43}));44}4546function sendResults() {47const results = playerWSs.map(player => {48return { "id": player.id, "name": player.name, "wins": player.wins }49});50const sortedResults = results.sort((a, b) => b.wins - a.wins);51const resultsMsg = JSON.stringify({52"type": "results",53"results": sortedResults54});55playerWSs.forEach(player => player.ws.send(resultsMsg));56adminWS.send(resultsMsg);5758}596061function handleAnswer(clientId, answer) {62const correct = answer.answer === "Paris";63const player = playerWSs.find(player => player.id === clientId);64if (correct) {65if (correctAnswersCount === 0) {66player.wins++;67}68correctAnswersCount++;69}70answersCount++;71if (answersCount === playerWSs.length) {72// sendNextQuestion();73sendResults();74} else {75adminWS.send(JSON.stringify({76"type": "answers",77"count": answersCount,78"correctAnswersCount": correctAnswersCount79}));80}81}828384app.ws('/player', function (ws, request) {85const clientId = clients++;86const playerData = { "ws": ws, "id": clientId, "wins": 0 }87playerWSs.push(playerData);88ws.onmessage = function (event) {89message = JSON.parse(event.data);90switch (message.type) {91case "registration":92const name = message.name;93console.log("Registration: " + clientId + "/" + name);94playerData.name = name;95sendCurrentPlayers();96break;9798case "answer":99const answer = message;100handleAnswer(clientId, answer);101break;102103default:104console.log("Unknown message: " + message);105break;106}107};108ws.onclose = function () {109console.log("Player disconnected: " + clientId);110playerWSs = playerWSs.filter(player => player.id !== clientId);111sendCurrentPlayers();112};113ws.onerror = function () {114console.log("Player error: " + clientId);115playerWSs = playerWSs.filter(player => player.id !== clientId);116sendCurrentPlayers();117};118});119120app.ws('/admin', function (ws, req) {121adminWS = ws;122sendCurrentPlayers(); // when admin registers her/himself, send current players123ws.onmessage = function (event) {124message = JSON.parse(event.data);125switch (message.type) {126case "start":127console.log("Start game");128sendNextQuestion();129break;130default:131console.log("Unknown message: " + message);132break;133}134};135136ws.onclose = (event) => {137console.log("Admin disconnected");138adminWS = null;139sendCurrentPlayers();140};141142ws.onerror = (event) => {143console.log("Admin error: " + event);144sendCurrentPlayers();145};146147});148149150const PORT = process.env.QUIZZY_PORT || 8800;151152var server = app.listen(PORT, function () {153console.log(`Quizzy running at http://127.0.0.1:${PORT}/`);154})
1/**2* A small web component which enable us to integrate a small quiz3* into a webpage.4*/5const quizzyStyles = new CSSStyleSheet();6quizzyStyles.replaceSync(`7:host {8display: block;9width: 100%;10height: 10lh;11background-color: #f0f0f012}13main {14height: 100%;1516.select-mode {17height: 100%;18display: flex;19flex-direction: column;20align-items: center;21gap: 1em;22align-content: space-around;23justify-content: space-around;2425button,26button[type="button"] {27width: 40%;28height: 2.5lh;29flex-grow: 0;30padding: 0.5em;31font-size: inherit;32}33}34}35`);3637class Quizzy extends HTMLElement {38constructor() {39super();4041const shadow = this.attachShadow({ mode: "open" });4243shadow.adoptedStyleSheets = [quizzyStyles];44shadow.innerHTML = `45<main>46<div class="select-mode">47<button id="clientButton">Join Quizzy</button>48<button id="adminButton">Administrate Quizzy</button>49</div>50</main>`;51}5253connectedCallback() {54const wsURL = this.getAttribute("ws-url");5556/**57* Client logic58*/59this.shadowRoot60.getElementById("clientButton")61.addEventListener("click", () => {62this.shadowRoot.innerHTML = `63<main>64<form>65<input type="text" id="username" placeholder="Username">66<button id="submit" type="button">Submit</button>67</form>68</main>`;6970const ws = new WebSocket(`${wsURL}/player`);7172ws.onmessage = (event) => {73const data = JSON.parse(event.data);74switch (data.type) {75case "question":76showQuestion(data);77break;78case "results":79showResults(data);80break;81default:82console.warn("unknown message", data);83break;84}85};86ws.onclose = (event) => {87console.log("connection closed", event);88};89ws.onerror = (event) => {90console.error("fatal error", event);91};9293const showResults = (data) => {94console.log("show results", data);95const main = this.shadowRoot.querySelector("main");96main.innerHTML = `<h1>Results</h1>`;97for (const result of data.results) {98main.innerHTML += `<p>${result.name}: ${result.wins}</p>`;99}100};101102const showQuestion = (data) => {103const main = this.shadowRoot.querySelector("main");104main.innerHTML = `<h1>Question</h1><p>${data.question}</p>`;105106const createAnswerButton = (answer) => {107const button = document.createElement("button");108button.innerText = answer;109button.onclick = submitAnswer(answer);110return button;111};112113for (const answer of data.answers) {114main.appendChild(createAnswerButton(answer));115}116};117118const submitAnswer = (answer) => {119return () => {120ws.send(121JSON.stringify({122type: "answer",123answer: answer,124}),125);126doWait();127};128};129130setTimeout(() => {131this.shadowRoot132.getElementById("submit")133.addEventListener("click", () => {134submitUsername();135});136});137138const submitUsername = () => {139const name =140this.shadowRoot.getElementById("username").value;141ws.send(142JSON.stringify({143type: "registration",144name: name,145}),146);147148doWait();149};150151const doWait = () => {152const main = this.shadowRoot.querySelector("main");153main.innerHTML = "Waiting for other players...";154};155});156157/**158* Admin logic159*/160this.shadowRoot161.getElementById("adminButton")162.addEventListener("click", () => {163this.shadowRoot.innerHTML = `164<main>165<h1>Players</h1>166<p id="players"></p>167<button id="startGame" type="button">Start Game</button>168</main>`;169170const ws = (this.ws = new WebSocket(`${wsURL}/admin`));171172ws.onmessage = (event) => {173const data = JSON.parse(event.data);174console.log("Received: " + event.data);175switch (data.type) {176case "players":177const players =178this.shadowRoot.getElementById("players");179players.innerText =180"[" +181data.players.length +182" players] " +183data.players184.map(185(player) =>186player.id + ": " + player.name,187)188.join(", ");189break;190case "question":191this.shadowRoot.querySelector("main").innerText = `192question: ${data.question}; correct answer: ${data.correct}193`;194break;195case "results":196const main = this.shadowRoot.querySelector("main");197main.innerText = "Result: " + event.data;198break;199default:200console.log("unknown: " + event.data);201break;202}203};204205ws.onclose = (event) => {206console.log("Connection closed: " + event);207const main = this.shadowRoot.querySelector("main");208main.innerText = "Connection closed - you need to restart.";209};210ws.onerror = (event) => {211console.log("Connection error", event);212};213214function startGame() {215ws.send(JSON.stringify({ type: "start" }));216}217218setTimeout(() => {219this.shadowRoot220.getElementById("startGame")221.addEventListener("click", () => {222startGame();223});224});225});226227console.log("Quizzy component connected", this.wsURL);228}229230disconnectedCallback() {231console.log("Quizzy component disconnected");232// Clean up any resources, event listeners, or connections,.... TODO233}234}235236customElements.define("ld-quizzy", Quizzy);
<script src="code/quizzy-component/quizzy.js"></script>
<div onclick="event.stopPropagation();">
<ld-quizzy ws-url="ws://localhost:5557">
<!-- When used as a real component, we have to either specify the
questions here (somehow encrypted) and send them to the server when
required, or refer to a set of questions stored on the server by
means of a parameter/attribute... -->
</ld-quizzy>
</div>