Jetzt Knockout.js lernen: Formulare und Listen binden

Aus der WPF-Welt kommend ist gerade das Thema MVVM und Datenbindung sehr interessant für mich (weil dadurch auch etwas verwöhnt). Mit Knockout.js ist dies nun auch in JavaScript komfortabel möglich. Aufbauend auf den einführenden Beispielen und den von mir bereitgestellten Slides habe ich mich gefragt, wie nun der Umgang mit Listen und Formularen stattfinden kann und gleich in die Tasten gegriffen, um dies zu testen. Das Resultat ist ein Beispiel und ein paar Erkenntnisse, die ich teilen möchte.

Aufgabenstellung

Als Zielsetzung soll eine HTML-Seite ein Eingabeformular für die Eingabe von Buchinformationen anbieten und die eingegebenen Bücher in einer Liste darstellen. Zudem soll eine einfache Validierung stattfinden. Zu lösen ist die Problemstellung durch Datenbindung und somit einer sauberen Trennung zwischen ViewModel und View. Eine Serverkommunikation ist nicht angedacht.

ViewModels

Für die Umsetzung dieses Beispiels dachte ich an zwei verschiedene ViewModels. Eines, nennen wir es BookViewModel dient der Bindung an das Eingabeformular. Es kann sich selbst validieren und auf den ursprünglichen Stand zurücksetzen. Dieses ist nachfolgend abgebildet.

var BookViewModel = function() {

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

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

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

    this.errorMessage = ko.observable('');

    

    this.hasErrors = ko.computed(function() { return (this.errorMessage() != ''); }, this);

    

 

    this.reset = function() {

        this.title('');

        this.author('');

        this.pages('');

        this.errorMessage('');

    };

 

    this.validate = function() {

        this.errorMessage('');

 

        if (this.title().length === 0 || this.author().length === 0 || this.pages().length === 0) {

            this.errorMessage('All fields are required');

        }

    };

};

Die einzelnen Eigenschaften sind Observables (ko.observable), wodurch Benachrichtigungen über Änderungen an den Abonnenten (in Falle dieses Beispiels nur das UI) versendet werden und die dargestellten Werte (in der UI) immer aktuell sind.

Mit hasErrors soll zudem gesteuert werden, ob eine Fehlermeldung anzuzeigen ist. Die tatsächliche Meldung ist in errorMessage zu finden. Beides wird über die Funktion validate gesetzt.

Wichtig: Bezüglich der observierten Eigenschaften ist mir aufgefallen (und das verhält sich auch unter WPF/Silverlight so), dass das direkte Zuweisen von Werten zu Observables (diese sind als Funktionen abgebildet), diese natürlich überschreiben und somit keine Überwachung mehr stattfindet. Daher immer so darauf zugreifen: this.title(‘Der neue Wert’); Andernfalls kann die Suche nach dem Fehler schon mal etwas länger dauern …

Ein zweites ViewModel, BooksViewModel, beinhält eine Auflistung aller “gespeicherten” Bücher und stellt eine Instanz des ersten ViewModels via currentBook zur Verfügung. Damit neue Elemente hinzugefügt werden können, wird addItem angeboten. Hier wird im ersten Schritt die Validierung ausgeführt. Ist diese erfolgreich, wird das neue Buch in die Auflistung übernommen. Zusätzlich gibt es noch die Funktion clear. Diese setzt lediglich die Liste zurück.

Hinweis: Für die Darstellung der Liste habe ich auf das knockout.simpleGrid zurück gegriffen.

Und das ist das BooksViewModel:

var BooksViewModel = function(items) {

    this.items = ko.observableArray(items);

 

    this.currentBook = new BookViewModel();

 

    this.addItem = function(formData) {

        this.currentBook.validate();

        if (!this.currentBook.hasErrors()) {

            this.items.push({

                title: formData[0].value,

                author: formData[1].value,

                pages: formData[2].value

            });

            this.currentBook.reset();

        }

    };

 

    this.clear = function() {

        this.items.removeAll();

    };

 

    this.gridViewModel = new ko.simpleGrid.viewModel({

        data: this.items,

        columns: [

            {

            headerText: "Title",

            rowText: "title"},

        {

            headerText: "Author",

            rowText: "author"},

        {

            headerText: "Pages",

            rowText: "pages"}

        ],

        pageSize: 5

    });

};

Zu beachten ist hier, dass die Auflistung der Elemente via ko.observableArray(items) erledigt wird. Also auch überwachbare Arrays sind möglich.

Das BooksViewModel enthält zusätzlich Initialdaten; die Erstellung und Übergabe ist weiter unten zu sehen.

View

Die View selbst ist nun nicht mehr besonders aufregend. Im Endeffekt werden ein paar div-Elemente verwendet und eine form, die zur Eingabe der Daten dient.

<div id="booksCollection">

<form data-bind="submit: addItem" id="newEntryForm">

    <div class="formArea">

        <h1>Add new Entry</h1>

        

        <div class="errorBox" data-bind="visible: currentBook.hasErrors">

            ERROR: <span data-bind="text: currentBook.errorMessage"/>

        </div>

        

        <div id="row">

          <div id="col">

              Title

          </div>

          <div id="col">

            <input data-bind="value: currentBook.title" placeholder="Enter new book title"></input>    

          </div>

        </div>

        <div id="row">

          <div id="col">

              Author

          </div>

          <div id="col">

            <input data-bind="value: currentBook.author" placeholder="Enter new author"></input>

          </div>

        </div>

        <div id="row">

          <div id="col">

              Pages

          </div>

          <div id="col">

            <input data-bind="value: currentBook.pages" placeholder="Enter new pages count"></input>

          </div>

        </div>            

        

        <button type="submit">Add book</button>

        

    </div>

</form>

        

<div class="collectionArea">

    <h1>Books</h1>

    <div data-bind='simpleGrid: gridViewModel'></div>

    

    <button data-bind="click: clear">Clear books</button>

</div>

</div>

Eine interessante Geschichte ist die action des Formulares. Hier wird eine submit-Bindung auf die Funktion addItem erstellt. Wird nun der Submit-Button betätigt, werden die Daten aus dem Formular an diese Funktion übergeben.

Hinweis: Dies könnte auch ohne Formular und mit einer click-Bindung implementiert werden, da currentBook ohnehin die eingegebenen Daten enthält.

Das div-Element, das zur Anzeige der Auflistung herangezogen wird, enthält eine simpleGrid-Bindung auf gridViewModel (dieses wird über das SimpleGrid zur Verfügung gestellt und enthält die Auflistung aller Bücher).

Damit die angezeigte Liste zurückgesetzt werden kann, wird über das ViewModel die Funktion clear angeboten. Damit diese aufgerufen wird, reicht es, eine Schaltfläche mit einem click-Binding auf diese Funktion zu erstellen:

<button data-bind="click: clear">Clear books</button>

Um die Bindungen zu aktivieren, muss noch ein wenig JavaScript hinzugefügt werden:

var initialData = [

    {

    title: "Windows Presentation Foundation 4.5",

    author: "Norbert Eder",

    pages: 400}

];

 

var firstViewModel = new BooksViewModel(initialData);

 

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

Hier werden lediglich Demodaten angelegt und bei der Erstellung des BooksViewModel-Objektes übergeben. Weit wichtiger an dieser Stelle ist der Aufruf ko.applyBindings. Damit wird das ViewModel an das Element gebunden, das durch den zweiten Parameter gefunden wird.

Hinweis: Wahlweise könnte der zweite Parameter auch weggelassen werden. In diesem Fall würde das gesamte Dokument nach dem Attribut data-bind durchsucht werden.

Ab diesem Zeitpunkt ist das Binding aktiv und die Werte werden synchronisiert.

So sieht’s aus

Hier nun die einzelnen Darstellungen der Website. Nachfolgend die Darstellung nach Öffnen der Seite:

Knockout.js - Anfängliche Darstellung

Durch die Validierung wurde festgelegt, dass alle Eingabefelder einen zugewiesenen Wert besitzen müssen. Ist dem nicht der Fall, wird ein Fehlerhinweis angezeigt:

Knockout.js - Validierung

Download

Wer sich mit diesem Beispiel ein wenig spielen möchte, findet unterhalb die Möglichkeit dieses herunter zu laden, auch steht es auf JSFiddle zur Verfügung (wer den Internet Explorer verwendet, sollte sich das Beispiel herunter laden).

Download Demo

Fazit

Ein wenig HTML-Markup, ein wenig JavaScript-Code, Bindungen und ein paar Styles, fertig ist die Eingabemaske und die entsprechende Liste. Die einzelnen Teile lassen sich schön voneinander trennen und somit gut pflegen. Kein Code der Felder, Werte etc. aktualisiert. Auf das Wesentliche reduziert. Eine feine Sache, die Lust auf mehr macht.

Feedback

Was kann man schöner, eleganter, einfacher lösen? Hast du hierzu Feedback? Lasse es mich bitte wissen!

Jetzt Knockout.js lernen: Die Serie

Veröffentlicht von Norbert Eder

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

Beteilige dich an der Unterhaltung

2 Kommentare

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

  1. Hallo Norbert,

    ich würde im BookViewModel die Eigenschaft "hasErrors" eher als function definieren, also etwa so

    this.hasErrors = ko.computed(function() { return (this.errorMessage() != ''); }, this);

    Dadurch reicht es dann aus, wenn man nur die Fehlermeldung setzt und man muß nicht zusätzlich noch ein Flag setzen.

Cookie-Einstellungen
Auf dieser Website werden Cookie verwendet. Diese werden für den Betrieb der Website benötigt oder helfen uns dabei, die Website zu verbessern.
Alle Cookies zulassen
Auswahl speichern
Individuelle Einstellungen
Individuelle Einstellungen
Dies ist eine Übersicht aller Cookies, die auf der Website verwendet werden. Sie haben die Möglichkeit, individuelle Cookie-Einstellungen vorzunehmen. Geben Sie einzelnen Cookies oder ganzen Gruppen Ihre Einwilligung. Essentielle Cookies lassen sich nicht deaktivieren.
Speichern
Abbrechen
Essenziell (1)
Essenzielle Cookies werden für die grundlegende Funktionalität der Website benötigt.
Cookies anzeigen