Meteor Experiments
Der Initiator
Vor ein paar Tagen fand ich zufällig einen alten Blogpost auf Quora in dem Angular und React verglichen werden. Der Artikel war nicht mehr ganz so frisch, aber schön kurz und das animiert in der heutigen Zeit ja umso mehr zum Lesen — mich auch.
Ich hatte den Artikel nur ansatzweise durchgelesen und schon kribbelte es in den Fingern das gleiche Beispiel in Meteor umzusetzen. Auch um zu sehen, wie sich Meteor im Vergleich schlägt. Zudem wollte ich auch mal praktisch ausprobieren, wie man in Meteor eine Komponente umsetzen könnte.
Die Aufgabe
Die Aufgabenstellung ist schnell erklärt. Es geht im Prinzip darum eine Komponente zu schreiben, die ein Rating anzeigt und gleichzeitig erlaubt dieses durch einen einfachen Klick zu verändern.
Nachfolgend ein Screenshot vom finalen Stand der Rating-Komponente:

Dann schauen wir mal, wie man das in Meteor umsetzen könnte.
Die Umsetzung
Mein erster Ansatz, getrieben von dem was ich schon kannte (#each), war ein Schleife zu erstellen. Sie sollte einfach die maximale Anzahl von Sternchen ausgeben, hier 10. Dazu habe ich ein entsprechendes Template erstellt (rating.html):
<template name="rating">
<ul class="rating">
{{#each ?}}
<li class="star {{filled}}">★</li>
{{/each }}
</ul>
</template>
Aber damit war ich noch nicht fertig. Das ? konnte da nicht stehen bleiben. Meteor bzw. eigentlich Blaze erlaubt an dieser Stelle nur eine der folgenden drei Varianten:
- Cursor,
- Array,
- null oder undefined.
Ich wollte aber einfach nur eine bestimmte Anzahl von Iterationen durchführen und ein Array oder einen Cursor hatte ich auch gerade nicht zur Hand. Also wie geht das?
Nach eine Suchanfrage bei google und Ergebnissen, die mich zu Stackoverflow geführt haben, bin ich dann zu der mir auch schon vorher offensichtlichen Lösung gekommen, die mir aber eigentlich nicht gefallen hat:
Ein Dummy-Array.
(Die Lösungen in Angular und React verwenden übrigens auch ein Array ;-)
rating.html:
<template name="rating">
<ul class="rating">
{{#each stars }}
<li class="star">★</li>
{{/each }}
</ul>
</template>
Dazu noch die passende JavaScript Datei (rating.js):
Template.rating.helpers({
stars: function () {
return this.stars; // or Template.currentData().stars
}
});
Der stars-Helper liefert an dieser Stelle ein Array, das genau die benötigte Länge (max) hat. Dies wird beim Erzeugen der Komponente angelegt und an die Template-Instanz gehängt (rating.js):
Template.rating.onCreated(function () {
this.data.stars = [];
for (var i = 1; i <= this.data.max; i++) {
this.data.stars.push(i);
}
});
Jetzt benötigte ich nur noch ein Anwendungsgerüst für das besagte Beispiel (index.html):
<head>
<title>Rating Demo</title>
</head>
<body>
Rating is {{currentRatingValue}} <br/>
<br/>
Clickable Rating <br/>
{{> rating max=10 value=currentRating }}
<br/><br/>
Readonly Rating <br/>
{{> rating max=10 value=currentRating readonly=true }}
</body>
Ich verwende das Rating-Template an genau zwei Stellen und versorge die Parameter mit den nötigen Daten. Oh, die folgenden Parameter muss man an das Template übergeben:
- max
Damit wird die Anzahl der anzuzeigenen Sterne festgelegt. - value
Das ist der eigentliche Rating Wert. - readonly (optional)
Steuert die Veränderbarkeit des Ratings.
Die dazu passende JavaScript-Datei index.js adressiert die noch offenen Punkte:
Template.body.onCreated(function () {
this.currentRatingValue = new ReactiveVar(5);
});
Template.body.helpers({
currentRating: function () {
return Template.instance().currentRatingValue;
},
currentRatingValue: function () {
return Template.instance().currentRatingValue.get();
}
});
Hier verwende ich onCreated um eine reaktive Variable anzulegen, die den Rating-Wert aufnehmen soll. Initial hat sie den Wert 5.
Die Helpermethoden liefern die benötigten Daten für die Anzeige. Auffällig ist, dass hier zwei Hilfsmethoden benötigt werden. Eine um den reinen Zahlenwert anzuzeigen und eine um die Variable an das Rating-Template weiterzugeben.
Würde ich nur den Zahlenwert an das Rating-Template übergeben, könnte ich nicht von der automatischen Aktualisierung der Anzeige profitieren.
Jetzt fehlen nur noch die korrekte Auszeichnung des aktuellen Ratings unter Verwendung einer CSS-Klasse und eine Möglichkeit das Rating via Mausclick zu verändern.
Dazu passen wir noch mal den Code in der Datei rating.html an:
<template name="rating">
<ul class="rating">
{{#each stars }}
<li class="star {{filled}}">★</li>
{{/each }}
</ul>
</template>
Diese neue Version unterscheidet sich nur an einer Stelle, dem Klassenattribut des <li>-Tags. Das habe ich um einen zusätzliche Klasse erweitert, die zur Laufzeit mit der Helpermethode filled ermittelt wird. filled liefert die entsprechende Klasse für die Auszeichnung des vergebenen Ratings. Um diese zu ermitteln wird einfach die im Array enthaltene Zahl gegen das anzuzeigene Rating gehalten und entsprechend die Klasse ausgewählt: filled oder eben nix. Damit sieht die rating.js dann wie folgt aus:
Template.rating.helpers({
stars: function () {
return this.stars; // or Template.currentData().stars
},
filled: function () {
return (this <= Template.instance().data.value.get()) ?
'filled' : '';
}
});
Zu guter Letzt fehlt noch das Userinterface zur Veränderung des Ratings. Dies wird über ein entsprechendes Click-Event implementiert:
Template.rating.events({
'click li': function () {
var data = Template.currentData();
if (!data.readonly) {
data.value.set(this);
}
}
});
Die Auswertung
Insgesamt muss man drei Konzepte lernen: reaktive Variablen, Helper-Methoden und Event-Methoden. Gefühlt liegt Meteor für mich damit in der Mitte zwischen Angular und React.
Beim Vergleich der Codezeilen steht Meteor mit insgesamt 52 Zeilen auch wieder zwischen React und Angular. Davon fallen insgesamt 33 Zeilen auf JavaScript und 19 Zeilen auf HTML. D.h. bzgl. JavaScript ist die Meteor Variante sogar ein wenig kürzer, als die Umsetzung mit React.
Also im Grunde tut sich da nicht viel zwischen den verschiedenen Lösungen.
So, das war mein erstes öffentliches Meteor Experiments.
Comments welcome ;-)