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 :

select tag

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

multiple select tag

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:

database 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:

raw form

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:

form with a choice for the status

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:

form with a radio list for status

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:

form with a checkbox list for categories

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 single not expanded single expanded
multiple is true multiple not expanded multiple expanded

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 :

optgroup feature

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,
));
 

expanded optgroup feature

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%'),
));
 

expanded optgroup feature customized

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

multiple optgroup feature

multiple expanded optgroup feature

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:

double list for tags

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.

autocomplete for authors

autocomplete for authors

autocomplete for authors

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:

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:

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:

all possibilities

Esto es una gran flexibilidad para un solo widget!



La presente, es una traducción al castellano realizada por Roberto G. Puentes Diaz, sobre el artículo make-your-choice de Fabien Potencier.

Creative Commons License Este trabajo esta licenciado bajo una Licencia Creative Commons Atribución-No Comercial-Sin Obras Derivadas 3.0 Unported.