Zend Form: Decoradores

Extendiendo un poco el tutorial de Formularios Zend ahora explicaré como funcionan los decoradores. Los decoradores son la manera de visualizar los diferentes elementos de la forma, básicamente se encargan de ‘renderizar’ los elementos de una forma (es decir es la parte de la Vista).

En el tutorial anterior no definí ningún decorador, esto es porque el Zend Framework inicializa los decoradores con algunos por defecto. Veremos un poco del código interno del Zend Framework para entenderlo a detalle.

en Zend/Form/Element.php nos daremos cuenta que en el __construct (inicialización del elemento) hasta al final hay una llamada a la función loadDefaultDecorators que es la siguiente:

public function loadDefaultDecorators()
{
    if ($this->loadDefaultDecoratorsIsDisabled()) {
        return;
    }

    $decorators = $this->getDecorators();
    if (empty($decorators)) {
        $this->addDecorator('ViewHelper')
            ->addDecorator('Errors')
            ->addDecorator('HtmlTag', array('tag' => 'dd'))
            ->addDecorator('Label', array('tag' => 'dt'));
    }
}

Estos cuatro decoradores son la base de como los elementos son renderizados. El sistema procesa en orden lineal de como definimos los decoradores, si no modificamos entonces el orden es de procesar primero ViewHelper, luego Errors, HtmlTag y por último Label.

Cada decorador al momento de procesar en la función render, el sistema le pasa una cadena con el contenido actual y regresa una cadena de texto que procesó. Idealmente debe de utilizarse la cadena del contenido actual, pero con esto también podemos incluso procesar los elementos procesados y sobreescribir todo el resultado o hacer otras cosas interesantes (aunque podría ser no recomendable en un elemento que tenga como propósito ser parte de una librería).

Los elementos por defecto funciona de la siguiente manera:

  • ViewHelper: renderea en sí el elemento mediante un helper. Por ejemplo un elemento Zend_Form_Element_Select que tiene un helper formSelect utilizará este último helper para mostrar el elemento como <select>...</select>
  • Errors: renderea las partes de los errores, utiliza una plantilla de la siguiente forma:
    <ul class="errores"[opciones html]>
    <li>[error1]</li>
    ....
    <li>[errorN]</li>
    </ul>
  • HtmlTag: Le da un poco de formato al elemento ya que lo agrega en una plantilla de la siguiente forma:
    <dd>
    ...
    </dd>
  • Label: Asigna una etiqueta de la siguiente forma:
    <dt><label>Etiqueta</label></dt>

Los decoradores por defecto tienen opciones para ser puestos al inicio (PREPEND) o al final (APPEND) del contenido, por defecto Errors se pone al final, HtmlTag pone el contenido en medio de las etiquetas y Label se pone al inicio del contenido.

Por lo que con las opciones por defecto la plantilla de como se verá nuestro elemento es:

<dt><label>[Etiqueta del elemento]</label></dt>
<dd>
    [Elemento]
    <ul class="errors">
        <li>[Errores]</li>
    </ul>
</dd>

Si queremos cambiar el orden, o forma de presentación tenemos que cambiar un poco el formato de salida; por ejemplo con el siguiente código (modificándolo de IndexController.php del tutorial de Zend Form):

$decoradores = array(
     'ViewHelper',
     'Errors',
     array('HtmlTag', array('tag' => 'div', 'class' => 'formElement')),
     array('Label', array('tag' => 'div'))
);

$nombre = $formulario->createElement('text', 'nombre_completo')
               ->setLabel('Nombre Completo')
               ->setDecorators($decoradores)
               ->setRequired(true);

Esto nos mostrará un resultado como el siguiente:

Ejemplo de Zend_Form_Decorator

Como hemos definido todos los decoradores que vamos a utilizar es fácil aplicarlo a los demás elementos (en el ejemplo anterior, sólo se aplicó al primer elemento.

Hay que hacer notar que sólo podemos utilizar una vez el decorador en un elemento, si agregamos otro HtmlTag por ejemplo, el último sobreescribirá al primero.

Si queremos construir algo más complejo existe un decorador que nos permite tener mucho mayor control: ViewScript, este decorador básicamente llamará a un archivo de la carpeta de vista (views/scripts) y le pasará tres parámetros:

  • content: El contenido actual producto del renderizado de los otros decoradores
  • element: El Zend_Form_Element que estamos renderizados
  • decorator: Una referencia al decorador ViewScript

Por defecto el resultado se agrega al final, si pasamos como opcion que la posición sea false entonces el contenido que resulte de renderizar este decorador será el único contenido.

Por ejemplo lo siguiente crea un “contenedor” al elemento:

$decoradores = array(
           'ViewHelper',
           'Errors',
           array('HtmlTag', array('tag' => 'div', 'class' => 'formElement')),
           array('Label', array('tag' => 'div')),
           array('ViewScript', array('viewScript' => 'decorador-contenedor.phtml', 'placement' => false)),
    );

Y el decorador está especificado como (archivo nombreado como: views/scripts/decorador-contenedor.phtml:

<div class="formElementContent">
	<?=$this->content ?>
</div>

El resultado es el siguiente (con un poco de CSS):
Ejemplo de Zend_Form_Decorator

Ahora bien, si queremos podemos prácticamente reconstruir todo los demás decoradores mediante un un decorador de ViewHelper y un decorador ViewScript de la siguiente manera:

$decoradores = array(
          'ViewHelper',
          array('ViewScript', array('viewScript' => 'decorador-avanzado.phtml', 'placement' => false)),
     );

Y el decorador seria de la siguiente manera:

<!-- Esta primera parte reemplazaria HtmlTag -->
<div class="formElementContent">
	<!--  parte que remplaza el decorador Label -->
	<div>
    <?php
    	$attribs = $this->element->getAttribs();
    	$label = $this->element->getLabel();

    	//nuevo, verificamos si el elemento es requerido,
    	//entonces agregamos un elemento en html al final de la etiqueta
    	if ($this->element->isRequired()) {
    		$attribs['escape'] = false;
    		$label = $this->escape($label). ' <span class="required">*</span>';
    	}

    	echo $this->formLabel($this->element->getName(), $label, $attribs);
    ?>
    </div>

    <!-- parte del elemento -->
    <?=$this->content ?>

    <!--
    	parte que reemplaza Errors
    	observen que estoy poniendo los errores
    	con simples saltos de linea en lugar de lista
     -->
	<?php if ($this->element->getMessages()) { ?>
	<div class="errors">
		<?=implode('<br />', $this->element->getMessages())?>
	</div>
	<?php } ?>

    <!-- esta parte es opcional si agregaron una descripción -->
    <div class="hint"><?= $this->element->getDescription() ?></div>
</div>

El resultado con un poco de CSS es el siguiente:

Ejemplo de Zend_Form_Decorator

Como se puede observar el anterior ejemplo nos da completo control sobre como mostrar los elementos.

También en un caso más complejo y avanzado podemos incluso definir con algunos cambios sobre el formulario principal (Zend_Form) para definir un decorador que nos permita sobre la vista definir el orden de los elementos, mostrar un formulario totalmente estilizado que no sea regular o incluso no mostrar elementos o crear nuevos para cosas como AJAX/Javascript por ejemplo.

Pero de eso hablaría en otro pequeño tutorial =).

Código Fuente:

DanguerArticle_Zend_Form_Decorator.tar.gz

Visitar Sitio Demo

Temas similares

  • http://- Gabriel

    Gracias maestro, estoy casi intentando captar las ideas, pero todavia no me quedo claro, lo de, como hacer por ejemplo, variar la posicion, en donde se va a mostrar el ejemplo de error. Me estoy volviendo loco, no lo puedo creer.

    Alguna ayudita mas??

  • http://www.danguer.com admin

    Ah mira, lo que pasa es que dentro de tu decorador personalizado tienes el $this->element, esto es el elemento de la forma, y este tiene un método llamado getMessages() que regresa nulo si no hubo errores o un arreglo con todos los mensajes de error que tenga el elemento.

    Dentro del archivo /app/views/decorador-avanzado.phtml verás que hay una parte que contiene:
    < ?php if ($this->element->getMessages()) { ?>

    < ?=implode(’
    ’, $this->element->getMessages())?>

    < ?php } ?>

    Esa es la parte que está mostrando los errores, por ejemplo simplemente para mostrar el elemento (sin etiqueta) puedes hacer:

    < ?=$this->content?> < ?php if ($this->element->getMessages()) { ?>

    < ?=implode(’
    ’, $this->element->getMessages())?>

    < ?php } ?>

    Eso mostrará al lado los errores.

  • http://www.danguer.com admin

    Bueno, me cortó el código, checate aqui el ejemplo:
    http://pastebin.com/f473b2f46

  • http://- Gabriel

    Gracias, esta tarde, cuando tenga mi tiempo libre, lo intentaré y te cuento como me fue, dale?

    Un abrazo.

  • http://- Gabriel

    Mira, yo hice algo parecido, quiero saber si voy encaminado, primero en mi controller puse:

    //Creo un Formulario//
    $form = new Zend_Form ( );
    $nombre2 = new Zend_Form_Element_Text ( ‘nombre’ );
    $nombre2->setRequired ( true );
    $nombre2->addValidator ( ‘Alnum’ );

    $submit = new Zend_Form_Element_Submit ( ‘submit’ );

    $form->addElement ( $submit );
    $form->addElement ( $nombre2 );

    //Decorador personalizado//

    $form->setDecorators ( array (array (‘ViewScript’, array (‘viewScript’ => ‘demogForm.phtml’ ) ) ) );

    Y en mi demogForm.phtml tengo lo sguente:

    <form
    action=”element->getAction() ?>”
    method=”element->getMethod() ?>”
    id=”element->getName() ?>”>
    element->nombre; ?>

    Hasta ahi voy bien, por lo menos ya se me muestra la configuracion del Formulario en mi decorator personalizado.

  • http://- Gabriel

    Mi duda es la siguente, si en el controlador luego pongo:

    if ($this->getRequest ()->isPost ()) {
    //verificar si es valido
    if (!$form->isValid ( $_POST )) {
    …..
    }

    Con esto verifica, y automaticamente, me insterta los mensajes de error debajo de los campos, eso es lo que no quiero, sino, que quero, yo decirle, donde poner los mensajes de error. Nose s me explique bien.
    Que podria hacer?

  • http://- Gabriel

    Mi duda es la siguente, si en el controlador luego pongo:

    if ($this->getRequest ()->isPost ()) {
    //verificar si es valido
    if (!$form->isValid ( $_POST )) {
    …..
    }

  • http://www.danguer.com admin

    Bueno, respecto a lo primero lo que estás haciendo es personalizar el formulario. El formulario (Zend_Form) solamente es un contenedor para los elementos, por tanto lo único que hace es poner las etiquetas de

    y dentro renderea todos los elementos. Creo que es mejor que pongas $elemento->setDecorators($decorador) en lugar de $forma->setDecorators($decorador)

    Respecto a tu segunda pregunta, el sistema cuando le preguntas $form->isValid() lo que hace es ir elemento a elemento y validarlo, el sistema regresa simplemente un true/false para saber si todos los elementos fueron válido o no.

    Si no fue válido los Validadores (Zend_Validate_*) agregarán un mensaje de error; pero al elemento, no a la forma (porque es un contenedor) tu puedes obtener esos mensajes con $this->element->getMessages() dentro del decorador para tu elemenento, no la forma.

  • http://- Gabriel

    buenisisimo, ahora lo que no entiendo es, como VALIDARLO, sin que me ponga los mensajes automaticamente.

    Es decir obtento los mensajes con $this->element->getMessages, bien?

    pero si pongo el isvalid() antes, al no ser valido, me pone solo, los mensajes de error, entonces, tendria que desahabilitar que me ponga los mensajes automaticamente para poderlos ponerlos yo a traves de
    $this->element->getMessages?

    Nose si me explique bien.

    Gracias por tu atencion,

  • http://www.danguer.com admin

    Ah mira, Si el sistema está poniendo los mensajes automáticamente puede ser porque está llamando al decorador “Errors” (tal vez por defecto del constructor del elemento), checa en tu HTML una vez procesado si algo como “

    ” cuando muestra los errores; si existe entonces está llamando al decorador Errors.

    Recuerda que tienes que sobreescribir los decoradores a todos los elementos de tu forma para que funcione, ya que si no mostrará el Errors de manera predeterminada.

    Saludos,

  • http://- Gabriel

    Muchas Gracias, pero te juro que no le encuntro el error,mira, para armar el formulario el controlador me ha quedado asi (uso un controlador, para empezar, despues lo ire mejorando)

    //Creo un Formulario//
    $form = new Zend_Form ( );
    $nombre2 = new Zend_Form_Element_Text ( ‘nombre’ );
    $nombre2->setRequired ( true );
    $nombre2->addValidator ( ‘Alnum’ );

    $submit = new Zend_Form_Element_Submit ( ’submit’ );

    $form->addElement ( $submit );
    $form->addElement ( $nombre2 );

    //Decorador personalizado//

    $form->setDecorators ( array (array (’ViewScript’, array (’viewScript’ => ‘demogForm.phtml’ ) ) ) );

    if ($this->getRequest ()->isPost ()) {
    //verificar si es valido
    if (!$form->isValid ($form )) {

    }

    Y en mi demogForm.phtml tengo lo sguente:

    getAction() ?>”
    method=”element->getMethod() ?>”
    id=”element->getName() ?>”>

    element->nombre; ?> //Muestra el campo nombre

    element->sumbmit; ?> // Muestra boton

    Ahora, dentro de este codigo, como lo modificarias, para poder llevar a obtener, que no muestre los mensajes automaticamenten, y los coloque donde yo queiera.?

    Estoy tan cerca de lograrlo por tu ayuda.!!!
    Muchisimas Gracias.

  • http://- Gabriel

    ojo, cuando muestro los campos del formulario,el blog me elimina los $this.

  • http://- Gabriel

    Hola, Maestro, primero y principal, te queria agredecer, ya que con vos fue la unica persona a la que le entendi este tema, si no sos maestro deberias dedicarte a la docencia =).

    Revice como 10 veces tu ejemplo, y ademas le agregue algo más del mio. El resultado fue el siguiente:

    con:

    $decoradores = array(
    ‘ViewHelper’,
    array(‘ViewScript’, array(‘viewScript’ => ‘decorador-avanzado.phtml’, ‘placement’ => false)), );

    Lo que hago con esto, es poder asignarle el decorador a cada elemento del formulario, y ahi poder configurarle donde quiero que se muestre el mensaje de error en el campo (Arriba, abajo, etc.)

    Luego tambien le agregue este decorador:

    $formulario->setDecorators ( array (array (‘ViewScript’, array (‘viewScript’ => ‘demogForm.phtml’ ) ) ) );

    Con esto, digamos que armo como quiero el formulario (en terminos de decorators) y le digo en dondo situar cada campo. Arriba te postié como es mi codigo del “demogForm.phtml”.

    Y aqui mi ultima duda (eso espero), porque avancé muchisimo. Si en ves de que muestre el mensaje de error abajo de cada elemento o arriba, me los muestre, todos Arriba del formulario en un recuadro. Es posible??
    Como lo harias?

    Desde ya, muchisimas gracias por toda tu buena onda!!!

  • http://www.danguer.com admin

    Hola, muchas gracias por los comentarios.
    Mira, pues para el formulario hay que utilizar un procedimiento similar (que de hecho ya lo estabas usando), por defecto el formulario agrega los siguientes decoradores:
    ‘FormElements’, ‘HtmlTag’ y ‘Form’

    El primero solamente renderea cada uno de los elementos, el segundo lo pone en una etiqueta ‘dl’ y el último pone la etiqueta de <form>

    Utilizando algo similar, cambiamos los decoradores a:
    ‘FormElements’, ‘ViewScript’, ‘HtmlTag’ y ‘Form’

    Donde ViewScript tiene el siguiente código:
    http://pastebin.com/f7b97560f

    Si notas la variable $this->element no es un elemento de la forma, sino la forma misma, entonces lo que debes hacer es ir elemento a elemento de la forma (con foreach) y verificar si tiene errores, si tiene errores los agregas a un arreglo y al final si tiene valores, los muestras.

    Nota que como es el elemento puedes poner algo como: ‘LABEL no debe ser vacio’, ya sea verificando la parte del arreglo asociativo y la etiqueta del elemento $elemento->getLabel(), esto es totalmente al gusto, en mi ejemplo solo muestra una vez el tipo de error (si no escribes nada aparece una sola vez el error, pero no especifica por ejemplo la etiqueta del elemento problemático)

    El código del controlador es simple:
    http://pastebin.com/f55176b6f

    Solamente para finalizar recuerda eliminar del decorador del elemento la parte de imprimir los mensajes de error para que no sean duplicados.

    Saludos,

  • http://- Gabriel

    Muchisimas gracias, por responder, tengo otra consulta, por ejemplo quiero que en algunos campos, el populate, este deshabilitado, como por ejemplo en esos sitios en donde el campo password no tiene populate (no se completa solo).

    Ya que encontra para deshabilitarlo en todo el formulario, pero no en un campo especifico.

    Un abrazo

  • http://- Gabriel

    Hola de nuevo, como estas? Bueno, siguiendo practicando me he encontrado con las siguientes dudas (asi puedo terminar mi forulario =) ). Las dudas son las siguientes, en el archivo decorador-avanzado.phtml para mostrar el “campo”
    del formulario, le pusimos “content ?> ” ok?. Pero por ejemplo, yo quiero customizar un Multi Check Box que contiene 3 elementos, entoces si pongo “content ?>, no los puedo customizar por cada elemento, sino que plasma todo de una. Tambien me pasa cuando quiero customizar un Captcha, ya que este te inserta 2 elementos (la imagen y el campo para insertar). Nose si me he explicado bien.

    Otra cosa que no termino de entender es lo siguiente, por ejemplo en un campo de texto comun, al hacer
    content ?>, como le puedo configurar, que si hay un error, mi CSS sea tal (quiero que el campo se ponga rojo por ejemplo) ?

    Desde ya. Muchas Gracias por tu atention, este blog esta de 10!!

  • http://www.danguer.com admin

    Hola gracias por los comentarios =), necesitas personalizar el elemento, recuerda que ViewHelper “renderea” el elemento de acuerdo a un helper, por ejemplo el Zend_Form_Element_Select tiene un formSelect como helper; necesitas verificar por cada elemento porque varian un poco el helper, por ejemplo el select que viene de los multi-opciones acepta más parametros, pero debes reemplazar el decorador del elemento en $this->element por:

    echo $this->{$this->element->helper}(
    $this->element->getName(),
    $this->element->getValue(),
    $this->element->getAttribs()
    );

    Puedes variar por ejemplo el getAttribs() para agregar la clase (array(‘class’ => ‘errror’) por ejemplo si es que tiene un mensaje de error, de esta manera el elemento recibe una clase de error que puedes hacer por ejemplo que el borde sea rojo, todo es cuestion de moverle al CSS y al decorador =)

    También recuerda que si usas esto en tu decorador en tu elemento no tiene caso que dejes el viewHelper en tus decoradores principales ya que el ViewHelper simplemente renderea el elemento en un elemento de formulario (lo sobreescribirá así que no vale la pena realizar doble operación)

    Saludos,

  • daniel

    Hola, estoy buscando la manera de trabajar con zend_form y tablas, ya que quiero que el formulario se vea de determinada manera y no me sale, alguien tiene alguna punta para esto, gracias.

  • Mictlantecuhtli

    Pues utilizar el siguiente código:

    Op

    ID

    Nombre

    Apellido Paterno

    Apellido Materno

    Correo

    data as $persona)
    {
    ?>
    <tr >
    <input type=”checkbox” name=”usuarios[]” id=”chkUsuario” value=”escape($persona->IdPersona)?>”>
    <a href=”?ctl=Usuario&op=capturar&id=IdPersona;?>”>
    escape($persona->IdPersona);?>
    escape($persona->Nombre);?>
    escape($persona->Paterno);?>
    escape($persona->Materno);?>
    escape($persona->Correo);?>

    Yo tengo definidos los siguientes estilos:

    .Grid
    {
    /*border: thin solid #003366; */
    font-family: Arial, Helvetica, sans-serif;
    font-size: 10px;
    color: #003366;
    cellpadding:1;
    cellspacing:1;
    background-color:white;
    position: static;
    top: 0px;
    margin-left:auto;
    margin-right:auto;
    margin-top: 0px;
    padding-top: 0px;
    width:100%;
    }
    .GridHeader
    {
    font-family:Ariel, Velvetica, sans-serif;
    font-weight: bold;
    font-size:10pt;
    color:#FFFFFF;
    background-color:#003366;
    }
    .GridRowPair
    {
    background-color:#BFD5FF;
    }
    .GridRowPair:hover
    {
    background-color:#6699FF;
    }
    .GridRowNoPair
    {
    background-color:#FFFFFF;
    }
    .GridRowNoPair:hover
    {
    background-color:#6699FF;
    }

    Jugando con los estilos y usando un contador para reconocer los renglones nones y pares se iluminan de distinto color además de configurar un tercer color para el evento OnmouseOver, con lo cual el renglón sobre el que se pasa el apuntador del mouse se ilumina de un tercer color.

    Saludos

  • Mictlantecuhtli

    Sorry

    Al postear el código se eliminaron algunos tags, puedes consultar el código de la tabla en la liga:
    http://pastebin.com/m300844fe

    un ejemplo del grid lo puedes consultar en la liga:
    http://img244.imageshack.us/my.php?image=gridsamplejo0.jpg

    Espero y te sirva esta info, veo que tiene tiempo que no posteas nada.

    Suerte

    Saludos

  • Alberto

    Hola,

    Estoy tratando de aplicar al $this->content una clase para que cuando muestre el error pinte el input con bordes rojos.

    ¿Dónde y cómo aplico esa clase que tengo definida en mi hoja de estilos?

    Me está costando comprender esto del Zend_Form a fondo…

    Gracias por la ayuda.

  • Surt

    Saludos Danguer,

    primero, muy buen articulo.
    Luego, lo que temias, preguntas :)

    he estado tratando de eliminar todo el html que para mi gusto zend_form mete de mas, (dl dd dt) en cada elemento del formulario.

    Actualmente en el loaddefault decorators de mi custom form hago un foreach de los elementos, para remover dd dt, pero claro, es ineficiente, así que estoy tratando de extender Element.php sobreescribiendo loaddefault decorator, para desacerme de sus dd dt.

    El problema es: En que lugar cuando instancio un elemento en zend_form se instancia Element.php de Zend, lo que quiero es que mi custom form extienda mi custom Element.php que a su vez extiende el por defecto, pero elminando de su laddefauldecorators los dd dt.

    Todo esto es debido a que si “re”-seteo los default decorators, pues me cargo algunos importantes de file y de capcha. por eso queria extender Element.php para que simplemente no añadiera dd dt

  • daniel

    Hola, amigos.

    Estoy intentando hacer un form en ZF, todo en un renglon.

    usuario: password:

    si alguien lo ha hecho ya le agradezco que me aclare las ideas.

    Saludos.

  • daniko

    Hola excelente el tutorial, pero ahora deseo que mi formulario pueda convertirse en varios idiomas, he estado leyendo q se puede configurar desde archivos estos idiomas por ejemplo lang_es.cvs o lang_en.cvs. no se alguien me puede ayudar con esto. Gracias

  • Anonymous

    Hola Daniel interesante lo que haces.

    Quisiera saber como seria el decorador para File, para Option, Chekc y select. Gracias de antemano