3 Knockout
3.1 Einführung
Knockout (kurz KO) ist eine JavaScript-Bibliothek, die sich hervorragend dazu eignet, Anwendungen zu kreieren, die dem Benutzer einen reibungslosen Programmfluss bieten ohne lästiges Neuladen der Seite. Die Eigenschaften von Knockout und dessen Abgrenzungen zu AngularJS wurden bereits in den vorherigen Kapiteln behandelt. Hier soll es nun ausschließlich um die Technik gehen – von der Installation bis zum praxistauglichen Anwendungseinsatz, das ist das Ziel dieses Kapitels.
3.2 Installation
Die schlanke JavaScript-Bibliothek kann direkt von der Webseite knockoutjs.com in der aktuellsten Version heruntergeladen werden.
Knockout steht auch in den meisten Package Managern zur Installation bereit. Beispielsweise kann die Bibliothek mit Bower (http://bower.io) oder NuGet (https://www.nuget.org) direkt ins Projekt integriert werden.
Die eigentliche Installation bzw. das Einbinden der Bibliothek in eine Webseite ist einfach. Die Referenz innerhalb einer HTML-Seite wird durch das <script>-Tag erzeugt (Listing 3.1).
<script type='text/javascript'
src='knockout-3.2.0.js'></script>
Listing 3.1: Einbinden von Knockout in eine HTML-Seite
Absolute Performancefreaks nutzen zur Einbindung von Knockout ein Content Delivery Network, beispielsweise Microsoft Ajax CDN oder das CDNJS. Der Vorteil liegt darin, dass der Anwender evtl. über eine andere Webanwendung bereits die Bibliothek lokal in den Browser-Cache geladen hat und somit Knockout nicht noch einmal vom eigenen Webserver bezogen werden muss.
3.2.1 Model-View-ViewModel
Der Aufbau und die Funktion des Designpatterns „Model-View-ViewModel (MVVM)“ wurde bereits im Kapitel „Technik“ ausführlich beschrieben. MVVM bildet die Basis einer jeden Knockout-Anwendung.
Ein ViewModel wird in Knockout mit einem herkömmlichen JavaScript-Objekt abgebildet (Listing 3.2).
var personViewModel = {
forename: 'Timm',
surname: 'Bremus',
age: 31
};
Listing 3.2: Deklaration eines ViewModels
Nachdem ein ViewModel erzeugt wurde, können die einzelnen Eigenschaften an eine View gebunden werden (Listing 3.3).
<p>Der Nachname der Person ist
<span data-bind='text: surname'></span></p>
Listing 3.3: Binden von Eigenschaften in einer View
3.2.2 Knockout aktivieren
Das Attribut data-bind ist zwar ein valides HTML5-Attribut, doch ohne eine gesonderte Behandlung mittels JavaScript kann der Webbrowser nur wenig damit anfangen. Es gilt nun, diesem Attribut Leben einzuhauchen und somit Knockout zu aktivieren. Hierzu führt man am Fuß der HTML-Seite folgenden Befehl aus (Listing 3.4).
<script>
$(document).ready(function() {
ko.applyBindings(personViewModel);
});
</script>
Listing 3.4: Aktivierung von Knockout
Geschickt ist es, sämtlichen JavaScript-Code immer ans Ende einer HTML-Seite zu positionieren. Das verkürzt die Ladezeit der Seite und stellt gleichzeitig sicher, dass beim Laden der Skripte das DOM bereits komplett geladen ist. Zudem ist es empfehlenswert, den ko.applyBindings-Befehl von Knockout in den DOM Ready Handler von jQuery zu packen.
Das wars! Knockout ist nun aktiviert, und die Werte des ViewModels werden direkt in der View gerendert und ausgegeben.
Es bleibt noch zu erwähnen, dass der Funktion ko.applyBindings neben dem ViewModel noch ein weiterer Parameter mitgegeben werden kann. An dieser Stelle kann ein HTML-Bereich definiert werden, für den das ViewModel gültig ist. Auf diese Weise können mehrere ViewModels an eine View gebunden werden, ohne dass es zu einem Konflikt kommt (Listing 3.5).
ko.applyBindings(viewModel,
document.getElementById('someElementId'))
Listing 3.5: Gültigkeit eines ViewModels festlegen
Zur Bestimmung eines HTML-Elements, für welches das ViewModel gültig ist, ist ausschließlich die JavaScript-Methode document.getElementById zu verwenden. jQuery kann an dieser Stelle nicht eingesetzt werden.
3.3 Observables
Nun wäre man theoretisch im Stande, Daten mittels eines ViewModels an eine View zu binden und dessen Eigenschaften anzuzeigen. Ein entscheidendes Puzzleteil fehlt aber noch zur Vervollständigung des gesamten Konstrukts. Wird ein Wert einer Eigenschaft im ViewModel verändert, so wird sich dieser auf dem Bildschirm nicht aktualisieren. Knockout stellt uns aus diesem Grund Observables, also einen Mechanismus zur Änderungsverfolgung von Eigenschaften, zur Verfügung. Hierzu muss das in Listing 3.2 implementierte ViewModel leicht modifiziert werden (Listing 3.6)
var personViewModel = {
forename: ko.observable('Timm'),
surname: ko.observable('Bremus'),
age: ko.observable(31)
};
Listing 3.6: ViewModel mit überwachten Eigenschaften
Es sind keine weiteren Anpassungen notwendig. Ab sofort wird das an eine Eigenschaft gebundene HTML-Element in der View aktualisiert, sobald sich der Wert der Eigenschaft im ViewModel ändert.
3.3.1 Lesen und Schreiben von Observables
Da nicht alle Browser die Getter- und Setter-Methoden von JavaScript unterstützen, bildet Knockout nach wie vor Observables über herkömmliche Funktionen ab. Auf diese Weise wird sichergestellt, dass eine mit Knockout realisiere Anwendung auch auf seltenen und älteren Browsers lauffähig ist.
- Um den Wert einer Eigenschaft zu lesen, wird die Observable ohne Parameter aufgerufen. Im vorherigen Beispiel würde personViewModel.forename() den Wert „Timm“ und personViewModel.age() den Wert „31“ zurückliefern.
- Um den Wert einer Eigenschaft zu schreiben, wird dieser als Parameter der Observable übergeben. Beispielweise würde der Vorname im vorherigen Beispiel mit personViewModel.forename('Kevin') geändert werden.
- Um gleich mehrere Observables mit Werten füllen zu können, kann eine verkettete Syntax verwendet werden, z. B. personViewModel.forename('Kevin').age(26).
3.3.2 Erweiterte Steuerung von Observables
Im Regelfall wird eine Observable immer sofort nach der Wertänderung in der View aktualisiert. Bei hochfrequentierten Änderungen einer Eigenschaft kann darunter jedoch die Performance der Anwendung leiden. Es ist daher sinnvoll, in einem solchen Fall die Häufigkeit der Aktualisierungen einer Eigenschaft zu begrenzen, beispielsweise mit einem Extender. Listing 3.7 beschreibt, wie man die Aktualisierung einer Eigenschaft in der View auf 50 Millisekunden beschränken kann. Alle Wertänderungen, die zwischen dem gewählten Intervall liegen, werden zwar in der Observable gespeichert, aber in der View nicht aktualisiert und angezeigt.
personViewModel.forename.extend({ rateLimit: 50 });
Listing 3.7: Limitierung der Aktualisierungen einer Eigenschaft
3.4 Observable Arrays
Bisher wurden lediglich einzelne Objekte in einem ViewModel als Observable deklariert und verwendet. Um eine Sammlung von Objekten zu überwachen, werden Observable Arrays verwendet. Diese werden oftmals in Szenarien verwendet, in denen viele Datensätze, beispielsweise eine Mailbox oder eine Liste von Daten, zum Einsatz kommen. In einer solchen Sammlung von Elementen (Collection) muss man häufig auf das Entfernen und Hinzufügen von Objekten reagieren, was erst durch den Einsatz eines Observable Arrays möglich ist. Listing 3.8 zeigt ein einfaches Beispiel, wie ein Observable Array sinnvoll und korrekt eingesetzt wird.
// HTML-Markup
<form data-bind="submit: addItem">
Neuer Eintrag:
<input data-bind='value: itemToAdd,
valueUpdate: "afterkeydown"' />
<button type="submit"
data-bind="enable: itemToAdd().length > 0">Add</button>
<p>Liste der Einträge:</p>
<select multiple="multiple" width="50"
data-bind="options: items"> </select>
</form>
// JavaScript-Code
var SimpleListModel = function(items) {
this.items = ko.observableArray(items);
this.itemToAdd = ko.observable("");
this.addItem = function() {
if (this.itemToAdd() != "") {
this.items.push(this.itemToAdd;
this.itemToAdd("");
}
}.bind(this);
};
ko.applyBindings(
new SimpleListModel(["Bremus", "Gerndt", "Nebel"]));
Listing 3.8: Initialisierung und Befüllung eines Observable Arrays
Dieses Beispiel verdeutlicht, wie einfach Elemente über ein in der View eingebettetes Eingabefeld in das hierfür im ViewModel vorgesehene Observable Array übertragen werden können. Gut zu erkennen ist, dass in einem ViewModel neben den Daten auch die Aktionen definiert werden, die in einer View zur...