Client und Server sauber verzahnt, nie nur eine Seite
Magentos alte jQuery-validation.js passt weder zu einem Theme ohne jQuery noch zu einer strikten Content Security Policy. Alpine-basierte Live-Validierung schließt diese Luecke, ersetzt aber niemals die serverseitige Prüfung, denn jede clientseitige Regel lässt sich mit deaktiviertem JavaScript oder einem direkten POST-Request umgehen.
Inhaltsverzeichnis
- 1. Warum Magentos jQuery-validation.js in Hyvä nicht mehr passt
- 2. Alpine-basierte Live-Validierung: das Grundkonzept
- 3. Praxisbeispiel: Live-Validierung im Registrierungsformular
- 4. Barrierefreie Fehlerausgabe: aria-invalid, aria-describedby, role=alert
- 5. Warum serverseitige Validierung Pflicht bleibt
- 6. Server-Fehler zurueckspielen: Session Messages und Ajax-Pattern
- 7. Praxisbeispiel: Checkout-Adressformular mit Laendervalidierung
- 8. Wiederverwendbare Validierungslogik über Formulare hinweg
- 9. Formulare testen: Tastatur, Screenreader, automatisiert
- 10. Zusammenfassung
- 11. FAQ
1. Warum Magentos jQuery-validation.js in Hyvä nicht mehr passt
Magentos Luma-Theme bringt mit mage/validation ein jQuery-Plugin mit, das Formularfelder anhand von data-validate-Attributen prueft und bei Verstoss automatisch Fehlermeldungen einblendet. Hyvä verzichtet bewusst auf jQuery und auf Knockout.js, weshalb dieses Plugin schlicht nicht zur Verfuegung steht, selbst wenn es technisch nachgeladen werden könnte.
Selbst wenn jQuery nachtraeglich eingebunden würde, widerspraeche das dem Grundgedanken von Hyvä, möglichst wenig JavaScript-Gepaeck auszuliefern, und würde zusätzlich mit der Content Security Policy kollidieren, da mage/validation an mehreren Stellen auf dynamische DOM-Manipulation setzt, die sich mit den CSP-Vorgaben von Hyvä nur schwer vertraegt. Die konsequente Alternative ist deshalb eine schlanke, Alpine-basierte Lösung.
2. Alpine-basierte Live-Validierung: das Grundkonzept
Das Grundprinzip ist einfach: Jedes Feld ist per x-model an ein Alpine-Datenobjekt gebunden, und eine Validierungsfunktion prueft bei jeder Änderung oder beim Verlassen des Feldes, ob der aktuelle Wert einer definierten Regel entspricht. Ergebnis ist ein einfaches errors-Objekt, das pro Feldname entweder eine Fehlermeldung oder einen leeren String enthält und direkt im Template ausgewertet wird.
Wichtig für die Wahrnehmung ist der Zeitpunkt der ersten Prüfung: Ein Feld sollte nicht bereits beim Laden der Seite als fehlerhaft markiert werden, sondern erst, nachdem der Nutzer es einmal verlassen hat, per @blur ausgeloest. Erst danach greift bei weiteren Eingaben eine sofortige Prüfung per @input mit leichtem Debounce, damit nicht bei jedem einzelnen Tastendruck neu validiert wird.
function formValidation(rules) {
return {
values: {},
errors: {},
touched: {},
validateField(name) {
const rule = rules[name];
if (!rule) return;
this.errors[name] = rule.test(this.values[name]) ? '' : rule.message;
},
onBlur(name) {
this.touched[name] = true;
this.validateField(name);
},
onInput(name) {
if (this.touched[name]) this.validateField(name);
},
isValid() {
return Object.values(this.errors).every((error) => !error);
},
};
}
3. Praxisbeispiel: Live-Validierung im Registrierungsformular
Ein Registrierungsformular mit E-Mail, Passwort und Passwortbestaetigung zeigt das Muster gut, weil hier gleich zwei Regeltypen gebraucht werden: einfache Formatpruefung per regulaerem Ausdruck für die E-Mail-Adresse, und eine feldabhaengige Regel für die Passwortbestaetigung, die den Wert eines anderen Feldes kennen muss. Beide Regeln lassen sich im selben rules-Objekt definieren, ohne die Validierungsfunktion selbst anzupassen.
Der Submit-Button bleibt so lange deaktiviert, bis isValid() true zurueckgibt, zusätzlich zu der ohnehin verpflichtenden serverseitigen Prüfung nach dem Absenden. Diese doppelte Absicherung verhindert unnoetige Formular-Rundreisen bei offensichtlichen Fehlern, ohne die eigentliche, autoritative Prüfung dem Client zu ueberlassen.
<form x-data="formValidation({
email: { test: v => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v || ''), message: 'Bitte eine gueltige E-Mail-Adresse angeben' },
password_confirmation: { test: v => v === values.password, message: 'Die Passwoerter stimmen nicht ueberein' },
})"
method="post" action="/customer/account/createpost">
<input type="email" name="email" x-model="values.email"
@blur="onBlur('email')" @input="onInput('email')"
:aria-invalid="errors.email ? 'true' : 'false'">
<button type="submit" :disabled="!isValid()">Konto erstellen</button>
</form>
4. Barrierefreie Fehlerausgabe: aria-invalid, aria-describedby, role=alert
Eine rot gefaerbte Umrandung allein reicht für Screenreader-Nutzer nicht aus, ein Fehler muss auch programmatisch mit dem Feld verknuepft sein. Das Attribut aria-invalid signalisiert den Fehlerzustand selbst, während aria-describedby auf die ID des Fehlertextes zeigt, sodass ein Screenreader beim Fokussieren des Feldes automatisch dessen Fehlermeldung mit vorliest.
Der Fehlertext-Container selbst bekommt zusätzlich role="alert", damit neu erscheinende Fehlermeldungen auch dann angesagt werden, wenn der Fokus gar nicht auf dem betroffenen Feld liegt, etwa nach einem serverseitigen Validierungsfehler nach dem Absenden. Ohne dieses Attribut bleiben nachtraeglich eingeblendete Fehler für Screenreader-Nutzer oft komplett unbemerkt.
<label for="email">E-Mail-Adresse</label>
<input id="email" type="email" name="email" x-model="values.email"
@blur="onBlur('email')"
:aria-invalid="errors.email ? 'true' : 'false'"
aria-describedby="email-error">
<p id="email-error" role="alert" x-show="errors.email" x-text="errors.email"
class="text-sm text-red-600"></p>
5. Warum serverseitige Validierung Pflicht bleibt
Jede clientseitige Regel ist letztlich nur eine Komfortfunktion, kein Sicherheitsmechanismus. Ein Angreifer kann JavaScript deaktivieren, den Alpine-Code manipulieren oder schlicht einen POST-Request direkt an den Endpunkt senden, ganz ohne das Formular jemals im Browser zu rendern. Magentos Data- und Form-Validatoren auf Serverseite müssen deshalb jede Regel unabhaengig vom Client erneut prüfen.
In der Praxis bedeutet das, dieselbe fachliche Regel zweimal zu pflegen: einmal im Alpine-Ausdruck für sofortiges UI-Feedback, und einmal in einem \Magento\Framework\Validator oder einem Data-Patch-basierten Input-Filter im Server-Code. Redundanz ist hier kein Designfehler, sondern die einzige Möglichkeit, gute UX und echte Sicherheit gleichzeitig zu gewährleisten.
<?php
declare(strict_types=1);
namespace Mironsoft\Core\Model\Validator;
use Magento\Framework\Validator\AbstractValidator;
/**
* Serverseitige Prüfung der Passwortbestaetigung, unabhaengig vom Client.
*/
class PasswordConfirmationValidator extends AbstractValidator
{
/**
* Prueft, ob Passwort und Bestaetigung uebereinstimmen.
*
* @param mixed $value
* @return bool
*/
public function isValid($value): bool
{
$isValid = $value['password'] === $value['password_confirmation'];
if (!$isValid) {
$this->_addMessages(['Die Passwoerter stimmen nicht ueberein.']);
}
return $isValid;
}
}
6. Server-Fehler zurueckspielen: Session Messages und Ajax-Pattern
Bei einem klassischen, vollstaendigen Form-Submit ohne Ajax verarbeitet der Controller die Eingaben, haengt bei einem Fehler eine Session-Message an und leitet zurück auf das Formular, das Hyvä-Template liest die Message über den bekannten Messages-Block aus und zeigt sie oberhalb des Formulars an. Dieses Muster funktioniert auch ganz ohne JavaScript und ist die robusteste Fallback-Ebene.
Bei einem Ajax-basierten Formular liefert der Controller stattdessen eine strukturierte JSON-Antwort mit einem errors-Objekt pro Feldname zurück, das die Alpine-Komponente direkt in ihr bestehendes errors-Objekt übernimmt. Dadurch landen serverseitige Fehler in genau derselben UI-Anzeige wie clientseitige Fehler, ohne dass zwei unterschiedliche Fehleranzeigen im selben Formular gepflegt werden müssen.
async function submitForm() {
const response = await fetch(this.$el.action, { method: 'POST', body: new FormData(this.$el) });
const data = await response.json();
if (!response.ok && data.errors) {
this.errors = { ...this.errors, ...data.errors };
return;
}
window.location.href = data.redirectUrl;
}
7. Praxisbeispiel: Checkout-Adressformular mit Laendervalidierung
Adressformulare im Checkout sind ein gutes Beispiel für Regeln, die sich abhaengig vom gewaehlten Land ändern, etwa unterschiedliche PLZ-Formate oder ein Pflichtfeld für Region in manchen Laendern, das in anderen komplett entfaellt. Die Alpine-Komponente haelt das gewaehlte Land als reaktiven Wert und wählt darauf basierend das passende PLZ-Pattern aus einer Regel-Map aus.
Auch hier gilt: Magentos Directory-Modul liefert dieselbe Laender- und Regionslogik bereits serverseitig aus, sodass die clientseitige Regel-Map idealerweise aus denselben Konfigurationsdaten generiert wird, statt eine zweite, unabhaengig gepflegte Quelle zu schaffen, die im Lauf der Zeit auseinanderdriften kann.
const postcodePatterns = {
DE: /^\d{5}$/,
AT: /^\d{4}$/,
NL: /^\d{4}\s?[A-Z]{2}$/,
};
function addressValidation(countryId) {
return {
country: countryId,
validatePostcode(value) {
const pattern = postcodePatterns[this.country];
return pattern ? pattern.test(value) : true;
},
};
}
8. Wiederverwendbare Validierungslogik über Formulare hinweg
Sobald mehrere Formulare ähnliche Regeln teilen, etwa E-Mail-Format bei Registrierung, Newsletter-Anmeldung und Kontaktformular gleichermassen, lohnt sich eine zentrale Regel-Bibliothek als eigenes JavaScript-Modul, aus dem jede Formular-Komponente ihre benoetigten Regeln importiert, statt denselben regulaeren Ausdruck an drei Stellen leicht unterschiedlich zu pflegen.
Diese zentrale Bibliothek lässt sich zusätzlich als eigener Alpine-Store für gemeinsame Validierungsfunktionen registrieren, wenn mehrere Komponenten gleichzeitig auf dieselben Regeln zugreifen sollen. Wichtig bleibt, dass diese Wiederverwendung sich ausschliesslich auf die Client-Ebene bezieht, die serverseitige Prüfung bleibt davon unberuehrt und unabhaengig bestehen.
9. Formulare testen: Tastatur, Screenreader, automatisiert
Ein manueller Test ausschliesslich per Tastatur, ohne Maus, deckt die haeufigsten Barrierefreiheits-Probleme schnell auf: Landet der Fokus nach einem Fehler auf dem ersten fehlerhaften Feld, ist jedes Feld per Tab erreichbar, und wird eine Fehlermeldung tatsaechlich vorgelesen, sobald sie erscheint. Ein kurzer Test mit einem echten Screenreader wie NVDA oder VoiceOver bestätigt, ob aria-describedby und role="alert" tatsaechlich wie erwartet funktionieren.
Für automatisierte Tests eignen sich End-to-End-Tests, die gezielt ungueltige Werte eintragen und prüfen, ob sowohl die clientseitige Fehleranzeige erscheint als auch ein direkter POST-Request ohne JavaScript vom Server korrekt abgelehnt wird. Nur beide Pfade zusammen bestaetigen, dass Client- und Server-Validierung tatsaechlich synchron arbeiten.
| Ebene | Zweck | Umgehbar | UX-Vorteil | Verpflichtend |
|---|---|---|---|---|
| Alpine-Live-Validierung | Sofortiges Feedback während der Eingabe | Ja, durch deaktiviertes JavaScript | Sehr hoch, Fehler werden sofort sichtbar | Nein, reine Komfortfunktion |
| HTML5-native Validierung | Grundlegende Formatpruefung im Browser | Ja, durch deaktivierte Validierung oder direkten POST | Mittel, browserabhaengige Darstellung | Nein, zusätzliche Absicherung |
| Magento Server-Validierung | Autoritative, fachliche Prüfung | Nein, läuft serverseitig | Gering direkt, verhindert aber fehlerhafte Daten | Ja, immer verpflichtend |
| Datenbank-Constraints | Letzte technische Absicherung | Nein, DB-Ebene | Kein direkter UX-Bezug | Ja, als letzte Verteidigungslinie |
| Barrierefreie Fehlerausgabe | Fehler für alle Nutzergruppen wahrnehmbar machen | Nein, betrifft Darstellung nicht Sicherheit | Sehr hoch für Screenreader-Nutzer | Ja, aus rechtlichen und ethischen Gruenden |
Mironsoft
Hyvä-Theme-Entwicklung und Luma-Migration
Noch auf Luma unterwegs oder ein Hyvä-Theme, das nicht rund läuft?
Wir entwickeln Hyvä-Themes für Magento von Grund auf oder migrieren bestehende Luma-Shops sauber, mit Tailwind CSS, Alpine.js und ohne unnötiges JavaScript-Gepäck.
Luma-zu-Hyvä-Migration
Bestehenden Shop strukturiert und ohne Funktionsverlust auf Hyvä umstellen.
Custom-Theme-Entwicklung
Individuelles Hyvä-Theme nach Design-Vorgaben von Grund auf umsetzen.
Performance-Optimierung
Core Web Vitals und Ladezeiten im Hyvä-Frontend gezielt verbessern.
10. Zusammenfassung
Formularvalidierung in Hyvä
Alpine statt jQuery-Legacy
Live-Validierung per x-model und einfachen Regelfunktionen ersetzt Magentos mage/validation ohne jQuery und ohne CSP-Konflikte.
Barrierefreiheit von Anfang an mitdenken
aria-invalid, aria-describedby und role=alert sorgen dafuer, dass Fehler auch für Screenreader-Nutzer wahrnehmbar sind.
Server bleibt die einzige Autoritaet
Jede clientseitige Regel muss serverseitig unabhaengig erneut geprueft werden, da Client-Validierung jederzeit umgangen werden kann.
Fehler-Formate synchron halten
Serverseitige Ajax-Fehlerantworten landen im selben errors-Objekt wie clientseitige Fehler, ohne zwei getrennte Anzeigen zu pflegen.