Kristof Dreier — where tech meets text

Willkommen im privaten Blog von Kristof Dreier, Webentwickler und Mac-Enthusiast aus Münster. Thematisch dreht es sich hier um Webdevelopment mit dem Zend Framework, kleinere Einschübe über Cocoa und vielleicht auch mal ein Blick in die Weiten von Ruby on Rails.

Live Validierung mit dem Zend Framework und jQuery

Montag, 16. Juni 2008 in

Seit geraumer Zeit arbeite ich mit dem Zend Framework und die wirklich gute Dokumentation läßt kaum Wünsche offen. Christian Vogt von hackthenet.de hat mich auf den jQuery-Trip gebracht, weswegen jQuery dann auch mein Wegbegleiter war, als es darum ging Zend_Form eine Livevalidierung zu verpassen.

Eins nach dem anderen

Vorweg gibt es erst einmal das fertige Beispiel zu bewundern und natürlich zum Download (2.0 MB, inklusive dem Zend Framework 1.5.1). Nun aber frisch ans Werk.

Zend_Form

Am Anfang habe ich mich ein wenig dagegen gesträubt Zend_Form einzusetzen, da es sich mir irgendwie widersetzte, die Formularerstellung in separate Dateien auszulagern, weil es ja Teil des Views sein sollte. Teil des Views sind die Formulare natürlich weiterhin, allein die Erstellung wird in Klassen verlegt, die jeweils von Zend_Form erben, welche schon einen Großteil von äußerst nützlichen Methoden mitbringt.
Im Gegensatz zu den anderen Frameworks wie CakePHP und Rails, werden die Validierungsregeln nicht im Model festgelegt, sondern in der von Zend_Form abgeleiteten Klasse.
Speziell für das erstellte Element, was dann folgendermaßen aussehen kann:

  1. <?php
  2.  
  3. class forms_Signup extends Zend_Form {
  4.   public function init() {
  5.     $username = new Zend_Form_Element_Text('username');
  6.     $username->setLabel('Benutzername')
  7.              ->setRequired(true)
  8.              ->addValidators(array(
  9.                 array('NotEmpty', true),
  10.                 array('Alnum', true)
  11.              ))
  12.              ->addClass('required');
  13.    
  14.     $this->addElements(array($username))   
  15.   }
  16. }
  17.  
  18. ?>

In die init-Methode kommt die Erstellung der Formularelemente, in diesem Fall $username welche als Zend_Form_Element_Text initialisiert wird und damit ein normales Inputfeld mit dem Namen username wird. In Zeile acht und neun fügen wir dem Objekt dann die Validierungsregeln hinzu — nur alphanumerische Zeichen und nicht leer — welche beim Abschicken des Formulares überprüft werden. $this->addElements(array($username)); in Zeile acht fügt dem Formular das so eben erstellte Element hinzu, damit es bei der Ausgabe berücksichtig wird.
Für die Validierung via Ajax ganz wichtig ist Zeile 10, da wir dort dem Element die Klasse required geben, anhand dessen wir später die zu überprüfenden Felder einfacher auslesen können.

Hinweis: Sollte einem Formular nur ein Element hinzugefügt werden, kann dies auch folgendermaßen geschehen: $this->addElement($element).

Aktion im Controller

Im Controller, der die Benutzerinteraktion mit dem View und dem Model koordiniert, übergeben wir dann das fertig erstellte Formular an das View und überprüfen auch direkt auf die Richtigkeit der Eingaben — per Button abgeschickte, sowie die, die über den XmlRequest reinkommen. Erst der Code, dann die Erklärung:

  1. <?php
  2.  
  3. class IndexController extends Zend_Controller_Action {
  4.   public function indexAction() {
  5.     $signupForm = new forms_Signup();
  6.        
  7.     $request = $this->getRequest();
  8.     if ($request->isPost()) {    
  9.       $formData = $request->getPost();
  10.      
  11.       if ($request->isXmlHttpRequest()) {
  12.         $this->_helper->viewRenderer->setNoRender();
  13.        
  14.         $signupForm->processAjax($formData);
  15.        
  16.         // Wir holen uns das Element, was gerade verarbeitet wird
  17.         $currentFied = key($formData);
  18.         $currentElement = $signupForm->getElement($currentFied);
  19.        
  20.         // Holen uns dann den passenden ErrorDecorator und formen ihn in JSON um
  21.         $response = Zend_Json::encode(array(
  22.           $currentFied => $currentElement->getDecorator('Errors')->setElement($currentElement)->render('')
  23.         ));
  24.        
  25.         // Und geben alles zurueck
  26.         return $this->getResponse()->setHeader('Content-Type', 'text/html')
  27.                                    ->setBody($response);
  28.       }
  29.      
  30.       if ($signupForm->isValid($formData)) {
  31.         // Logik zur Verarbeitung von gueltigen Formulardaten
  32.       } else {
  33.         $signupForm->populate($formData);
  34.       }
  35.     }  
  36.      
  37.     $this->view->form = $signupForm; 
  38.   }
  39. }
  40.  
  41. ?>

Nachdem wir in Zeile fünf unser Formular initialisiert haben, überprüfen wir danach, ob ein Post-Request vorliegt und speichern die Werte des Formulars, im Falle des Falles, zwischen. In Zeile elf schauen wir nun, ob die Anfrage über einen XmlRequest gestartet wurde. Sollte dies der Fall sein, deaktivieren wir die Ausgabe des Views — wir benötigen in der Antwort ja nur die Fehlermeldung — verarbeiten die Ajaxanfrage via $signupForm->processAjax($formData), und geben eventuelle Fehlermeldung im JSON-Format zurück, welches auch jQuery ohne Probleme versteht.

Hinweis: Die Methode processAjax benötigt nicht alle Daten für das Formular, sondern kann diese auch teilweise verarbeiten was sich für die Livevalidierung anbietet, da dabei nicht alle Daten einmal vorhanden sind.

Sollte kein Ajaxrequest vorliegen, überprüfen wir via isValid das vollständige Formular und geben bei Bedarf automatisch die Fehlermeldung aus. Wir erinnern uns: die Validierungsregeln haben wir ja bereits beim Anlegen der Form_Signup angelegt.

Ich seh' dich!

Was wäre das schönste Formular ohne die eigentlich Ausgabe, und deshalb freut es mich umso mehr, dass dieser Abschnitt der kürzeste wird:

  1. <?php echo $this->form; ?>

Das war's. Da Zend_Form eine magische __toString Methode mitbringt, die die ganze Umwandlung in sauberen XHTML-Code erledigt, können wir den PHP-Teil abschließen und zu jQuery übergehen.

Hinweis: Das ist übringens ein schöner Vorteil, wenn man sich auf die Nutzung von Zend_Form einlässt. Das Formular bläht den eigentlich HTML-Code nicht mehr unnötig auf und das Formular lässt sich natürlich auch an jeder beliebig anderen Stellen erstellen und einbinden. DRY in Perfektion.

jQuery, ick liebe dir

Die eigentlichen Vorbereitungen sind abgeschlossen, nun geht es an den Teil, der den ganzen Prozess anstoßen wird und die Livevalidierung startet. Gleiches Prozedere, erst der Code dann die Erklärung:

  1. $(document).ready(function(){
  2.   $.each($('.required'), function (key, element) {
  3.     $(element).keydown(function (event) {
  4.       timeoutFunction('validateForm', element.name, 500);
  5.     });
  6.   });
  7. });
  8.  
  9. function validateForm(name) {
  10.   formName = "#"+name;
  11.   if (!(data = $(formName).val())) {
  12.     data = '';
  13.   }
  14.   $.post(
  15.     "/index/index",
  16.     name+"="+data,
  17.     function (json) {
  18.       if (json != true) {
  19.         $(formName).next('.error').remove();
  20.         $(formName).after(json[name]);
  21.       } else {
  22.         $(formName).next('.error').remove();
  23.       }
  24.     },
  25.     "json"
  26.   );
  27. }
  28.  
  29. var timeout = null;
  30. function timeoutFunction(functionName, name, time) { 
  31.   clearTimeout(timeout);
  32.   eval('timeout = setTimeout("'+functionName+'(\''+name+'\')", '+time+')');
  33. }

Sobald der DOM fertig gerendert ist, suchen wir nach allen Elementen, die die required-Klasse besitzen, um diese dann mit einem keydown-Event auszurüsten, was nur abgefeuert wird, sofern nach der Benutzereingabe eine halbe Sekunde vergangen ist. Angefragt wird unserer index-Action aus der Controller mit dem Wert aus dem aktuell geänderten Feld. Durch das json in Zeile neun bekommen wir die zurückgelieferten Daten vom Zend Framework direkt passend aufbereitet und brauchen diese nur noch in den DOM inizieren.

Das war's!

Naja, vielleicht nicht so ganz: Da bliebe die Frage offen, wie man zum Beispiel zwei Felder mit einandern vergleichen kann, was ganz nützlich bei wiederholender E-Mail- oder Passworteingabe sein kann. Meine Erklärung geht auch nicht näher auf die Zend_Form_Decorators ein, die aber in meinem Beispiel auftauchen, welches ich zum Download anbiete. Das wäre auch ein Teil, den ich eventuell in einem anderen Artikel berücksichtige, der aber den Rahmen dieses Artikel jetzt sprengen würde. Ebenso das Anpassen der Fehlermeldungen, was meinem Geschmack nach noch irgendwie ein wenig umständlich mit Zend_Forms gelöst ist. Vielleicht hat da ja wer einen Tipp?

Abschließen bleibt mir nur zu sagen, dass ich mich über konstruktive Kritik sehr freuen würde, auch unter dem Aspekt, dass dies mein erster Versuch ist eine Technik zu erklären, mit der ich mich selber noch beschäftige. Ich hoffe natürlich, dass das Beispiel keine Fehler hat, ebenso wie dieser Artikel, falls dies nicht so sein sollte, schreibt mir doch bitte eben einen Kommentar und ich bessere, so schnell wie es mir möglich ist, aus. Viel Spaß!

Kommentare

    Uli
    Jun 16, 11:18

    Beim Testen ist mir aufgefallen das das zweite Passwort nicht auf Gleichheit getestet wird…

    und eine visuelle Bestätigung eines richtig ausgefüllten Feldes würde ich mir noch wünschen

    sonst top!

    René Pardon
    Mai 9, 21:30

    Hallo,

    super Beschreibung. Ich bin bereits vorher auf diesen Blog gestoßen, da ich mich jedoch noch nicht mit dem Zend-Framework auseinander gesetzt hatte, konnte ich damit nicht recht viel anfangen. Ich werde das so mal probieren, vlt. wenn es möglich ist ohne json und jquery, dafür mit prototype :)

    greets h32Lg 8)

    Toni
    Mai 25, 06:16

    Top Beispiel!
    In Listing zwei, Zeile fünf wird das Formular initialisiert mittels:
    $signupForm = new forms_Signup();

    In Listing eins, Zeile drei wird die Klasse als
    class Form_Signup
    benannt. Ich nehme mal an Listing zwei, Zeile fünf sollte dann
    $signupForm = new Form_Signup();
    lauten. Oder habe ich etwas nicht verstanden?

    LG,
    Toni

    Kristof Dreier
    Mai 25, 09:57

    Hallo Toni,

    danke erstmal fuer den Hinweis, habe das Listing entsprechend abgeaendert.
    Die Klasse muss forms_Signup heissen und nicht wie im Listing Form_Signup.

    Gruesse,
    Kris

Dein Kommentar


Textile-Hilfe