Im letzten Abschnitt, hatten wir ein Beispiel welches ein "Element für das Geburtsdatum" gezeigt hat:
<div class="element"> <?php echo $form->dateOfBirth->renderLabel() ?> <?php echo $this->formText('dateOfBirth[day]', '', array( 'size' => 2, 'maxlength' => 2)) ?> / <?php echo $this->formText('dateOfBirth[month]', '', array( 'size' => 2, 'maxlength' => 2)) ?> / <?php echo $this->formText('dateOfBirth[year]', '', array( 'size' => 4, 'maxlength' => 4)) ?> </div>
Wie könnte man dieses Element als Zend_Form_Element
Element
darstellen? Wie kann man einen Decoratpr schreiben um es darzustellen?
Die Fragen darüber wie das Element arbeiten würde beinhalten:
Wir würde man den Wert setzen und empfangen?
Wie würde der Wert geprüft werden?
-
Wie würde man trotzdem getrennte Formulareingaben für diese drei Abschnitte erlauben (Tag, Monat, Jahr)?
Die ersten zwei Fragen drehen sich um das Formularelement selbst: Wie würden
setValue()
und getValue()
arbeiten?
Es gibt aktuell eine weitere implizite Frage durch die Frage über den Decorator: Wie
würde man getrennte Datenabschnitte vom Element empfangen und/oder setzen?
Die Lösung besteht darin die setValue()
Methode des Elements zu
überladen um eigene Logik anzubieten. In diesem speziellen Fall sollte unser Element
drei getrennte Verhalten haben:
-
Wenn ein Integer Zeitpunkt angegeben wird, sollte er verwendet werden um Tag, Monat und Jahr zu erkennen und zu speichern.
-
Wenn ein textueller String angegeben wird sollte er in einen Zeitpunkt gecastet werden, und dieser Wert sollte verwendet werden um Tag, Monat und Jahr zu erkennen und zu speichern.
-
Wenn ein Array angegeben wird welches die Schlüssel für Tag, Monat und Jahr enthält, dann sollten diese Werte gespeichert werden.
Intern werden Tag, Monat und Jahr getrennt gespeichert. Wenn der Wert des Elements
empfangen wird, wird das in einem normalisierten String Format getan. Wir überschreiben
getValue()
und ordnen die getrennten Datenabschnitte dem
endgültigen String zu.
So würde die Klasse aussehen:
class My_Form_Element_Date extends Zend_Form_Element_Xhtml { protected $_dateFormat = '%year%-%month%-%day%'; protected $_day; protected $_month; protected $_year; public function setDay($value) { $this->_day = (int) $value; return $this; } public function getDay() { return $this->_day; } public function setMonth($value) { $this->_month = (int) $value; return $this; } public function getMonth() { return $this->_month; } public function setYear($value) { $this->_year = (int) $value; return $this; } public function getYear() { return $this->_year; } public function setValue($value) { if (is_int($value)) { $this->setDay(date('d', $value)) ->setMonth(date('m', $value)) ->setYear(date('Y', $value)); } elseif (is_string($value)) { $date = strtotime($value); $this->setDay(date('d', $date)) ->setMonth(date('m', $date)) ->setYear(date('Y', $date)); } elseif (is_array($value) && (isset($value['day']) && isset($value['month']) && isset($value['year']) ) ) { $this->setDay($value['day']) ->setMonth($value['month']) ->setYear($value['year']); } else { throw new Exception('Ungültiger Datumswert angegeben'); } return $this; } public function getValue() { return str_replace( array('%year%', '%month%', '%day%'), array($this->getYear(), $this->getMonth(), $this->getDay()), $this->_dateFormat ); } }
Diese Klasse bietet einige nette Flexibilitäten -- wir können Standardwerte von unserer Datenbank setzen, und sicher sein das der Wert richtig gespeichert und dargestellt wird. Zusätzlich können wir erlauben den Wert von einem Array zu setzen welches über die Formulareingabe übergebenen wurde. Letztendlich haben wir getrennte Zugriffe für jeden Datenabschnitt, welche wir jetzt in einem Decorator verwenden können um ein kombiniertes Element zu erstellen.
Bei der erneuten Betrachtung des Beispiels im letzten Abschnitt, nehmen wir wir an dass
wir wollen das unsere Benutzer Jahr, Monat und Tag separat eingeben.
PHP erlaubt es uns glücklicherweise die Array Schreibweise zu
verwenden wenn Elemente erstellt werden, deshalb ist es möglich diese drei Entitäten in
einem einzelnen Wert zu fangen -- und wir erstellen jetzt ein
Zend_Form
Element das solch einen Array Wert verarbeiten kann.
Der Decorator ist relativ einfach: er holt Tag, Monat und Jahr vom Element, und übergibt jedes einem eigenen View Helfer um die individuellen Formular Eingaben darzustellen; diese werden dann für das endgültige Markup gesammelt.
class My_Form_Decorator_Date extends Zend_Form_Decorator_Abstract { public function render($content) { $element = $this->getElement(); if (!$element instanceof My_Form_Element_Date) { // wir wollen nur das Date Element darstellen return $content; } $view = $element->getView(); if (!$view instanceof Zend_View_Interface) { // verwenden von View Helfers, deshalb ist nichts zu tun // wenn keine View vorhanden ist return $content; } $day = $element->getDay(); $month = $element->getMonth(); $year = $element->getYear(); $name = $element->getFullyQualifiedName(); $params = array( 'size' => 2, 'maxlength' => 2, ); $yearParams = array( 'size' => 4, 'maxlength' => 4, ); $markup = $view->formText($name . '[day]', $day, $params) . ' / ' . $view->formText($name . '[month]', $month, $params) . ' / ' . $view->formText($name . '[year]', $year, $yearParams); switch ($this->getPlacement()) { case self::PREPEND: return $markup . $this->getSeparator() . $content; case self::APPEND: default: return $content . $this->getSeparator() . $markup; } } }
Jetzt müssen wir kleinere Änderungen an unserem Formular Element durchführen und Ihm sagen das wir den obigen Decorator als Standard verwenden wollen. Das benötigt zwei Schritte. Erstens müssen wir das Element über den Pfad des Decorators informieren. Wir können das im Constructor tun:
class My_Form_Element_Date extends Zend_Form_Element_Xhtml { // ... public function __construct($spec, $options = null) { $this->addPrefixPath( 'My_Form_Decorator', 'My/Form/Decorator', 'decorator' ); parent::__construct($spec, $options); } // ... }
Es ist zu beachten dass das im Constructor getan wird und nicht in
init()
. Es gibt hierfür zwei Gründe. Erstens erlaubt dies das
Element später zu erweitern um Logik in init
hinzuzufügen ohne
das man sich Gedanken über den Aufruf von parent::init()
machen
muss. Zweitens erlaubt es zusätzliche Plugin Pfade über die Konfiguration zu übergeben
oder in einer init
Methode die dann das Überladen des
standardmäßigen Date
Decorators mit einem eigenen Ersatz erlaubt.
Als nächstes überladen wir die loadDefaultDecorators()
Methode
um unseren neuen Date
Decorator zu verwenden:
class My_Form_Element_Date extends Zend_Form_Element_Xhtml { // ... public function loadDefaultDecorators() { if ($this->loadDefaultDecoratorsIsDisabled()) { return; } $decorators = $this->getDecorators(); if (empty($decorators)) { $this->addDecorator('Date') ->addDecorator('Errors') ->addDecorator('Description', array( 'tag' => 'p', 'class' => 'description' )) ->addDecorator('HtmlTag', array( 'tag' => 'dd', 'id' => $this->getName() . '-element' )) ->addDecorator('Label', array('tag' => 'dt')); } } // ... }
Wie sieht die endgültige Ausgabe aus=? Nehmen wir das folgende Element an:
$d = new My_Form_Element_Date('dateOfBirth'); $d->setLabel('Geburtsdatum: ') ->setView(new Zend_View()); // Das sind Äquivalente: $d->setValue('20 April 2009'); $d->setValue(array('year' => '2009', 'month' => '04', 'day' => '20'));
Wenn man dieses Element ausgibt erhält man das folgende Markup (mit einigen wenigen Modifikationen an Leerzeichen für die Lesbarkeit):
<dt id="dateOfBirth-label"><label for="dateOfBirth" class="optional"> Geburtsdatum: </label></dt> <dd id="dateOfBirth-element"> <input type="text" name="dateOfBirth[day]" id="dateOfBirth-day" value="20" size="2" maxlength="2"> / <input type="text" name="dateOfBirth[month]" id="dateOfBirth-month" value="4" size="2" maxlength="2"> / <input type="text" name="dateOfBirth[year]" id="dateOfBirth-year" value="2009" size="4" maxlength="4"> </dd>
Wir haben jetzt ein Element das mehrere zusammengehörende Formular Eingabefelder
darstellen kann, und die getrennten Felder anschließend als einzelne Entität behandelt
-- das Element dateOfBirth
wird als Array an das Element übergeben,
und das Element wird anschließend, wie vorher erwähnt, die passenden Datenabschnitte
erstellen und einen Wert zurückgeben den wir für die meisten Backends verwenden können.
Zusätzlich können wir verschiedene Decoratore mit dem Element verwenden. Wenn wir einen
Dojo DateTextBox
Dijit Decorator verwenden wollen -- welche Stringwerte akzeptiert und zurückgibt --
können wir das ohne Änderung des Elements selbst durchführen.
Am Ende erhält man eine einheitliche API für ein Element welche man verwenden kann um ein Element zu beschreiben welches einen kombinierten Wert repräsentiert.