Cuando empezamos a investigar como hacerlo suelen surgir miles de dudas. Quizá no entendemos del todo bien que son los decorators ni nos llegamos a hacer una idea de lo flexibles que son; comúnmente pensamos que no es posible hacer markups complicados, pero veremos que no es así.

Zend_Form_Decorator_Abstract

Esta es la clase base a partir de la cual extienden todos los decorators anteriormente nombrados. Los métodos más importantes que implementa son:

setOptions() //setea las diferenes opciones del decorator.
setOptions("placement", APPEND / PREPEND) //si el contenido generado por el decorator se agrega al principio o al final.
setOptions("separator", string) //separador entre el contenido viejo y el nuevo

Standard Decorators

Zend trae por default los siguientes decorators, veamos los métodos más usados de cada uno, todos los decorator tienen los métodos nombrados anteriormente para Zend_Form_Decorator_Abstract:

  • Callback: permite generar contenido de acuerdo a un callback en particular.

    setCallback($callback)
  • Description: muestra la descripción seteada en un Zend_Form o Zend_Form_Element.
    setTag($tag): el tag del elemento que contiene el texto, por defecto <p>
    setEscape($bool): si queremos escapar o no los caracteres HTML. Por defecto es true.
    setClass($class): class del elemento. Por defecto "hint".
  • Errors: lista los errores que contiene el elemento.
  • Fieldset: pone dentro de un fieldset el contenido recibido.
    setLegend($legend): setea el legend del fieldset.
  • Form: genera el tag >form> del formulario.
  • FormElements: forms, display groups y subforms son colecciones de elementos. Este decorator recorre cada uno de estos elementos y genera su contenido, por lo que es un decorator que siempre debe estar presente.
  • FormErrors: parecido al Errors, pero muestra todos los mensajes de error en la parte superior del formulario en lugar de hacerlo para cada elemento en particular.
  • HtmlTag: nos permite generar el tag html que querramos.
    setTag($tag)
    setOption("openOnly", $bool): crea solamente el tag de apertura.
    setOption("closeOnly", $bool): crea solamente el tag de cierre
  • Label: en general todos los elementos del form tienen un texto asociado, este texto es generado por este decorator.
  • ViewHelper: es el elemento propiamente dicho, por ejemplo un <input/>, un <textarea/>, etc.

Setear Decorators

Podemos agregar decorators a forms (y subforms) y elementos. Cada uno de estos objetos puede tener asociados todos los decorators que queramos. El orden de los decorators es importante porque generalmente determina el markup final, no es lo mismo tener <input /> <label /> que <label /> <input />, de un ejemplo a otro lo que cambia es el orden en el que fueron incluidos los decorators ViewHelper y Label.

Los decorators asociados a un objeto se comportan como una cola, por lo que si queremos agregar un decorator al principio (o en cualquier otro lugar que no sea el final) tendremos que setear todos los decorators otra vez.

Para agregar / setear decorators tenemos dos métodos, setDecorators() y addDecorator():

  • setDecorators() setea los decorators del elemento, es decir que si ya hay algún decorator seteado lo sobreescribe.
  • addDecorator() agrega un decorator al final de los decorators existentes.

Estos métodos son tanto para elementos, como para forms, subforms y display groups.

$element->setDecorators($decorators);
$form->addDecorator($decorator);

Aquí suelen empezar las dudas, pero veamos algunos ejemplos de uso, suponiendo que nuestro elemento es un text input:

$element->setDecorators(array("ViewHelper", "Label", "HtmlTag"));

Logramos generar <div> <label /> <input /> </div>

$element->setDecorators(array("ViewHelper", "Label", "HtmlTag", "HtmlTag"));

Aquí podemos pensar que tenemos dos divs, pero no es el caso, porque el último html tag sobreescribe al primero. Para tener dos HtmlTag distintos hay que asignarle un alias a alguno de ellos. Antes de esto, veamos un poco más las diferentes formas de pasar un decorator.

Es importante ver y entender este ejemplo porque aquí vemos todas las posibilidades existentes. Obviamente no es del todo válido porque tenemos 3 decorators que se llaman igual (“HtmlTag”) y otros 2 que se llaman “miHtmlTag”, por lo que se estarían sobreescribiendo y solo quedaría uno de cada tipo, pero veamoslo como un ejemplo educativo.

$element->setDecorators(array(
    // agregamos un decorator del tipo HtmlTag
    "HtmlTag",
    // idéntico al primer ejemplo, el hecho de que lo pasemos dentro de un array no cambia nada
    array("HtmlTag"),
    /* aca vemos un ejemplo mas concreto del caso anterior. Pasamos el decorator como un array cuando queremos setear alguna opción adicional.
     el primer elemento del array es el nombre del decorator, y el segundo un array de opciones a setearle al decorator */
    array("HtmlTag", array("tag" => "span")),
    /* este ejemplo es igual al anterior, pero ahora al decorator le damos un alias. En el futuro no nos referiremos a él como el decorator HtmlTag, sino como "miHtmlTag" */
    array(array("miHtmTag" => "HtmlTag"), array("tag" => "span")),
    // por último, aqui creamos un HtmlTag dándole otra vez un alias, pero esta vez sin pasarle las opciones
    array(array("miHtmlTag" => "HtmlTag"))
));

Teoría de funcionamiento

Ahora que ya sabemos como agregar decorators, como darles un alias para poder usar varios decorators del mismo tipo y como setear las opciones; vamos a hablar sobre como van trabajando los distintos decorators.

Cada decorator tiene la función de generar un markup en particular. El contenido generado por un decorator es pasado al siguiente, este agrega (al principio o al final) su propio markup al contenido que recibe y se lo pasa al siguiente decorator que vuelve a hacer lo mismo.

Supongamos que nuestro elemento tiene los siguientes decorators:

$element->setDecorators(array(“ViewHelper", “Label", “HtmlTag", “Error"));

1) Al ejecutar el método render() se inicia el proceso. Al primer decorator se le pasa una cadena vacía (“”) .

$html = “";

2) El decorator ViewHelper genera el HTML del elemento asociado, si nuestro elemento es un text input obtendremos como resultado:

$html = $html . '<input type="text" name="…" />';

3) El siguiente decorator es el Label, por defecto el contenido que genera lo agrega al principio (placement = prepend):

$html = '<label>...</label>' . $html = '<label>...</label><input type="text" name="…" />';

4) El HtmlTag por defecto funciona como wraper, es decir que envuelve el contenido que recibe, y el tag por defecto es div:

$html = '<div>' . $html . '</div>' = '<div><label>...</label><input type="text" name="…" />>/div>';

5) El decorator Errors se aplica solamente si hay algún error. Si no hay ningún error no hace nada. Supongamos que ocurrió algún error al validar el form, el markup será así:

$html = $html . '<ul><li>Error 1</li><li>Error N</li></ul>';

Por lo que aquí termina el proceso, y el markup final es:

<div>
            <label>...>/label>
            <input type="text" name="…" />
        </div>
        <ul>
            <li>Error 1>/li>
            <li>Error N>/li>
        </ul></div>

Como vimos el markup se fue armando paso a paso y los decorators trabajaron desde adentro hacia afuera, es decir que los decorators que están más afuera son los últimos en ser agregados, ya que los decorators agregan contenido adicional pero generalmente no modifican el contenido que reciben.

Un poco de teoría sobre placement

¿Estos dos decorators son iguales?

array('ViewHelper', 'Label') ¿=? array('Label', 'ViewHelper')

Quizás la respuesta rápida es decir que no, pero en realidad si, porque el comportamiento por default del Label es agregar el contenido al principio, entonces en el primer caso tenemos:

1) <input ... />

2) <label ... /><input ... />

y en el segundo caso

1) <label ... />

2) <label ... /><input ... />

Es un pequeño detalle que hay que tener en cuenta, porque muchas veces cuando pensamos que algo no se puede hacer o es muy difícil, se soluciona simplemente sabiendo manejar el placement de cada decorator.

Algunos ejemplos

Ahora veamos un ejemplo de un markup un poco mas complejo, por ejemplo tenemos el siguiente form:

public function init() {

    $this->addElement('text', 'uno, array('label' => 'Uno'));
    $this->addElement('text', 'dos, array('label' => 'Dos'));

}

¿cómo generamos el siguiente markup?

>div class="row">
    <div><label>Uno</label><input type='text' name='uno' /></div>
    <div><label>Dos</label><input type='text' name='dos' /></div>
>/div>

Veamos:

public function init() {

    $this->addElement('text', 'uno', array('label' => 'Uno'));
    $this->addElement('text', 'dos', array('label' => 'Dos'));
    $this->addElement('text', 'tres', array('label' => 'Tres'));

    $this->setElementDecorators(array('Label', 'ViewHelper', 'HtmlTag'));

    // nos falta el div que los envuelve...
    $this->getElement('uno')->addDecorator('HtmlTag', array('class' => 'row', 'openOnly' => true));
    $this->getElement('tres')->addDecorator('HtmlTag', array('closeOnly' => false));

}

Por si no se entiende bien explico lo que hicimos. Con setDecorators() setemos los decorators de todos los elementos del form agregados hasta el momento. Como los tres elementos tienen los mismos decorators, en vez de setearlos de a uno lo hacemos asi, si en el futuro los queremos cambiar los cambiamos en un solo lugar y listo.

Como cada decorator funciona sobre un elemento, no podemos envolver los tres elementos aplicando un decorator a uno solo de ellos. Por lo tanto, al primer decorator le agregamos el tag de apertura y al último el tag de cierre. Al terminar el render(), el resultado final será el esperado.

Por último, otro ejemplo bastante común es como maquetar una tabla:

<form>
<table>
<tr><td><label>Uno</label></td><td><input name='uno' /></td></tr>
<tr><td><label>Dos</label></td><td><input name='dos' /></td></tr>
<tr><td><label>Tres</label></td><td><input name='tres' /></td></tr>
</table>
</form>

Los decorators de los elementos los seteamos así:

$this->setElementDecorators(
            array(
                "ViewHelper",
                array("HtmlTag", array("tag" => td")),
                array("Label" , array("tag" => "td")),
                array(array("tr" => "HtmlTag"), array("tag" => "tr"))
            )
);

Explicado rápidamente, creamos el input, lo envolvemos en un >td>, creamos el label con otro tag td y se ubica al principio (ya tenemos los dos >td> en orden), y por último un HtmlTag >tr> que envuelva todo lo anterior.

Ahora faltaría el tag table. Podemos hacer como en el ejemplo anterior, usar dos HtmlTag en el primer y último elemento y listo. Pero veamos otra posibilidad. Modificamos los decorators del form de la siguiente manera:

$this->setDecorators(
            array(
                "FormElements",
                array("HtmlTag", array("tag" => "table")),
                "Form"
            )
);

El decorator FormElements renderiza todos los elementos. Es decir, que nos quedaría la tabla sin el tag table no? entonces le agregamos el tag table que falta, y por último el tag form. Listo! No me van a decir que no fue fácil eh! Resumiendo el tema, para saber maquetar cualquier formulario necesitamos conocer a fondo los decorators y todas las opciones de configuración de cada uno, y quizás un poquito de imaginación.

Por último un consejo: el manual oficial es nuestro mejor amigo, ténganlo en cuenta ante cualquier duda, ya que todas las respuestas están ahí. Pero mucho más amigo aún, es el código fuente, es la manera más rápida de entender el funcionamiento de las cosas y conocer métodos que no sabemos que existen. En este caso, estamos hablando de los decorators pero se puede generalizar a cualquier otra cosa.

Continuando con la guía:

Si tienen dudas de como funciona algo no hay nada mejor que mirar el código y entenderlo. ZF se caracteriza por su simpleza, así que no piensen que mirar el código del framework es una pérdida de tiempo. Van a entender mejor las cosas y a ganar una cantidad de conocimiento impresionante.

Ir al siguiente capítulo: Construyendo aplicaciones multi idioma con Zend_Translate