Das WebKit im Schaufenster #
Eine Oberfläche für ein EULANDA-Plugin zu bauen fühlt sich an wie HTML schreiben - nur dass am
Ende kein nacktes Formular steht, sondern ein fertiger, themenfähiger, mehrsprachiger Dialog.
Das WebKit aus EulandaXtools liefert dafür einen ganzen Satz eul-*-Bausteine: Sie kennen Hell-
und Dunkelmodus, sie sind lokalisiert, sie sehen über alle Plugins gleich aus - und man bindet
sie ein, indem man ein Tag schreibt. Kein Framework-Projekt, kein Build-Schritt, keine
Node-Toolchain.
Dieses Kapitel ist die Einladung: ein Rundgang durch die Bausteine, jeweils mit einem Blick in
ein echtes Fenster und der einen Zeile, die ihn erzeugt. Am schnellsten erlebt man das live -
Show-EulWebUiStyleguide öffnet genau diese Galerie als Dialog samt Theme-Umschalter; alle
Abbildungen hier stammen daraus.
Schnellstart: installieren und ausprobieren #
Man braucht nur Windows mit Windows PowerShell - kein Visual Studio, kein Node.
EulandaXtools bereits installiert (mindestens 1.0.141)? Dann startet der Rundgang mit zwei Zeilen in einem PowerShell-Fenster:
Import-Module EulandaXtools
Show-EulWebUiStyleguide
(Sollte PowerShell melden, dass Skripte nicht erlaubt sind, dieser Sitzung einmalig erlauben:
Set-ExecutionPolicy -Scope Process RemoteSigned -Force.)
Noch nicht installiert oder soll aktualisiert werden? Das folgende Skript prüft die Online-Version, holt EulandaXtools bei Bedarf und öffnet danach den Style Guide. Es ist eingeklappt - direkt kopieren oder zum Lesen aufklappen:
Installations-/Update-Skript anzeigen
# EulandaXtools bei Bedarf aktualisieren und den Style Guide öffnen.
$ProgressPreference = 'SilentlyContinue'
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$module = 'EulandaXtools'
$base = "https://files.eulanda.eu/$($module.ToLower())"
# Modulpfad im (ggf. nach OneDrive umgelenkten) Benutzerprofil - Windows PowerShell 5.1
$modulesRoot = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'WindowsPowerShell\Modules'
$installRoot = Join-Path $modulesRoot $module
# Online-Version von files.eulanda.eu (IIS) lesen
$online = (Invoke-RestMethod "$base/version.txt") -replace '[^\d.]', ''
Write-Host "Online (files.eulanda.eu): $module $online" -ForegroundColor Cyan
# Höchste lokal installierte Version ermitteln
$installed = $null
if (Test-Path $installRoot) {
$installed = Get-ChildItem $installRoot -Directory |
Where-Object { $_.Name -as [version] } |
Sort-Object { [version]$_.Name } -Descending |
Select-Object -First 1 -ExpandProperty Name
}
if ($installed) { Write-Host "Lokal installiert: $module $installed" -ForegroundColor Cyan } else { Write-Host "Lokal installiert: (noch nicht vorhanden)" -ForegroundColor Cyan }
if (-not $installed -or [version]$online -gt [version]$installed) {
Write-Host "Aktualisiere auf $online ..." -ForegroundColor Yellow
$tmp = Join-Path $modulesRoot ".$module-download"
$ext = Join-Path $tmp 'extracted'
if (Test-Path $tmp) { Remove-Item $tmp -Recurse -Force }
New-Item -ItemType Directory -Path $ext -Force | Out-Null
$zip = Join-Path $tmp "$module.zip"
Invoke-WebRequest "$base/$($module.ToLower()).zip" -OutFile $zip
Expand-Archive $zip -DestinationPath $ext -Force
# Das Manifest (.psd1) ist die verbindliche Quelle für die Versionsnummer
$psd1 = Get-ChildItem $ext -Recurse -Filter "$module.psd1" | Select-Object -First 1
if (-not $psd1) { throw "$module.psd1 im Download nicht gefunden." }
$version = (Import-PowerShellDataFile $psd1.FullName).ModuleVersion
$target = Join-Path $installRoot $version
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
New-Item -ItemType Directory -Path $target -Force | Out-Null
Copy-Item (Join-Path $psd1.Directory.FullName '*') $target -Recurse -Force
Get-ChildItem $target -Recurse -File | Unblock-File -ErrorAction SilentlyContinue # Mark of the Web entfernen
Remove-Item $tmp -Recurse -Force
Write-Host "Installiert: $module $version" -ForegroundColor Green
} else {
Write-Host "Bereits aktuell - kein Download." -ForegroundColor Green
}
Write-Host "Starte den Style Guide ..." -ForegroundColor Cyan
Import-Module $module -Force
Show-EulWebUiStyleguide
Was das Skript tut:
- Versionsabgleich: Es liest die Online-Version aus
version.txtund vergleicht sie mit der höchsten lokal installierten - heruntergeladen wird nur, wenn das Web wirklich neuer ist. - Sichere Versionsbestimmung: Der Download landet zuerst in einem Temp-Ordner; die maßgebliche Versionsnummer kommt aus dem Manifest (
.psd1), nicht aus dem Dateinamen. Erst damit entsteht der endgültige Zielordner...\Modules\EulandaXtools\<Version>\. Genau so macht es auch ErpXe beim Nachladen. - OneDrive-fest: Der Modulpfad wird über den Windows-Known-Folder „Eigene Dokumente" aufgelöst - also auch bei nach OneDrive umgeleitetem Dokumente-Ordner.
- Sofort lauffähig:
Set-ExecutionPolicy -Scope Process RemoteSignederlaubt das Laden des EV-signierten Moduls in dieser Sitzung;Unblock-Filenimmt den frisch entpackten Dateien das „Mark of the Web". DanachImport-Module -Force- und der Beispieldialog geht auf.
Ein manuelles Freischalten vorab ist nicht nötig. Das Skript wird in ein PowerShell-Fenster eingefügt (interaktiv ausgeführt, nicht als
.ps1-Datei gestartet) - die Execution Policy blockt nur Skript-/Moduldateien, nicht eingefügte Befehle. Deshalb läuft die erste Zeile auch unter der Standardrichtlinie „Restricted", ohne Adminrechte, undScope Processbetrifft nur diese eine Sitzung. Wer das Skript stattdessen als.ps1speichert und startet, ruft es mitpowershell -ExecutionPolicy Bypass -File <datei>.ps1auf. (Einzige echte Ausnahme: eine per Gruppenrichtlinie zentral erzwungene Policy.)
Ab jetzt steht Show-EulWebUiStyleguide jederzeit bereit und alle eul-*-Bausteine sind einsatzbereit. Und damit der Rundgang:
Buttons #
Der Anfang ist unspektakulär und genau deshalb angenehm: Ein eul-button ist ein Button, der
schon richtig aussieht. Fünf Varianten transportieren die Bedeutung (primary, secondary, ghost,
success, danger), drei Größen die Gewichtung - mehr muss man nicht entscheiden.
Varianten und Größen - jede Farbe trägt eine Bedeutung.
<eul-button variant="success">Speichern</eul-button>
<eul-button variant="ghost"><eul-icon name="settings"></eul-icon></eul-button>
Formularfelder #
Text, E-Mail, Zahl, Auswahl, Checkbox, Radio, Schalter - die üblichen Verdächtigen, aber aus
einem Guss. Das Sahnehäubchen ist eul-tag-input: ein Feld mit Autocomplete, das Vorschläge
filtert und als Chips übernimmt - ideal, wenn der Anwender Spalten oder Schlagworte
zusammenstellt.
Von Text bis Schalter, plus das Tag-Input mit Vorschlagsliste.
<eul-input type="email" placeholder="name@firma.de"></eul-input>
<eul-switch checked>Aktiv</eul-switch>
Form-Layout #
Formulare sollen ohne Handarbeit ordentlich aussehen. eul-form mit eul-form-row legt Label
und Eingabe ab 560 px Breite nebeneinander und stapelt sie darunter - per Container-Query, nicht
per Fensterbreite. Der graue Hilfetext unter einem Feld ist eine einzige Zeile.
Label und Eingabe sortieren sich je nach Platz selbst.
<eul-form>
<div class="eul-form-row">
<label>E-Mail</label>
<div>
<eul-input type="email"></eul-input>
<div class="eul-form-help">Wird für Benachrichtigungen genutzt.</div>
</div>
</div>
</eul-form>
Datepicker #
Datum, Datum mit Uhrzeit, Zeitraum - eul-datepicker deckt alle drei ab, mit deutschem Format
und Kalender-Popup. Ein Attribut entscheidet, welcher Typ.
Drei Picker-Typen aus einer Komponente.
<eul-datepicker type="range" placeholder="von - bis"></eul-datepicker>
HTML-Editor #
Wo der Anwender formatierten Text braucht - ein Anschreiben, eine Mail-Vorlage - liefert
eul-htmleditor einen WYSIWYG-Editor mit drei Toolbar-Stufen. Wichtig fürs Backend:
el.mailHtml gibt sauberes, auf das EULANDA-Subset reduziertes HTML zurück, kein
Editor-Wirrwarr.
Formatierter Text, der als sauberes HTML zurückkommt.
<eul-htmleditor toolbar="mail" height="160"></eul-htmleditor>
const html = document.querySelector('eul-htmleditor').mailHtml;
Cards / Tönungen #
eul-card ist der Rahmen für alles. Über tone bekommt eine Karte eine Bedeutung (info,
success, warning, danger), über elevation Tiefe, über level eine saubere Verschachtelung -
aus drei Attributen entsteht eine ganze Gestaltungssprache.
Variant, Tone, Elevation und verschachtelte Level.
<eul-card tone="warning">Bitte prüfen</eul-card>
Tabs / Accordion #
Zwei klassische Gliederungen, beide rein über CSS-Klassen, ohne eine Zeile JavaScript:
eul-tabs für Reiter nebeneinander, eul-accordion für aufklappbare Abschnitte untereinander.
Je das erste Element trägt selected beziehungsweise open.
Tabs oben, Accordion darunter - rein deklarativ.
<eul-tabs>
<div class="eul-tabs-list">
<button class="eul-tab" selected>Allgemein</button>
<button class="eul-tab">Optionen</button>
</div>
<div class="eul-tab-panel" selected>...</div>
<div class="eul-tab-panel">...</div>
</eul-tabs>
Feedback #
Rückmeldung an den Anwender ist Einzeiler-Sache. EulUI.toast wirft eine kurze Meldung ein
(vier Varianten), EulUI.alert und EulUI.confirm liefern Hinweis und Rückfrage als Promise.
Dazu eul-spinner und eul-progress für alles, was dauert.
Kurzmeldungen, Dialoge und Ladeanzeigen.
EulUI.toast('Gespeichert', { variant: 'success' });
if (await EulUI.confirm('Wirklich löschen?')) { /* ... */ }
Wizard #
Mehrstufige Abläufe - Import, Einrichtung, Migration - führt eul-wizard Schritt für Schritt.
Jede Seite trägt ihren Titel im data-title, die Schritt-Navigation entsteht von selbst.
Geführter Ablauf mit Fortschrittsleiste.
<eul-wizard>
<div class="eul-wizard-content">
<div class="eul-wizard-page" data-title="Start" active>...</div>
<div class="eul-wizard-page" data-title="Prüfen">...</div>
</div>
</eul-wizard>
Tree / Listview #
Hierarchien zeigt eul-tree - mit Checkboxen, Einfachauswahl und einer Echtzeitsuche, die den
Baum beim Tippen filtert. Für flache Listen gibt es eul-listview. Daten kommen als Property,
die Auswahl liest man über .checkedIds oder .selectedId zurück.
Baum mit Sofortfilter und Mehrfachauswahl, plus Liste.
<eul-tree id="t" checkboxes searchable></eul-tree>
document.getElementById('t').data = [
{ id: 'a', label: 'Eigenschaften', children: [{ id: 'farbe', label: 'Farbe' }] }
];
Grid #
Die Tabelle, die im echten Plugin am häufigsten gebraucht wird. eul-grid bringt Suche,
Sortierung und Pagination schon als Attribute mit; Spalten und Zeilen setzt man über .columns
und .data. Sind keine Daten da, erscheint automatisch ein Empty-Overlay.
Volltextsuche, Spaltensortierung und Pagination ohne Zutun.
<eul-grid search sort pagination></eul-grid>
const g = document.querySelector('eul-grid');
g.columns = ['ArtNr', 'Bezeichnung', 'Bestand', 'Preis'];
g.data = rows;
Attribute im Überblick #
Alles opt-in: ein Grid ohne Attribute ist eine schlichte Tabelle.
| Attribut | Wirkung |
|---|---|
search | Volltextsuche über alle Spalten |
sort | Spaltensortierung |
pagination | Seitenweise Anzeige (10 Zeilen) |
selectable | Zeilenauswahl mit Marker |
auto-select | wählt nach dem Laden die erste Zeile |
commands | Kommandozeile (neu, bearbeiten, löschen, aktualisieren) |
export | Export nach CSV, Excel und Zwischenablage |
auto-filter | Trichter an Spalten mit wenigen verschiedenen Werten |
column-setup | Spalten ein-/ausblenden, sortieren, Breiten merken |
fixed-layout | Tabelle bleibt in Containerbreite, Text endet in Ellipse |
Zeilenauswahl gegen Mehrfachauswahl #
Zwei verschiedene Dinge, die man leicht verwechselt - und deren Vermischung teuer wird:
Zeilenauswahl (selectable) beantwortet wo bin ich. Ein Klick markiert
die Zeile mit einem Strich, damit sichtbar ist, welchen Datensatz die Widgets
daneben gerade zeigen. Sie folgt auch den Pfeiltasten und dem Mausrad -
beides bewegt die Auswahl um eine Zeile. Gemeldet wird das über
eul-grid-row-click.
Mehrfachauswahl (Spalten-Eigenschaft check: true) beantwortet was habe
ich ausgewählt. Die Spalte zeigt eine echte Checkbox: per Tab erreichbar, mit
LEER schaltbar, und ein Klick darauf verschiebt die Zeilenauswahl nicht.
g.columns = [
'id', // Schlüsselspalte
{ name: 'Auswahl', check: true, checkAll: true }, // echte Checkboxen
'Tabelle'
];
g.data = rows;
g.checkedKeys = rows.map((r) => r[0]); // alles vorauswählen
g.addEventListener('eul-grid-check-change', (e) => {
// e.detail = { key, checked, keys, cells, row }
// key ist null, wenn der Kopf-Haken alle auf einmal geschaltet hat
console.log(e.detail.keys);
});
checkedKeys führt die Auswahl über den Schlüssel der ersten Zelle -
genau wie die Zeilenauswahl. Damit überlebt sie Sortierung, Suche und
Seitenwechsel. checkAll: true setzt zusätzlich einen Alle/Keine-Haken in den
Spaltenkopf; er bezieht sich auf die Zeilen, die nach Suche und Filter gerade
gelten, und zeigt eine teilweise Auswahl als unbestimmt an.
Haken nie übereul-grid-row-clickschalten. Es liegt nahe, eine Häkchen-Spalte als Text zu führen und im Zeilenklick umzuschalten. Das geht schief: dasselbe Ereignis feuert auch bei Pfeiltasten und Mausrad. Wer scrollt, schaltet dann Haken um, ohne es zu wollen - und mit der Tastatur ist ein Haken gar nicht erreichbar, ohne ihn im selben Moment zu setzen. Dafür gibt escheck: true.
Spalten als Objekt #
Statt eines Namens darf jede Spalte ein Objekt sein:
| Eigenschaft | Wirkung |
|---|---|
name | Überschrift |
type | int, number, money - rechtsbündig und formatiert |
filter: true | Trichter im Kopf, Werte distinct aus den geladenen Daten |
sum: true | Summenzeile unter der Spalte |
hidden: true | Spalte bleibt im DOM (Schlüssel!), wird nur nicht gezeigt |
check / checkAll | Auswahl-Spalte, siehe oben |
hidden ist bewusst kein Entfernen: die Schlüsselspalte muss im DOM bleiben,
sonst hat die Zeilenauswahl keinen Schlüssel mehr.
Export nach CSV, Excel und Zwischenablage #
export setzt drei Knöpfe in den Tabellenkopf. Alles läuft im Browser, ohne
Server-Roundtrip und ohne zusätzliche Bibliothek:
| Ziel | Format |
|---|---|
| CSV | Semikolon-getrennt, mit BOM (Excel öffnet Umlaute richtig) |
| Excel | echtes .xlsx, clientseitig gepackt |
| Zwischenablage | TSV - passt direkt in eine Tabellenkalkulation |
Exportiert wird, was man sieht. Nicht der Rohbestand: Suche und
Spaltenfilter wirken auch auf den Export, und ausgeblendete Spalten bleiben
draussen - hidden: true, per CSS versteckte (die Schlüsselspalte) und alles
mit export: false. Wer eingrenzt und dann exportiert, bekommt genau seine
Auswahl.
<eul-grid export export-name="offene-posten"></eul-grid>
g.columns = [
{ name: 'id', hidden: true },
'Beleg',
{ name: 'Intern', export: false }, // im Grid sichtbar, nicht im Export
{ name: 'Betrag', type: 'money' }
];
g.addEventListener('eul-grid-export', (e) => {
console.log(e.detail.format, e.detail.rows.length); // csv | xlsx | copy
});
Der Dateiname ist <export-name>_JJJJ-MM-TT; ohne export-name dient die
id des Grids als Basis. Das Ereignis eul-grid-export meldet Format und
Zeilen - nützlich fürs Protokoll, wenn ein Anwender Daten aus dem Haus trägt.
Sichtzustand über einen Detail-Sprung retten #
Wer aus der Liste in eine Detailmaske springt und zurückkehrt, will nicht auf Seite 1 landen:
const state = g.viewState; // vor dem Wechsel
// ... Detail bearbeiten, Daten neu laden ...
g.restoreViewState(state); // Seite und markierte Zeile sind zurück
Charts #
Zahlen werden Bild: eul-chart umhüllt Chart.js für Balken, Torte, Linie und Doughnut. Ein
Attribut wählt den Typ, .data nimmt das vertraute Chart.js-Format.
Aus einem Datenobjekt wird ein fertiges Diagramm.
<eul-chart type="bar" height="220"></eul-chart>
document.querySelector('eul-chart').data = {
labels: ['Jan', 'Feb', 'Mär'],
datasets: [{ label: 'Umsatz', data: [42, 48, 55] }]
};
Icons #
40 randscharfe Lucide-Icons als Inline-SVG, skalierbar und einfärbbar. size bestimmt die
Größe, color die Bedeutung - und im Button wird daraus mit einem Tag ein Icon-Button.
Ein konsistentes Icon-Set, das überall passt.
<eul-icon name="trash-2" color="danger"></eul-icon>
<eul-button><eul-icon name="save"></eul-icon> Speichern</eul-button>
EULANDA-Icons #
Eigene Bilddateien - Logos, Symbole - liegen im Modul-Ordner lib/img/ und sind im Dialog als
eulimg/... erreichbar; den Virtual-Host dafür richtet Show-EulWebView2Dialog automatisch
ein. eul-image zeigt sie scharf in jeder Größe.
Modul-eigene Bilder, ohne Pfad-Gefummel.
<eul-image src="eulimg/eulanda-logo.png" width="48"></eul-image>
App-Layout #
Und schließlich der Rahmen, der alles zusammenhält: eul-app mit Kopfleiste und Body, darin
eul-sidebar (Navigationsleiste links, Seiten rechts) oder eul-ribbon (Reiter oben). Der
Seitenwechsel läuft über ein data-page-Attribut - genau dieses Muster trägt auch der Style
Guide selbst.
Der Plugin-Rahmen als Sidebar oder Ribbon.
<eul-sidebar>
<nav slot="nav">
<a data-page="start" active>Start</a>
<a data-page="export">Export</a>
</nav>
<eul-page id="start" active>...</eul-page>
<eul-page id="export">...</eul-page>
</eul-sidebar>
Gruppen in der Navigationsleiste #
Ab einer gewissen Zahl von Menüpunkten wird die flache Liste unübersichtlich. Dann bekommt die Navigation Gruppen wie die Outlook-Bar der EULANDA-Klassik: Überschrift, auf- und zuklappbar, der Zustand bleibt je Gerät gemerkt.
<nav slot="nav">
<div class="eul-nav-group" data-group="master" open>
<button type="button" class="eul-nav-group-head">
<span>Stammdaten</span><span class="eul-nav-caret"></span>
</button>
<div class="eul-nav-group-items">
<a data-page="addresses">Adressen</a>
<a data-page="articles">Artikel</a>
</div>
</div>
</nav>
Verdrahtet wird das einmal beim Aufbau mit EulUI.wireNavGroups(nav, 'meinPlugin.navGroups') -
der zweite Wert ist der Schlüssel, unter dem der Auf-/Zu-Zustand liegt. Der Aufruf arbeitet
delegiert: Gruppen, die später dazukommen (etwa aus einem Plugin-Manifest), brauchen kein
erneutes Verdrahten, nur ein EulUI.applyNavGroupState(nav, key), damit auch für sie der
gemerkte Zustand gilt. Ohne open im Markup startet eine Gruppe zugeklappt - richtig für
Selten-Gebrauchtes wie eine System-Gruppe.
Am Handy wird daraus eine Schublade #
Unter 640px Fensterbreite hat eine Navigation mit Gruppen keinen Platz mehr neben dem Inhalt. Sie wird dann zur Schublade: aus dem Bild geschoben, ein Knopf mit drei Balken links in der Kopfleiste holt sie über den Inhalt, dahinter dunkelt eine Fläche ab. Geschlossen wird sie durch die Auswahl eines Ziels, einen Tipp auf die Abdunklung, ESC oder sobald das Fenster wieder breiter wird.
Dieselbe Navigation am Telefon: Knopf in der Kopfleiste, Gruppen zugeklappt, eine aufgeklappt.
Dafür ist nichts zu tun - kein Markup, kein Aufruf, kein eigenes CSS. Zwei Dinge sollte man trotzdem wissen:
Nur mit Gruppen. Eine flache Navigation (nur Verweise) bleibt der waagerechte Streifen von früher; geprüft wird das am Element selbst. Bestehende Dialoge ändern ihr Verhalten also nicht.
In der Schublade starten alle Gruppen zugeklappt, auch die mit
open- sonst wäre das Menü zwanzig Zeilen lang. Was man dort aufklappt, gilt für den Moment und wird nicht gespeichert: ein Tipp unterwegs soll die Gliederung am Arbeitsplatz nicht umschreiben.
Eigene Klick-Handler auf den Menü-Verweisen stören nicht, auch nicht mit stopPropagation - die
Schublade hört in der Aufnahme-Phase mit und schliesst zuverlässig.
All das kommt aus einem einzigen Bundle, das Show-EulWebView2Dialog automatisch einsetzt -
Design-Tokens, Komponenten, Sprache. Man schreibt die eul-*-Tags, der Rest ist erledigt. Wer
loslegen will: WebView2 in PowerShell zeigt das Gerüst,
Dialog-Aufbau und Bausteine die Struktur dahinter.















