Web-Entwicklung

Agenda

Grundlagen

  • HTML/CSS/JS
  • HTTP-Protokoll
  • JSON

Fortgeschrittenes

  • JavaScript
  • Responsive Design
  • WebComponents / lit
  • NodeJS
  • Datenbank

Betrieb

  • Server einrichten
  • SSL- / TLS-Zertifikate

Architektur & Paradigmen

  • Funktionale Programmierung
  • Objektorientierte Programmierung
  • Entwurfsmuster (Singleton, Factory, Builder...)
  • SOLID-Prinzipien
  • Dependency Injection
  • MVC
  • Redux

Interessante Technologien

  • Canvas-Element
  • CSS-Animationen
  • WebSockets
  • WebWorker
  • ServiceWorker
  • WebRTC
  • WebGL
  • Progressive Web Application
  • CEF / Electron
  • WebAssembly

Workflows

  • Git
  • Test Driven Development
  • Continuous Integration / Continuous Delivery

HTML

  • HyperText Markup Language
  • Erste Version 1993 von Tim Berners-Lee
  • Dokumentationsmedium
  • Ursprünglich rein akademische Verwendung
  • Verlinkungen der Dokumente macht sie "hyper"
VersionErscheinungElemente ca.
HTML 1.0199320
HTML 2.0199550
HTML 3.2199770
HTML 4.01199990
HTML 5.02014110
  • Heute ein "lebender Standard" (Living Standard)
  • Stetige inkrementelle Erweiterung

Grundgerüst

							
								
							
						

Beispiel

							
								
							
						

Beispiel

Aufbau eines Elements

							
								
							
						
							
								
							
						
							
								
							
						

CSS

  • Cascading Style Sheet
  • Nach größerer Verbreitung von HTML
  • Webseiten ansprechend gestalten

Beispiel

							
								selector [, selector, selector, ...] {
									property: value;
								}
							
						

Beispiel

							
								html {
									background-color: red;
									font-size: 30px;
								}
							
						

Beispiel

							
								
							
						

Beispielhafte CSS-Eigenschaften

  • background-color: red
  • font-family: Georgia, serif, Arial
  • text-decoration: line-through underline

Selektoren

  • Umfangreiche Element-Selektoren zur Anwendung der Styles
  • Kinder, Enkel...
  • Geschwister, direkte Nachfolger
  • Attribute
  • Pseudoklassen

Selektoren

							
								#my-id /* ID */
								html, body /* Mehrfachselektion */
								div > p /* p die direkt unterhalb eines div sind */
								div  p /* p die irgendwo unterhalb eines div sind */
								a:visited /* besuchte Links */
								div:hover /* divs über die man mit der Maus fährt */
								span.important /* span mit der Klasse "important" */
							
						

Layout

Es gibt zwei grundlegende Blocktypen:

block

inline

Block

Beansprucht eine ganze Zeile und verursacht Zeilenumbrüche

							
								display: block;
							
						

Kann Breite und Höhe haben

							
								display: block;
								width: 20px;
								height: 20px;
							
						

Inline

Wie einfacher Text, der mit anderen in der gleichen Zeile stehen kann

							
								display: inline;
							
						

Festlegen von Breite und Höhe sind wirkungslos

							
								display: inline;
								width: 20px; /* kein Effekt */
								height: 20px; /* kein Effekt */
							
						

Inline-Block

Mischform von inline und block

							
								display: inline-block;
								width: 20px;
								height: 20px;
							
						

Spezifität

Regeln können andere Regeln überschreiben

							
								
							
						
							
								#id:hover { display: none }

								#id { display: block }

								.class.clazz { display: inline }

								div { display: inline-block }
							
						

Box Model

Es gibt zwei Varianten des Modells:

content-box (default)

border-box

							
								box-sizing: content-box;
								box-sizing: border-box;
							
						

Beispiel

							
								div {
									border: 3px solid red;
									padding: 10px;
									width: 100px;
								}

								.border-box {
									box-sizing: border-box;
								}

								.content-box {
									box-sizing: content-box;
								}
							
						
							
								
							
						

Beispiel

Positioning

							
								/* default, da wo es in der Seite steht */
								position: static;
								/* relativ zu seiner eigentlichen Position */
								position: relative;
								/* relativ zu seinem nächsten non-static parent o. window */
								position: absolute;
							
						
							
								position: relative;
								position: absolute;
							
						

erlauben Verschiebung mittels

							
								top: 10px;
								left: 10px;
								right: 10px;
								bottom: 10px;
							
						

Beispiel Menü

							
								.menu-item {
									display: inline-block;

									position: relative;
								}

								.menu-item .children {
									display: none;

									position: absolute;
								}

								.menu-item:hover .children {
									display: block;
								}
							
						
							
								
							
						

JS

  • JavaScript stammt aus dem Jahr 1995 von Netscape
  • Syntaktisch an Java angelehnt
  • Ursprünglich nur optionale, verzichtbare Effekte (unobstrusive)
  • Mittlerweile von ECMA standardisiert
VersionECMA StandardJahr
ES1ECMAScript 11997
ES2ECMAScript 21998
ES3ECMAScript 31999
ES5ECMAScript 52009
ES6ECMAScript 20152015
...
ECMAScript 20202020

Code wird zwischen script-Tags geschrieben

							
								<!DOCTYPE html>
								<html lang="de">
									<head>
										<meta charset="UTF-8">
									</head>
									<body>
										<script>// code</script>
									</body>
								</html>
							
						

DOM-API

							
								// findet das erste Element
								const parent = document.querySelector("#id");

								// findet alle Elemente
								const elements = document.querySelectorAll("#id");

								// Element erstellen
								const newElement = document.createElement("div");

								newElement.addEventListener("click", (event) => {
									// Click-Event
								});

								// Element anhängen
								parent.appendChild(newElement);

								newElement.remove();
							
						

Auslagern in verschiedene Dateien

							
								<!DOCTYPE html>
								<html>
									<head>
										
										
									</head>
									<body>
										
										<script src="index.js"></script>
									</body>
								</html>
							
						

Parsen eines HTML-Dokuments

							
								
							
						

Verschiedene Varianten, JavaScript erst am Ende auszuführen

Damals mit jQuery:

							
								<!DOCTYPE html>
								<html>
									<head>
										<script>
											$(document).ready((event) => {
												const element = document.querySelector("#hello");
											});
										</script>
									</head>
									<body>
										
Hello World!
</body> </html>

script-Tag am Ende

							
								<!DOCTYPE html>
								<html>
									<head>
									</head>
									<body>
										
Hello World!
... <script src="index.js"></script> </body> </html>

Event-basiert, z.B. per DOMContentLoaded

							
								<!DOCTYPE html>
								<html>
									<head>
										<script>
											document.addEventListener("DOMContentLoaded", (event) => {
												// wird ausgeführt, sobald das Dokument geparst wurde
												const element = document.querySelector("#hello");
											});
										</script>
									</head>
									<body>
										
Hello World!
</body> </html>

Moderne Variante

							
								<script src="index.js" defer></script>
							
						

document-Operationen

							
								document.open();
								document.write("
Booo!
"); document.close();
							
								<body>
									<script>document.write("

Dies ist ein Text.

")</script> <script> const button = document.querySelector("button"); button.addEventListener("click", () => { document.write("Und pfutsch!"); }); </script> </body>

HTTP-Protokoll

  • OSI-Schichten 5, 6 und 7
  • Anfrage-Antwort-Prinzip
  • Basiert auf einfachem Text
VersionJahr
0.91991
1.01996
1.11999
2.02015
3.02022

Request an www.google.de

							
								GET / HTTP/1.1
								Host: www.google.de
								User-Agent: curl/7.88.1
								Accept: */*
							
						

Response von www.google.de

							
								HTTP/1.1 200 OK
								Date: Tue, 04 Apr 2023 15:45:26 GMT
								Expires: -1
								Cache-Control: private, max-age=0
								Content-Type: text/html; charset=ISO-8859-1
								Content-Security-Policy-Report-Only: [...]
								Server: gws
								X-XSS-Protection: 0
								X-Frame-Options: SAMEORIGIN
								Set-Cookie: [...]
								Accept-Ranges: none
								Vary: Accept-Encoding
								Transfer-Encoding: chunked
								
								<!doctype html><html>...</html>
							
						

Einfach mal testen:

							
								curl -v --http1.0 http://www.google.de
								curl -v --http1.1 http://www.google.de
								curl -v --http2 http://www.google.de
								curl -v --http2-prior-knowledge http://www.google.de
								curl -v --http3 http://www.google.de
							
						

Verschiedene "Verben" bzw. "Methoden"

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE
  • ...

Beispiel:

							
								git clone https://git.furnco.de/r/public/web-development/examples.git
								cd examples/templates/005-http-rest
								node index.js
							
						
							
								curl -X GET -is http://localhost:3000/todo
								curl -X POST -H "Content-Type: application/json" -d '{"name": "Einkaufen"}' -is http://localhost:3000/todo
								curl -X POST -H "Content-Type: application/json" -d '{"name": "Putzen"}' -is http://localhost:3000/todo
								curl -X PUT -H "Content-Type: application/json" -d '{"name": "Schlafen"}' -is http://localhost:3000/todo/1
								curl -X DELETE -is http://localhost:3000/todo/0
							
						
							
								HTTP/1.1 200 OK
								X-Powered-By: Express
								Content-Type: application/json; charset=utf-8
								Content-Length: 13
								ETag: W/"d-KMmUcQ1kigqtwzZnU+jVDkYP3Co"
								Date: Wed, 05 Apr 2023 12:05:05 GMT
								Connection: keep-alive
								Keep-Alive: timeout=5
								
								["Einkaufen"]
							
						
							
								app.get("/todo", (req, res) => {
									return res.json(todos);
								});
								
								// [...]
								
								app.post("/todo", (req, res) => {
									// [...]
										return res.json(todos);
									// [...]
								});
							
						

GET und POST auf klassischen Webseiten

							
								

Beispiel:

							
								cd examples/templates/006-http-web
								node index.js
							
						

JSON

  • JavaScript Object Notation
  • Basiert auf reinem Text
  • Format zur strukturierten Speicherung von Daten
  • Attribute müssen immer in doppelte Anführungsstriche (") gesetzt werden

Definition eines einfachen Objekts in JS

							
								const o = {
									x: 1,
									y: 2
								};
							
						

Entsprechung in JSON

							
								{
									"x": 1,
									"y": 2
								}
							
						

Mögliche Typen

  • Number (bzw. Integer / Float)
  • Boolean
  • String
  • Array
  • Object

Der äußere Container kann ein Array oder ein Object sein

							
								{
									"key": "value",
								}
							
						
							
								[
									"foo",
									"bar"
								]
							
						

JavaScript

c / c++ Java C# JS
Formale Spezifikation ja
Übersetzung Nativ kompiliert Bytecode Interpretiert / kompiliert (JIT)
Typisierung statisch dynamisch
Speicher-Management Manuell Garbage Collector

Begrifflichkeiten

							
								function add(x, y) {
									return x + y;
								}
								
								const a = 5;
								const b = add(a, 6);
								const c = [];
							
						
Operator, Aufruf (Call), Ausdruck (Expression), Parameter, Argument, Literal,
Zuweisung (Assignment), Linke Hand (Left hand), Rechte Hand (Right hand)

Vorbereiten der Beispiele (bei Bedarf)

							
								# im Ordner 'examples/'
								. hooks/post-checkout
							
						

Ausführung

							
								# node [Script-Datei], z.B.
								node index.js
							
						

Ausgaben

							
								console.log("Hello World!");
							
						

Variablen

							
								//  < es6
								var x = 1;
								
								// >= es6
								let y = 2;
								const z = 3;
							
						
							
								var yes = false;

								if (yes) {
									var a = 1;
								}
								
								console.log(a);
							
						
							
								undefined
							
						
							
								var yes = false;

								if (yes) {
									let a = 1;
								}
								
								console.log(a);
							
						
							
								ReferenceError: a is not defined
							
						
							
								function foo() {
									var yes = false;
								
									if (yes) {
										var a = 1;
									}
								}
								
								foo();
								
								console.log(a);
							
						
							
								ReferenceError: a is not defined
							
						

var

  • hat function-Scope
  • wird durch hoisting ("hochziehen") aus dem Block heraus an den Start der Funktion gezogen
							
								var yes = false;
								var a;

								if (yes) {
									a = 1;
								}
								
								console.log(a);
							
						

let, const

  • haben intuitiven Block-Scope (wie viele andere Programmiersprachen auch)
  • daher nur an Ort und Stelle gültig in der angegebenen Reihenfolge

Datentypen

							
								let x = 5; // Number (Integer)
								let f = 1.25; // Number (Float)
								let b = true; // Boolean
								let s = "hallo"; // String
								let a = []; // Array
								let o = {}; // Objekt
								let nan = NaN; // Not a Number
								let n = null; // Null
								let u = undefined; // Undefiniert
							
						

Alternation

							
								if (x == 1) {
									// Code
								}
								else if (x == 2) {
									// alternativer Code
								}
								else {
									// Fallback
								}
							
						

Schleifen

							
								for (let i = 0; i < 5; i++) {
									// Code
								}
							
						
							
								const a = [1, 2, 3];
								
								for (const element of a) {
									// Code
								}
							
						
							
								while (i == 1) {
									// Code
								}
							
						
							
								do {
									// Code
								} while (i == 1)
							
						

Arrays

							
								let a = [1, 2, 3];
								
								a.push(4);
								a.push(5);
								
								a.pop();
								
								console.log(a);
							
						
							
								[1, 2, 3, 4]
							
						

Objekte

							
								let o = {x: 1, y: 2};
								
								console.log(o.z);
								
								console.log(o);
								
								o.z = 3;
								
								console.log(o);
							
						
							
								undefined
								{ x: 1, y: 2 }
								{ x: 1, y: 2, z: 3 }
							
						
Objekte funktionieren intern wie Hash-Maps
							
								let o = {};
								
								o.a = 1;
								o["a"] = 1; // alternativ
								
								o["$ !"] = 2; // so sind auch komplexe Schlüssel möglich
								
								// o."$ !" = 2; => Syntaxfehler
								// o.$ ! = 2; => Syntaxfehler
								
								o[true] = 3;
								
								console.log(Object.keys(o));
							
						
							
								[ 'a', '$ !', 'true' ]
							
						

Funktionen

							
								function foo() {
									console.log("foo");
								}
								
								function bar(a) {
									return a;
								}
								
								foo();
								
								const a = bar(1);
								
								console.log(a);
							
						
							
								foo
								1
							
						
							
								let sum = function(a, b) {
									return a + b;
								}
								
								let subtract = function() {
									console.log(arguments);
								
									return arguments[0] - arguments[1];
								}
								
								console.log(sum(1, 2));
								console.log(subtract(1, 2));
							
						
							
								3
								[Arguments] { '0': 1, '1': 2 }
								-1
							
						

Funktionen als Konstruktor

							
								function Rectangle(a, b) {
									this.a = a;
									this.b = b;
								}
								
								const r = new Rectangle(2, 3);
								
								console.log(r.a);
							
						
							
								2
							
						

Objektorientierung

							
								function Rectangle(a, b) {
									this.a = a;
									this.b = b;
								}
								
								// jede Funktion besitzt einen Prototypen
								Rectangle.prototype.getArea = function() {
									return this.a * this.b;
								}
								
								const r1 = new Rectangle(2, 3);
								const r2 = new Rectangle(4, 5);
								
								console.log(r1.getArea());
								console.log(r2.getArea());
							
						
							
								6
								20
							
						
Dynamische Änderung des Prototypen
							
								function Rectangle(a, b) {
									this.a = a;
									this.b = b;
								}
								
								const r = new Rectangle(2, 3);
								
								console.log(r.getArea);
								
								Rectangle.prototype.getArea = function() {
									return this.a * this.b;
								}
								
								console.log(r.getArea);
							
						
							
								undefined
								[Function (anonymous)]
							
						

Aufgabe

Bringe dem Array eine Methode bei, welche die Summe aller enthaltenen Zahlen zurückgibt
							
								const a = [1, 2, 3];
								
								console.log(a.sum());
							
						
							
								6
							
						

Lösung

							
								Array.prototype.sum = function() {
									let sum = 0;
								
									for (let number of this) {
										sum += number;
									}
								
									return sum;
								}
							
						

Vererbung

							
								// Oberklasse
								function Shape() {
								}
								
								Shape.prototype.getType = function() {
									return "Shape";
								}
								
								// Unterklasse
								function Rectangle(a, b) {
									this.a = a;
									this.b = b;
								}
								
								// Prototype der Oberklasse in die Unterklasse kopieren
								Rectangle.prototype = Object.create(Shape.prototype);
								// Konstruktor der Oberklasse am Prototypen durch den eigenen ersetzen
								Rectangle.prototype.constructor = Rectangle;
								
								const r = new Rectangle(2, 3);
								
								console.log(r.getType());
								
								// .getType() selber implementieren
								Rectangle.prototype.getType = function() {
									return "Rectangle";
								}
								
								console.log(r.getType());
							
						
							
								Shape
								Rectangle
							
						

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push

Objektorientierung in es6

							
								class Rectangle {
									constructor(a, b) {
										this.a = a;
										this.b = b;
									}
								
									getArea() {
										return this.a * this.b;
									}
								}
								
								const r = new Rectangle(2, 3);
								
								console.log(r.getArea());
							
						
							
								6
							
						

Vererbung in es6

							
								// Oberklasse
								class Shape {
									getType() {
										return "Shape";
									}
								}
								
								// Unterklasse erbt von Oberklasse
								class Rectangle extends Shape {
									constructor(a, b) {
										super();
								
										this.a = a;
										this.b = b;
									}
								}
								
								const r = new Rectangle(2, 3);
								
								console.log(r.getType());
								
								// Implementierung per Prototype weiterhin möglich, falls man so will
								Rectangle.prototype.getType = function() {
									return "Rectangle";
								}
								
								console.log(r.getType());
							
						
							
								Shape
								Rectangle
							
						

Asynchronität

Asynchron: Es wird nicht auf die Beendigung des Befehls gewartet, sondern mit dem Programm fortgefahren. Die Vollendung des asynchronen Befehls tritt irgendwann in der Zukunft ein.

							
								setTimeout(callback, time); // Ausführung nach [time] Millisekunden
								setInterval(callback, interval); // Ausführung alle [interval] Millisekunden
							
						
							
								setTimeout(function() {
									// Code, der nach 2 Sekunden ausgeführt wird
									console.log("zweiter");
								}, 2000);
								
								console.log("erster");
							
						
							
								erster
								zweiter
							
						

Aufgabe: Schreibe ein Programm, das zwei weitere beliebige Texte mit jeweils 2 Sekunden Verzögerung von einander ausgibt, d.h.

"erster" -> 2s -> "zweiter" -> 2s -> "Text A" -> 2s -> "Text B"

							
								setTimeout(function() {
									console.log("zweiter");
								
									// weiterer Code
								}, 2000);
								
								console.log("erster");
							
						
Lösung: Callback-Hell oder Pyramide des Todes
							
								setTimeout(function() {
									console.log("zweiter");
								
									setTimeout(function() {
										console.log("Text A");
								
										setTimeout(function() {
											console.log("Text B");
										
											
										}, 2000);
									}, 2000);
								}, 2000);
								
								console.log("erster");
							
						
Hilfsmittel: Promises
							
								new Promise(function (resolve, reject) {
									// ich bin fertig
									resolve();
								
									// es ist ein Fehler aufgetreten
									reject();
								});
							
						
							
								const p = new Promise(function (resolve, reject) {...});
								const p2 = new Promise(function (resolve, reject) {...});
								const p3 = new Promise(function (resolve, reject) {...});
								const p4 = new Promise(function (resolve, reject) {...});
								
								p
									.then(function() { return p2; })
									.then(function() { return p3; })
									.then(function() { return p4; });
							
						
							
								console.log("erster");
								
								delay(2000)
									.then(function () {
										console.log("zweiter");
								
										return delay(2000);
									})
									.then(function() {
										console.log("Text A");
								
										return delay(2000);
									})
									.then(function() {
										console.log("Text B");
									})
							
						
Aufgabe: Implementiere die Funktion "delay"
							
								const delay = function() {
									// implementieren
								}
								
								console.log("erster");
								
								delay(2000)
									.then(function () {
										console.log("zweiter");
								
										return delay(2000);
									})
									.then(function() {
										console.log("Text A");
								
										return delay(2000);
									})
									.then(function() {
										console.log("Text B");
									})
							
						
Lösung:
							
								const delay = function(ms) {
									return new Promise(function(resolve, reject) {
										setTimeout(function() {
											resolve();
										}, ms);
									});
								}
								
								console.log("erster");
								
								delay(2000)
									.then(function () {
										console.log("zweiter");
								
										return delay(2000);
									})
									.then(function() {
										console.log("Text A");
								
										return delay(2000);
									})
									.then(function() {
										console.log("Text B");
									})
							
						
Rückgabewerte mit Promises
							
								const p = new Promise(function(resolve, reject) {
									resolve("ok");
								});
								
								p.then(function(value) {
									console.log(value);
								});
							
						
							
								ok
							
						
Fehlerbehandlung mit Promises
							
								const p = new Promise(function(resolve, reject) {
									reject(new Error("Fehler"));
								});
								
								p
									.then(function() {
										console.log("Ich werde nicht aufgerufen")
									})
									.catch(function(e) {
										console.log(e.message);
									});
							
						
							
								Fehler
							
						
Datei lesen mit NodeJS
							
								const fs = require("fs");
								
								fs.readFile("./hello.txt", {encoding: "utf8"}, function(error, data) {
									console.log(data);
								});
							
						
							
								Hello World!
							
						
Aufgabe: "Promisifiziere" analog zu "delay" in der letzten Aufgabe den Aufruf von fs.readFile()
							
								const fs = require("fs");
								
								function readFile() {
									// implementieren
								}
								
								readFile("./hello.txt").then(function(content) {
									console.log(content);
								});
							
						
							
								Hello World!
							
						
Lösung:
							
								const fs = require("fs");
								
								function readFile(path) {
									return new Promise(function (resolve, reject) {
										fs.readFile(path, {encoding: "utf8"}, function(error, data) {
											resolve(data);
										});
									});
								}
								
								readFile("./hello.txt").then(function(content) {
									console.log(content);
								});
							
						
							
								Hello World!
							
						
Aufgabe: Nutze die vorherige Lösung nun für eine Promise-Kette, sodass die Inhalte der zwei Dateien "hello.txt" und "hallo.txt" nacheinander ausgegeben werden:
							
								Hello World!
								
								Hallo Welt!
								
							
						
Lösung:
							
								const fs = require("fs");
								
								function readFile(path) {
									return new Promise(function (resolve, reject) {
										fs.readFile(path, {encoding: "utf8"}, function(error, data) {
											resolve(data);
										});
									});
								}
								
								readFile("./hello.txt")
									.then(function(content) {
										console.log(content);
								
										return readFile("./hallo.txt");
									})
									.then(function(content) {
										console.log(content);
									});
							
						
							
								Hello World!
								
								Hallo Welt!
							
						

Async - Await

  • Syntaktischer Zucker für Promises
  • Nutzung von await nur im Kontext einer async-Funktion möglich
							
								async function wrapper() {
									const p = new Promise(function(resolve, reject) {
										resolve("Hello from Promise!");
									});
									
									const result = await p;
									
									console.log(result);
								}
								
								wrapper();
							
						
							
								Hello from Promise!
							
						
Aufgabe: Schreibe die Lösung der vorherigen Aufgabe nach Async - Await um
Lösung:
							
								const fs = require("fs");
								
								function readFile(path) {
									return new Promise(function (resolve, reject) {
										fs.readFile(path, {encoding: "utf8"}, function(error, data) {
											resolve(data);
										});
									});
								}
								
								async function wrapper() {
									const content1 = await readFile("./hello.txt");
									const content2 = await readFile("./hallo.txt");
								
									console.log(content1);
									console.log(content2);
								}
								
								wrapper();
							
						
							
								Hello World!
								
								Hallo Welt!
							
						

Destructuring

Auflösen von komplexen Strukturen

							
								// Objekt destrukturieren (per Attribut-Namen)
								const { a, b } = { a: 1, b: 2 };
								
								console.log(a);
								console.log(b);
								
								// Array destrukturieren (per Position)
								const [ c, d ] = [3, 4];
								
								console.log(c);
								console.log(d);
							
						
							
								1
								2
								3
								4
							
						
							
								// Bei Objekten Elemente umbenennen oder auch auslassen
								const { a: x, b: y } = { a: 1, b: 2, c: 3 };
								
								console.log(x);
								console.log(y);
								
								// Bei Arrays Elemente auslassen
								const [ , a, b, , c] = [3, 4, 5, 6, 7, 8];
								
								console.log(a);
								console.log(b);
								console.log(c);
							
						
							
								1
								2
								4
								5
								7
							
						
							
								// Verschachteltes Destructuring
								const {a: [, x, ] } = { a: [0, 1, 2]};
								
								console.log(x);
							
						
							
								1
							
						
Aufgabe: x soll die 5 enthalten
							
								const o = { a: [0, {b: [0, 5]}, 2]};
								
								const  { /* Destructuring-Ausdruck */ } = o;
								
								console.log(x);
							
						
							
								5
							
						
Lösung:
							
								const o = { a: [0, { b: [0, 5] }, 2]};

								const { a: [, { b: [, x] }]} = o;
								
								console.log(x);
							
						
							
								5
							
						
Aufgabe: x soll die 3 enthalten
							
								const o = { a: [0, {b: [0, { c: [{ x: 5 }, { d: 3}] }]}, 2]};
								
								const  { /* Destructuring-Ausdruck */ } = o;
								
								console.log(x);
							
						
							
								3
							
						
Lösung:
							
								const o = { a: [0, {b: [0, { c: [{ x: 5 }, { d: 3}] }]}, 2]};

								const  { a: [, {b: [, { c: [, { d: x}] }] }] } = o;
								
								console.log(x);
							
						
							
								3
							
						

Destructuring zum parallelen Ausführen von mehreren Promises

							
								const fs = require("fs");
								
								function readFile(path) {
									return new Promise(function (resolve, reject) {
										fs.readFile(path, {encoding: "utf8"}, function(error, data) {
											resolve(data);
										});
									});
								}
								
								async function wrapper() {
									const [ content1, content2 ] = await Promise.all([
										readFile("./hello.txt"),
										readFile("./hallo.txt")
									]);
								
									console.log(content1);
									console.log(content2);
								}
								
								wrapper();
							
						

Try-Catch-Finally

							
								function throwError() {
									try {
										throw new Error("Fehler!");
										return "Hello World!";
									}
									catch (e) {
										return e.message;
									}
									finally {
										console.log("Finally wird immer ausgeführt");
										return "Finally!";
									}
								}
								
								const result = throwError();
								console.log(result);
							
						
							
								Finally wird immer ausgeführt
								Finally!
							
						

Anwendungsbeispiel für Try-Catch-Finally

							
								function readFromDatabase() {
									try {
										db.connect();
										return db.read();
									}
									catch (e) {
										console.log(e.stack);
										return "";
									}
									finally {
										if (db.open) {
											db.close();
										}
									}
								}
							
						

Scope

							
								function a() {
									const x = 5;
								
									return function() {
										return x * x;
									};
								}
								
								const squareX = a();
								const result = squareX();
								
								console.log(result);
							
						
							
								25
							
						

this

							
								class MyClass {
									constructor(a) {
										this.a = a;
									}
								
									getA() {
										return this.a;
									}
								}
								
								const x = new MyClass(5);
								const y = new MyClass(10);
								
								console.log(x.getA());
								console.log(y.getA());
							
						
							
								5
								10
							
						
							
								global.a = 5;

								function foo() {
									this.a += 10;
								}
								
								foo();
								
								console.log(a);
							
						
							
								15
							
						
							
								class MyClass {
									/* ... */
									setA() {
										function f() {
											this.a += 100;
										}
										f();
									}
								}
								
								const x = new MyClass(5);
								
								x.setA();
								
								console.log(x.getA());
							
						
							
								TypeError: Cannot read property 'a' of undefined
							
						
							
								class MyClass {
									/* ... */
									setA() {
										const me = this;
										function f() {
											me.a += 100;
										}
										f();
									}
								}
								
								const x = new MyClass(5);
								
								x.setA();
								
								console.log(x.getA());
							
						
							
								105
							
						

Arrow-Funktionen

  • Kompakte Schreibweise einer normalen Funktion
  • Bei Einzeilern ist das return nicht notwendig
  • this-Pointer des aktuellen Kontextes wird beibehalten
							
								// Einzeiler
								const f1 = _ => 1;
								const f2 = () => 1;
								const f3 = (x) => x * x;
								const f4 = (x, y) => x + y;
								
								// Mehrzeiler
								const f5 = (x, y) => {
									return x * y;
								};
							
						
							
								class MyClass {
									/* ... */
									setA() {
										const f = () => {
											this.a += 100;
										}
										f();
									}
								}
								
								const x = new MyClass(5);
								
								x.setA();
								
								console.log(x.getA());
							
						
							
								105
							
						

Generator-Funktionen

							
								function* generator() {
									let i = 0;
								
									while(true) {
										yield i;
										i++;
									}
								}
								
								const g = generator();
								
								console.log(g.next().value);
								console.log(g.next().value);
								console.log(g.next().value);
							
						
							
								1
								2
								3
							
						

Generator-Funktionen

							
								function* generator() {
									yield 1;
									yield 2;
									yield 3;
								}
								
								const g = generator();
								
								for (const i of g) {
									console.log(i);
								}
							
						
							
								1
								2
								3
							
						

Nützliche Array-Funktionen

Aufgabe: Schreibe ein Programm, das den Gesamtumsatz aus dem ersten Halbjahr berechnet
							
								const revenues = [
									{month: 0, revenue: 1000},
									{month: 1, revenue: 2000},
									{month: 2, revenue: 3000},
									{month: 3, revenue: 4000},
									{month: 4, revenue: 5000},
									{month: 5, revenue: 6000},
									{month: 6, revenue: 6000},
									{month: 7, revenue: 5000},
									{month: 8, revenue: 4000},
									{month: 9, revenue: 3000},
									{month: 10, revenue: 2000},
									{month: 11, revenue: 1000}
								];
								
								let totalRevenue = 0;
								
								/* ... */
								
								console.log(totalRevenue);
							
						
Lösung
							
								totalRevenue = revenues
									.filter((revenue) => revenue.month < 6)
									.map((revenue) => revenue.amount)
									.reduce((sum, amount) => sum + amount, 0);
							
						
							
								21000
							
						
							
								const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
								
								a.filter((e) => e > 2); // Filterung
								a.map((e) => e * 2); // Umformung
								a.reduce((p, e) => p + e, 0); // Zusammenrechnung / Reduzierung
								a.some((e) => e > 5); // true wenn min. ein Element die Bedingung erfüllt
								a.every((e) => e > 5); // true wenn alle Elemente die Bedingung erfüllen
							
						

Map

  • auch als "assoziatives Array" bezeichnet
  • iterierbare Liste
  • ideal zur Assoziation von z.B. Hashes zu Objekt-Instanzen o.ä.
  • nutzbar ab es6 / es2015
  • löst herkömmliche Objekte als Hash-Map ab
							
								const m = new Map();
								
								m.set("a", 0);
								m.set("a", 1);
								m.set("b", 2);
								
								m.has("c"); // => false
								
								m.delete("b");
								
								console.log(m.get("a")); // => 1
							
						
							
								m.forEach((k, v) => console.log(k, v)) // => "a" 1 ...
								
								for (const entry of m.entries()) {
									console.log(entry); // => ["a", 1] ...
								}
								
								for (const key of m.keys()) {
									console.log(key); // => "a" ...
								}
								
								for (const value of m.values()) {
									console.log(value); // => "1" ...
								}
							
						

Set

  • enthält nur einzigartige Werte
  • iterierbare Liste
  • ideale Struktur, wenn einzelne Werte einzigartig sein sollen
  • nutzbar ab es6 / es2015
  • löst individuelle Lösungen mittels Arrays und Array.prototype.indexOf o.ä. ab
							
								const s = new Set();
								
								s.add("a");
								s.add("a");
								
								s.has("c"); // => false
								
								s.delete("a");
								
								console.log(s.size); // => 0
							
						
							
								s.forEach((k, v) => console.log(k, v)) // => "a" "a" ...
								
								for (const entry of m.entries()) {
									console.log(entry); // => ["a", "a"] ...
								}
								
								for (const key of m.keys()) {
									console.log(key); // => "a"...
								}
								
								for (const value of s.values()) {
									console.log(value); // => "a" ...
								}
							
						

DOM-Events

  • funktionale Ausführung von Code
  • Trigger können Klicks, Scrollen, Tastendrücke, Änderungen der Fenstergröße usw. sein
							
								function doSomething(e) {
									console.log("capture");
								}
								
								function click(e) {
									console.log("bubble");
								}
								
								div.addEventListener("click", doSomething, true);
								
								button.addEventListener("click", click, false /* default */);
							
						
							
								capturing
								bubble
							
						

Aufgabe: Erweitere das vorherige Beispiel so, dass das div-Element einen weiteren Click-Handler bekommt, der den Text "bubble2" anzeigt, nachdem "bubble" in der Konsole geloggt wurde.

Erwartete Ausgabe:

							
								capturing
								bubble
								bubble2
							
						

Lösung:

							
								function doSomething(e) {
									console.log("capture");
								}
								
								// neu
								function doSomething2(e) {
									console.log("bubble2");
								}
								
								function click(e) {
									console.log("bubble");
								}
								
								div.addEventListener("click", doSomething, true);
								div.addEventListener("click", doSomething2, false); // neu
								
								button.addEventListener("click", click, false /* default */);
							
						
Die Bubble-Phase stoppen
							
								function click(e) {
									e.stopPropagation()
								}
							
						

Aufgabe: Ändere das Ergebnis des vorherigen Beispiels, sodass die Bubble-Phase auf Ebene des button-Elements gestoppt wird. Was ändert sich in der Ausgabe?

Lösung:

							
								function doSomething(e) {
									console.log("capture");
								}
								
								function doSomething2(e) {
									console.log("bubble2");
								}
								
								function click(e) {
									e.stopPropagation();
									console.log("bubble");
								}
								
								div.addEventListener("click", doSomething, true);
								div.addEventListener("click", doSomething2, false);
								
								button.addEventListener("click", click, false /* default */);
							
						
Events programatisch triggern:
							
								button.dispatchEvent(new Event("click"));
							
						
Events sind synchron:
							
								doSomethingElse();
								
								button.dispatchEvent(new Event("click"));
								
								doAnotherThing(); // wird erst ausgeführt, wenn zuvor alle Event-Handler fertig sind
							
						

Strings

							
								const empty1 = "";
								const empty1 = '';
								
								const s1 = 'abc';
								const s2 = "abc";
								
								const s3 = 'a"b"c';
								const s4 = "a\"b\"c";
								
								const s5 = 'a\'b\'c';
								const s6 = "a'b'c";
								
								const s7 = `a'b'"c"`;
							
						
Strings zusammenfügen
							
								const age = 10;
								
								// Konkatenation
								const s = "My age is " + number + "!";
								
								// Interpolation
								const s = `My age is ${number}!`;
								
								// => "My age is 10!"
							
						
Template Strings
							
								function sayHello() {
									return "Hello";
								}
								
								let interpolation = `${sayHello()}`;
								
								// => "Hello"
								
								interpolation = `5 + 5 = ${5+5}`;
								
								// => "5 + 5 = 10"
							
						
lit
							
								return html`
hello world!
`
							
								function myTag() {
									console.log(arguments);
								
									// return 5;
									// return true;
									return "abc";
								}
								
								let interpolation = myTag`a ${5} b ${6} c`;
								
								// => "abc"
							
						
							
								Arguments(3) [Array(3), 5, 6]
									0 : (3) ['a ', ' b ', ' c', raw: Array(3)]
									1 : 5
									2 : 6
							
						
Aufgabe

Schreibe ein Tag, welches die Zahlen aus den dynamischen Ausdrücken zusammenrechnet (strings.js)

Lösung
							
								function add(parts, ...numbers) {
									return numbers.reduce((p, c) => p + c, 0);
								}
								
								let sum = add`a ${5} b ${6} c`;
								
								console.log(sum);
								
								// => 11
							
						
Rest-Operator
							
								function add(...numbers) {
									return numbers.reduce((p, c) => p + c, 0);
								}
								
								add(1, 2, 3); // => 6
							
						
							
								const o1 = {a: 1, b: 2};
								const o2 = {x: 5, y: 6, ...o1};
							
						
							
								const a1 = [4, 5, 6];
								const a2 = [1, 2, 3, ...a1];
							
						

Relevante Browser-APIs

window
							
								window.innerWidth; // innere Auflösung des sichtbaren Fensters
								window.innerHeight;
								window.outerWidth; // äußere Auflösung des Browserfensters inklusive Adresszeile usw.
								window.outerHeight;
								
								window.addEventListener(); // Events an das äußerste "Dach"-Element hängen
							
						
location
							
								location.href; // aktuelle URL
								location.host; // aktuelle Domain, z.B. "www.google.de"
								location.protocol; // aktuelles Protokoll, z.B. "https:"
							
						
Navigator
							
								navigator.language; // bevorzugte Browsersprache, z.B. "de"
								navigator.userAgent; // z.B. "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) [...]"
								navigator
									.mediaDevices
									.getDisplayMedia(); // Fordere Erlaubis zum Streamen von Anwendungen oder Bildschirmen an
							
						
Document
							
								document.body; // <body>-Element der Seite
								document.activeElement; // aktives Element der Seite, welche gerade den Fokus besitzt
								document.cookie; // für die aktuelle Seite gesetzte Cookies
								document.execCommand("copy"); // Texte in die Zwischenablage zu schreiben
								document.createElement("div"); // erzeuge neues DOM-Element mit angegebenen Tag
							
						
LocalStorage
							
								// auf 5MB Daten pro Domain begrenzt
								localStorage.getItem(key); // Element holen
								localStorage.setItem(key, value); // Element setzen
								localStorage.removeItem(key); // Element entfernen
							
						

CSS

Warum CSS?

  • Erhöhte Zugänglichkeit des Inhalts
  • Wiederverwendbarkeit

Syntax

							
								selector [, selector2, selector3, ...] {
									property1: value;
									property2: value;
								}
							
						

Beispiel

							
								html, body {
									width: 100%;
									height: 100%;
									margin: 0;
								}
							
						

Selektoren

							
								/* Elemente */
								html, body {...}
								
								/* IDs */
								#my-id {...}
								
								/* Klassen */
								.my-class {...}
								
								/* Pseudo-Klassen */
								div:hover {...}
								
								/* Attribute */
								div[my-attribut="abc"] {...}
								
								/* Kombinationen */
								div.my-class#my-id {...}
							
						

Variablen

							
								html {
									--my-background-color: #abcdef;
								}
								
								html div {
									background-color: var(--my-background-color, white);
								}
							
						

Das display-Attribut

block

  • untereinander angeordnet
  • nehmen sich die volle Breite
  • width und height möglich

inline

  • Standard bei Custom Elements
  • nur so groß wie der content
  • width und height nicht möglich
  • nebeneinander angeordnet

inline-block

  • Mischform von block und inline
  • nur so groß wie der content
  • width und height möglich
  • nebeneinander angeordnet

none

  • unsichtbar
  • Platz wird nicht länger beansprucht, so als wenn das Element nicht im DOM wäre

Das position-Attribut

static

  • Standard
  • Element wird am Ort der Verankerung im HTML dargestellt

relative

  • Verschiebung zur eigentlichen static-Position, z.B. mittels left: 20px

absolut

  • relative Verschiebung zum nächsten nicht-static-Parent (sonst window)

Aufgabe

Implementiere folgendes Beispiel-Menü

Lösung

							
								.menu-item {
									display: inline-block;

									position: relative;
								}

								.menu-item .children {
									display: none;

									position: absolute;
								}

								.menu-item:hover .children {
									display: block;
								}
							
						
							
								
							
						

Viewport

							
								<!DOCTYPE html>
								<html>
									<head>
										<meta charset="utf-8"></meta>
										<meta
											name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
										>
									</head>
									<body>
										...
									</body>
								</html>
							
						

Ein Muss für alle responsiven Seiten

							
								npm install -g http-server
								cd pfad/zum/projekt
								http-server --port 8080
							
						

Einheiten

							
								div {
									width: 1px; /* Pixel */
									width: 1pt; /* Points */
									width: 1em; /* Breite des "M" in der Schriftgröße des Elements */
									width: 1rem; /* Breite des "M" in der Schriftgröße des HTML-Elements */
									width: 1vw; /* Prozent der View Width */
									width: 1%; /* Prozent des Elternelements */
									
									width: 5fr; /* Fraction (nur im CSS Grid verfügbar) */
								}
							
						

Aufgabe

Größeneinheiten in einer HTML-Datei ausprobieren

CSS Flexbox

							
								.container {
									display: flex; /* soll ein flexibler Container sein */
									display: inline-flex;
								
									flex-direction: column; /* Kinder spaltenweise anordnen */
									flex-direction: row; /* Kinder zeilenweise anordnen */
								
									align-items: center;
									justify-content: center;
								
									gap: 20px; /* Abstände zwischen den Achsen */
								}
								
								.child {
									flex-grow: 1; /* Anteiliger Platz, wenn mehr als nötig da ist */
									flex-shrink: 1; /* Anteiliger Platz, wenn weniger als nötig da ist */
									flex-basis: 20px; /* Grundlage zur Berechnung der finalen Größe */
									order: 1; /* Erlaubt die Darstellung in anderer Reihenfolge als durch das HTML vorgegeben */
								}
							
						

Aufgabe

Mit CSS Flexbox rumspielen

CSS Grid

Terminologie

							
								.grid {
									display: grid; /* inline-grid; */
					
									grid-template-columns: 1fr 1fr 1fr;
									grid-template-rows: 1fr 1fr;
									
									gap: 5px 10px;
								}
							
						
							
								<div class="grid">
									<span>A</span>
									<span>B</span>
									<span>C</span>
									<span>D</span>
									<span>E</span>
									<span>F</span>
								</div>
							
						
							
								.grid {
									grid-template-columns: [left] 1fr [column1] 1fr [column2] 1fr [right];
									grid-template-rows: [top] 1fr [row1] 1fr [bottom];
								}
								
								.a {
									grid-column: 2 / span 2;
									grid-row: 1;
								}
								
								.b {
									grid-column: left / column2;
									grid-row: 2;
								}
							
						
							
								<div class="grid">
									<span class="a">A</span>
									<span class="b">B</span>
								</div>
							
						
							
								.grid {
									grid-template-areas:
										"header header header"
										"content content content";
								}
					
								.a {
									grid-area: header;
								}
					
								.b {
									grid-area: 2 / 1 / 2 / 3; /* start-row / start-column / end-row / end-column */
										/* alternativ auch Namen der Lines möglich */
								}
							
						

Aufgabe

Implementiere folgendes Design:

Lösung

							
								.grid {
									display: grid;
									
									grid-template-rows: 1fr 5fr 1fr;
									
									gap: 2px;
					
									grid-template-areas:
										"navigation header header header"
										"navigation content content content"
										"footer footer footer footer"
								}
					
								.navigation {
									grid-area: navigation;
								}
					
								.header {
									grid-area: header;
								}
					
								.content {
									grid-area: content;
								}
					
								.footer {
									grid-area: footer;
								}
							
						
							
								<div class="grid">
									<div class="area navigation">Navigation</div>
									<div class="area header">Header</div>
									<div class="area content">Content</div>
									<div class="area footer">Footer</div>
								</div>
							
						

Platzierug des Inhalts

							
								.grid {
									align-items: center;
									justify-items: center;
								}
							
						

Media Queries

  • "Medienabfragen"
  • Styles auf Basis von Eigenschaften des Ausgabemediums
  • Große Bildschirme, kleine Bildschirme, Printmedien...
  • Hinweis: Sollten immer als letztes (unten) in einem Stylesheet sein

Syntax

							
								@media screen and (min-width: 640px) and (max-width: 1024px) {
									.navigation {
										display: none;
									}
								}
							
						

Bedeutung: Wenn die Seite auf einem Display angezeigt wird, welches zwischen 640px und 1024px in der Breite anzeigen kann, dann blende die Navigation aus

Werte für die Ausgabemedien (optional, Standard ist "all")

  • all
  • screen
  • print

Werte für die Eigenschaften mit Typ und Erklärung (kleiner Auszug)

  • width (Number, Breite)
  • height (Number, Höhe)
  • orientation (portrait | landscape, Quer- oder Hochformat)
  • hover (none | hover, Hat Zeiger zum Hovern)
  • forced-colors (none | active, erzwungene Farbpalette z.B. bei Sehbehinderung)

Aufgabe

Entwerfe eine kleine Seite mit @media und zeige ein beliebiges Element ab einer bestimmten Höhe oder Breite an (die Größe des Displays kann dynamisch mit den Entwicklertools im Gerätesimulator geändert werden)

Aufgabe

Passe die Vorlage "media-queries-responsive-layout.html" mit @media-Queries so an, dass das Menü links ab einer maximalen Breite von 480 Pixel und kleiner nach links oben in die Ecke minimiert wird und nur per Hover wieder 50% Breite und 100% Höhe bekommt

Lösung

							
								@media screen and (max-width: 480px) {
									.menu {
										grid-area: unset;
					
										position: absolute;
									}
									
									.menu:hover {
										height: 100%;
										width: 50%;
									}
					
									.grid {
										grid-template-columns: 0fr 5fr;
									}
								}
							
						

Transform

  • rein optisch, d.h. "physische" Größe bleibt erhalten
  • eine Art "Shader" auf Grafikebene
  • Effekte wie Drehung, Vergrößerung, Verschiebung usw.

Syntax

							
								.foo {
									transform: translateX(20px) translateY(20px);
									transform-origin: left;
								}
							
						

Mögliche Effekte

  • translate
  • rotate
  • scale
  • scew

Die meisten besitzen zusätzlich spezifischere X-, Y- oder 3D-Versionen

Aufgabe

Probiere die genannten Transform-Effekte in einer beliebigen Seite aus

Transition

  • Elemente können "weich" verändert werden
  • Mögliche Attribute, deren Änderungen animiert werden können sind z.B. width, height, transform, color und viele weitere
  • Das Attribut display lässt sich jedoch nicht animieren

Syntax

							
								.foo {
									width: 100%;
									background-color: red;
									/* transition: [Attribut] [Dauer] [Zeitfunktion] */
									transition: width 1s ease-out;
								}
					
								.foo:hover {
									width: 50%;
								}
							
						

Beispiel

Animation

  • Es können zusätzlich eigene Animationen entworfen werden
  • Definition per Keyframes

Syntax

							
								.foo {
								  animation-duration: 3s;
								  animation-name: slidein;
								}
								
								@keyframes slidein {
								  from {
									margin-left: 100%;
									width: 300%;
								  }
								
								  to {
									margin-left: 0%;
									width: 100%;
								  }
								}
							
						

Alternative Syntax

							
								.foo {
								  /* ... */
								}
								
								@keyframes slidein {
								  1% {
									margin-left: 100%;
								  }
								
								  100% {
									margin-left: 0%;
								  }
								}
							
						

Beispiel

3D Transform

  • Neben den gewohnten X- und Y-Achsen (Breite und Höhe) gibt es auch noch die Z-Achse
  • Bewegung entlang der Sichtachse, d.h. zum bzw. vom Betrachter weg
  • Perspektivische Effekte möglich

Beispiel

Bedingungen / Optionen

							
								.container {
									perspective: 1000px; /* "Entfernung" des Betrachters */
								
									transform-style: preserve-3d; /* oder "flat" */
								}
								
								.element {
									backface-visibility: hidden; /* oder "visible" */
								}
							
						

Beispiel

Aufgabe

Implementiere einen drehenden Würfel (Vorlage "templates/008-css/css-3d-cube.html")

Web Components

Was sind Web Components?

Vorteile

  • Entwicklung eigener HTML-Elemente, welche sich nahtlos neben nativen Elementen nutzen lassen
  • Hohe Wiederverwendbarkeit in mehreren Projekten
  • Keine Libraries oder Frameworks notwendig, um eigene Elemente zu entwickeln
  • Kapselung des Scopes sowohl von JS (Variablen...) als auch CSS (id, class...)

Ansätze in der Vergangenheit und Alternativen heute

jQuery

https://api.jqueryui.com/accordion/#entry-examples
							
								
							
						

angular

							
								
							
						

Aufbau einer Web Component

							
								
							
						
							
								
							
						

Light DOM vs Shadow DOM

Suche von Elementen im Light- und Shadow DOM

							
								
							
						

Auf Attribute reagieren

							
								
							
						
							
								
							
						

Aufgabe

Lasse die eigene Komponente "hello-sayer" auf eine Änderung des "name"-Attributs reagieren, indem der dort eingetragene Name in der Komponente angezeigt wird (Beispiel: name="Joe" => Hello Joe!)

(/examples/templates/web-components/hello-sayer.html)

Musterlösung

							
								
							
						

Elemente aus dem LightDOM platzieren mit <slot>

							
								
							
						

Beispiel

							
								
							
						

Mehrfache benannte <slot>s

							
								
							
						

Beispiel

							
								
							
						

Aufgabe

Entwerfe eine CardComponent (Vorlage "card-component.html") in folgendem Stil und nutze dabei sinnvolle <slot>s:

							
								
							
						

Styling einer Web Component

Ein :host-Block mit einem display gehört zum guten Ton!

							
								
							
						

Innerhalb einer Komponente können beliebige Styles für verschiedene Selektoren verwendet werden

							
								
							
						

Aufgabe

Welche Wechselwirkungen haben Styles zueinander, die innerhalb und außerhalb einer Komponente definiert sind? (Vorlage "web-component-styles.html")

Einige globale Styles werden dennoch vererbt:

  • Sämtliche Styles, die standardmäßig einen Wert von inherit besitzen (z.B. color oder font-family)
  • Eigene CSS-Variablen wie z.B. --my-custom-color

Aufgabe

Passe das vorherige Ergebnis (Vorlage "web-component-styles.html") so an, dass ein beliebiger Style per CSS-Variable von außen gesetzt werden kann (z.B. color oder background-color)

Erinnerung CSS-Variablen:

							
								/* Definition */
								html {
									--my-variable: #abcdef;
								}
								
								/* Usage */
								.my-class {
									background-color: var(--my-variable, white);
								}
							
						

In der Anfangszeit von Web Components wurde oft folgendes Muster zur Individualisierung verwendet:

							
								/* Global Scope */
								html {
									--my-border-color: #abcdef;
									--my-border-width: 1px;
									--my-margin-top: 1rem;
									--my-margin-bottom: 1rem;
								}
								
								/* Inside Web Component */
								.container {
									border-color: var(--my-border-color, white);
									border-width: var(--my-border-width, 1px);
									margin-top: var(--my-margin-top, 0.5rem);
									margin-bottom: var(--my-margin-bottom, 0.5rem);
								}
							
						

Eleganteres Stylen von Web Components mittels part

							
								/* Global Scope */
								my-component::part(inner) {
									border-color: yellow;
									border-width: 2px;
								}
								
								/* Inside Web Component */
								

Aufgabe

Passe die CardComponent aus einer vorherigen Übung an (Vorlage "card-component.html"), sodass man Titel und Inhalt einfach von außen per parts stylen kann.

parts von eingebetteten Web Components exportieren und verfügbar machen

							
								
								
Inner Text

Aufgabe

Passe das Beispiel (Vorlage "exportparts.html") entsprechend an, sodass von außen sowohl der Inner Text als auch der Outer Text stylebar sind

Stylen von LightDOM-Kindern

Web Components können direkte Kinder, die via slot eingefügt werden, stylen. Hierfür gibt es den Pseudo-Elemente-Selektor ::slotted([selector]), welcher Elemente selektiert, die in einem Slot stecken:

							
								/* Inside Web Component */
								::slotted(*) { /* Selectors like elements, #id or .class */
									font-style: italic;
								}
							
						

Aufgabe

Definiere mit dem Pseudo-Elemente-Selektor ::slotted() Styles, sodass per slot eingefügte Elemente mit der Klasse red einen roten und Elemente mit der Klasse blue einen blauen Hintergrund haben. Die Schriftart soll jeweils weiß sein (Vorlage "slotted.html")

Events in Web Components

  • Events können an einer Web Component selbst oder innerhalb des Shadow Dom dispatcht werden
  • Das Shadow Root der Komponente stellt eine Art "gläserne Decke" dar
  • Standard-Events der UI wie click bubbeln durch das Shadow Dom nach oben durch
  • Ein CustomEvent muss entsprechend konfiguriert werden
							
								button.dispatchEvent(new CustomEvent(
									"tabactivated", // Name des Events, frei wählbar
									{
										detail: "any data", // beliebige Daten wie Strings, Zahlen, Booleans usw.
										bubbles: true, // bestimmt, ob das Event durch alle Eltern nach oben steigt
										cancelable: true, // Abbrechbar per event.preventDefault()
										composed: true // durch eventuelle Shadow Root steigen, falls vorhanden.
									}
								));
							
						

Aufgabe

Entwickle eine Tab-Komponente (Vorlage "tab-componenmt.html"), welche beim Click auf einen Registerreite / Tab ein Event feuert, welches die Außenwelt über das gerade aktivierte Tab (tab-id) informiert

Lösung

							
								this.root.innerHTML = `
									<style>
										::slotted(div) {
											display: none;
										}
						
										::slotted(div.active) {
											display: block;
										}
									</style>
									
									<div><slot name="tab"></slot></div>
									<div><slot name="content"></slot></div>
								`;
							
						
							
								connectedCallback() {
									const firstContent = this.querySelector("div");
							
									firstContent.classList.add("active");
							
									this.addEventListener("click", (e) => {
										if (e.target.tagName === "SPAN" && e.target.hasAttribute("tab-id")) {
											const oldContent = this.querySelector("div.active");
											const newContent = this.querySelector(`[tab-id="${e.target.getAttribute("tab-id")}"]`);
							
											oldContent.classList.remove("active");
											newContent.classList.add("active");
							
											this.dispatchEvent(new CustomEvent("tabclick", {composed: true, bubbles: true, detail: e.target.getAttribute("tab-id"), cancelable: true}));
										}
									});
								}
							
						

NodeJS

Was ist NodeJS?

  • "JavaScript auf dem Server"
  • basiert auf der V8-Engine von Google
  • Laufzeitumgebung für JavaScript mit eigener API

Überblick über die Node-API

Die API ist in viele verschiedene Module aufgeteilt

fsDateisystem
cryptoKryptografie
zlibDatenkompression
httpWebserver
netNetzwerk (TCP)
tlsVerschlüsseltes Netzwerk (TCP)

Aufruf eines Programmes in der Kommandozeile

							
								node [Node-Parameter] path/to/script.js [Anwendungsparameter]
							
						

z.B.

							
								node --inspect index.js --port 3000
							
						

Einbinden von Modulen

							
								const fs = require("fs");
							
						

Aufbau eines Moduls

							
								module.exports = {
									foo: 5,
									sayHello: function(name) {
										return "Hello " + name + "!";
									}
								}
							
						

oder

							
								exports.foo = 5;
								exports.sayHello = function(name) {
									return "Hello " + name + "!";
								};
							
						

Aufgabe

Schreibe ein Modul, welches eine Square-Klasse zur Verfügung stellt, mit welchem man den Umfang und den Flächeninhalt eines Rechtecks berechnen kann:

							
								const Square = require("./square");
								
								const square = new Square(2, 3);
								
								console.log(square.getArea()); // => 6
								console.log(square.getPerimeter()); // => 10
							
						

Module für die Kommandozeile

package.json

							
								npm init -y
							
						
							
								{
									...
									"bin": {
										"my-command": "./index.js"
									}
								}
							
						

index.js

							
								#! /usr/bin/env node
								
								console.log("Hello world from the command line!");
							
						

Installation

							
								npm install -g path/to/project
							
						
z.B.
							
								npm install -g .
							
						

Ausführung

							
								my-command
							
						

Kommandozeilenparameter

							
								console.log(process.argv);
							
						

angelehnt an die Parameter aus der Programmiersprache c:

							
								int main(int argc, char *argv[])
							
						

Aufgabe

Mache das vorherige Programm für die Konsole aufrufbar, sodass die Länge und Breite für das Rechteck per Konsolenargumente übergeben werden können und der Flächeninhalt ausgegeben wird:

							
								my-command 2 3
							
						

Buffer

Array-artige Struktur für rohe Bytes

							
								const b1 = Buffer.from("abc"); // bevorzugt
								const b2 = Buffer.alloc(100, 0); // Länge mit initialen Daten
								const b3 = Buffer.allocUnsafe(100); // Länge ohne initiale Daten
								const b4 = new Buffer("abc"); //  offiziell nicht empfohlen
								
								console.log(b1); // => <Buffer 61 62 63>
								console.log(b1.toString("hex")); // => "616263"
							
						

Mit Buffern arbeiten:

							
								const a = Buffer.from("a") + Buffer.from("b");
								const b = Buffer.concat([Buffer.from("a"), Buffer.from([98])]);
								const c = b.toString()
								
								console.log(a); // => ab
								console.log(b); // => <Buffer 61 62>
								console.log(c); // => ab

								for (const byte of a) {
									console.log(byte); // => 97 98
								}
							
						

Aufgabe

Implementiere eine XOR-Verschlüsselung (^-Operator), sodass jedes Byte einer Original-Nachricht der Reihe nach mit dem entsprechenden Byte eines Schlüsselwortes verschlüsselt wird und gebe die verschlüsselte Nachricht als Text aus.

Aufgabe

Implementiere zur vorherigen Verschlüsselung eine Entschlüsselung.

Lösung

							
								const word = Buffer.from("abcdef");
								const key = Buffer.from("xyz");
								const encrypted = Buffer.alloc(word.length, 0);
								
								// encrypt
								for (let i = 0; i < word.length; i++) {
									encrypted[i] = word[i] ^ key[i % key.length];
								}
								
								// decrypt
								for (let i = 0; i < word.length; i++) {
									word[i] = encrypted[i] ^ key[i % key.length];
								}
								
								console.log(word.toString()); // => "abcdef"
							
						

Streams

Konstanter Fluss von (meist) großen Datenmengen in kleinen Paketen

Gängige Typen von Streams

  • Standard In / Out
  • HTTP-Requests / -Responses
  • TCP-Sockets
  • Lese- und Schreib-Streams auf Dateien
  • Große Datenbank-Ergebnisse mittels Cursor
							
								const readableStream = fs.createReadStream("./source.txt");
								const writableStream = fs.createWriteStream("./target.txt");
								
								readableStream.on("data", (data /* Buffer */) => {
									// process data here
									writableStream.write(data)
								});
								
								readableStream.on("end", (data) => {
									// stream has finished, e.g. EOF (end of file)
								});
								
								// alternatively pipe content
								readableStream.pipe(writableStream);
							
						

Aufgabe

Lese eine beliebige Datei als Readable Stream, verschlüssele den Inhalt mit der XOR-Methode und schreibe die verschlüsselten Bytes in eine Zieldatei

Aufgabe

Ändere das Programm, sodass auf Konsolenebene ein Pipe (|) ermöglicht wird:

							
								echo "Hello World!" | node index.js
							
						

Nutze dafür die implizit vorhandenen Streams von Standard In / Out:

							
								process.stdin; // Readable Stream
								process.stdout; // Writable Stream
							
						

Kryptographie

Mithilfe des nativen crypto-Moduls ist es möglich, moderne sichere kryptografische Verfahren zu verwenden:
							
								const crypto = require("crypto");
							
						
Hierzu nutzt NodeJS selber die weit verbreitete OpenSSL-Bibliothek
Einen MD5-Hash erzeugen:
							
								const crypto = require("crypto");

								const hash = crypto.createHash("md5");
								const md5 = hash.update("abcdefg")
									.digest(); // Buffer (default)
									// .digest("hex") // Hexadezimal
									// .digest("utf8") // Bytes forciert als Chars
								
								console.log(md5);
							
						

Aufgabe

Erzeuge jeweils den md5- und den sha256-Hash als Hex-Wert der beiden Texte in der Datei text-examples.txt und vergleiche jeweils ihren md5- und sha256-Hash miteinander

Zippen

Mit dem nativen Modul zlib ist es möglich, Kompression in seinen Anwendungen zu nutzen.
							
								const zlib = require("zlib");

								// Komprimieren
								zlib.deflate("abc" /* buffer | string */, (err, compressed) => {});
								
								// Dekomprimieren
								zlib.inflate(compressed /* buffer */, (err, decompressed) => {});
								
								// Als Stream
								const zipper = zlib.createDeflate();
								const unzipper = zlib.createInflate();
								
								source.pipe(zipper).pipe(target);
								source.pipe(unzipper).pipe(target);
							
						

Aufgabe

Nutze die einfachen Varianten zlib.deflate und zlib.inflate, um einen beliebigen Text zu komprimieren und anschließend wieder zu dekomprimieren

Aufgabe

Nutze die Stream-Varianten zlib.createDeflate und zlib.createInflate, um jeweils ein getrenntes Programm für die Kompression und Dekompression mit process.stdin und process.stdout zu machen, sodass in der Konsole folgende Nutzung möglich ist:

							
								echo "abc" | node compress.js | node decompress.js
								cat file.txt | node compress.js > file.zipped
								cat file.zipped | node decompress.js > file.txt
							
						

TCP-Verbindungen

  • Bidirektionale Verbindung
  • Duplexfähig
  • Besteht immer aus Server- und Client-Socket
Mit dem nativen Modul net ist es möglich, eine TCP-Verbindung aufzubauen
							
								/* Server */
								const net = require("net");

								const server = net.createServer((socket) => {
								
									// new incoming connection
									socket.on("data", (data) => {
										// data is a buffer
									});
								
									socket.write("Hello World!");
								});
								
								server.listen(3000);
							
						
							
								/* Client */
								const net = require("net");

								const socket = net.connect(3000, () => {
									// 'connect' listener
								});
								
								socket.on("data", (data) => {
									// data is a buffer
								});
							
						

Aufgabe

Implementiere die Clientlogik in der tcp-client.js, um dem Server ein "Hello Server!" zurück zu senden

Aufgabe

Sende eine festgelegte Datei vom Server zum Client. Der Server liest die Datei ein und sendet die Inhalte zum Client, welcher diese wiederum in eine Zieldatei schreibt. Eventuell darf auch noch eine Kompression mittels Zip / Deflate implementiert werden.

Kurze Wiederholung des HTTP-Protokolls

Request

							
								GET / HTTP/1.1
								Host: localhost:3456
							
						

Response

							
								HTTP/1.1 200
								Content-Type: text/plain
								Content-Length: 12
								
								Hello World!
							
						

Aufgabe

Implementiere mithilfe des TCP-Sockets einen minimalen HTTP-Server, welcher Hello <name> als Text zurück gibt, wobei <name> aus dem Query-Parameter gelesen werden soll

HTTP-Server

							
								const http = require("http");
						
								const server = http.createServer((req, res) => {
									res.writeHeader(200, {
										"Content-Type": "text/plain", /* Put Content-Type here */
									});
									
									console.log(req.rawHeaders);
									console.log(req.method);
									console.log(req.url);
									
									res.write(/* response content */);
									res.end(); /* important if no Content-Length is specified */
								});
								
								server.listen(3456);
							
						

Aufgabe

Experimentiert mit verschiedenen Möglichkeiten von Content-Length und dem Vorhandensein von res.end(), mit längerer oder kürzerer Content-Length als der tatsächliche Body oder teilt den Body in zwei res.write() und verzögert künstlich mit einem setTimeout() den zweiten Teil und schaut euch im Browser bei den Entwickler-Tools die Timings der Response an.

Worker Threads

Was sind Worker Threads?

Eine bidirektionale nachrichten-basierte Implementierung von Multithreading in NodeJS

Beispiel:

							
								// Main Thread
								const { Worker } = require('worker_threads');

								const data = 'some data';
								
								const worker = new Worker("path/to/worker.js", { workerData: data });
								
								worker.on('message', message => console.log('Reply from Thread:', message));
								
								// Worker Thread
								const { workerData, parentPort } = require('worker_threads');

								const result = workerData.toUpperCase();
								
								parentPort.postMessage(result);
							
						

Weitere nützliche Funktionen:

							
								// Main Thread
								worker.on('message', message => processMessage(message));
								worker.on('error', error => console.error(error));
								
								worker.postMessage({a: 1}); // send any data to the worker
								
								worker.terminate(); // manually terminate worker thread
								
								// Worker Thread
								parentPort.on('message', message => doSomething(message)); // receive messages from the main thread
								
								process.exit(0); // terminate self, optionally with exit code
							
						

Aufgabe

Nutze die in den examples vorhandene Funktion isPrime (010-nodejs/mt-main.js und 010-nodejs/mt-worker.js) und implementiere mit Hilfe der Worker Threads die Suche nach Primzahlen. Die gewünschte maximale Zahl, bis zu der die Primzahlen gesucht werden sollen als auch die Anzahl der Threads, mit der gleichzeitig gesucht werden soll, sollen möglichst einfach parametrisierbar sein (einfache Variable im Code genügt). Probiert verschiedene Thread-Anzahlen aus und vergleicht einmal die benötigten Zeiten.

Hinweise:

  • Bei der Suche bis zu 1.000.000 und 8 Threads soll der erste Thread von 0 bis 125.000 suchen, der zweite von 125.000 bis 250.000 usw.
  • Nutzt Promises, um auf alle Ergebnisse warten zu können. (s. Promise.all)
  • per workerData können auch Objekte übergeben werden
  • Die gefundenen Primzahlen können ausgegeben werden, müssen aber nicht. In erster Linie geht es darum, die Arbeit auf mehrere Threads aufzuteilen.