Weiter geht es mit Knockout.js. Nachdem ich nun über den Einstieg gebloggt, als auch ein Beispiel bezüglich Formulare und Listen gezeigt habe, möchte ich mich mit diesem Beitrag mit dem Thema Templates beschäftigen und ein Beispiel implementieren, welches den Umgang damit aufzeigt. Vorlagen sind gerade bei wiederkehrenden Darstellungen (Listen etc.) eine hervorragende Sache, gibt es doch nur eine Stelle diese zu pflegen/ändern.

Aufgabenstellung

Konstruierte Beispiele sind immer so eine Sache, da oft der praxistaugliche Nutzen fehlt. Daher habe ich mich dazu entschieden, einen Teil meines Blogs mit Knockout.js nachzubilden. Konkret geht es um den Bereich Lesestoff.

Wie nachfolgend abgebildet, setzt sich diese Liste aus zwei zusammengehörigen Teilen zusammen:

  • Die Jahresangabe und der dazugehörigen Statistik über die Anzahl der gelesenen Bücher und Seiten.
  • Die Liste der Bücher, die im entsprechenden Jahr gelesen wurden.

Nobert Eder's Lesestoff

Wie man bereits vermuten kann, bietet sich diese Darstellung wunderbar für die Verwendung von Vorlagen an. Die Umsetzung dieser Liste mittels Knockout.js unter Verwendung von Vorlagen, ist nun das Ziel des nachfolgenden Beispiels.

Templates / View

Bevor ich mich den ViewModels zuwende, muss ein Plan bezüglich der Vorlagen her, da sich daraus der weitere Aufbau ergibt. Wie bereits oben erwähnt, setzt sich die Seite aus zwei Teilen zusammen, dem Jahr (sowie der dazugehörigen Statistik) und der Buchliste zum Jahr. Es bietet sich daher an, eine Vorlage für das Jahr selbst und eine weitere für die Buchliste zu erstellen.

Nun zur Vorlage für die Jahresdarstellung, welcher wir den Namen year-template geben. Dieses besteht aus einem h3-Tag für die Anzeige des Jahres. Hier erfolgt eine Datenbindung auf eine Eigenschaft year, die durch das ViewModel geliefert werden muss. Die Auflistung der Bücher selbst findet über eine unsortierte Liste statt. Diese bekommt über eine template-Bindung nun ein Template für die Darstellung eines einzelnen Buches gesetzt und durchläuft mittels foreach alle Einträge der Auflistung books.

Damit die Anzahl der Bücher und der Seiten dargestellt werden kann, muss da ViewModel diese Werte berechnen und zur Verfügung stellen. Dies soll durch booksCount und pages passieren.

<script type="text/html" id="year-template">

    <h3 data-bind="text: year"></h3>

    <ul class="kastenlightul" data-bind="template: { name: 'book-template', foreach: books }">

    </ul>

    <p>

        <strong data-bind="text: booksCount"/> B&uuml;cher<br/>

        <strong data-bind="text: pages"/> Seiten<br/>

    </p>

</script>

Im nächsten Schritt wird die Vorlage book-template definiert. Diese definiert nun, wie ein einzelnes Buch dargestellt wird. Die Darstellung erfolgt durch das year-template in einer unsortierten Liste, wodurch li-Elemente gerendert werden müssen.

Ein li-Elemente enthält nun die notwendigen Elemente zur Darstellung des Buches selbst, das ist nicht weiter aufregend. Interessant ist hier die attr-Bindung. Darüber können jegliche Attribute des betreffenden DOM-Elementes gesetzt werden. Dies ist besonders interessant für Link- und Image-Elemente, zumal hier zusätzlich meist mehrere Attribute zu setzen sind.

<script type="text/html" id="book-template">

    <li class="kastenlight">

        <img data-bind="attr: { src: coverlink, alt: title }"/>

        <a data-bind="attr: { href: titlelink, title: title }">

            <span>

                <strong data-bind="text: title"></strong>

            </span>

        </a>

        <br /> <span data-bind="text: author"/><br /> <span data-bind="text: pages"/> Seiten<br /><strong>Bewertung</strong>: <span data-bind="text: rating"/>/5

        <div class="buy">

            <a data-bind="attr: { href: amazonlink }" target="_blank">Auf Amazon kaufen</a>

        </div>

    </li>

</script>

Was nun noch fehlt ist die Stelle, die den Stein nun tatsächlich ins Rollen bringt. Eine Stelle, die beim Öffnen gerendert wird und auf das erste Template verweist.

Dazu wird ein einfaches div-Element erstellt und die dafür notwendige Bindung gesetzt:

<div data-bind="template: { name: 'year-template', foreach: years }">

</div>

Über die template-Bindung wird nun die Vorlage year-template für jedes Vorkommen in years angewandt.

Hinweis: Für dieses Beispiel wurden die bereits vorhandenen Styles verwendet. Diese befinden sich natürlich im Download, der ganz unten zu finden ist.

ViewModels

Auf JavaScript-Seite müssen nun die obigen Anforderungen abgedeckt werden. Für das book-template wird eine Repräsentation eines Buches mit den angegebenen Eigenschaften benötigt.

var BookViewModel = function(item) {

    this.title = ko.observable(item.title);

    this.titlelink = ko.observable(item.titlelink);

    this.coverlink = ko.observable(item.coverlink);

    this.author = ko.observable(item.author);

    this.pages = ko.observable(item.pages);

    this.rating = ko.observable(item.rating);

    this.amazonlink = ko.observable(item.amazonlink);

}

Als Parameter wird ein Objekt übergeben, welches alle notwendigen Informationen hat und das BookViewModel auffüllt.

Hinweis: Die Daten werden in diesem Beispiel via JSON gehalten und könnten an einigen Stellen direkt verwendet werden, habe ich aber hinsichtlich der Übersichtlichkeit und der möglichen Erweiterungen nicht getan.

Zur Darstellung eines Jahres, wie in year-template gefordert, wird nachfolgendes ViewModel verwendet:

var YearViewModel = function(initYear, initBooks) {

    var self = this;

    self.year = ko.observable(initYear);

    self.books = ko.observableArray();

    

    $.each(initBooks, function() {

            var item = new BookViewModel(this);

            self.books().push(item);

        });

    

    self.booksCount = ko.computed(function() {

        return self.books().length;

    });

    

    self.pages = ko.computed(function() {

        var pagesCount = 0;

        $.each(self.books(), function() {

            pagesCount += this.pages();

        });

        return pagesCount;

    });

}

Dieses ist nun schon ein weniger interessanter, als dass es ein Array der Bücher für dieses Jahr enthält, als auch zwei berechnete Eigenschaften booksCount und pages.

Nun fehlt noch das ViewModel, welches als “Einstieg” dient und an das div-Element der View gebunden wird, sowie eine Auflistung der in den Daten enthaltenen Jahre enthält:

var BooksViewModel = function() {

    var self = this;

    self.years = ko.observableArray();

    

    self.init = function(initialItems) {

        $.each(initialItems, function() {

            var year = new YearViewModel(this.year, this.books);

            

            self.years().push(year);

        });

    }

};

Diesem ViewModel wird über die Funktion init das JSON übergeben, wodurch sich in weiterer Folge die gesamte Struktur aufbaut.

Nun noch die Instanziierung und das Anwenden der Bindungen via:

var firstViewModel = new BooksViewModel();

firstViewModel.init(jsonBooklist.years);

 

ko.applyBindings(firstViewModel, $('booksCollection').get(0));

Fertig.

So sieht’s aus

Wenig überraschend sieht das Ergebnis dem Original sehr ähnlich :)

Lesestoff via Knockout.js

Fazit

In diesem Beispiel wurde der Umgang mit benannten Vorlagen gezeigt. Natürlich gibt es auch noch die Möglichkeit, Vorlagen dynamisch zu wählen oder überhaupt eine externe Template Engine zu verwenden.

Mir gefällt die einfache Art und Weise wie eine Problemstellung wie diese umgesetzt werden kann.

Download/Showcase

Dieses Beispiel kann nachfolgend herunter geladen werden. Wer möchte kann auch einen Blick auf die Online-Demo werfen.

Download Demo

Feedback

Wie immer freue ich mich natürlich auch hierzu über Feedback, schließlich kann man nie auslernen und es gibt immer etwas, das verbessert werden kann.

Jetzt Knockout.js lernen: Die Serie

Über den Autor

Norbert Eder

Ich bin ein leidenschaftlicher Softwareentwickler und Fotograf. Mein Wissen und meine Gedanken teile ich nicht nur hier im Blog, sondern auch in Fachartikeln und Büchern.