Knockout für den Rivalen



Ähnliche Dokumente
Diese Ansicht erhalten Sie nach der erfolgreichen Anmeldung bei Wordpress.

mysql - Clients MySQL - Abfragen eine serverbasierenden Datenbank

Abamsoft Finos im Zusammenspiel mit shop to date von DATA BECKER

Task: Nmap Skripte ausführen

Über die Internetseite Hier werden unter Download/aktuelle Versionen die verschiedenen Module als zip-dateien bereitgestellt.

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3

OP-LOG

Wordpress: Blogbeiträge richtig löschen, archivieren und weiterleiten

Zwischenablage (Bilder, Texte,...)

4 Aufzählungen und Listen erstellen

Primzahlen und RSA-Verschlüsselung

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster

Step by Step Webserver unter Windows Server von Christian Bartl

Adminer: Installationsanleitung

Wir arbeiten mit Zufallszahlen

ARAkoll 2013 Dokumentation. Datum:

plus Flickerfeld bewegt sich nicht

SEMINAR Modifikation für die Nutzung des Community Builders

2. Im Admin Bereich drücken Sie bitte auf den roten Button Webseite bearbeiten, sodass Sie in den Bearbeitungsbereich Ihrer Homepage gelangen.

.htaccess HOWTO. zum Schutz von Dateien und Verzeichnissen mittels Passwortabfrage

Artikel Schnittstelle über CSV

Guide DynDNS und Portforwarding

3. GLIEDERUNG. Aufgabe:

Professionelle Seminare im Bereich MS-Office

2. Im Admin Bereich drücken Sie bitte auf den roten Button Webseite bearbeiten, sodass Sie in den Bearbeitungsbereich Ihrer Homepage gelangen.

Anbindung an easybill.de

Benutzerhandbuch. Leitfaden zur Benutzung der Anwendung für sicheren Dateitransfer.

Seite 1 von 14. Cookie-Einstellungen verschiedener Browser

Fax einrichten auf Windows XP-PC

Kostenstellen verwalten. Tipps & Tricks

Mit der Maus im Menü links auf den Menüpunkt 'Seiten' gehen und auf 'Erstellen klicken.

Design Patterns 2. Model-View-Controller in der Praxis

SANDBOXIE konfigurieren

Im Folgenden wird Ihnen an einem Beispiel erklärt, wie Sie Excel-Anlagen und Excel-Vorlagen erstellen können.

KURZANLEITUNG CLOUD OBJECT STORAGE

Berechnungen in Access Teil I

Success! Bestellausgabe

Bilder zum Upload verkleinern

Der vorliegende Konverter unterstützt Sie bei der Konvertierung der Datensätze zu IBAN und BIC.

Updatebeschreibung JAVA Version 3.6 und Internet Version 1.2

Nach der Anmeldung im Backend Bereich landen Sie im Kontrollzentrum, welches so aussieht:

teamsync Kurzanleitung

! " # $ " % & Nicki Wruck worldwidewruck

Folgeanleitung für Fachlehrer

Windows. Workshop Internet-Explorer: Arbeiten mit Favoriten, Teil 1

Aufklappelemente anlegen

Bereich METIS (Texte im Internet) Zählmarkenrecherche

Live Update (Auto Update)

Die Dateiablage Der Weg zur Dateiablage

Facebook I-Frame Tabs mit Papoo Plugin erstellen und verwalten

Excel Pivot-Tabellen 2010 effektiv

Handbuch. NAFI Online-Spezial. Kunden- / Datenverwaltung. 1. Auflage. (Stand: )

WordPress. Dokumentation

Eine Anwendung mit InstantRails 1.7

Einstellungen im Internet-Explorer (IE) (Stand 11/2013) für die Arbeit mit IOS2000 und DIALOG

OLXTeamOutlook 1.5 für Outlook 2003, 2002/XP, 2000 und 97/98

Reporting Services und SharePoint 2010 Teil 1

IBM Software Demos Tivoli Provisioning Manager for OS Deployment

INDEX. Öffentliche Ordner erstellen Seite 2. Offline verfügbar einrichten Seite 3. Berechtigungen setzen Seite 7. Öffentliche Ordner Offline

Professionelle Seminare im Bereich MS-Office

HOWTO Update von MRG1 auf MRG2 bei gleichzeitigem Update auf Magento CE 1.4 / Magento EE 1.8

Windows 8.1. In 5 Minuten Was ist alles neu? Word

Folgeanleitung für Klassenlehrer

WEBSEITEN ENTWICKELN MIT ASP.NET

Einfach wie noch nie. Der mypackage-ansatz. Ihre Lösung zur automatisierten Client-Bereitstellung. mypackage im Überblick

Plugins. Stefan Salich Stand

TeamSpeak3 Einrichten

Zimmertypen. Zimmertypen anlegen

NEWSLETTER // AUGUST 2015

Access [basics] Rechnen in Berichten. Beispieldatenbank. Datensatzweise berechnen. Berechnung im Textfeld. Reporting in Berichten Rechnen in Berichten

Er musste so eingerichtet werden, dass das D-Laufwerk auf das E-Laufwerk gespiegelt

ÖKB Steiermark Schulungsunterlagen

Windows Vista Security

WOT Skinsetter. Nun, erstens, was brauchen Sie für dieses Tool zu arbeiten:

4D Server v12 64-bit Version BETA VERSION

MORE Profile. Pass- und Lizenzverwaltungssystem. Stand: MORE Projects GmbH

Anleitung zur Webservice Entwicklung unter Eclipse

Anleitung: Sammel-Rechnungen für Lizenzen bei Swiss Basketball

Datensicherung. Beschreibung der Datensicherung

GEORG.NET Anbindung an Ihr ACTIVE-DIRECTORY

Lokale Installation von DotNetNuke 4 ohne IIS

Kommunikations-Management

5.2 Neue Projekte erstellen

Stammdatenanlage über den Einrichtungsassistenten

Viele Bilder auf der FA-Homepage

Wie richten Sie Ihr Web Paket bei Netpage24 ein

Änderungsbeschreibung HWS32 SEPA Überweisungen

ARCO Software - Anleitung zur Umstellung der MWSt

DOKUMENTATION VOGELZUCHT 2015 PLUS

tentoinfinity Apps 1.0 EINFÜHRUNG

Erstellen von x-y-diagrammen in OpenOffice.calc

Datenbank-Verschlüsselung mit DbDefence und Webanwendungen.

Kurzeinführung Excel2App. Version 1.0.0

Installation der SAS Foundation Software auf Windows

Der Kalender im ipad

Konfiguration VLAN's. Konfiguration VLAN's IACBOX.COM. Version Deutsch

Was ist PDF? Portable Document Format, von Adobe Systems entwickelt Multiplattformfähigkeit,

Leitfaden zur Nutzung von binder CryptShare

Kleines Handbuch zur Fotogalerie der Pixel AG

Transkript:

SCHWERPUNKT Created for Tilman Börner (tilman.boerner@dotnetpro.de) - 2533 on 28.03.2013 14:19:41 MVVM für JavaScript-UIs Knockout für den Rivalen MVVM ist das gängige Architektur-Pattern für WPF und Silverlight. Und wenn man HTML5 verwendet? Dann nimmt man die JavaScript-Bibliothek KnockoutJS. Sie realisiert das MVVM-Pattern in einem clientseitigen Datenmodell. Auf einen Blick Die Rolle von JavaScript haben viele Entwickler lange Zeit unterschätzt. Java- Script galt als zweitklassige Sprache, geprägt von Sicherheitsrisiken und nur im Notfall zu verwenden. Doch die Welt hat sich gewandelt: Mit Ajax wurden Webseiten dynamischer und benutzerfreundlicher, und spätestens seit HTML5 ist klar: JavaScript gehört die Zukunft bei der Entwicklung interaktiver Webapplikationen, Das MVVM-Pattern Matthias Jauernig (M.Sc.) wenn RIA-Technologien (Rich Internet Applications) wie Silverlight und Flash keine Option arbeitet als Senior expert (.NET) bei der SDX AG in Frankfurt am Main. SDX unterstützt sind. Und darüber hinaus dringt JavaScript mit Enterprise-Unternehmen bei der Umsetzung von Businesslösungen in den Kompetenzfeldern Custom Development, Business Intelligence, Cloud Development sowie Projektmanagement (Scrum). Sie erreichen ihn Projekten wie Node.js [1] oder dem Entwicklungsmodell von Windows 8 immer mehr auch in Bereiche vor, die bisher klassischen Programmierplattformen wie.net oder Java vorbehalten waren. Da verwundern auch nicht Empfehlungen, Entwickler sollten verstärkt in das Erlernen von JavaScript investieren [2]. über den Blog von SDX unter Die steigende Popularität von JavaScript (JS) http://flurfunk.sdx-ag.de oder bewirkt zudem, dass immer mehr Frameworks seinen privaten Blog unter entstehen, die die Entwicklung JS-lastiger Webapplikationen vereinfachen wollen. Prominen- www.minddriven.de. testes Beispiel ist hier wohl jquery, das sich steigender Beliebtheit erfreut und mittlerweile auch Inhalt Bei der Änderung von Werten von Microsoft aktiv unterstützt und weiterentwickelt wird. jquery bietet eine eingängige Syntax automatisch das HTML-UI aktualisieren. und ermöglicht die einfache Auswahl und Manipulation von Elementen des DOM (Document Arrays überwachen und in der View darstellen. Object Model). Durch die Verfügbarkeit zahlloser Das UI an einen SQL Server anbinden. Plug-ins und die Browserunabhängigkeit von Grundlagen jquery steigt die Produktivität bei der Entwicklung interaktiver Webapplikationen deutlich. Vom Model-View-Controller Was jquery nicht oder nur ungenügend unterstützt, Listing 1 zum Model-View-ViewModel, www.dotnetpro.de/ A0808MVVM dnpcode A1111Knockout ist die Erstellung komplexer, dynamischer UIs auf der Basis zugrunde liegender Daten, zum Beispiel von einem Server. Entwickler müssen UI- Updates bei der Änderung von Daten manuell durchführen, indem Event-Handler für die jeweilige Aktion ausgeführt werden. Gerade bei nichttrivialen UIs mit vielen Abhängigkeiten der Daten untereinander steigt die Komplexität stark an. Die Entwicklung mit jquery skaliert hier nicht gut. Auch die Bindung mittels Ajax empfangener Daten an das UI kann mit jquery standardmäßig nur imperativ und manuell erfolgen. Schließlich können Daten auch nur relativ umständlich ausgewertet und weiterverwendet werden, etwa um var depot = function () { ; sie an einen Server zurückzusenden. jquery bietet insgesamt kein konsistentes Datenmodell, auf dem das JavaScript-UI aufbauen könnte. Ein solches clientseitiges Datenmodell liefert die Java- Script-Bibliothek KnockoutJS [3], die das MVVM- Pattern in JavaScript nutzbar macht. Dieser Artikel stellt die Version 1.3.0 von KnockoutJS vor. Das MVVM-Pattern [4] hat seine Ursprünge bei Microsoft, wo es als Spezialisierung des Presentation-Model-Patterns entwickelt wurde. MVVM stellt das gängige Architektur-Pattern bei der Entwicklung von Applikationen mit WPF beziehungsweise Silverlight dar. Seine Bestandteile Model, View und ViewModel werden nachfolgend kurz erläutert. Das Model beinhaltet das Datenmodell und die Businesslogik, die von der Anwendung zur Verfügung gestellt werden. Es repräsentiert den funktionalen Kern der Anwendung. Die View visualisiert Daten aus dem Model und stellt die Model-Funktionalität einem Endanwender im UI zur Verfügung. Das ViewModel als dritter Bestandteil bildet schließlich das Bindeglied zwischen View und Model. Das ViewModel bestimmt, welche Daten in welcher Form dem UI zur Verfügung stehen sollen und welche Aktionen ausgeführt werden können. Das ViewModel ist somit eine logische Abstraktion des UI auf der Basis von Daten und Operationen auf diesen. Einfaches ViewModel für Depotinformationen. this.id = ko.observable(0); this.depotname = ko.observable("neues Depot"); this.location = ko.observable("bank"); this.budget = ko.observable(0); this.feeperyear = ko.observable(0); var depotmanagerviewmodel = new depot(); ko.applybindings(depotmanagerviewmodel); 34 11. 2011 www.dotnetpro.de

SCHWERPUNKT Silverlight und WPF zeichnen sich dadurch aus, dass sie durch die Bindung von Daten und Operationen des ViewModels an das UI eine hohe Produktivität beim Einsatz des MVVM-Patterns erreichen. Bei ihnen bietet sich MVVM geradezu an. Die Vorteile sind, dass die Daten nicht nur im UI (der View), sondern auch in einem logischen Datenmodell vorliegen, auf dem mit ihnen agiert werden kann. Das ViewModel spiegelt dadurch immer den aktuellen Zustand des UI wider. Mit diesem Ansatz ist in der Regel weniger Code zur Bereitstellung einer Funktionalität erforderlich. Durch die Trennung derverantwortlichkeiten steigt zudem die Testbarkeit und Wartbarkeit des Codes. Weiterhin skaliert MVVM sehr gut mit steigender Komplexität in der Abhängigkeit der Daten voneinander, sodass der Entwickler stets den Überblick behält und sich auf die Modellierung der Funktionalität konzentrieren kann. KnockoutJS in a nutshell KnockoutJS (KO) hat sich zum Ziel gesetzt, die Eigenschaften und Vorteile des MVVM- Patterns auf dem Client in JavaScript nutzbar zu machen. KO ist eine freie JavaScript- Bibliothek, die unter der MS-PL-Lizenz zur Verfügung steht. Sie wird von Steven Sanderson [5] entwickelt, der seit November 2010 bei Microsoft im Web-Platform-Team beschäftigt ist. Die Bibliothek kann direkt von der Website [3] heruntergeladen oder per NuGet (Paket knockoutjs) installiert werden. Die Grundidee von KO ist es, das UI mit seinen Abhängigkeiten zum Datenmodell weniger über Verhalten, sondern vielmehr über den Zustand der zugrunde liegenden Daten zu modellieren. Grundelement ist eine deklarative 2-Wege-Bindung von Daten an das UI. Dies soll zu einem UI führen, das jederzeit den Zustand des zugrunde liegenden Datenmodells widerspiegelt und mit diesem konsistent gehalten wird. KO ist cross-browser-kompatibel und unterstützt neben allen aktuellen Browsern auch ältere wie den IE6. Werte beobachten Das MVVM-Pattern wird in KO clientseitig realisiert. Bei derview handelt es sich dann um den DOM-Baum einer HTML-Seite, der die Daten darstellt. Das ViewModel ist hingegen ein in JavaScript definiertes Datenobjekt, das KO an die View bindet. Dieser Artikel zeigt die wichtigste Funktionalität von KO anhand eines durchgehenden Beispiels, das sukzessive erweitert Listing 2 Deklarative Bindung des ViewModels an die View. <div id="depotmanager"> <h1>depot Manager (für '<span data-bind="text: depotname()!== ''? depotname() : '-'"></span>')</h1> <div><label for="inputdepotname">depotname</label></div> <input id="inputdepotname" type="text" data-bind="value: depotname, valueupdate: 'afterkeydown'" /> <div><label for="inputlocation">ort</label></div> <input id="inputlocation" type="text" data-bind="value: location" /> <div><label for="inputbudget">budget</label></div> <input id="inputbudget" type="text" data-bind="value: budget" /> <div><label for="inputdepotcosts">depotgebühr</label></div> <input id="inputdepotcosts" type="text" data-bind="value: feeperyear" /> </div> wird. Dabei handelt es sich um die Anforderung eines Fachbereichs, der sich einen vereinfachten Manager für Finanzdepots wünscht. Der Nutzer soll ein Budget vorgeben können, das ihm zur Verfügung steht, und dann ein Depot mit eigenen Positionen füllen können. Die Summe der Kosten dieser Positionen soll innerhalb des Budgets bleiben. Abbildung 1 stellt die Umsetzung dieser Funktionalität dar. Zunächst sollen dem Fachbereich die grundlegenden Depotverwaltungsfunktionen bereitgestellt werden. Hierzu muss ein Entwickler als Erstes ein ViewModel in JavaScript anlegen. Dabei sind viele Notationen möglich, zum Beispiel die klassische Object-Literal-Notation [6]. Dieser Artikel verwendet Konstruktor-Funktionen [7], mit denen mehrere Objekte gleichen Typs mit Properties und Funktionen erzeugt werden können. Listing 1 zeigt die Definition eines einfachen depot-objekts und die Verwendung einer Instanz davon als ViewModel depot- ManagerViewModel. Das Listing verdeutlicht einige Grundlagen von KO. Zunächst gibt es das ko-objekt. Hierüber lässt sich die komplette Funktionalität von KnockoutJS ansprechen. Zentral ist die Funktion ko.observable(), mit der sich Daten-Properties auf dem ViewModel definieren lassen, die KO auf Änderungen hin überwacht. Das bedeutet, dass alle UI-Felder, die von einer solchen Property abhängen, bei einer Änderung der Property automatisch aktualisiert werden, was später noch an praktischen Beispielen gezeigt wird. Wird ko.observable() ein Wert übergeben, so dient dieser als Initialwert der Property. Wurde das ViewModel erzeugt, muss es noch an das UI gebunden werden. Dies geschieht über ko.applybindings(), wie in Listing 1 zu sehen ist. Bindung an das UI Doch wie kommen die Daten in die View? Dies geschieht durch deklarative Bindings von HTML-Elementen über das data-bind- Attribut. Allgemein sind die data-attribute Teil der HTML5-Spezifikation und erlauben Frameworks wie jquery und KnockoutJS die Definition eigener Attribute, zum Beispiel data-val zur Validierung (jquery) oder data-bind zur Datenbindung. data-bind lassen sich mehrere Optionen übergeben. Wichtig ist zunächst die Information, welche Property desviewmodels an welches Attribut des HTML-Elements gebunden werden soll. Eine Bindung des Depotnamens an den Wert eines input-controls ist somit beispielsweise über databind='value: depotname' möglich. Die angegebene Property muss auf dem ViewModel definiert sein, das an das UI gebunden wurde. Neben value unterstützt KO abhängig vom HTML-Element noch weitere Attribute, etwa text, css, visible oder enable. Eine vollständige Liste finden Sie in der KO-Dokumentation [8]. Sollten diese Attribute nicht den eigenen Anforderungen entsprechen, zum Beispiel weil ein Wert bei der Bindung animiert werden soll, lassen sich über Custom Bindings [9] eigene Attribute definieren. www.dotnetpro.de 11. 2011 35

SCHWERPUNKT _ MVVM für JavaScript-UIs Listing 2 stellt die deklarative Bindung des bisherigen ViewModels an die View dar. Dabei wurden noch ein paar bisher nicht erwähnte Features eingebaut, um den Fachbereich zufriedenzustellen. Bei der Überschrift Depot Manager soll in Klammern noch der Depotname mit ausgegeben werden. Für leere Namen soll nur ein Bindestrich dargestellt werden. Dies lässt sich durch ein Binding erreichen, welches den gesamten JavaScript-Ausdruck enthält, der zur korrekten Darstellung benötigt wird: [Abb. 1] Der Depot Manager. [Abb. 2] View-Element bei Werteänderung automatisch aktualisieren. <span data-bind="text: depotname()!== ''? depotname() : '-'"></span> Damit wird ein wesentlicher Aspekt von KO ersichtlich: In Bindings ist die Angabe beliebiger JavaScript-Ausdrücke möglich, deren Term gebunden wird. Dies macht es einfach in Situationen, wo eine Daten-Property zum Beispiel individuell formatiert werden soll. Allerdings sollte diese Möglichkeit nicht überstrapaziert werden, da solcher Code als Zeichenkette in einem Attribut nur schwer zu debuggen ist und die Wartbarkeit leidet. Hier kann die Definition separaterview-properties auf demviewmodel sinnvoll sein, die die Anzeigelogik für eine Daten-Property kapseln. Alternativ sind Funktionen denkbar, die die Formatierung übernehmen und gegen die View mit der Property als Parameter gebunden werden. Weiterhin sind die Klammern bei depot- Name() zu beachten, die im obigen Java- Script-Ausdruck Verwendung finden. Generell gilt: Bei den ViewModel-Properties wie depotname handelt es sich um Objekte des Typs ko.observable(). Diese stellen Funktionen dar, sodass Klammern bei Bindings in der Regel angegeben werden müssen. Nur wenn das Binding lediglich aus der Property besteht, können die Klammern weggelassen werden. Optionen für valueupdate Die Binding-Option valueupdate legt fest, wann eine Werteänderung im UI die Anpassung des ViewModels nach sich ziehen soll. Mögliche Werte sind: change (Standard): Aktualisierung, wenn der Fokus des Controls verlassen wird. keyup:aktualisierung, wenn der Nutzer eine Taste loslässt. keypress: Aktualisierung, wenn der Nutzer eine Taste gedrückt hat. afterkeydown: Aktualisierung, sobald der Nutzer ein Zeichen eingibt (Echtzeit-Aktualisierung). [Abb. 3] UI-Update eines dependentobservables. Was wird mit diesem Binding erreicht? Immer wenn der Benutzer den Depotnamen ändert, wird dieser automatisch in die Überschrift übernommen, siehe Abbildung 2. DieView bleibt damit konsistent mit dem sie definierenden ViewModel, ohne dass der Entwickler dies ausprogrammieren muss. Intern führt KO eine Liste der Abhängigkeiten auf Observables, sodass bei einer Werteänderung nur die betroffenen View- Elemente aktualisiert werden. Durch die Binding-Option valueupdate in einer Textbox kann darüber hinaus definiert werden, wann eine Änderung des Benutzers in das ViewModel geschrieben werden soll. Standardmäßig ist dies der Fall, wenn der Benutzer die Textbox verlässt. Mit valueupdate: afterkeydown kann aber zum Beispiel definiert werden, dass direkt bei Eingabe eines Zeichens eine Aktualisierung stattfinden soll und Änderungen damit sofort auf ViewModel und View durchschlagen. Eine Liste der möglichen Werte für valueupdate liefert der Kasten Optionen für valueupdate. Totale Abhängigkeit Generell lassen sich zwei Arten von Observables unterscheiden. Die einen vom Typ ko.observable() haben Sie bereits kennengelernt. Sie erlauben die Bindung einfacher Werte an die View. Als zweite Ausprägung gibt es den Typ ko.dependentobservable(). Damit definierte ViewModel-Properties berechnen sich aus anderen Observables. Dabei lässt sich eine beliebige JavaScript- Funktion angeben, aus der KO automatisch die Abhängigkeiten extrahiert. KO sorgt dafür, dass Änderungen an den verwendeten Observables automatisch das dependentobservable und die gebundenen View-Elemente aktualisieren. Das gilt für beliebige Hierarchiestufen. Damit sind auch dependentobservables denkbar, die von weiteren dependentobservables abhängen, und so weiter. Für das Beispiel soll eine Property depot- Info angelegt werden, die sich aus dem Depotnamen und dem Ort des Depots zusammensetzt und bei Änderungen automatisch aktualisiert wird. Listing 3 zeigt die Definition dieser Property, die nachfolgend wieder in der Überschrift gebunden werden kann: <h1>depot Manager (<span databind="text: depotinfo"></span>)</h1> In Listing 3 wird depotinfo als dependentobservable durch eine einfache Java- Script-Funktion definiert. Durch Aufruf von bind(this) wird diese an das ViewModel gebunden, indem KO die Abhängigkeiten extrahiert und zur Nachverfolgung speichert. KO reagiert nun automatisch auf Änderungen an den in depotinfo verwendeten Observables depotname und location mit einer Aktualisierung von depotinfo und den gebundenen View-Elementen, siehe Abbildung 3. 36 11. 2011 www.dotnetpro.de

27 EURO FÜR IHRE KAFFEEPAUSE Sichern Sie sich jetzt die dotnetpro und profitieren Sie von Desktop-, Web- und Mobil-Anwendungen auf Basis von Microsoft.NET sowie von Programmiersprachen, Tools und Vorgehensweisen. Die dotnetpro erscheint zwölf Mal im Jahr. Holen Sie sich das dotnetpro-jahresabonnement. Sie sparen damit 27 Euro gegenüber dem Kauf am Kiosk und die dotnetpro kommt direkt zu Ihnen nach Hause. Also gleich abonnieren und dann ab ins Café! www.dotnetpro.de/abo

SCHWERPUNKT _ MVVM für JavaScript-UIs Listing 3 Einfaches zusammengesetztes Observable. var depot = function () {... this.depotinfo = ko.dependentobservable(function () { return "'" + this.depotname() + "' bei '" + this.location() + "'";.bind(this)); Generell sind solche abhängigen Observables in einer Vielzahl von Fällen nützlich, da mit ihnen berechnete Werte sehr einfach realisierbar sind, ohne dass sich ein Entwickler über das Tracking der Abhängigkeiten Gedanken machen muss. Meist treten sie in der Form read only auf, sie können allerdings auch als writeable definiert werden, was später noch gezeigt wird. Überwachte Collections Mit der bisherigen Umsetzung kann der Fachbereich bereits grundlegende Depotdaten bearbeiten, doch sofort kommt die nächste Anforderung: Es soll die Möglichkeit geschaffen werden, einzelne Positionen des Depots zu verwalten, mit denen das Budget belastet wird. Neben einzelnen Werten erlaubt KO auch die Überwachung von Collections aus Datenelementen mit dem Typ ko.observablearray(). KO reagiert mit View- Updates auf das Hinzufügen und Löschen von Array-Elementen sowie auf das Ändern der Anordnung der Array-Elemente. Listing 4 Datenmodell für Depotpositionen. var positiontypes = [ {name: "Fix", value: 0, {name: "Pro Stück", value: 1]; var positioncost = function () { this.value = ko.observable(0); this.type = ko.observable (positiontypes[0]); ; var depotposition = function () { this.id = ko.observable(0); this.name = ko.observable ("Neues Depotelement"); this.quantity = ko.observable(1); this.costs = new positioncost(); ; Listing 4 zeigt das grundlegende Datenmodell in JavaScript zur Umsetzung der Anforderung. Eine zentrale Rolle kommt dem Objekt depotposition zu, das die Daten einer Position des Depots beinhaltet. Eine Position könnte zum Beispiel die Contoso-Aktie sein. Neben einem Namen kann die Stückzahl der verwalteten Positionselemente eingetragen werden, beispielsweise 10 Aktien. Weiterhin lassen sich die Kosten angeben, die durch diese Position entstehen. Zur Kapselung existiert das Objekt positioncost, worüber der Kostenbetrag und der Typ der Kosten angegeben werden. Die Typen werden im Array positiontypes geführt. Kosten sind fix für die komplette Position oder pro Stück der Position, etwa pro Aktie, möglich. Listing 5 stellt dar, wie ein Array aus Depotpositionen inklusive Funktionalität zum Hinzufügen und Entfernen im Objekt depot angelegt werden kann. Die Property positions ist als ko.observablearray() definiert. Als initialen Parameter bekommt dieses bereits eine neue Depotposition zugewiesen, sodass neue Depots initial stets eine leere Position besitzen. Kollektive Bindung Die Darstellung von überwachten Arrays lässt sich konfigurieren, indem man angibt, wie die Einzelelemente des Arrays dargestellt werden sollen. So ist auch das Rendern beim Hinzufügen neuer Elemente zu der Collection für KO kein Problem. In Listing 5 KO vor Version 1.3 war dies nur durch separate Templates möglich, wie etwa die jquery Template Engine [10]. Dies hatte zur Folge, dass das Template immer getrennt vom eigentlichen Markup in einem separaten <script>-tag definiert werden musste, um dann über ein Template-Binding gebunden zu werden. Seit Version 1.3 ist diese explizite Definition eines Templates nicht mehr nötig. Stattdessen können Templates für Array-Elemente direkt (inline) im HTML- Markup definiert werden. Listing 6 zeigt dies anhand des observablearrays positions. Interessant ist vor allem die Zeile <tbody data-bind="foreach: positions">. Damit wird das positions-array an den Tabellen-Body gebunden. foreach gibt an, dass jeglicher Markup innerhalb des <tbody>-knotens zur Darstellung eines Array-Elements verwendet wird. Dabei ändert sich der Binding- Kontext vom ViewModel auf das jeweilige Array-Element. Somit können dessen Properties innerhalb des impliziten Templates direkt mit data-bind gebunden werden. Interessant sind zwei weitere Aspekte von Listing 6. Zum einen zeigt es, wie costs.type an eine Combobox (select) gebunden wird. Mit options und optionstext wird angegeben, woher der Wertevorrat der Combobox stammt. Mit value wird definiert, welche ViewModel-Property gebunden werden soll und bei Änderungen aktualisiert wird. Zum anderen zeigt Listing 6 für den Löschen-Link das Binden eines DOM-Elements an eine Funktion auf dem ViewModel. Dem Binding click kann eine Funktion übergeben werden, die ausgeführt werden soll. In diesem Beispiel wird die Funktion removeposition mit dem Parameter $data aufgerufen. KO interpretiert $data als die Depotposition, die gerade gerendert wird. Etwas unschön ist, dass aufgrund der Übergabe eines Parameters die Bindung der removeposition-funktion in eine separate Funktion gekapselt werden muss. Ein Umstand, den KO hoffentlich in einer zukünftigen Version beseitigt. Definition der Positionen im Depotdatenmodell. var depot = function () {... this.positions = ko.observablearray([new depotposition()]); this.addposition = function () { this.positions.push(new depotposition()) ; this.removeposition = function (position) { this.positions.remove(position) ; ; 38 11. 2011 www.dotnetpro.de

SCHWERPUNKT Listing 6 Tabelle mit Inline-Template zur Anzeige eines Arrays. [Abb. 4] Ein Array von Depotpositionen an das UI binden. Um die Operationen auf den Positionen zu vervollständigen, definiert Listing 6 noch einen Button, dessen click-event von KO an die addposition()-funktion desview- Models gebunden ist. Und schon stehen die Positionen an die Tabelle gebunden und mit entsprechenden Operationen zum Hinzufügen und Löschen zur Verfügung. Durch die implizite 2-Wege-Bindung von KO wird die View zu jedem Zeitpunkt entsprechend den Array-Daten aus dem ViewModel dargestellt. Erstellt ein Nutzer eine Position oder löscht er eine bestehende Position, so spiegeln sich diese Änderungen direkt in der View wider, siehe Abbildung 4. Funktionalität leicht gemacht Dem Fachbereich gefallen die neuen Funktionen. Doch als er die aktuelle Version benutzt, kommen ihm noch ein paar Ideen fürverbesserungen. So möchte er die Summe der Kosten einer Position und dann schließlich auch über alle Positionen stets aktuell berechnet und angezeigt haben. Zudem besteht der Wunsch, dass keine neuen Positionen mehr hinzugefügt werden dürfen, wenn das Budget von den aktuellen Kosten aufgebraucht wurde. Uh, das wird teuer, schmunzelt der KO-Entwickler und macht sich ans Werk. Diese Funktionalität mit KO abzubilden, stellt sich als recht unproblematisch heraus und kann mit wenigen zusätzlichen dependentobservables und entsprechenden Bindings realisiert werden. Als Erstes soll es um die Anzeige der Positionssummen gehen. Für die Kosten der Einzelpositionen definieren Sie auf dem Objekt depotposition ein dependentobservable namens subtotal, das sich in Abhängigkeit von der Stückzahl und den angegebenen Kosten berechnet. Hierbei wird auch der Kostentyp berücksichtigt, wodurch die Kosten fix oder pro Stück ermittelt werden. Die Gesamtsumme kann nun aus den Zwischensummen aller Depotpositionen und <table> <thead> <tr> <th>bezeichnung</th> <th>stück</th> <th>kosten</th> <th>aktion</th> </tr> </thead> <tbody data-bind="foreach: positions"> <tr> <td><input data-bind="value: name" /></td> <td><input data-bind="value: quantity" /></td> <td class="multi"> <input data-bind="value: costs.value" /> <select data-bind="options: positiontypes, optionstext: 'name', value: costs.type"></select> </td> <td><a href="#" data-bind='click: function() { depotmanagerviewmodel.removeposition($data) '>Löschen</a></td> </tr> </tbody> </table> <input type="button" value="neue Position" data-bind="click: addposition " /> der globalen Depotgebühr berechnet werden. Dazu wird ein dependentobservable grandtotal auf dem Objekt depot definiert, das die Berechnung durchführt. Listing 7 zeigt den JavaScript-Code dafür. Die Bindung an das UI erfolgt analog zu den bisherigen Beispielen über data-bind='text: ' an den entsprechenden Stellen im Tabellen- Zeilen-Template beziehungsweise dem Footer der Tabelle und wird hier nicht gezeigt. Der interessierte Leser kann den aktuellen Stand unter [11] ansehen. [Abb. 5] Depotkosten automatisch aktualisieren. Was wurde allein durch die Definition dieser Properties und die Bindung an das UI erreicht? Immer wenn sich ein Kostenfaktor ändert, sei es durch Änderung der Positionskosten, der Depotgebühr oder durch das Löschen einer Position, aktualisiert KO die von diesen Werten abhängigen Properties subtotal beziehungsweise grand- Total automatisch, siehe Abbildung 5. Diese Änderungen werden direkt in der View dargestellt, ohne dass der Entwickler dies über zahlreiche Event-Trigger und ver- [Abb. 6] Budget überschritten Button deaktiviert. www.dotnetpro.de 11. 2011 39

SCHWERPUNKT _ MVVM für JavaScript-UIs Listing 7 Properties zur Berechnung von Depotkosten. var depotposition = function () {... this.subtotal = ko.dependentobservable(function () { var quantity = parseint(this.quantity(), 10); var costs = parsefloat(this.costs.value()); var coststype = parseint(this.costs.type().value, 10); return (coststype === 0)? costs : (quantity * costs);.bind(this)); ; var depot = function () {... this.grandtotal = ko.dependentobservable(function () { var fee = parsefloat(this.feeperyear()); var total =!isnan(fee)? fee : 0; for (var i = 0; i < this.positions().length; i++) { var subtotal = this.positions()[i].subtotal(); total += (!isnan(subtotal)? subtotal : 0); return total;.bind(this)); this.isbudgetavailable = ko.dependentobservable(function () { return this.budget() - this.grandtotal() > 0;.bind(this)); ; Listing 8 Formatierung mit dependentobservable. depotmanagerviewmodel.budgetformatted = ko.dependentobservable({ read: function () { return depotmanagerviewmodel.budget() + " ";, write: function (value) { var floatvalue = parsefloat(value.replace(/[^\.\d]/g, '')); depotmanagerviewmodel.budget(!isnan(floatvalue)? floatvalue : 0); ); Listing 9 Formatierungslogik in einen Formatter auslagern. var formatters = { currencyeuro: function (value) { return { read: function () { return ko.utils.unwrapobservable(value).tofixed(2).replace('.', ',') + " ";, write: function (newvalue) { if (ko.iswriteableobservable(value)) { var floatvalue = parsefloat (newvalue.replace(',','.').replace(/[^\.\d]/g, '')); value(!isnan(floatvalue)? floatvalue : 0); ; schachtelten Code manuell implementieren muss. Bei der Abbildung solcher Abhängigkeiten kann KO seine Fähigkeiten in beeindruckender Weise entfalten. Die zweite Anforderung lautete, dass keine Positionen hinzugefügt werden dürfen, wenn das Budget aufgebraucht ist. Dies umzusetzen ist trivial. Dafür genügt ein Binding der enable-eigenschaft des Hinzufügen-Buttons an die Depotkosten und das Depotbudget: <input type="button" value="neue Position" data-bind="click: addposition, enable: budget() - grandtotal() > 0" /> Da Logik direkt in den Bindings nicht gerade sauber ist, kann diese auch in eine eigene dependentobservable-property auf depot ausgelagert werden. Damit wird die Property zudem in anderen Teilen des UI wiederverwendbar, zum Beispiel zur Anzeige eines Hinweises, dass das Budget verbraucht ist. Und fertig ist die Funktionalität! Abhängig davon, ob die Kosten das Budget übersteigen, kann der Benutzer Positionen hinzufügen oder nicht, siehe Abbildung 6. Format-Sache Der Fachbereich ist sehr angetan von der rasch umgesetzten Funktionalität. Nur eine Sache stört ihn noch: Diese Geldbeträge, können die auch mit einem Euro-Zeichen formatiert dargestellt werden? Können sie, sagen Sie und machen sich an die Umsetzung. In manchen Fällen sollen nicht die Rohdaten des ViewModels in der View angezeigt werden, sondern spezielle Formatierungen davon. In Silverlight/WPF lässt sich eine solche Konvertierungslogik mit einer Implementierung des Interfaces IValue- Converter definieren. In KO gibt es eine solche Lösung zunächst nicht. Trivialerweise ließen sich spezielle ViewModel-Properties anbieten, welche sowohl formatierte Werte an die View reichen als auch formatierte Werte aus der View lesen und in die richtige Daten-Property des ViewModels schreiben. Dies ist in KO über schreibbare dependentobservables möglich. Listing 8 zeigt dies anhand des Depotbudgets. Dabei wird ausgenutzt, dass man einem dependentobservable zwei Funktionen read und write übergeben kann, wobei read zur Formatierung in der View aufgerufen wird und write zur Rückformatierung, wenn sich der Wert in der View ändert. Nun funktioniert diese Lösung zwar, ist allerdings nicht sehr elegant, schließlich ist 40 11. 2011 www.dotnetpro.de

SCHWERPUNKT die Formatierungslogik in der jeweiligen Property enthalten und muss für jede weitere Property dupliziert werden. Eine bessere Alternative stellt die Auslagerung in separate Formatierungsobjekte dar, die im Binding über eine neue Option formatter gebunden werden können. Diese Lösung stammt von [12] und soll an dieser Stelle nur erwähnt werden, da die Komplexität der Umsetzung recht hoch ist. Im Beispiel des Depotbudgets kann der ursprüngliche Datenwert dann direkt weiterverwendet werden, das Binding des input-controls ändert sich wie folgt: <input id="inputbudget" type="text" data-bind="formattedvalue: budget, formatter: formatters.currencyeuro" /> budget wird nun über die Option formattedvalue gebunden, und der Formatter currencyeuro übernimmt die eigentliche Formatierung. Die Logik zur Formatierung von Euro-Werten entspricht grob der zuvor genannten Lösung und ist in Listing 9 dargestellt. Im Hintergrund ist noch weitere Logik zur Definition undverarbeitung der neuen Binding-Optionen erforderlich, siehe [12]. Die bis hierhin umgesetzte Funktionalität inklusive Formatierung aller Geldbeträge können Sie unter [13] erforschen. Generell steht damit ein gutes Mittel bereit, um erweiterbare ausgelagerte Formatierungslogik zu definieren. Bleibt zu wünschen, dass diese Möglichkeit in ähnlicher Form direkten Einzug in eine kommende KO-Version erhält. Daten, Daten, Daten Die umgesetzte Logik überzeugt den Fachbereich, der seine Anforderungen an das UI vollständig abgedeckt sieht. Was ihm zum vollen Glück noch fehlt, ist die Nutzung einer zentralen SQL-Datenbank als Datenbasis. Auch das ist machbar. Generell lassen sich Daten mit KO wie in JavaScript üblich per Ajax von einem Webserver holen. Über die Formatierung dieser Daten muss sich ein Entwickler keine Gedanken machen, wenn die Bindings an das Model der abgerufenen Daten mit KO korrekt gesetzt sind. Im Beispiel sollen die Daten von ASP.NET MVC 3 von einer Controller-Action GetDepot() abgerufen werden, die als Resultat Daten im JSON-Format zurückgibt. KO selbst verfügt leider über keine Funktionalität zum Umgang mit Daten via Ajax. Hier müssen Sie auf Standardfunktionalität wie zum Beispiel die Ajax-Möglichkeiten von jquery zurückgreifen. Generell ergibt Listing 10 Depotdaten abrufen und umwandeln. Mapping Die Lösung liegt in der Definition einer Mapping-Option, die das Mapping auf depotposition übernimmt. Listing 12 zeigt das so angepasste Mapping der abgerufenen Daten. In den Mapping-Optionen ist ein individuelles Mapping der Property positions vorgesehen. Immer wenn eine Depotposition bei dem Mapping-Vorgang erzeugt wird (create), wird ein neues depotvar depotmanagerviewmodel; $(document).ready(function () { $.getjson('/depotmanager/getdepot', function (depotdata) { depotmanagerviewmodel = new depot(); ko.mapping.fromjs(depotdata, {, depotmanagerviewmodel); ); ); sich dadurch allerdings folgendes Problem: Angenommen, es werden Daten als JSON- Objekt abgerufen. Die Properties eines solchen Datenobjekts werden nicht als Observables geliefert, was aber nötig ist, damit KO diese an das UI binden und überwachen kann. Es ist natürlich möglich, diese Daten händisch zu mappen und so die entsprechenden Observables zu definieren, doch das ist fehleranfällig und aufwendig. Zum Glück gibt es aber das KO-Plug-in knockout.mapping [14] (NuGet-Paket Knockout.Mapping), welches diese Aufgabe übernehmen kann. Automatisiert wandelt es jede Property eines Datenobjekts in ein ko.observable() um, sodass dieses an das UI gebunden werden kann. Zudem lassen sich Optionen angeben, mit denen sich das Mapping individuell gestalten lässt, falls dies erforderlich sein sollte. Das Mapping-Plug-in stellt die Funktion ko.mapping.fromjs(data, options, existing) zur Verfügung, mit der ein Datenobjekt data in ein ViewModel überführt werden kann. Standardmäßig wird dabei ein neues ViewModel erzeugt. Durch Angabe des dritten Parameters existing ist es allerdings möglich, die Daten in ein bestehendes ViewModel zu schreiben. Genau diese Funktionalität ist für das Beispiel erforderlich. Sie haben bereits ein ViewModel vom Typ depot, das über einige Funktionalität in Form von dependent- Listing 11 JSON-Daten zurückgeben. Observable-Properties verfügt, die Sie weiterverwenden möchten. Listing 10 zeigt prinzipiell, wie nach dem Laden des DOM-Baums mit jquery Daten von einer Datenquelle abgerufen und mit KO in ein bestehendes ViewModel geschrieben werden können. Dabei wird die ASP.NET-MVC-3-Controller-Action GetDepot() aufgerufen, die aus einer Datenquelle gelesene Depotdaten als JsonResult zurückgibt, siehe Listing 11. Leider funktioniert diese Lösung noch nicht vollständig. Bei der Ausführung ergibt sich das Problem, dass die Property subtotal auf den gelesenen Depotpositionen des ViewModels nicht definiert ist. Der Grund dafür ist die Erzeugung des Arrays der Positionsdaten durch das Mapping- Plug-in. subtotal ist auf dem Objekt depot- Position definiert, das Mapping-Plug-in weiß aber von diesem Objekt nichts und befüllt ein separates Datenobjekt, das wiederum nichts von subtotal weiß. [HttpGet] public JsonResult GetDepot() { return Json(DataProvider.GetDepot(), JsonRequestBehavior.AllowGet); www.dotnetpro.de 11. 2011 41

SCHWERPUNKT _ MVVM für JavaScript-UIs Listing 12 Individuelles Mapping für Depotdaten. var depotmanagerviewmodel; $(document).ready(function () { $.getjson('/depotmanager/getdepot', function (depotdata) { var mapping = { 'positions': { create: function (options) { var position = new depotposition(); return ko.mapping.fromjs(options.data, {, position); depotmanagerviewmodel = new depot(); ko.mapping.fromjs(depotdata, mapping, depotmanagerviewmodel); ); ); Listing 13 Ein Depot speichern. var unmapped = ko.mapping.tojs(depotmanagerviewmodel); $.ajax({ type: 'POST', url: '/DepotManager/SaveDepot', data: JSON.stringify(unmapped), contenttype: 'application/json; charset=utf-8' ); Position-Objekt angelegt, dessen Properties ebenfalls über das KO-Mapping-Plugin befüllt werden. Mit diesem Mapping ist es möglich, die von ASP.NET MVC 3 abgerufenen Daten in ein KO-ViewModel zu überführen und mit der bestehenden Funktionalität an das UI zu binden. Das KO-Mapping-Plug-in ist flexibel genug, mithilfe vielfältiger Optionen den Mapping-Vorgang individuell zu gestalten. Die Persistierung eines ViewModels durch einen Server kann ebenfalls über das KO-Mapping-Plug-in erfolgen. Rufen Sie dazu die Methode ko.mapping.tojs(view- Model) auf, welche die aktuellen Daten einesviewmodels in ein normales JavaScript- Objekt überführt. Dieses kann leicht per JSON an einen Server übersendet werden. Listing 13 zeigt, wie das Depot-ViewModel auf diese Art serialisiert und mittels jquery an eine Servermethode SaveDepot() gesendet werden kann. Bei dieser Servermethode handelt es sich um eine ASP.NET-MVC-3- Controller-Action mit der Signatur SaveDepot(Depot depot). ASP.NET MVC 3 erkennt aus dem übergebenen ContentType application/json, dass es sich bei den gesendeten Daten um ein JSON-Objekt handelt, und kann daraus automatisch ein Objekt der Model-Entität Depot deserialisieren. Fazit Eine steigende Anzahl von Webanwendungen verfügt über eine hohe Interaktivität auf der Basis von JavaScript. Gleichermaßen steigen die Ansprüche der Nutzer und die Komplexität der umzusetzenden Anforderungen. Dies macht ein konsistentes Datenmodell auf dem Client immer wichtiger, auf dessen Basis das UI und Aktionen darauf gestaltet werden können. Dieser Artikel hat gezeigt, dass KnockoutJS ein solches Datenmodell auf Basis des bekannten MVVM-Patterns in Java- Script liefern kann. Es beinhaltet Features wie deklarative 2-Wege-Bindings, die Verfolgung von Abhängigkeiten und die automatische Aktualisierung des UI bei Änderungen des Datenmodells. Dies erlaubt Entwicklern eine hohe Produktivität, ohne manuell auf Änderungen am ViewModel reagieren zu müssen, was bei einer hohen Komplexität zu sehr verschachteltem Code führt. KO erlaubt es Entwicklern, auf Basis des ViewModels viel direkter ihre eigentliche Absicht zum Ausdruck zu bringen. Als Framework steckt KnockoutJS noch eher in den Kinderschuhen. Die gebotenen Features sind bereits beeindruckend, allerdings fehlt einem Entwickler weitergehende Funktionalität im Umgang mit dem Datenmodell. Dazu gehören das Laden und Speichern eines ViewModels ebenso wie (Datentyp-)Validierungen auf der Clientseite. Insgesamt lässt sich sagen, dass KnockoutJS einen mächtigen Ansatz zur Datenmodellierung in JavaScript auf Basis von MVVM darstellt. Derzeit entstehen mehrere Frameworks, die ähnliche Ansätze realisieren und das MVC-Konzept in JavaScript umsetzen, zum Beispiel BackboneJS [15] oder AngularJS [16]. Wird an KO konsequent weitergearbeitet, kann es unter diesen Frameworks eine wichtige Rolle bei der Entwicklung der nächsten Generation dynamischerwebanwendungen spielen. [ml] [1] Node.js-Website, http://nodejs.org/ [2] Mike Loukides, Why a JavaScript hater thinks everyone needs to learn JavaScript in the next year, www.dotnetpro.de/sl1111knockout1 [3] KnockoutJS-Homepage, http://knockoutjs.com/ [4] Das MVVM-Pattern, http://en.wikipedia.org/ wiki/model_view_viewmodel [5] Steven Sandersons Website, http://blog.stevensanderson.com/ [6] SelfHtml, Object-Literale, www.dotnetpro.de/sl1111knockout2 [7] SelfHtml, Eigene Objekte mittels Konstruktoren, www.dotnetpro.de/sl1111knockout3 [8] KnockoutJS-Dokumentation, www.dotnetpro.de/sl1111knockout4 [9] Custom Bindings, www.dotnetpro.de/sl1111knockout5 [10] jquery-templates, www.dotnetpro.de/sl1111knockout6 [11] jsfiddle, Anzeige der Depotkosten, www.dotnetpro.de/sl1111knockout7 [12] jsfiddle, Zentrale Formatter-Funktionalität, www.dotnetpro.de/sl1111knockout8 [13] jsfiddle, Finale DepotManager-Funktionalität, www.dotnetpro.de/sl1111knockout9 [14] Mapping-Plug-in für KnockoutJS, www.dotnetpro.de/sl1111knockout10 [15] BackboneJS, www.dotnetpro.de/sl1111knockout11 [16] AngularJS, http://angularjs.org 42 11. 2011 www.dotnetpro.de