1
0
Fork 0
mirror of https://github.com/TheTaz25/denis.ergin.git synced 2025-07-06 13:18:49 +00:00

Start with Web-API's, update page overview

This commit is contained in:
Denis Ergin 2025-01-20 21:21:28 +01:00
parent 5958201436
commit 0218f9a613
8 changed files with 562 additions and 0 deletions

View file

@ -0,0 +1,170 @@
<section>
<section>
<h2>Files API</h2>
</section>
<section>
<p>Als wir über Eingabemöglichkeiten in HTML mit dem <code>&lt;input /&gt;</code> Element betrachtet haben, haben wir auch den <code>type="file"</code> kennen gelernt.</p>
<p>Sobald der Nutzer eine Datei ausgewählt hat, können wir im dazugehörigen <em>change-Eventlistener</em> auf diese Datei und deren Inhalt zugreifen. Wir müssen also nicht unbedingt den Inhalt an einen Server senden.</p>
<p>Es ist aber auch möglich, mit der normalen DOM-API auf die Dateien zuzugreifen.</p>
</section>
<section>
<p>Sehen wir uns einmal an, was wir beim <code>change-event</code> so erhalten.</p>
<pre class="js"><code data-trim data-line-numbers is:raw>
const input = document.getElementById('file-input');
input.addEventListener('change', onFile, false);
function onFile (event) {
// Alle selektierten Dateien befinden sich im Element in "files"
const selected = event.target.files;
console.log(selected);
}
</code></pre>
</section>
<section>
<p>Das Objekt das wir hier erhalten stammt von der besonderen Klasse <em>FileList</em>. Das Objekt verhält sich im weitesten Sinne wie ein Array. Also ist auch der Zugriff mittels der Array-Syntax auf einzelne Elemente möglich.</p>
<p>Sehen wir uns mal den Inhalt genauer an. Innerhalb einer <em>FileList</em> befinden sich <em>File</em> Objekte. Dort befinden sich ein paar Informationen zur ausgewählten Datei.</p>
</section>
<section>
<pre class="js"><code data-trim data-line-numbers is:raw>
function onFile (event) {
const file = event.target.files[0];
console.log(file);
}
</code></pre>
<p>Im Log finden wir folgendes:</p>
<pre><code data-trim data-line-numbers is:raw>
File {
lastModified: 1730637507228,
name: "datei.png",
size: 2583226,
type: "image/png"
}
</code></pre>
</section>
<section>
<p>Neben den Allgemeinen Informationen wie Dateiname, Dateigröße oder das letzte Änderungsdatum, bietet uns die darunter liegende Klasse "Blob".</p>
<p>Diese Klasse bietet uns ein paar Methoden um den Inhalt aus der Datei zu erhalten. Für einfache Text-Dateien gibt es hierfür die Convenience-Funktion <code>.text()</code>. Diese Funktion ist async, sprich muss mit einem <code>await</code> versehen werden.</p>
</section>
<section>
<p>Das Ergebnis der Funktion resultiert nach dem <code>await</code> den Textinhalt der Datei.</p>
<p>Bei anderen Dateien (z.B. Bilder) fällt ein wenig mehr Arbeit an.</p>
<p>Insgesamt ist es in JavaScript nicht notwendig den Inhalt aus einer Textdatei zu extrahieren; Bei einem Versand mit dem Form-Element sorgt der Browser dafür, dass der Inhalt korrekt übertragen wird.</p>
</section>
<section>
<p>Wollen wir aber den Inhalt der Datei noch in der Web-Ansicht editiert, betrachtet oder sonst in irgendeiner Art und Weise modifizieren, müssen wir - zumindest für Binär-Dateien wie Bildern - noch der Inhalt extrahiert werden.</p>
<p>Im Folgenden Beispiel anhand eines Bildes, betrachten wir 2 Möglichkeiten um den Inhalt vor dem Absenden anzuschauen.</p>
</section>
<section>
<p>Das Bild zur Vorschau darstellen</p>
<pre class="js"><code data-trim data-line-numbers="2-5|7,14|8|10|11-13" is:raw>
function onFile(event) {
const file = event.target.files[0];
const previewElement = document
.getElementById('preview');
if (file) {
const url = URL.createObjectURL(file);
preview.src = url;
preview.onload = function () {
URL.revokeObjectURL(url);
}
}
}
</code></pre>
</section>
<section>
<p>Was passierte:</p>
<ol>
<li>Wir entnehmen die "hochgeladene" Datei und besorgen uns das Element zur Preview</li>
<li>Um einen "Abbruch" abzuhandeln prüfen wir ob wirklich <code>File</code> vorhanden ist</li>
<li>Mit <code>URL.createObjectURL(file);</code> erstellen wir einen <em>internen</em> Browserlink</li>
</ol>
</section>
<section>
<ol start="4">
<li>Die resultierende URL können wir dem Preview-Element als <code>src</code> anhängen</li>
<li>Sobald das Bild geladen wurde, können wir mit der <code>onload</code> Funktion darauf reagieren.</li>
<li>Um den Speicher zu befreien soll man <code>URL.revokeObjectURL(url);</code> aufrufen.</li>
</ol>
</section>
<section>
<p>Variante 2: Den eigentlichen Datei-Inhalt "parsen" und die Binär-Daten zur Preview verwenden.</p>
<p>Hier ist etwas "mehr" Arbeit zu verrichten. Vorteil mit dieser Art und Weise ist aber, dass wir den Datei-inhalt direkt verfügbar haben und modifizieren können (Zumindest mit dem konkreten Wissen wie man das macht).</p>
</section>
<section>
<pre class="js"><code data-trim data-line-numbers="5-7|10|11|12" is:raw>
function onChange(event) {
const file = event.target.files[0];
const preview = document.getElementById('preview');
function parseImage(e) {
// ...
}
if (file) {
const reader = new FileReader();
reader.onload = parseImage;
reader.readAsArrayBuffer(file);
}
}
</code></pre>
</section>
<section>
<ol>
<li>Wir legen eine Funktion parseImage an, was die genau macht implementieren wir gleich</li>
<li>Wir legen eine neue <code>FileReader</code>-Instanz an</li>
<li>Der <code>reader</code> erwartet eine Funktion für ein <code>onload</code> Event, wir weisen ihr die <code>parseImage</code>-Funktion zu</li>
<li>Zuletzt rufen wir die Funktion <code>readAsArrayBuffer</code> der FileReader-Instanz auf. Bei Erfolg ruft diese die Funktion <code>parseImage</code> auf.</li>
</ol>
</section>
<section>
<pre class="js"><code data-trim data-line-numbers="2-3|5-7|9|10|12" is:raw>
function parseImage(e) {
const arrayBuffer = e.target.result;
const uint8Array = new Uint8Array(arrayBuffer);
const base64 = btoa(uint8Array.reduce((data, byte) =>
data + String,fromCharCode(byte), ''
);
const mimteType = file.type || 'image/jpeg';
const dataUrl = `data:${mimeType};base64,${base64}`;
preview.src = dataUrl;
}
</code></pre>
</section>
<section>
<ol>
<li>Das Resultat der Funktion <code>readAsArrayBuffer</code> erhalten wir in unserer Funktion im Event. Dieses nehmen wir und konvertieren es in ein Uint8Array</li>
<li>In den Zeilen 5 bis 7 konvertieren wir die Informationen in Characters und wandeln diese in einen Base-64 kodierten String um</li>
<li>Darauffolgend entnehmen wir den Bild-Typen (JPEG)</li>
<li>Wir fassen alle gesamelten Informationen in einen korrekt definierten String zur Bild-Darstellung zusammen</li>
<li>Zu guter letzt verwenden wir die Bildinformationen im <code>src</code>-Attribut des <code>img</code>-Elementes</li>
</ol>
</section>
<section>
<p>Aus dieser Position heraus haben wir alle Bildinformationen vorhanden und können mit diesen Arbeiten / modifizieren.</p>
<p>Wie wir diese Informationen modifizieren um das Bild zu modifizieren ist außerhalb des Scopes der Vorlesung.</p>
</section>
</section>

View file

@ -0,0 +1,242 @@
<section>
<section>
<h2>History API</h2>
</section>
<section>
<p>
Mit der History API können wir per JavaScript mit der Browser-History
interagieren.
</p>
<p>
Während sich der User durch verschiedene Dokumente klickt, wird jeder
Seitenbesuch in die History gelegt.
</p>
<p>
Durch die Buttons neben der URL-Bar interagiert der Nutzer mit der
Browser-History und springt darin vor und zurück.
</p>
</section>
<section>
<p>
Das <code>history</code> Objekt ist im Browser im globalen Scope verfügbar.
</p>
<p>
Es bietet einige Funktionen um zu anderen Seiten zu navigieren und die
bisherige history zu modifizieren.
</p>
</section>
<section>
<h3>history.back()</h3>
</section>
<section>
<p>
Mit dieser Funktion springen wir in der Browser-History einen Eintrag aus
dem aktuellen Tab zurück.
</p>
</section>
<section>
<h3>history.forward()</h3>
</section>
<section>
<p>
Mit dieser Funktion springt der Browser in der History um einen Eintrag
"nach vorne", sofern in der History nach vorne ein Eintrag vorhanden ist.
</p>
</section>
<section>
<h3>history.go(<i>delta</i>)</h3>
</section>
<section>
<p>
Mittels go können wir mehrere Einträge in der Browser-History
überspringen.
</p>
<p>
Mit dem <i>delta</i> Parameter können wir die Anzahl der Einträge bestimmen.
(Es sind auch negative Zahlen möglich)
</p>
</section>
<section>
<p>
Die Funktionen <code>back, forward &amp; go</code> sind <strong
>asynchron</strong
> und lösen <code>popstate</code> Events aus.
</p>
<p>
Dieser Umstand ist wichtig, wenn wir sogenannte <em
>Single State Applications</em
> entwickeln. Wir müssen dann in der Lage sein eine Navigation abzubrechen
und den Zustand in der UI zu aktualisieren.
</p>
</section>
<section>
<p>
Für den normalen Webentwickler ist dieser Umstand "weniger wichtig",
moderne Bibliotheken und Frameworks kapseln die Logik komplett ab.
</p>
<p>
Aber sehen wir uns das <code>popstate</code> Event kurz etwas näher an:
</p>
</section>
<section>
<pre
class="js"><code data-trim data-line-numbers is:raw>
// Das Event kommt am window an
window.addEventListener('popstate', (event) =&gt; {
console.log(`location: ${document.location}`);
// in event.state stehen ggf. weitere infos
console.log(`state: ${JSON.stringify(event.state)}`);
})
</code></pre>
<p>
Dieses Event ist für den Umstand wichtig, wenn der Nutzer mittels der
Browser-Navigation die Seite ändert.
</p>
</section>
<section>
<p>
Für den Fall das der User mit der Seite interagiert und eine Navigation
auslöst, können wir einfach das Event abfangen.
</p>
<pre
class="js"><code data-trim data-line-numbers is:raw>
// aTag ist nur ein generisches a-tag auf das ein User klickt
aTag.addEventListener('click', (event) =&gt; {
// Unterbricht die Event-Kette
// Verhindert dass der Browser die Seite wechselt
event.preventDefault();
// Ab hier können wir die UI aktualisieren
});
</code></pre>
</section>
<section>
<h3>history.pushState(<em>state, unused, url</em>)</h3>
</section>
<section>
<p>
Mit <code>pushState</code> fügen wir einen neuen Eintrag in der Browser-History
hinzu.
</p>
<p>
Der Browser wird nicht automatisch die neue URL laden, sondern dann wenn
es notwendig ist (z.B. wenn der Browser neu startet.). Wir können aber die
neue "gepushte" URL mit <code>history.go()</code> aufrufen.
</p>
</section>
<section>
<p>
Wir können nur URL's auf der gleichen Origin angeben, ansonsten wirft die
Funktion einen Error.
</p>
</section>
<section>
<h3>history.replaceState(<em>state, unused, url</em>)</h3>
</section>
<section>
<p>
Ähnlich zu <code>pushState</code>, modifiziiert diese Funktion die
Browser-History.
</p>
<p>
Anders aber ist aber, dass der aktuelle (oberste) History-Eintrag von
dieser Funktion überschrieben wird.
</p>
</section>
<section>
<p>
Besonders nützlich ist diese Funktionalität, wenn der User auf einer Seite
die URL mehrmals durch Aktionen ändert, die aber keinen Reload der Seite
triggern.
</p>
<p>Gängige Beispiele hierfür sind:</p>
<ul>
<li>Filtern von Suchergebnissen</li>
<li>Tab-Navigation innerhalb einer Seite</li>
</ul>
</section>
<section>
<h3>Die URL-Klasse</h3>
</section>
<section>
<p>
Die URL-Klasse ist hilfreiche um eine Strukturierte URL zu generieren.
</p>
<p>
Sie bietet viele Funktionen um eine URL durch verschiedene Teile zu
erweitern (Query-Parameter zum Beispiel)
</p>
</section>
<section>
<p>
Das URL-Objekt lässt sich folgendermaßen aus dem Konstruktor erstellen
</p>
<pre
class="js"><code data-trim data-line-numbers is:raw>
const baseUrl = "https://www.denis-ergin.de";
// Erstellt eine URL zu
// "https://www.denis-ergin.de/slides/javascript/00-intro"
const url = new URL("/slides/javascript/00-intro", baseUrl);
/**
* new URL(Pfad, BaseUrl);
*/
</code></pre>
<p>Diese URL kann z.B. auch für die History-API verwendet werden.</p>
</section>
<section>
<p>
Das resultierende URL Objekt hat nun Möglichkeiten auf bestimmte Teile
zuzugreifen und zu modifieren, sowie die komplette URL zu generieren.
</p>
<pre
class="js"><code data-trim data-line-numbers is:raw>
const baseUrl = "https://www.denis-ergin.de";
const url = new URL("/slides/javascript/00-intro", baseUrl);
console.log(
url.host, // Hostname + ggf Port
url.hostname, // z.B. google
url.origin, // scheme + domain + port
url.pathname, // Pfad ("/...")
url.search, // Der Query-String (kommt nach dem "?")
url.protocol, // Protokoll (":" inbegriffen)
);
</code></pre>
</section>
<section>
<p>
Wenn wir URL's modifizieren oder generieren müssen, sollten wir die
gegebene URL-Klasse verwenden, anstatt einen selbst-gebauten String zu
kreiieren.
</p>
<p>
Die URL-Klasse hat einen entscheidenden Vorteil: Potenziell "kritische"
Zeichen werden von der Klasse korrekt behandelt.
</p>
</section>
</section>

View file

@ -0,0 +1,21 @@
---
import Title from "./title.astro";
import HistoryApi from "./history.astro";
import FilesApi from './files.astro';
import IntersectionApi from './intersection.astro';
// import ProxyApi from './proxy.astro';
// import SelectionApi from './selection.astro';
// import ServiceWorker from './service-worker.astro';
// import SpeechApi from './speech.astro';
// import AnimationApi from './animations.astro';
import StorageApi from "./storage.astro";
---
<div class="slides">
<Title />
<HistoryApi />
<StorageApi />
<FilesApi />
<IntersectionApi />
</div>

View file

@ -0,0 +1,5 @@
<section>
<section>
<h2>Intersection-API</h2>
</section>
</section>

View file

@ -0,0 +1,90 @@
<section>
<section>
<h2>Storage API</h2>
</section>
<section>
<p>
Mit der Storage-API haben wir eine einfache aber wirkungsvolle Art, um Informationen zu speichern und später wieder abzufragen.
</p>
<p>
Dafür stehen uns 2 "dedizierte" Speicher-Orte zur Verfügung: Der sessionStorage und der localStorage.
</p>
<p>
Beide haben die gleichen Funktionen um Inhalte zu speichern, nur die "Speicherdauer" variiert.
</p>
</section>
<section>
<p>
Während wir im <code>localStorage</code> Inhalte auf "unbestimmte" Zeit speichern können, werden Daten im <code>sessionStorage</code> gelöscht, wenn der letzte Tab der Website geschlossen wird, auf dem die Daten gespeichert wurden.
</p>
<p>
Da beide Arten die gleiche API besitzen, betrachten wir nur die langfristige Variante zum Speichern von Daten.
</p>
</section>
<section>
<h3>Daten speichern</h3>
</section>
<section>
<p>
Daten im <em>storage</em> werden anhand eines <strong>Key-Value-Paares</strong> gespeichert. Das lässt uns mit der Gefahr im Fall der Fälle ungewollt Daten zu überschreiben. Einmal überschrieben können wir auch nicht mehr auf die Daten davor zugreifen.
</p>
<p>
Wichtig ist zu beachten, dass nur Strings gespeichert werden können. Wollen wir also JSON-Formattierte Daten speichern wollen, müssen wir diese vorher mit <code>JSON.stringify()</code> zu einem String umwandeln.
</p>
</section>
<section>
<pre
class="js"><code data-trim data-line-numbers is:raw>
const toSave = "Hallo TINF24BX!";
const key = 'greeting';
localStorage.setItem(key, toSave);
</code></pre>
</section>
<section>
<h3>Daten abfragen</h3>
</section>
<section>
<p>
Wir können die Daten aus dem storage mithilfe des Key's auch wieder abholen:
</p>
<pre class="js"><code data-trim data-line-numers>
const data = localStorage.getItem('key');
console.log(data);
</code></pre>
<p>
Auch hier gilt wieder: Wenn wir strukturierte Dateien im JSON-Format abgespeichert haben, so müssen die Daten auch wieder mit <code>JSON.parse()</code> umformattiert werden um diese zu nutzen.
</p>
</section>
<section>
<h3>Daten löschen</h3>
</section>
<section>
<p>Sollten wir Daten aus dem Storage löschen wollen, geht das ganz einfach mit dem <em>key</em> und der Funktion <code>localStorage.removeItem(<em>key</em>)</code>.</p>
<pre class="js"><code data-trim data-line-numbers is:raw>
const key = 'greeting';
let data = localStorage.getItem(key);
console.log(data); // Wir erwarten hier etwas...
localStorage.removeItem(key);
data = localStorage.getItem(key);
console.log(data); // undefined...
</code></pre>
</section>
<section>
<p>Wenn wir den gesamten Storage löschen möchten, gibt es die Funktion <code>localStorage.clear()</code>. Daraufhin werden alle gespeicherten <em>Key-Value</em>-Paare gelöscht.</p>
</section>
</section>

View file

@ -0,0 +1,3 @@
<section>
<h1>JavaScript Web-API's</h1>
</section>

View file

@ -118,3 +118,26 @@ Im letzten Abschnitt der HTML-Reihe widmen wir uns den ersten *Interaktiven* Ele
<div class="mb-1" />
<Card to="#" color="green">Cheat-Sheet</Card>
<Spacer />
## Einführung in CSS
Bis jetzt sehen die ersten Webseiten die wir gebaut haben sehr simpel aus. Mit den folgenden Slides sollen CSS und dessen Mechanismen näher gebracht werden.
### # CSS - Basics
Im folgenden Slide-Deck werden wir Erfahren, wie man mit CSS (HTML)-Elemente selektiert und was eigentlich genau hinter dem kompletten System steckt.
Unter anderem wird gezeigt was "Cascading" in "Cascading Style Sheets" bedeutet und wie wir überhaupt CSS in unsere Webseite einbinden können.
Dies wird alles als Grundlage dienen die wir in den darauffolgenden Aufgaben anwenden werden.
<Card to="/slides/css/00-intro" color="blue">Zu den Slides</Card>
<Spacer />
### # CSS - Erste Styles
Wir wissen nun wie wir gezielt einzelne Elemente in HTML stylen können. Nun lernen wir die entsprechenden Styling-Direktiven kennen um den visuellen Output zu modifizieren.
Den Beginn werden Farben machen und welche Möglichkeiten wir haben um Farben zu definieren. Danach werden wir uns mit den Möglichkeiten auseinander setzten, wie man Text auf seine verschiedenen Arten und Weisen styled.
Im weiteren Verlauf des Slidedecks schauen wir uns die Möglichkeiten an, wie man Elemente mit einer Rahmung (Border) versieht, als auch die Möglichkeiten Abstände zwischen Elementen zu modifizieren.
Alle vorgestellten Style's in Bezug auf Abstände werden mit der neueren "Inline-Block" Syntax beschrieben.
<Card to="/slides/css/01-basic-styles" color="blue">Zu den Slides</Card>

View file

@ -0,0 +1,8 @@
---
import Reveal from "../../../layouts/Reveal.astro";
import Slides from "../../../components/slides/javascript/09-web-api/index.astro";
---
<Reveal title="JavaScript Web-API Beispiele">
<Slides />
</Reveal>