Jetzt Knockout.js lernen: Formularvalidierung mit Undo

In Jetzt Knockout.js lernen: Observables erweitern der Serie zu Knockout.js wurde gezeigt, wie Observables erweitert werden können. Als Beispiel diente eine Validierung auf Basis von einzelnen Observables. Nun hängen diese in der Regel aber in einem Formular zusammen, wodurch eine Gesamtvalidierung und eine damit verbundene Freischaltung von Schaltflächen bzw. weiteren Interaktionsmöglichkeiten einhergeht. Dieser Beitrag greift das Validierungsbeispiel auf und erweitert es im Rahmen dieser Anforderung.

Ausgangspunkt

Den Ausgangspunkt dieses Beitrags bietet dieses Beispiel, zu dem Florian einen sinnvollen Erweiterungswunsch hinterlassen hat:

Interessant wäre noch ein Save-Button, der nur aktiv ist, wenn es keine Fehler auf dem gesamten Formular mehr gibt, sowie ein Cancel- bzw. Undo-Button der bei eventuellen Fehlern, die Werte zurücksetzt auf den ursprünglichen Wert.

Aus meiner Sicht handelt es sich hierbei um eine gängige Anforderung, die ich natürlich aufgegriffen habe. Wer den Hintergrund des Beispiels erfahren möchte, kann dies hier vollständig nachlesen. Als Zusammenfassung: Das Beispiel zeigt, wie ein eigener Extender für Observables unter Knockout.js geschrieben werden kann, um einzelne Observables zu validieren. Als Validierung wurde eine Erweiterung hinsichtlich Pflichtfelder implementiert. Eine Gesamtvalidierung war nicht vorgesehen.

Gesamtvalidierung

Aktuell wird jedes Observable einzeln validiert. Entsprechend der Anforderung sollen nun zwei Schaltflächen für das Speichern, als auch das Zurückstellen auf die zuletzt gültigen Werte (im Fehlerfalle) hinzugekommen. Damit diese auch entsprechend freigeschalten sind, muss das ViewModel bekannt geben, in welchem Status es sich gesamt befindet. Dieser ergibt sich aus den Status jedes einzelnen Observables. Dies können wir uns berechnen lassen (ko.computed) und nennen wir hasErrors.

self.hasErrors = ko.computed(function() 

{ 

   return self.title.hasError() || 

          self.author.hasError() || 

          self.pages.hasError(); 

 

});

Hinweis: self ist eine Referenz auf this und wurde im Vergleich zum originalen Beispiel hinzugefügt, um innerhalb der anonymen Funktion auf die Eigenschaften und Funktionen des ViewModels zugreifen zu können.

Damit könnten nun auch bereits die beiden neuen Schaltflächen gesteuert werden:

<input type="button" value="Save" data-bind="disable: hasErrors"></input>

<input type="button" value="Undo" data-bind="enable: hasErrors"></input>

Auf die Speichern-Schaltfläche möchte ich an dieser Stelle nicht weiter eingehen, viel spannender ist hier der Undo-Button. Dieser soll ja – im Fehlerfalle – die zuletzt gültigen Werte hinterlegen. Dies bedeutet, dass diese erfasst werden müssen.

Nun ist es so, dass die einzelnen Eingabefelder eine value-Bindung besitzen und das Durchschreiben der Werte via valueUpdate auf den Wert afterkeydown gesetzt wurde. Das ViewModel wird also in Echtzeit aktualisiert. Darauf können wir also nicht aufbauen, da der Benutzer beispielsweise per Backspace alle Zeichen von “Beispiel” löschen könnte, durch die Echtzeit-Aktualisierung würde der zuletzt valide Wert durch “B” repräsentiert. Worauf wir uns jedoch hängen können ist das Verlassen des Fokus, also dem OnBlur-Ereignis.

Dazu brauchen wir eine Funktion, die uns den beim Verlassen vorhandenen – gültigen – Wert übernimmt:

self.updateLastValidValue = function(observable) {

  if (!observable.hasError()) {

      observable.lastValidValue(observable());

  }

};

Als Parameter wird das an das Eingabefeld gebundene Observable übergeben. So kann auch auf die durch den Extender geschriebenen Eigenschaften (siehe hasError) zugegriffen werden. Läuft die Validierung ohne Fehler, dann ist ein entsprechender Wert vorhanden und kann übernommen werden. Hierzu wird eine neue Eigenschaft namens lastValidValue mit dem Wert gesetzt.

Damit dies nun bei allen Eingabefeldern berücksichtigt wird, ist deren Bindung zu aktualisieren, hier an Hand der Titel-Eingabe:

<input data-bind='value: title, valueUpdate: "afterkeydown", event: { blur: function() { updateLastValidValue(title); }}' />

So werden die Werte nun weiterhin in Echtzeit übernommen und zusätzlich beim Verlassen des Feldes die Funktion updateLastValidValue des ViewModels aufgerufen. Das ist allerdings noch nicht alles, denn ein etwaiger initial gesetzter Wert würde nicht berücksichtig werden. An dieser Stelle bietet es sich an, den bereits existierenden Extender (requireField) zu erweitern:

ko.extenders.requiredField = function(target, message) {   

    target.hasError = ko.observable();

    target.validationMessage = ko.observable();

    target.lastValidValue = ko.observable();

 

    function validate(newValue) {

       target.hasError(newValue ? false : true);

       target.validationMessage(newValue ? "" : message || "* required");

    }

    

    function setInitialValueIfValid() {

        if (!target.hasError()) {

            target.lastValidValue(target());

        }  

    }

 

    validate(target());

    setInitialValueIfValid();

    target.subscribe(validate);

 

    return target;

};

Neu ist die Funktion setInitialValueIfValid(). Diese wird beim Erstellen von requiredField durchlaufen (siehe den Aufruf direkt nach der initialen Validierung) und, wenn ein Wert vorhanden ist, lastValidValue mit dem entsprechenden Wert beschrieben.

Zu guter Letzt fehlt noch das tatsächliche Zurückschreiben der Werte, wenn die Undo-Schaltfläche geklickt wird. Dazu benötigen wir eine Funktion, nennen wir sie resetToValidValues:

self.resetToValidValues = function() {

  self.title(self.title.lastValidValue());

  self.author(self.author.lastValidValue());

  self.pages(self.pages.lastValidValue());

};

Außerdem muss die click-Bindung auf der Schaltfläche gesetzt werden:

<input type="button" value="Undo" data-bind="enable: hasErrors, click: resetToValidValues"></input>

Fertig ist die Gesamtvalidierung des Formulars, inklusive der Möglichkeit, die zuletzt gültigen Werte zurück zu schreiben.

Ergebnis

An dieser Stelle nun zwei Screenshots, die die Implementierung visuell veranschaulichen. Der erste Screenshot zeigt das gesamte Formular ohne Validierungsfehler:

Knockout.js: Erfolgreiche Validierung eines Formulars

Und hier nun im Fehlerfalle:

Knockout.js: Validierungsfehler eines Formulars

Download / Showcase

Wie immer gibt es auch dieses Beispiel vollständig für eigene Tests. Unter jsfiddle.net/VgvuK/ findet sich die entsprechende Spielwiese. Wer möchte, kann sich dies auch unterhalb ansehen, sollte hierbei allerdings auf die Nutzung eines IE verzichten:

Fazit

Die notwendigen Erweiterungen der “Feld-Validierung” konnte in meinen Augen mit sehr wenig Aufwand auf eine Gesamtvalidierung erweitert werden, wodurch für den Benutzer sehr schnell ersichtlich ist, welche Aktionen zur Verfügung stellen. Ein äußerst sauberer Weg, durch die mögliche Auftrennung.

Feedback

Gerne nehme ich – wie immer – konstruktive Kritik, Anregungen und Anmerkungen entgegen. Einfach einen kurzen Kommentar hinterlassen, es findet sich sicherlich die eine oder andere Verbesserungsmöglichkeit.

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.

Schreibe einen Kommentar

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

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