.
Abbildung 3: Berechnete Felder können beispielsweise die Länge von Wörtern anzeigen.
totalName wird dann an die data-bind-text-Eigenschaft des div-Elements gebunden. Jedes Mal, wenn sich jetzt einer der beiden Namen Vorname oder Nachname verändert, berechnet Knockout.js den Wert neu und weist ihn dem Element in der View zu. Dieser Mechanismus eignet sich beispielsweise auch, um Konvertierungen zwischen verschiedenen Währungen oder Zeiten vorzunehmen.
throttle verzögert die Auslösung
Sofern der Event-Handler, der bei jedem Tastendruck ausgelöst wird, nur auf dem Client läuft, also nicht auf einen Server zugreifen muss, passiert das in zufriedenstellender Geschwindigkeit. Was aber, wenn für den eingegebenen Wert vom Server Daten geholt werden müssen? Dann würde bei jedem Tastendruck eine Anfrage über Ajax an den Server abgeschickt werden. Dieser Roundtrip, bis die Daten beim Client angelangt und verarbeitet sind, kann je nach Bandbreite und Geschwindigkeit des Servers dauern, was beim Eintippen eines Suchbegriffs nervtötend ist.
Für so ein Szenario bietet Knockout.js die Methode throttle an: Drosselung. Damit ist es möglich, das Setzen einer berechneten Observable zu verzögern. Beispiel: myval ist als ko.observable() an das value-Attribut des Eingabefelds gebunden. Über den Parameter valueUpdate wird die Bindung derart deklariert, dass sie myval bei jedem Tastendruck setzt.
mytext wiederum ist ein berechneter Wert, der sich im Wesentlichen aus myval ermittelt. An die Definition von mytext wird die Drosselung über die Methode extend angehängt. Damit verzögert Knockout.js eine Berechnung so lange, bis der Anwender eine gewisse Zeit nichts mehr eingegeben hat. Wie lange Knockout.js hier wartet, hängt von dem Parameter throttle ab. Der Wert ist die Anzahl der Millisekunden, die gewartet wird. In Code sieht das so aus:
viewModel.mytext = ko.computed(function(){
return viewModel.myval() }).extend({ throttle : 1000 });
Bitte beachten Sie: Sie können diese Berechnung nicht innerhalb der JSON-Deklaration von viewModel ausführen. Versuchen Sie das, erhalten Sie eine Fehlermeldung. Sie müssen die Berechnung nach der Deklaration dem Objekt viewModel hinzufügen. Der Grund ist, dass Sie innerhalb der JSON-Deklaration keinen Zugriff auf das Objekt selbst haben. this zeigt zu diesem Zeitpunkt noch auf DOMWindow, also das umgebende Fensterobjekt. viewModel selbst ist noch nicht definiert.
throttle lässt sich nun hervorragend für Ajax-Anfragen verwenden. Zum Beispiel so:
viewModel.mytext = ko.computed(function(){
$.getJSON('../jquerydata/service/index.php', {
"value" : viewModel.myval() },
function(da){
console.log(da.toString());
}
);
}).extend({ throttle : 1000 });
Die Ajax-Funktion getJSON von jQuery wird erst dann aufgerufen, wenn der Anwender eine Sekunde keinen Text mehr eingegeben hat. Als URL-Parameter value wird der Wert von myval übermittelt. Die übergebene Callback-Funktion im Aufruf von getJSON stellt das Ergebnis dar. Hier ließe sich natürlich auch eine Observable setzen und damit das Ergebnis direkt in der View anzeigen.
function(da){
if (da.person)
{
viewModel.lastname(da.person.lastname);
}
}
Wenn Sie Tipp 2 verwenden und sich den Inhalt des ViewModels anzeigen lassen, sehen Sie, dass sich bei der Eingabe in das input-Element nach dem Return schon etwas tut. Das Datenfeld lastname im ViewModel spiegelt den Wert im Control input wider.
Auf einen Event reagieren
Jetzt wissen Sie, wie Sie Daten in der View anzeigen oder wie Sie in die View eingegebene Daten von dort auslesen. Stellt sich nur die Frage, wie Sie auf Events wie einen Mausklick reagieren können, beispielsweise wenn der Anwender einen Knopf betätigt.
Auch dafür ist Knockout.js selbstverständlich gerüstet. Die Ereignisbindung läuft genauso wie die Datenbindung. Sie fügen dem HTML-Code nur eine neue Anweisung hinzu:
<button data-bind="click: buttonClicked">
Klick mich
</button>
Laden Sie jetzt die HTML-Seite neu, erhalten Sie eine Fehlermeldung auf der Konsole: Die Datenbindung wird nicht verstanden. Das ist auch kein Wunder, denn das ViewModel verfügt über kein Feld, das es der Eigenschaft click: buttonClicked zuordnen kann.
Es fehlt also noch das passende Feld. Nur würde ein Datenfeld in diesem Fall keinen Sinn machen; ein
"buttonClicked": ko.observable()
führt also zu nichts. (Memo für den Autor: Das ist auch klar, denn es sollte ja etwas ausgeführt werden, wenn man auf den Knopf klickt.) Trotzdem: Was ausgeführt werden soll, muss über das Feld erreichbar sein, etwa eine Funktion. Schreiben wir aber
"buttonClicked": alert("Hallo");
führt das nicht zum gewünschten Ergebnis. Das Alert wird sofort beim Neuladen der Seite ausgeführt, ein Klick auf den Knopf hingegen führt nicht zu der Dialogbox. So einfach geht das in JavaScript nicht. Hier muss eine anonyme Funktion den Aufruf kapseln.
"buttonClicked": function(){ alert("Hallo"); }
Diese Zeile hat das gewünschte Ergebnis: Beim Reload passiert nichts. Erst ein Klick auf den Knopf ruft das Alert auf und die Dialogbox erscheint. Wenn wir das Hallo jetzt noch durch
this.lastname()
ersetzen, haben wir Folgendes:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title></title>
<script src="knockout-2.0.0.js"
type="text/javascript">
</script>
</head>
<body>
<input data-bind="value: lastname" />
<button data-bind="click: buttonClicked">
Klick mich
</button>
<h2>Debug</h2>
<div data-bind="text: ko.toJSON(viewModel)">
</div>
<script type="text/javascript">
var viewModel =
{
"lastname": ko.observable("Gertrud"),
"buttonClicked": function(){ alert(this.lastname()); }
}
ko.applyBindings(viewModel);
viewModel.lastname("Huber");
</script>
</body>
</html>
Jetzt sorgt ein Klick auf den Knopf dafür, dass der in das Eingabefeld eingegebene Text in der Dialogbox angezeigt wird. Probieren Sie es aus. Geben Sie Meier ein und klicken Sie auf den Knopf. Schon erscheint in der Dialogbox Meier. Und auch im ViewModel wird das Feld lastname auf den Wert Meier gesetzt.
Mehrzeiliges