1
0
Fork 0
mirror of https://github.com/TheTaz25/denis.ergin.git synced 2025-07-08 07:38:46 +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>