Al mostrar un formulario, queremos a menudo que el usuario haga una elección de entre una lista de posibilidades.
En HTML, una opción es representada por una etiqueta( tag de ahora en adelante) select :

Puedes añadir un atributo multiple para que acepte varias opciones:

El sfWidgetFormChoice
Sin embargo, una elección también puede ser representada por una lista de botones radio (para una única opción) o una lista de casillas de verificación (checkboxes) (múltiples opciones).
Para unificar todas estas posibilidades, symfony 1.2 viene con un nuevo widget llamado sfWidgetFormChoice. El
sfWidgetFormChoice es un widget abstracto en el sentido de que delega la representación/visualización (se usa los términos indistintamente) a otro
widget (el renderer widget).
Tomemos un simple ejemplo para ilustrar todas las posibles combinaciones. En un proyecto, tenemos el siguiente schema:

// config/schema.yml
propel:
demo_article:
id: ~
author_id: { type: integer, foreignReference: id, foreignTable: demo_author, onDelete: cascade, onUpdate: cascade, required: true }
status: varchar(255)
title: varchar(255)
content: longvarchar
published_at: timestamp
demo_category:
id: ~
name: varchar(255)
demo_author:
id: ~
name: varchar(255)
demo_tag:
id: ~
name: varchar(255)
demo_tag_article:
tag_id: { type: integer, primaryKey: true, foreignReference: id, foreignTable: demo_tag, onDelete: cascade, onUpdate: cascade, required: true }
article_id: { type: integer, primaryKey: true, foreignReference: id, foreignTable: demo_article, onDelete: cascade, onUpdate: cascade, required: true }
demo_category_article:
category_id: { type: integer, primaryKey: true, foreignReference: id, foreignTable: demo_category, onDelete: cascade, onUpdate: cascade, required: true }
article_id: { type: integer, primaryKey: true, foreignReference: id, foreignTable: demo_article, onDelete: cascade, onUpdate: cascade, required: true }
Se trata de un clásico esquema (schema) para un simple CMS. Los artículos tienen un autor (demo_author) y puede tener muchos tags (demo_tag) y muchas categorías (demo_category). Cada artículo tiene también un valor status que puede ser uno de estos valores: published, draft, o deleted. El valor status se almacena como texto plano, ninguna tabla se ha creado para almacenar los estados.
Vamos a jugar con el modelo DemoArticle creando un módulo que proporciona la base de operaciones CRUD:
$ php symfony propel:build-all
$ php symfony propel:generate-module frontend article DemoArticle
Si navegas a la pagina de edición, verás algo como esto:

Si echas una vistazo a la clase generada form para el modelo DemoArticle (lib/form/base/BaseDemoArticle.class.php), verás que symfony utiliza sfWidgetFormPropelChoice para el widget author_id y sfWidgetFormPropelChoiceMany
para los widgets demo_category_article_list y demo_tag_article_list . Symfony adivina el mejor
widget para usar, basado en la definición del esquema (schema).
sfWidgetFormPropelChoice representa un widget de opción única sobre la base de un objeto Propel y
sfWidgetFormPropelChoiceMany representa un widget de opción múltiple también sobre la base de un objeto Propel.
Personalizando el Formulario
Lo primero que podemos hacer para personalizar nuestro formulario es convertir el widget status a una opción:

Primero, tenemos que definir los estados en el modelo de la clase DemoArticlePeer :
// lib/model/DemoArticlePeer.php class DemoArticlePeer extends BaseDemoArticlePeer { static protected $choices = array( 'published' => 'published', 'draft' => 'draft', 'deleted' => 'deleted' ); static public function getStatusChoices() { return self::$choices; } }
Entonces, editamos la clase DemoArticleForm para cambiar el widget y el validador asociados con el campo status:
// lib/form/DemoArticleForm.class.php class DemoArticleForm extends BaseDemoArticleForm { public function configure() { $this->widgetSchema['status'] = new sfWidgetFormChoice(array( 'choices' => DemoArticlePeer::getStatusChoices() )); $this->validatorSchema['status'] = new sfValidatorChoice(array( 'choices' => array_keys(DemoArticlePeer::getStatusChoices()) )); } }
El sfWidgetFormChoice toma un array de opciones para usar en el select como las opciones choices .
El sfValidatorChoice también tiene unas choices como parametro que son los válidos valores para la columna status (las claves del array DemoArticlePeer::getStatusChoices() ).
Jugando con las Opciones
Radio button list
Es hora para jugar un poco con el widget sfWidgetFormChoice! Como se puede ver en la captura de pantalla anterior, la situación es ahora representada por un select. Pero como el número de valores para el estado es bastante bajo, habría sido mejor mostrar los estados como una lista de botones radio:

Eso es muy fácil de lograr. El sfWidgetFormChoice tiene una opcion expanded que cambia la salida de un select a una lista de botones de radio:
$this->widgetSchema['status'] = new sfWidgetFormChoice(array( 'choices' => DemoArticlePeer::getStatusChoices(), 'expanded' => true, ));
Checkboxes list
La lista de las categorías es también bastante pequeña, por lo que sería mejor mostrarlas como una lista de casillas de verificación:

La opcion expanded que hemos utilizado para un opciones simples también puede utilizarse para widgets de elección múltiple. Como el widget se ha generado en la clase base form y no necesitan ser cambiada, solo debemos establecer la opcion expanded a true:
$this->widgetSchema['demo_category_article_list']->setOption('expanded', true);
Resumen
El siguiente cuadro resume las diferentes configuraciónes de sfWidgetFormChoice y el widget renderer
usado por symfony:
sfWidgetFormChoice |
expanded is false |
expanded is true |
|---|---|---|
multiple is false |
sfWidgetFormSelect |
sfWidgetFormSelectRadio |
multiple is true |
sfWidgetFormSelectMany |
sfWidgetFormSelectCheckbox |
El mismo cuadro con algunas capturas de pantalla:
sfWidgetFormChoice |
expanded is false |
expanded is true |
|---|---|---|
multiple is false |
![]() |
![]() |
multiple is true |
![]() |
![]() |
Agrupa tus Opciones
Una de las menos conocidas posibilidades de la etiqueta select es la forma en que puedes agrupar sus opciones con el atributo optgroup :

La familia widgets sfWidgetFormChoice ha sido construida para soportar las agrupaciones. Sólo necesitas pasar una array de arrays para las opciones choices:
$choices = array( 'Europe' => array('France' => 'France', 'Spain' => 'Spain', 'Italy' => 'Italy'), 'America' => array('USA' => 'USA', 'Canada' => 'Canada', 'Brazil' => 'Brazil'), ); $this->widgetSchema['country'] = new sfWidgetFormChoice(array('choices' => $choices));
Puede, por supuesto, ampliarla con la lista de botones de radio:
$this->widgetSchema['country'] = new sfWidgetFormChoice(array( 'choices' => $choices, 'expanded' => true, ));

También puede personalizar el diseño utilizado por el render widget:
$this->widgetSchema['country'] = new sfWidgetFormChoice(array( 'choices' => $choices, 'expanded' => true, 'renderer_options' => array('template' => '%group% %options%'), ));

Y sí, también trabaja con las de opcion multiple :


Más con JavaScript
Eso fue bastante fácil. Vamos a añadir algunos JavaScript a la mezcla para explorar más posibilidades.
Lista Doble
Si nuestro CMS se utiliza ampliamente, vamos a tener más y más tags, y será cada vez más difícil detectar los tags asociados con el artículo actual. Para este tipo de situaciones, un widget de lista doble es una de las mejores soluciones:

Hasta ahora, symfony ha elegido el mejor widget para utilizar sobre la base de una configuración sencilla (multiple y expanded).
Pero el sfWidgetFormChoice no es capaz de hacer nuestro select como una lista doble.
Afortunadamente, sabemos que sfWidgetFormChoice delega la representación a otro widget. Cambiar el widget de representación/visualización es tan simple como una modificación de la opción renderer_class.
Si instalas el sfFormExtraPlugin, encontrarás un montón de widgets y validadores interesantes que son muy útiles pero no están en el núcleo porque son dependencias de terceros.
El widget sfWidgetFormSelectDoubleList es uno de ellos:
$this->widgetSchema['demo_tag_article_list']->setOption('renderer_class', 'sfWidgetFormSelectDoubleList');
Si actualizas la página ahora, no funcionará porque el widget depende de algunos JavaScript para trabajar correctamente. La documentación de la API del widget contiene todo lo que necesitas saber para configurarlo correctamente:
// apps/frontend/modules/article/templates/_form.php use_javascript('/sfFormExtraPlugin/js/double_list.js') ?> onsubmit="double_list_submit(this, 'double_list_select'); return true;"> echo $form ?>
Autocompletado
No hemos jugado con el campo author_id aun. Imaginemos que tenemos un montón de autores en nuestro CMS, realmente muchos. No es muy fácil encontrar algo de una lista muy larga de nombres en un select drop-down. Por lo tanto, vamos a convertir este en un widget Autocompletado.



Eso es impresionante, ¿no? Para hacer que funcione, tendremos que trabajar un poco más que antes.
sfFormExtraPlugin contiene dos widgets Autocompletado basados en la biblioteca jQuery:
sfWidgetFormJQueryAutocompleter: Puede ser utilizado para cualquier tarea Autocompletar.sfWidgetFormPropelJQueryAutocompleter:Está optimizado para autocompletados relacionados Propel.
En nuestra situación, vamos a utilizar uno basado en Propel:
// lib/form/DemoArticleForm.class.php $this->widgetSchema['author_id']->setOption('renderer_class', 'sfWidgetFormPropelJQueryAutocompleter'); $this->widgetSchema['author_id']->setOption('renderer_options', array( 'model' => 'DemoAuthor', 'url' => $this->getOption('url'), ));
Nos han pasado algunas de las opciones para el widget mediante el establecimiento de renderer_options. En estas opciones, te habrás dado cuenta de la url se establece en una opción url del formulario ($this->getOption('url')). Cuando se crea una instancia del formulario, el primer argumento del constructor son los valores por defecto, y el segundo es un un array de opciones:
public function executeEdit($request) { // ... $this->form = new DemoArticleForm($article, array('url' => $this->getController()->genUrl('article/ajax'))); // ... }
Ahora tenemos que crear la accion article/ajax. Cuando el widget llama a esta acción, pasa varios parámetros de la solicitud:
q: La cadena dada por el usuariolimit: El número máximo de resultados para devolver
Aquí está el código:
// apps/frontend/modules/article/actions/actions.class.php public function executeAjax($request) { $this->getResponse()->setContentType('application/json'); $authors = DemoAuthorPeer::retrieveForSelect($request->getParameter('q'), $request->getParameter('limit')); return $this->renderText(json_encode($authors)); } // lib/model/DemoAuthorPeer.php class DemoAuthorPeer extends BaseDemoAuthorPeer { static public function retrieveForSelect($q, $limit) { $criteria = new Criteria(); $criteria->add(DemoAuthorPeer::NAME, '%'.$q.'%', Criteria::LIKE); $criteria->addAscendingOrderByColumn(DemoAuthorPeer::NAME); $criteria->setLimit($limit); $authors = array(); foreach (DemoAuthorPeer::doSelect($criteria) as $author) { $authors[$author->getId()] = (string) $author; } return $authors; } }
Ahora, para cada widget JavaScript , también tenemos que agregar algunos archivos a la plantilla (template) del formulario para que funcione correctamente:
// apps/frontend/modules/article/templates/_form.php use_javascript('/sfFormExtraPlugin/js/jquery.autocompleter.js') ?> use_stylesheet('/sfFormExtraPlugin/css/jquery.autocompleter.css') ?>
Terminamos. Ahora tenemos un widget Autocompletado que es capaz de mostrar los nombres de los autores, y enviar el ID del autor al formulario. Y gracias al validador, estamos seguros de que sólo IDs válidos son enviados y guardados en la base de datos.
Formulario Final
Aquí está la formilario final que muestra las diferentes manera de pedir al usuario por una opción:

Esto es una gran flexibilidad para un solo widget!



