|
|
@@ -0,0 +1,387 @@
|
|
|
+<?xml version="1.0" encoding="UTF-8"?>
|
|
|
+<!-- EN-Revision: 20573 -->
|
|
|
+<!-- Reviewed: no -->
|
|
|
+<sect1 id="learning.form.decorators.composite">
|
|
|
+ <title>Créer et rendre des éléments composites</title>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Dans <link linkend="learning.form.decorators.individual">la dernière section</link>, nous
|
|
|
+ avions un exemple traitant un élément "date de naissance":
|
|
|
+ </para>
|
|
|
+
|
|
|
+
|
|
|
+ <programlisting language="php"><![CDATA[
|
|
|
+<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>
|
|
|
+]]></programlisting>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Comment représenteriez-vous cet élément en tant que
|
|
|
+ <classname>Zend_Form_Element</classname>?
|
|
|
+ Comment écrire un décorateur qui s'assure de son rendu ?
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <sect2 id="learning.form.decorators.composite.element">
|
|
|
+ <title>L'élément</title>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Les questions à se poser sur le fonctionnement de l'élément sont:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <itemizedlist>
|
|
|
+ <listitem>
|
|
|
+ <para>
|
|
|
+ Comment affecter et récupérer une valeur?
|
|
|
+ </para>
|
|
|
+ </listitem>
|
|
|
+
|
|
|
+ <listitem>
|
|
|
+ <para>
|
|
|
+ Comment valider la valeur?
|
|
|
+ </para>
|
|
|
+ </listitem>
|
|
|
+
|
|
|
+ <listitem>
|
|
|
+ <para>
|
|
|
+ Comment proposer l'affectation personnalisée d'une valeur composées de trois
|
|
|
+ segments (jour, mois, année)?
|
|
|
+ </para>
|
|
|
+ </listitem>
|
|
|
+ </itemizedlist>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Les deux première questions se positionnent sur l'élément de formulaire lui-même, comment
|
|
|
+ vont fonctionner les méthodes <methodname>setValue()</methodname> et
|
|
|
+ <methodname>getValue()</methodname>?
|
|
|
+ L'autre question nous suggère de nous questionner sur comment récupérer les segments
|
|
|
+ représentant la date, ou comment les affecter dans l'élément?
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ La solution est de surcharger la méthode <methodname>setValue()</methodname> dans l'élément
|
|
|
+ pour proposer sa propre logique. Dans le cas de notre exemple, notre élément devrait avoir
|
|
|
+ trois comportements distincts:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <itemizedlist>
|
|
|
+ <listitem>
|
|
|
+ <para>
|
|
|
+ Si un timestamp entier est utilisé, il doit aider à la détermination des entités
|
|
|
+ jour, mois, année.
|
|
|
+ </para>
|
|
|
+ </listitem>
|
|
|
+
|
|
|
+ <listitem>
|
|
|
+ <para>
|
|
|
+ Si une chaine est utilisée, elle devrait être transformée en timestamp, et cette valeur
|
|
|
+ sera utiliser pour déterminer les entités jour, mois, année.
|
|
|
+ </para>
|
|
|
+ </listitem>
|
|
|
+
|
|
|
+ <listitem>
|
|
|
+ <para>
|
|
|
+ Si un tableau contenant les clés jour, mois, année est utilisé, alors les valeurs
|
|
|
+ doivent être stockées.
|
|
|
+ </para>
|
|
|
+ </listitem>
|
|
|
+ </itemizedlist>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ En interne, les jour, mois et année seront stockés distinctement. Lorsque la valeur de l'élément
|
|
|
+ sera demandée, nous récupèrerons une chaine formatée et normalisée. Nous surchargerons
|
|
|
+ <methodname>getValue()</methodname> pour assembler les segments élémentaires composant la date.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Voici à quoi ressemblerait la classe:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="php"><![CDATA[
|
|
|
+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('Valeur de date invalide');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function getValue()
|
|
|
+ {
|
|
|
+ return str_replace(
|
|
|
+ array('%year%', '%month%', '%day%'),
|
|
|
+ array($this->getYear(), $this->getMonth(), $this->getDay()),
|
|
|
+ $this->_dateFormat
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+]]></programlisting>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Cette classe est fléxible : nous pouvons affecter les valeurs par défaut depuis une base de
|
|
|
+ données et être certains qu'elles seront stockées correctement. Aussi, la valeur peut être
|
|
|
+ affectée depuis un tableau provenant des entrées du formulaire. Enfin, nous avons tous les
|
|
|
+ accesseurs distincts pour chaque segment de la date, un décorateur pourra donc créer
|
|
|
+ l'élément comme il le voudra.
|
|
|
+ </para>
|
|
|
+ </sect2>
|
|
|
+
|
|
|
+ <sect2 id="learning.form.decorators.composite.decorator">
|
|
|
+ <title>Le décorateur</title>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Toujours en suivant notre exemple, imaginons que nous voulions que notre utilisateur
|
|
|
+ saisissent chaque segment jour, mois, année séparément. Heureusement, PHP permet
|
|
|
+ d'utiliser la notation tableau pour créer des éléments, ainsi nous pourrons capturer
|
|
|
+ ces trois valeurs en une seule et nous crérons un élément
|
|
|
+ <classname>Zend_Form</classname> traitant avec des valeurs en tableau.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Le décorateur est relativement simple: Il va récupérer le jour, le mois et l'année de
|
|
|
+ l'élément et passer chaque valeur à une aide de vue qui rendra chaque champ
|
|
|
+ individuellement. Nous les rassemblerons ensuite dans le rendu final.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="php"><![CDATA[
|
|
|
+class My_Form_Decorator_Date extends Zend_Form_Decorator_Abstract
|
|
|
+{
|
|
|
+ public function render($content)
|
|
|
+ {
|
|
|
+ $element = $this->getElement();
|
|
|
+ if (!$element instanceof My_Form_Element_Date) {
|
|
|
+ // Nous ne rendons que des éléments Date
|
|
|
+ return $content;
|
|
|
+ }
|
|
|
+
|
|
|
+ $view = $element->getView();
|
|
|
+ if (!$view instanceof Zend_View_Interface) {
|
|
|
+ // Nous utilisons des aides de vue, si aucune vue n'existe
|
|
|
+ // nous ne rendons rien
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+]]></programlisting>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Il faut maintenant préciser à notre élément d'utiliser notre décorateur par défaut.
|
|
|
+ Pour ceci, il faut informer l'élément du chemin vers notre décorateur. Nous pouvons
|
|
|
+ effectuer ceci par le constructeur:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="php"><![CDATA[
|
|
|
+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);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ...
|
|
|
+}
|
|
|
+]]></programlisting>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Notez que l'on fait cela en constructeur et non dans la méthode <methodname>init()</methodname>.
|
|
|
+ Ceci pour deux raisons. D'abord, ceci permet d'étendre dans le futur notre élément afin d'y
|
|
|
+ ajouter de la logique dans <methodname>init</methodname> sans se soucier de l'appel à
|
|
|
+ <methodname>parent::init()</methodname>. Ensuite, celà permet aussi de redéfinir le décorateur
|
|
|
+ par défaut <classname>Date</classname> dans le futur si celà devient nécessaire, via le
|
|
|
+ constructeur ou la méthode <methodname>init</methodname>.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Ensuite, nous devons réécrire la méthode <methodname>loadDefaultDecorators()</methodname> pour
|
|
|
+ lui indiquer d'utiliser notre décorateur <classname>Date</classname>:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="php"><![CDATA[
|
|
|
+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'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ...
|
|
|
+}
|
|
|
+]]></programlisting>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ A qyuoi ressemble le rendu final ? Considérons l'élément suivant:
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="php"><![CDATA[
|
|
|
+$d = new My_Form_Element_Date('dateOfBirth');
|
|
|
+$d->setLabel('Date de naissance: ')
|
|
|
+ ->setView(new Zend_View());
|
|
|
+
|
|
|
+// Ces deux procédés sont équivalents:
|
|
|
+$d->setValue('20 April 2009');
|
|
|
+$d->setValue(array('year' => '2009', 'month' => '04', 'day' => '20'));
|
|
|
+]]></programlisting>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Si vous affichez cet élément, vous obtiendrez ce rendu (avec quelques modifications
|
|
|
+ concernant la mise en page du manuel et sa lisibilité):
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <programlisting language="html"><![CDATA[
|
|
|
+<dt id="dateOfBirth-label"><label for="dateOfBirth" class="optional">
|
|
|
+ Date de naissance:
|
|
|
+</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>
|
|
|
+]]></programlisting>
|
|
|
+ </sect2>
|
|
|
+
|
|
|
+ <sect2 id="learning.form.decorators.composite.conclusion">
|
|
|
+ <title>Conclusion</title>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Nous avons maintenant un élément qui peut rendre de multiples champs de formulaire,
|
|
|
+ et les traiter comme une seule entité -- la valeur <varname>dateOfBirth</varname>
|
|
|
+ sera passée comme un tableau à l'élément et celui-ci créra les segments de date
|
|
|
+ appropriés et retournera une valeur normalisée.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Aussi, nous pouvons toujours utiliser des décorateurs différents avec l'élément. Si
|
|
|
+ nous avions voulu utiliser un décorateur
|
|
|
+ <ulink url="http://dojotoolkit.org/">Dojo</ulink> <classname>DateTextBox</classname>
|
|
|
+ -- qui accepte et retourne des chaines -- we aurions pu, sans modification sur
|
|
|
+ l'élément lui-même.
|
|
|
+ </para>
|
|
|
+
|
|
|
+ <para>
|
|
|
+ Enfin, vous avez une API uniforme pour décrire un élement se composant se plusieurs
|
|
|
+ segments distincts.
|
|
|
+ </para>
|
|
|
+ </sect2>
|
|
|
+</sect1>
|