• Twittern!
  • Facebook

Zend_Form ist eine Klasse, mit der man schnell und einfach Web-Formulare zusammenbasteln kann. Man sagt einfach, welche Inputfelder man benötigt, gibt Beschriftung und Validatoren (Eingabeprüfungen) an, fertig. Um die Ausgabe als (X)HTML braucht man sich nicht kümmern, man braucht nur die Instanz der Formularklasse per echo aufrufen – schon wird das Formular fertig formatiert ausgegeben (naja, ein bissel CSS muss man da schon noch hinzufügen, aber die meiste Arbeit nimmt Zend_Form uns ab).

Aber es wird eine Zeit kommen, da reicht einem das Standard-Layout des Zend Framework nicht mehr aus. Wenn die Ansprüche steigen, wird es Zeit, sich mit den Dekoratoren von Zend_Form zu befassen. Das Thema wird von vielen Entwicklern eher gemieden, dabei geht es eigentlich ganz einfach.

Auf die Möglichkeiten zur Erstellung eines Formulars mit Zend Framework möchte ich an dieser Stelle nicht eingehen. Ein recht aktuelles Tutorial zum Thema finden Sie im Zend Framework Tutorial von Uwe E. Schirm.

Inhalt

Was ist ein Dekorator? – Ein (ganz klein) wenig zur Theorie

Die Dekoratoren von Zend_Form basieren auf dem Decorator Entwurfsmuster. Ein Dekorator erweitert dabei die Funktionalität einer Klasse, indem er sich quasi um sie herum legt. Es handelt sich dabei also um eine Art Schachtel, die die verfügbare Funktionalität der enthaltenen Klasse erweitert oder ändert. Die API verändert sich dabei nicht, d. h. auf den Dekorator wird letztlich genauso zugegriffen wie auf das Original Objekt.

Dekoratoren können dabei beliebig tief verschachtelt sein. Sie haben eine eindeutige Hierarchie, wobei die äußerste Schachtel alle anderen umschließt und somit den „Hut aufhat“. Weier O’Phinney vergleicht diesen Aufbau mit einer Zwiebel. Im Innersten befindet sich unser Formular und wie die einzelnen Zwiebelhäutchen wird drum herum das HTML aufgebaut.

Jede Zwiebelschicht (also jeder Dekorator) übernimmt dabei eine ganz spezielle Aufgabe.

Ein einfaches Beispiel

Ein einfaches, gut verständliches Beispiel habe ich – ebenfalls von Weier O’Phinney – im Entwicklerbereich von Zend gefunden. Ich möchte es hier aufführen, weil es das Decorator Pattern gut verdeutlicht.

Stellen Sie sich vor, Sie haben eine Klasse für ein (Programm-)Fenster. Sie benötigen dann eine Dekorator für die Ausgabe des Fensters, einen für die Scrollbar, Fenstertitel usw. Die Instanziierung dieser Klasse sähe folgendermaßen aus:

$window = new WindowScrollbarDecorator(
  new WindowTitleDecorator(
    new WindowDecorator(
      new Window()
    )
  )
);

Und der Aufruf der render-Funktion wäre:

$window->render();

Diese Methode ruft WindowScrollbarDecorator::render() auf, die ruft WindowTitleDecorator::render() auf, diese wiederum WindowDecorator::render() und diese Window::render(). Jede dieser Klassen hat ihre spezielle Aufgabe. Und zusammen erzeugen sie ein Fenster mit Scrollbar und Titel.

Dekoratoren bei Zend_Form

Sowohl die Formularelemente als auch das Formular selbst verfügen über Dekoratoren (und es gibt weitere z. B. für Display-Groups). Ich möchte mal behaupten, dass die Anpassung von Dekoratoren für Formularelemente die häufigste Anforderung ist, auf die ein Programmierer trifft.Deshalb bezieht sich dieses Tutorial auch auf diesen Anwendungsfall und kann bei Bedarf einfach entsprechend adaptiert werden.

Die Dekoratoren-Klassen von Zend_Form heißen Zend_Form_Decorator_xxx.

Jeder Dekorator übernimmt den Content des Vorgängers und verändert ihn. Initial ist der Content ein leerer String, der nach und nach mit (HTML-) Inhalten gefüllt wird. Die Dekoratoren können dabei ihren Senf vorne anfügen (append), hinten anfügen (prepend) oder den vorhandenen Content ersetzen (replace). Letzteres heißt in der Regel, dass sowohl vor als auch hinter dem Content etwas angefügt wird (z. B. Wrappen mit <dd>, <dt> o. ä. Tags).

Die meisten Zend_Form Elemente haben folgende Dekoratoren:

  • Viewhelper – das sind die Dekoratoren für die Formularfelder (input, select etc.), die vom Elementtyp abhängig sind,
  • Errors – für die Anzeige von Fehlern,
  • Description – für die Description, also Erläuterungen zum Formularfeld, Eingabehilfen etc.,
  • HtmlTag – das ist per Default das <dd> Tag, in dem die vorgenannten eingefasst (also gewrappt) werden,
  • Label – die Beschriftung des Formularfeldes, i. d. R. <dt> Tag.

Mit der Methode setDecorators() lassen sich die Dekoratoren eines Form-Elements individuell einstellen. So können Dekoratoren eingefügt oder entfernt, eigene Dekoratoren verwendet oder aber auch einfach nur andere Parameter an die bestehenden Dekoratoren übergeben werden.

Dekoratoren–Attribute anpassen und Dekoratoren entfernen

Der letztgenannte ist auch der einfachste Weg. Damit lassen sich bereits verschiedene Anpassungen ohne großen Aufwand durchführen. Um z. B. das Formular-Label nicht vor, sondern hinter dem Formularfeld anzuzeigen, setzen Sie das Placement einfach auf ‘append':

$label = $element->getDecorator('label');
$label->setOption('placement', 'append');

Ein anderes Beispiel: Versteckte Eingabefelder (hidden) werden standardmäßig wie andere Eingabefelder mit (leerem) Label und in Tags ausgegeben:

<dt id="id-label">&nbsp;</dt>
<dd id="id-element">
  <input id="id" name="id" type="hidden" value="80" />
</dd>

Das bläht nicht nur den Quellcode unnötig auf, sondern ist auch gestalterisch ein Problem: Wenn wir die dt und dd’s per CSS angepasst haben, kann es sein, dass das hidden-Feld immer Platz frisst, also dort eine Lücke ist, wo keine sein soll. Also wollen wir den HTML-Output für die hidden-Felder anpassen:

<div><input type="hidden" name="id" value="80" id="id"></div>

Dazu wird der HtmlTag-Dekorator angepasst (div statt dd) und der Label-Dekorator entfernt. Kein Label-Dekorator = kein Label, einfach oder?

Das Ganze könnte dann so aussehen:

$element = $this->createElement('hidden', 'id', array('value' => $value));
$element->setDecorators(array(
                'ViewHelper',
                'Description',
                'Errors',
                array('HtmlTag', array('tag' => 'div'))
                ));

Per setDecorators() werden die Dekoratoren in hierarchischer Reihenfolge dem Element zugeteilt. Der letztgenannte Dekorator ist auch immer der zuletzt ausgeführte, der alle anderen umschachtelt. In diesem Fall ist es HtmlTag, der als tag-Attribut ‘div’ übergeben bekommt.

Achtung! Werden in der setDecorators() Methode Attribute von Dekoratoren geändert, muss dieser Dekorator als Array übergeben werden, damit der Konstruktor erkennt, dass Attribute enthalten sind. Das erste Array-Element ist der Dekorator-Name, das zweite ein Array mit allen Attributen.

Da die Dekoratoren und ihre Reihenfolge ja eigentlich für das Element schon festgelegt sind, kann man die Attribute aber auch ändern, indem per getDecorator() das entsprechende Dekorator-Objekt geholt und per setOption() angepasst wird:

$element = $this->createElement('hidden', 'id', array('value' => $value));
$htmlTag = $element->getDecorator('htmlTag');
$htmlTag->setOption('tag', 'div');
$element->removeDecorator('label');

Mit removeDecorator() wird der Label-Dekorator anschließend entfernt.

Meiner Ansicht nach sind im obigen Beispiel beide Varianten gleichwertig. Bei komplexeren Anpassungen hat die erste Variante einen deutlichen Vorteil hinsichtlich der Übersichtlichkeit.

Einen eigenen Dekorator schreiben

Zend Framework bringt bereits eine Menge Standard-Dekoratoren mit. Eine Übersicht finden Sie in der Dokumentation.

Doch was, wenn diese nicht ausreichen, auch nicht mit Anpassungen? Dann lassen sich auch eigene Dekoratoren kreieren.

Es gibt zwei mögliche Ansätze: Die Implementierung des Zend_Form_Decorator_Interface oder die Erweiterung von Zend_Form_Decorator_Abstract, die bereits alle nötigen Methoden außer render() implementiert.

Für mein Beispiel nehmen wir einmal an, dass wir die Description, also die Hinweise für die Eingabefelder, nicht als Text unter dem Eingabefeld haben möchten, sondern wir wünschen uns einen Infobutton hinter dem Eingabefeld mit Tooltip.

Für die Anzeige des Tooltips bediene ich mich des Tooltip-Tools von Walter Zorn (wobei es natürlich auch die Möglichkeit gibt, die Funktionalität des Dojo-Toolkits zu verwenden, das bereits im Zend Framework integriert ist).

Bisher sah der HTML-Code für die Description so aus:

<p class="description">hier stehen die Hinweise zum Inputfeld</p>

Der Image-Button mit Tooltip wird durch folgenden HTML-Code erzeugt:

<img onmouseover="Tip('hier stehen die Hinweise zum Inputfeld')" onmouseout="UnTip()" src="images/icons/info.gif" alt="info" />

Auf die Einbindung und Konfiguration des Tools möchte ich an dieser Stelle nicht eingehen, das wurde bereits von Walter Zorn hervorragend dokumentiert.

Der neue Dekorator ist schnell erstellt:

class My_Form_Decorator_MyTooltip extends Zend_Form_Decorator_Abstract
{
  public function render($content)
  {
    $element = $this->getElement();
    $description = trim($element->getDescription());
    $output = '';
    $placement = $this->getPlacement();
    $separator = $this->getSeparator();
    switch ($placement) {
      case 'PREPEND':
        return $output . $separator . $content;
      case 'APPEND':
      default:
        return $content . $separator . $output;
    }
  }
}

Zunächst holen wir uns die Description des Elements und schneiden Leerzeichen vorn und hinten ab. Anschließend wird der HTML-Output der Description generiert.

Schließlich wird der Output mit dem Content verbunden, die Reihenfolge bestimmt Placement. Wie oben beschrieben kann das dann nämlich als Parameter übergeben werden, was den Dekorator flexibler macht. Das Gleiche gilt für den Separator.

Wir speichern die Klasse entsprechend ihrem Namen unter My/Form/Decorator/MyTooltip.php.

Neuen Dekorator zuordnen

Nun müssen wir unserem Formularelement noch sagen, dass wir fortan einen neuen Description-Dekorator einsetzen möchten.

Sollen alle Formularelemente ihre Beschreibung fortan als Tooltip erhalten, macht es Sinn, die Zuordnung in eine Methode auszulagern, oder sogar die createElement() Methode der Zend_Form Klasse zu überschreiben. Wie Sie das in Ihrem Projekt am besten organisieren, überlasse ich Ihnen. Ich möchte hier nur am Beispiel eines Formularelementes zeigen, wie der Dekorator für ein einzelnes Element-Objekt angepasst wird.

$email = $this->createElement('text', 'email', array('label' => 'E-Mail Adresse',
                'description' => '

Geben Sie hier Ihre E-Mail Adresse an. Ihre Daten sind bei uns geschützt und werden nicht weitergegeben.

'));
$email->addPrefixPath('My_Form_Decorator', 'My/Form/Decorator/', 'decorator');
$email->setDecorators(array(
                'ViewHelper',
                'MyTooltip',
                'Errors',
                array('HtmlTag', array('tag' => 'dd')),
                array('Label', array('tag' => 'dt')),
                ));

Dem Element wird beim Erzeugen die Description als Option übergeben. Anschließend muss ein PrefixPath hinzugefügt werden, damit der eigenkreierte Dekorator überhaupt gefunden werden kann. Übergeben wird der Präfix der Klasse sowie der Pfad, wo die zugehörige Datei zu finden ist.

Leider ist es im Zend Framework (noch) nicht möglich, mittendrin einen Dekorator auszutauschen oder hinzuzufügen, ohne alle anderen Dekoratoren aufzulisten. Daher wird die Methode setDecorators() benötigt. Sie sehen oben die Standard-Dekoratoren-Definition, nur “Description” wurde durch “MyTooltip” ersetzt.

Nicht vergessen: Tooltip-Skript in das Default-Layout einbinden und ein Info-Icon uploaden! ;)

Ergänzung 24.09.09 – Ersetzen / Escapen der Anführungszeichen

Das gehört jetzt zwar nicht unbedingt zum Thema, aber es vervollständigt dieses Tutorial. Die Darstellung mit dem o. g. JavaScript funktioniert nämlich nicht, wenn Sie Anführungszeichen in Ihrer Description verwenden. Daher habe ich in der render() Methode noch zwei Zeilen hinzugefügt (na klar, könnte auch alles in einer einzigen gemacht werden):

...
$description = trim($description); // die Zeile war schon drin, und die nächsten beiden sind neu
$description = str_replace('"', '\'', $description);
$description = addslashes($description);

$output = ...

In Zeile 3 werden doppelte durch einfache Anführungszeichen ersetzt und anschließend in Zeile 4 mit einem Backslash versehen. So können auch Descriptions mit einfachen oder doppelten Anführungszeichen fehlerfrei in der Tooltip-Box angezeigt werden.

Anmerkung: Ich hoffe, ich bin vor lauter Decorator (mit c) und Dekoratoren (mit k) nicht zu sehr durcheinander geraten. Wenn doch, bitte ich um Nachsicht.

Fehler, Fragen und Hinweise wie immer gerne hier in den Kommentaren oder direkt an mich.

Themen: Dies & das | Noch kein Kommentar »