Sterne-Bewertung ohne jQuery
jQuery für ein simples Sterne-Widget zu laden ist wie einen LKW zu mieten, um einen Brief zu transportieren. Alpine.js bietet mit x-data, x-on und x-bind alles, was ein vollständiges, barrierefreies Rating-Widget braucht – in unter 50 Zeilen, ohne npm, ohne Build-Step, direkt im Template.
Inhaltsverzeichnis
- 1. Warum Alpine.js statt jQuery für ein Rating-Widget?
- 2. Die Grundstruktur: x-data und reaktiver State
- 3. Hover-State: Sterne-Preview beim Darüberfahren
- 4. Klick-Persistenz: Bewertung speichern und anzeigen
- 5. Halbe Sterne mit SVG-Clip und Alpine-Logik
- 6. Barrierefreiheit: ARIA-Attribute und Tastatursteuerung
- 7. Integration in Magento 2 und Hyvä Themes
- 8. Bewertung per Fetch an eine API senden
- 9. Alpine.js vs. jQuery Rating: direkter Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Alpine.js statt jQuery für ein Rating-Widget?
In vielen Magento-Projekten findet man noch immer jQuery-Plugins wie jQuery Star Rating oder Raty.js, die einen vollständigen DOM-Manipulations-Layer mitbringen, nur damit fünf Sterne auf Klick und Hover reagieren. Das Resultat sind zusätzliche HTTP-Requests, globale Namespace-Verschmutzung und Markup, das im DOM nachträglich durch JavaScript erzeugt wird – was serverseitiges Rendering und SEO erschwert. Alpine.js löst dasselbe Problem deklarativ: Der State lebt direkt im HTML-Attribut, kein externes Paket wird geladen, der DOM bleibt serverseitig vollständig lesbar.
Der entscheidende Vorteil von Alpine.js für diesen Anwendungsfall ist die Direktive x-data, die einen lokal abgegrenzten reaktiven Daten-Scope erzeugt. Alle Daten des Widgets – aktuelle Bewertung, Hover-Vorschau, Lade-State – leben in diesem Objekt. Keine globalen Variablen, keine Event-Delegation über den gesamten Document-Body, kein Konfliktpotenzial mit anderen Widgets auf derselben Seite. Jedes Rating-Widget auf der Seite ist vollständig isoliert, obwohl sie dasselbe Template verwenden.
Für Hyvä Themes in Magento 2 ist Alpine.js die native Wahl: Hyvä lädt Alpine.js als Standard-JavaScript-Layer und schließt jQuery aus dem Frontend-Stack aus. Wer für ein Rating-Widget jQuery nachladen würde, würde damit bewusst die Performance-Strategie von Hyvä unterlaufen. Die alpine-native Implementierung passt sich nahtlos in das bestehende Event-System von Hyvä ein und profitiert von der CSP-konformen Inline-Script-Registrierung.
2. Die Grundstruktur: x-data und reaktiver State
Das Rating-Widget beginnt mit einem x-data-Objekt, das alle benötigten State-Variablen enthält: rating für die gespeicherte Bewertung (initial 0), hoverRating für die Vorschau beim Darüberfahren (initial 0), submitted als Boolean-Flag nach dem Absenden und loading für den API-Call-State. Dieses Objekt ist der einzige State-Container – Alpine.js stellt automatisch sicher, dass sich das DOM bei jeder Änderung dieser Werte aktualisiert, ohne dass manuelles DOM-Traversal nötig ist.
Die Sterne selbst werden über eine x-for-Schleife über ein Array [1, 2, 3, 4, 5] gerendert. Jeder Stern ist ein <button>-Element mit x-on:click, x-on:mouseenter und x-on:mouseleave. Die aktive Klasse – ob der Stern ausgefüllt oder leer dargestellt wird – wird über x-bind:class berechnet: ein Stern ist aktiv, wenn sein Index kleiner oder gleich dem aktuell angezeigten Wert ist, also i <= (hoverRating || rating). Diese einzige Formel steuert sowohl den Hover-Preview als auch die persistierte Bewertung.
// Alpine.js Rating Widget — minimal state, full reactivity
function ratingWidget() {
return {
rating: 0, // persisted selection
hoverRating: 0, // preview on hover
submitted: false,
loading: false,
stars: [1, 2, 3, 4, 5],
// Returns true if star i should render as filled
isActive(i) {
return i <= (this.hoverRating || this.rating);
},
setHover(i) { this.hoverRating = i; },
clearHover() { this.hoverRating = 0; },
select(i) {
this.rating = i;
this.$dispatch('rating-selected', { value: i });
}
};
}
3. Hover-State: Sterne-Preview beim Darüberfahren
Der Hover-State ist das visuell auffälligste Element eines Rating-Widgets und technisch der Punkt, an dem naive Implementierungen fragil werden. Das übliche jQuery-Muster manipuliert Klassen auf allen Geschwister-Elementen – bei fünf Sternen sind das fünf separate DOM-Operationen pro Maus-Event. Mit Alpine.js ist das Prinzip umgekehrt: statt des DOMs wird nur eine einzige Variable hoverRating verändert. Alpine berechnet dann für jedes Stern-Element eigenständig, ob es die aktive Klasse erhält – deklarativ statt imperativ.
Die Direktive x-on:mouseenter="setHover(i)" setzt hoverRating auf den Wert des aktuellen Sterns. x-on:mouseleave="clearHover()" setzt ihn zurück auf 0. Die visuelle Klasse wird mit :class="{ 'text-yellow-400': isActive(i), 'text-slate-300': !isActive(i) }" gebunden. Das Ergebnis: wenn der Nutzer über Stern 3 fährt, zeigt das Widget sofort drei gelbe und zwei graue Sterne – ohne DOM-Traversal, ohne querySelectorAll, ohne manuelle Schleife.
Ein wichtiges Detail für die korrekte UX: mouseleave muss auf dem Container des gesamten Widgets registriert werden, nicht auf jedem einzelnen Stern. Andernfalls flackert der Hover-State, wenn der Cursor zwischen zwei Sternen liegt und kurz keinen davon berührt. Mit x-on:mouseleave.self="clearHover()" am Container-Element wird clearHover() nur ausgelöst, wenn der Mauszeiger den Container-Bereich verlässt – nicht beim Wechsel zwischen Stern-Buttons.
4. Klick-Persistenz: Bewertung speichern und anzeigen
Eine Bewertung zu speichern bedeutet in Alpine.js: die Variable rating zu setzen und optional in localStorage zu persistieren. Die select(i)-Methode setzt this.rating = i und löst ein Custom Event aus. Für die optionale Persistenz im localStorage – nützlich, wenn eine Bewertung pro Session nur einmal abgegeben werden soll – reicht ein einzeiliger localStorage.setItem-Aufruf im selben Handler. Beim Initialisieren des Widgets liest init() den gespeicherten Wert aus und setzt this.rating entsprechend.
Nach dem Absenden wechselt das Widget in einen Read-only-Mode: die Buttons werden mit :disabled="submitted" deaktiviert, der Hover-State greift nicht mehr, und eine Bestätigungsnachricht wird mit x-show="submitted" eingeblendet. Dieser State-Übergang ist in Alpine.js vollständig deklarativ: man ändert submitted auf true, und sämtliche abhängigen Direktiven – x-show, :disabled, :class – aktualisieren sich automatisch. Kein manuelles Durchlaufen des DOM.
// Extended rating widget with localStorage persistence
function ratingWidget(productId) {
return {
rating: 0,
hoverRating: 0,
submitted: false,
loading: false,
stars: [1, 2, 3, 4, 5],
storageKey: `rating_${productId}`,
init() {
const saved = localStorage.getItem(this.storageKey);
if (saved) {
this.rating = parseInt(saved, 10);
this.submitted = true; // already rated this session
}
},
isActive(i) { return i <= (this.hoverRating || this.rating); },
setHover(i) { if (!this.submitted) this.hoverRating = i; },
clearHover() { this.hoverRating = 0; },
select(i) {
if (this.submitted) return;
this.rating = i;
localStorage.setItem(this.storageKey, i);
}
};
}
5. Halbe Sterne mit SVG-Clip und Alpine-Logik
Halbe Sterne sind optisch ansprechend für die Anzeige eines Durchschnittswerts (z.B. 3,7 von 5) und erfordern eine leicht erweiterte Alpine-Logik. Die Implementierung nutzt SVG-Sterne mit einem <clipPath>-Element: Jeder Stern besteht aus zwei überlagerten SVG-Pfaden – einem grauen Hintergrund und einem gelben Vordergrund. Der Vordergrund wird über einen dynamischen clip-path zu 50% oder 100% eingeblendet, abhängig davon, ob der angezeigte Wert eine halbe oder volle Bewertung für diesen Stern enthält.
Die Alpine-Hilfsfunktion starFill(i, value) gibt 'full', 'half' oder 'empty' zurück: Wenn value >= i ist der Stern voll, wenn value >= i - 0.5 ist er halb, sonst leer. Im Template wird dann mit :style="{ clipPath: starFill(i, displayValue) === 'half' ? 'inset(0 50% 0 0)' : 'none' }" gearbeitet. Halbe Sterne werden typischerweise nur für die Anzeige des Durchschnittswerts verwendet – die Eingabe bleibt bei ganzen Zahlen, da Nutzer keine halben Sterne als Bewertung eingeben sollen.
6. Barrierefreiheit: ARIA-Attribute und Tastatursteuerung
Ein barrierefreies Rating-Widget braucht ARIA-Attribute, die Screenreadern den aktuellen State kommunizieren. Jeder Stern-Button erhält :aria-label="`${i} von 5 Sternen`" und :aria-pressed="rating === i". Der Container bekommt role="radiogroup" und aria-label="Produktbewertung". Mit diesen Attributen gibt ein Screenreader beim Navigieren zwischen den Buttons an, wie viele Sterne aktiv sind – ohne visuell sichtbaren Text, der das Design stören würde.
Tastatursteuerung ist bei nativen <button>-Elementen bereits eingebaut: Tab-Navigation, Enter und Space lösen click aus. Für Pfeil-Navigation innerhalb der Sterngruppe – wie es ARIA für Radiogroups vorsieht – fügt man x-on:keydown.arrow-right.prevent="select(Math.min(rating + 1, 5))" und x-on:keydown.arrow-left.prevent="select(Math.max(rating - 1, 1))" am Container hinzu. Alpine.js unterstützt Keyboard-Modifikatoren nativ, sodass keine manuelle Event-Listener-Registrierung nötig ist.
<!-- Accessible Alpine.js rating widget markup -->
<div
x-data="ratingWidget('prod-42')"
role="radiogroup"
aria-label="Produktbewertung"
x-on:mouseleave.self="clearHover()"
x-on:keydown.arrow-right.prevent="select(Math.min(rating + 1, 5))"
x-on:keydown.arrow-left.prevent="select(Math.max(rating - 1, 1))"
class="flex items-center gap-1"
>
<template x-for="i in stars" :key="i">
<button
type="button"
x-on:click="select(i)"
x-on:mouseenter="setHover(i)"
:aria-label="`${i} von 5 Sternen`"
:aria-pressed="rating === i"
:disabled="submitted"
:class="isActive(i) ? 'text-yellow-400' : 'text-slate-300'"
class="text-2xl transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-teal-500 rounded"
>★</button>
</template>
<span x-show="submitted" x-cloak class="ml-3 text-sm text-teal-700 font-semibold">Vielen Dank!</span>
</div>
7. Integration in Magento 2 und Hyvä Themes
In Hyvä Themes wird das Rating-Widget als .phtml-Template eingebunden. Das Alpine-Skript wird als Inline-Script am Ende des Templates platziert und mit $hyvaCsp->registerInlineScript() für die Content Security Policy registriert. Das Widget selbst lebt im Template-Markup und nutzt das von Hyvä bereitgestellte Alpine.js – kein zusätzliches <script src> nötig. Die Produkt-ID wird als PHP-Variable in das x-data-Attribut interpoliert: x-data="ratingWidget('<?= $escaper->escapeHtmlAttr($productId) ?>')".
Für die Darstellung vorhandener Bewertungen aus Magento-Daten übergibt man den Durchschnittswert als PHP-Variable und rendert das Read-only-Widget mit einem separaten Template-Block. Der Write-Mode (Eingabe) ist nur für eingeloggte Kunden sichtbar, gesteuert über $customerSession->isLoggedIn() im Block-ViewModel. Diese Trennung von Lese- und Schreibmodus hält das Template übersichtlich und ermöglicht unterschiedliche Caching-Strategien für beide Varianten.
8. Bewertung per Fetch an eine API senden
Wenn der Nutzer einen Stern auswählt und auf "Bewertung abgeben" klickt, sendet die submit()-Methode die Daten per fetch an eine REST-Endpoint. Während des API-Calls ist loading auf true gesetzt – das Template blendet einen Spinner ein und deaktiviert den Submit-Button, um Doppel-Submissions zu verhindern. Bei Erfolg wird submitted = true gesetzt, was das gesamte Widget in den Read-only-State überführt. Bei einem Fehler wird eine Fehlermeldung in errorMessage geschrieben, die mit x-show="errorMessage" angezeigt wird.
Alpine.js bietet mit $fetch keinen eingebauten HTTP-Client – fetch aus der Browser-API wird direkt genutzt. Das ist kein Nachteil: Die native fetch-API ist in allen modernen Browsern verfügbar, benötigt kein zusätzliches Polyfill und gibt Promises zurück, die in async/await-Methoden von Alpine-Komponenten problemlos genutzt werden. Der Magento REST-Endpoint für Produktbewertungen lautet /rest/V1/reviews und erwartet ein JSON-Objekt mit productId, rating und optional nickname.
// Submit method with fetch, loading state and error handling
async submit() {
if (!this.rating || this.submitted || this.loading) return;
this.loading = true;
this.errorMessage = '';
try {
const response = await fetch('/rest/V1/reviews', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({
productId: this.productId,
rating: this.rating,
nickname: this.nickname || 'Anonym'
})
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
this.submitted = true;
localStorage.setItem(this.storageKey, this.rating);
this.$dispatch('rating-submitted', { productId: this.productId, rating: this.rating });
} catch (err) {
this.errorMessage = 'Bewertung konnte nicht gespeichert werden. Bitte versuche es erneut.';
console.error('[RatingWidget]', err);
} finally {
this.loading = false;
}
}
9. Alpine.js vs. jQuery Rating: direkter Vergleich
Der Vergleich zwischen einer jQuery-basierten und einer Alpine.js-basierten Rating-Implementierung zeigt deutliche Unterschiede in Codemenge, DOM-Abhängigkeit und Wartbarkeit. Beide Ansätze erzielen dasselbe visuelle Ergebnis, unterscheiden sich aber fundamental in ihrer Architektur.
| Kriterium | jQuery Rating (Raty.js) | Alpine.js Rating | Ergebnis |
|---|---|---|---|
| Abhängigkeiten | jQuery + Plugin (~90 KB gzip) | Alpine.js (~15 KB gzip) | Alpine 6× kleiner |
| DOM-Generierung | JS erzeugt Markup zur Laufzeit | Markup serverseitig vollständig | Alpine SEO-freundlicher |
| Mehrere Widgets | Globale Selektoren, Konflikte möglich | Isolierter x-data-Scope | Alpine konfliktfrei |
| Accessibility | Manuell nachzurüsten | ARIA nativ in Direktiven | Alpine wartbarer |
| Hyvä-Kompatibilität | Erfordert jQuery-Nachladen | Nativ, kein Extra-Script | Alpine empfohlen |
Besonders relevant ist der Unterschied bei der DOM-Generierung. jQuery-basierte Rating-Plugins ersetzen ein einfaches Input-Element durch generierten DOM-Code – Sterne, Bilder oder SVGs werden zur Laufzeit eingefügt. Das macht serverseitiges Rendering und Crawling schwieriger und verhindert, dass CSS korrekt auf den anfänglichen Render-State angewendet wird. Alpine.js rendert dagegen das vollständige Markup im Template und aktiviert nur die reaktiven Bindings – der initiale Render-State ist immer korrekt, auch ohne JavaScript.
Mironsoft
Alpine.js Frontend-Entwicklung für Hyvä Themes und Magento 2
Alpine.js-Widgets für euren Magento-Shop?
Wir entwickeln performante, barrierefreie Alpine.js-Komponenten für Hyvä Themes – Rating-Widgets, Produktkonfiguratoren, Filter und mehr. Kein jQuery, kein Build-Step-Overhead, vollständig CSP-konform.
Widget-Entwicklung
Rating, Galerie, Konfigurator – Alpine.js-Komponenten ohne jQuery-Abhängigkeit
jQuery-Migration
Bestehende jQuery-Plugins auf Alpine.js migrieren und Performance verbessern
Hyvä-Integration
Nahtlose CSP-konforme Integration in bestehende Hyvä-Theme-Strukturen
10. Zusammenfassung
Ein Alpine.js Rating-Widget ohne jQuery ist kein Kompromiss, sondern eine Verbesserung gegenüber jQuery-basierten Plugins. Der State lebt vollständig in x-data, der DOM bleibt serverseitig vollständig, und ARIA-Attribute machen das Widget für alle Nutzer zugänglich. Hover-State, Klick-Persistenz, localStorage-Integration und API-Anbindung passen in unter 80 Zeilen JavaScript – ohne externe Abhängigkeiten, ohne Build-Step, mit nativem Browser-fetch.
Für Hyvä-Themes-Projekte in Magento 2 ist Alpine.js die einzig sinnvolle Wahl: Es ist bereits geladen, es ist CSP-konform einsetzbar, und es schließt den Widerspruch zwischen jQuery-freiem Frontend und jQuery-abhängigen Widgets. Das hier vorgestellte Pattern – isolierter x-data-Scope, deklarative Bindings, async/await für API-Calls – lässt sich direkt auf andere interaktive Elemente übertragen und bildet eine solide Grundlage für den gesamten Alpine.js-Komponentenstack.
Alpine.js Rating-Widget — Das Wichtigste auf einen Blick
State-Management
Alles in x-data: rating, hoverRating, submitted, loading. Kein globaler State, kein Namespace-Konflikt bei mehreren Widgets auf einer Seite.
Hover ohne DOM-Traversal
Nur eine Variable hoverRating ändern – Alpine berechnet für jeden Stern automatisch die korrekte CSS-Klasse. Kein querySelectorAll, kein manelles Schleife.
Accessibility
role="radiogroup", aria-pressed, aria-label per x-bind und Pfeil-Tastaturnavigation per x-on:keydown – alles deklarativ im Template.
Hyvä & CSP
Alpine.js ist in Hyvä nativ enthalten. Inline-Script mit $hyvaCsp->registerInlineScript() registrieren. Kein jQuery nachladen, kein Performance-Verlust.