Symfony 1.4, la guía definitiva

10.3. Widgets de formulario

Symfony incluye muchos tipos diferentes de widgets para crear los formularios. Todos los widgets admiten como mínimo la opción default.

Al crear el formulario también puedes definir el título de cada widget e incluso sus atributos HTML:

$this->form = new sfForm();
$this->form->setWidgets(array(
  'nombre'    => new sfWidgetFormInput(array('label' => 'Tu nombre'), array('size' => 25, 'class' => 'miclase')),
  'email'   => new sfWidgetFormInput(array('default' => '[email protected]', 'label' => 'Tu Email'), array('onclick' => 'this.value = "";')),
  'asunto' => new sfWidgetFormChoice(array('choices' => array('Asunto A', 'Asunto B', 'Asunto C'))),
  'mensaje' => new sfWidgetFormTextarea(array(), array('rows' => '20', 'cols' => 5)),
));

Symfony emplea estos parámetros al mostrar el widget, pero su valor se puede redefinir en la plantilla mediante los atributos del método renderRow().

Nota Una alternativa a utilizar el método setWidgets() junto con un array asociativo es el uso de varios métodos setWidget($nombre, $widget).

10.3.1. Widgets estándar

A continuación se muestran todos los tipos de widgets disponibles y el código HTML que generan al ejecutar el método renderRow():

// Campo de texto
$form->setWidget('nombre_completo', new sfWidgetFormInput(array('default' => 'José Pérez')));
  <label for="nombre_completo">Nombre Completo</label>
  <input type="text" name="nombre_completo" id="nombre_completo" value="José Pérez" />

// Textarea
$form->setWidget('direccion', new sfWidgetFormTextarea(array('default' => 'Introduce aquí tu dirección'), array('cols' => 20, 'rows' => 5)));
  <label for="direccion">Direccion</label>
  <textarea name="direccion" id="direccion" cols="20" rows="5">Introduce aquí tu dirección</textarea>

// Campo de contraseña
// Por razones de seguridad estos widgets no admiten el parámetro 'default'
$form->setWidget('contrasena', new sfWidgetFormInputPassword());
  <label for="contrasena">Contrasena</label>
  <input type="password" name="contrasena" id="contrasena" />

// Campo oculto
$form->setWidget('id', new sfWidgetFormInputHidden(array('default' => 1234)));
  <input type="hidden" name="id" id="id" value="1234" />

// Campo de selección
$form->setWidget('soltero', new sfWidgetFormInputCheckbox(array('value_attribute_value' => 'soltero', 'default' => true)));
  <label for="soltero">Soltero</label>
  <input type="checkbox" name="soltero" id="soltero" value="true" checked="checked" />

Además de todas las opciones comentadas anteriormente, cada widget dispone de opciones adicionales que puedes consultar en la documentación de la API.

10.3.2. Widgets de listas

Symfony dispone de un widget llamado choice que debes utilizar siempre que el usuario realice una selección entre una lista de valores, independientemente de si el usuario selecciona uno o más valores. El comportamiento del widget choice se controla con dos parámetros opcionales llamados multiple (vale false por defecto) y expanded (también vale false por defecto):

multiple = false multiple = true
expanded = false Lista desplegable (<select>) Cuadro desplegable (<select multiple>)
expanded = true Lista de radiobutton Lista de checkbox

El widget choice necesita al menos un parámetro llamado choices y que sea un array asociativo con el valor y el texto de cada opción. A continuación se muestra un ejemplo de su sintaxis:

// Lista desplegable (select)
$form->setWidget('pais', new sfWidgetFormChoice(array(
  'choices'   => array('' => 'Selecciona un país', 'us' => 'EEUU', 'ca' => 'Canada', 'uk' => 'Reino Unido', 'otro'),
  'default'   => 'uk'
)));

// Symfony genera el siguiente código HTML
<label for="pais">Pais</label>
<select id="pais" name="pais">
  <option value="">Selecciona un país</option>
  <option value="us">EEUU</option>
  <option value="ca">Canada</option>
  <option value="uk" selected="selected">Reino Unido</option>
  <option value="0">otro</option>
</select>

// Cuadro desplegable (se pueden elegir varias opciones a la vez)
$form->setWidget('idiomas', new sfWidgetFormChoice(array(
  'multiple' => 'true',
  'choices'  => array('en' => 'Inglés', 'fr' => 'Francés', 'ottro'),
  'default'  => array('en', 0)
)));

// Symfony genera el siguiente código HTML
<label for="idiomas">Idiomas</label>
<select id="idiomas" multiple="multiple" name="idiomas[]">
  <option value="en" selected="selected">Inglés</option>
  <option value="fr">Francés</option>
  <option value="0" selected="selected">otro</option>
</select>

// Lista de radiobutton
$form->setWidget('sexo', new sfWidgetFormChoice(array(
  'expanded' => true,
  'choices'  => array('h' => 'Hombre', 'm' => 'Mujer'),
  'class'    => 'lista_de_sexos'
)));

// Symfony genera el siguiente código HTML
<label for="sexo">Sexo</label>
<ul class="lista_de_sexos">
  <li><input type="radio" name="sexo" id="sexo_h" value="h"><label for="sexo_h">Hombre</label></li>
  <li><input type="radio" name="sexo" id="sexo_m" value="m"><label for="sexo_m">Mujer</label></li>
</ul>

// Lista de checkbox
$form->setWidget('aficiones', new sfWidgetFormChoice(array(
  'multiple' => 'true',
  'expanded' => true,
  'choices' => array('Programación', 'Otro')
)));

// Symfony genera el siguiente código HTML
<label for="aficiones">Aficiones</label>
<ul class="aficiones_list">
  <li><input type="checkbox" name="aficiones[]" id="aficiones_0" value="0"><label for="aficiones_0">Programación</label></li>
  <li><input type="checkbox" name="aficiones[]" id="aficiones_1" value="1"><label for="aficiones_1">Otro</label></li>
</ul>

Nota Como has podido observar, Symfony genera automáticamente un atributo id para cada campo del formulario. El valor de este atributo es una combinación del nombre y del valor del widget. Puedes redefinir el valor del atributo id widget por widget o también puedes utilizar el método setIdformat() para establecer el formato a utilizar en todos los campos:

// en modules/mimodulo/actions/actions.class.php
$this->form = new sfForm();
$this->form->setIdFormat('mi_formulario_%s');

10.3.3. Widgets para claves externas

Siempre que se editan los objetos del modelo de datos mediante formularios, surge la necesidad de mostrar un tipo especial de lista: los objetos relacionados con el objeto que se está modificando. Esto sucede tanto para las relaciones uno-a-muchos como las relaciones muchos-a-muchos. Afortunadamente, el plugin sfPropelPlugin que incluye Symfony dispone de un widget llamado sfWidgetFormPropelChoice que se encarga de todo esto. Si utilizas Doctrine, el plugin sfDoctrinePlugin dispone de un widget similar llamado sfWidgetFormDoctrineChoice.

Si se considera el ejemplo de un modelo llamado Seccion que tiene varios modelos de tipo Articulo, cuando modificas un artículo deberías poder elegir la sección a la que pertenece de entre una lista de valores. Para ello, el formulario ArticleForm debe utilizar el widget sfWidgetFormPropelChoice:

$formularioArticulo = new sfForm();
$formularioArticulo->setWidgets(array(
  'id'         => sfWidgetFormInputHidden(),
  'titulo'     => sfWidgetFormInputText(),
  'seccion_id' => sfWidgetFormPropelChoice(array(
    'model'  => 'Seccion',
    'column' => 'nombre'
  )
)));

El código anterior hace que el formulario de los artículos muestre la lista de todas las secciones disponibles, siempre que hayas definido un método __toString() en la clase del modelo Seccion. El motivo es que Symfony obtiene primero todos los objetos de tipo Seccion y llena el widget choice aplicando una instrucción echo a cada objeto. Por tanto, el modelo Seccion debe contener al menos el siguiente método:

// en lib/model/Seccion.php
public function __toString()
{
  return $this->getNombre();
}

El widget sfWidgetFormPropelChoice es una versión ampliada del widget sfWidgetFormChoice, por lo que puedes utilizar la opción multiple para manejar las relaciones muchos-a-muchos y la opción expanded para modificar la forma en la que se muestra el widget.

Si quieres ordenar la lista de opciones de alguna forma especial o si quieres filtrarla para no mostrar todas las opciones, puedes utilizar la opción criteria para pasar un objeto de tipo Criteria al widget. Doctrine también incluye esta característica a través de la opción query, que permite pasar un objeto de tipo Doctrine_Query.

10.3.4. Widgets para fechas

Los widgets de fecha y hora muestran varias listas desplegables con los valores disponibles para el día, mes, año, hora o minuto.

// Fecha
$anos = range(1950, 1990);
$form->setWidget('fecha', new sfWidgetFormDate(array(
  'label'   => 'Fecha de nacimiento',
  'default' => '01/01/1950',  // puede ser un timestamp o cualquier cadena que entienda la función strtotime()
  'years'   => array_combine($anos, $anos)
)));

// Symfony genera el siguiente código HTML
<label for="fecha">Date of birth</label>
<select id="fecha_month" name="fecha[month]">
  <option value=""/>
  <option selected="selected" value="1">01</option>
  <option value="2">02</option>
  ...
  <option value="12">12</option>
</select> /
<select id="fecha_day" name="fecha[day]">
  <option value=""/>
  <option selected="selected" value="1">01</option>
  <option value="2">02</option>
  ...
  <option value="31">31</option>
</select> /
<select id="fecha_year" name="fecha[year]">
  <option value=""/>
  <option selected="selected" value="1950">1950</option>
  <option value="1951">1951</option>
  ...
  <option value="1990">1990</option>
</select>

// Hora
$form->setWidget('inicio', new sfWidgetFormTime(array('default' => '12:00')));

// Symfony genera el siguiente código HTML
<label for="inicio">Inicio</label>
<select id="inicio_hour" name="inicio[hour]">
  <option value=""/>
  <option value="0">00</option>
  ...
  <option selected="selected" value="12">12</option>
  ...
  <option value="23">23</option>
</select> :
<select id="inicio_minute" name="inicio[minute]">
  <option value=""/>
  <option selected="selected" value="0">00</option>
  <option value="1">01</option>
  ...
  <option value="59">59</option>
</select>

// Fecha y hora
$form->setWidget('fin', new sfWidgetFormDateTime(array('default' => '01/01/2008 12:00')));

// Symfony muestra el widget en HTML mediante cinco listas desplegables para el mes, día, año, hora y minuto

Obviamente es posible modificar la opción format de la fecha para mostrarla con formato europeo en vez del formato internacional (%day%/%month%/%year% en vez de %month%/%day%/%year%) y también puedes mostrar la hora con el formato de 12 horas en vez de 24 horas. Además, puedes redefinir el valor que se muestra como primera opción de cada lista desplegable y puedes limitar el número de valores que se muestran. Una vez más, todas estas opciones avanzadas se explican en la documentación de la API.

Los widgets de fechas son un buen ejemplo del poder de los widgets de Symfony. Como acabas de ver, un widget no solo es un campo simple de formulario, sino que puede ser una combinación de varias etiquetas HTML que Symfony gestiona de forma transparente para el programador.

10.3.5. Widgets para internacionalización

Las aplicaciones multi-idioma modifican el formato de las fechas en función del idioma del usuario (ver Capítulo 13 para los detalles sobre la internacionalización). Para facilitar la traducción de las fechas de los formularios, Symfony incluye el widget sfWidgetFormI18nDate, que espera un parámetro llamado culture para decidir el formato más adecuado para la fecha. También puedes utilizar un parámetro llamado month_format para que la lista desplegable de los meses muestre sus nombres (en el idioma del usuario) en vez simplemente números.

// Fecha
$anos = range(1950, 1990);
$form->setWidget('fecha', new sfWidgetFormI18nDate(array(
  'culture'      => $this->getUser()->getCulture(),
  'month_format' => 'name',   // Valores permitidos: 'name' (valor por defecto), 'short_name' y 'number'
  'label'        => 'Fecha de nacimiento',
  'default'      => '01/01/1950',
  'years'        => array_combine($anos, $anos)
)));

// Si el idioma del usuario es el inglés, Symfony genera el siguiente código HTML
<label for="fecha">Fecha de nacimiento</label>
<select id="fecha_month" name="fecha[month]">
  <option value=""/>
  <option selected="selected" value="1">January</option>
  <option value="2">February</option>
  ...
  <option value="12">December</option>
</select> /
<select id="fecha_day" name="fecha[day]">...</select> /
<select id="fecha_year" name="fecha[year]">...</select>

// Si el idioma del usuario es el francés, Symfony genera el siguiente código HTML
<label for="fecha">Fecha de nacimiento</label>
<select id="fecha_day" name="fecha[day]">...</select> /
<select id="fecha_month" name="fecha[month]">
  <option value=""/>
  <option selected="selected" value="1">Janvier</option>
  <option value="2">Février</option>
  ...
  <option value="12">Décembre</option>
</select> /
<select id="fecha_year" name="fecha[year]">...</select>

Existen widgets similares para la hora (sfWidgetFormI18nTime) y para la fecha y hora (sfWidgetFormI18nDateTime).

Los formularios suelen contener otras dos listas desplegables que también dependen del idioma o cultura del usuario: el selector de idioma y el selector de país. Symfony incluye dos widgets específicos para estas listas. Además, no es necesario pasarles la opción choices, porque Symfony ya se encarga de llenarlos con los valores adecuados y en el idioma del usuario (siempre que el idioma del usuario sea uno de los 250 idiomas soportados por Symfony).

// Lista de países
$form->setWidget('pais', new sfWidgetFormI18nCountryChoice(array('default' => 'Reino Unido')));

// Si el idioma del usuario es el inglés, Symfony genera el siguiente código HTML
<label for="pais">Pais</label>
<select id="pais" name="pais">
  <option value=""/>
  <option value="AD">Andorra</option>
  <option value="AE">United Arab Emirates</option>
  ...
  <option value="ZWD">Zimbabwe</option>
</select>

// Lista de idiomas
$form->setWidget('idioma', new sfWidgetFormI18nLanguageChoice(array(
  'languages' => array('en', 'fr', 'de'),  // opcionalmente se pueden limitar los idiomas que se muestran
  'default'   => 'en'
)));

// Si el idioma del usuario es el inglés, Symfony genera el siguiente código HTML
<label for="idioma">Idioma</label>
<select id="idioma" name="idioma">
  <option value=""/>
  <option value="de">German</option>
  <option value="en" selected="selected">English</option>
  <option value="fr">French</option>
</select>

10.3.6. Widgets para archivos

Trabajar con archivos en Symfony es igual de sencillo que hacerlo con cualquier otro tipo de widget:

// Campo para subir un archivo
$form->setWidget('foto', new sfWidgetFormInputFile());

// Symfony genera el siguiente código HTML
<label for="foto">Foto</label>
<input id="foto" type="file" name="foto"/>
// Si el formulario tiene un widget de este tipo, el método 'renderFormTag()' añade la opción 'multipart' a la etiqueta <form>

// Campo para subir un archivo editable
$form->setWidget('foto', new sfWidgetFormInputFileEditable(array('default' => '/images/imagen.png')));
// Symfony genera el código HTML del campo para subir archivos y añade una previsualización del archivo actual

Nota Existen plugins desarrollados por terceros que incluyen muchos widgets adicionales. Entre otros, puedes encontrar editores avanzados de texto, calendarios y muchos otros componentes gráficos avanzados creados con diferentes librerías de JavaScript. Todos ellos los puedes encontrar en la sección de plugins del sitio web oficial de Symfony.