Meteor Experiments

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

Nachfolgend ein Screenshot vom finalen Stand der Rating-Komponente:

Image for post
Image for post

Dann schauen wir mal, wie man das in Meteor umsetzen könnte.

Die Umsetzung

<template name="rating">
<ul class="rating">
{{#each ?}}
<li class="star {{filled}}">&#x2605;</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">&#x2605;</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}}">&#x2605;</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

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 ;-)

Developer since the 80'th, working at Forschungszentrum Jülich (http://www.fz-juelich.de) …

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store