Modificando el double list del sfFormExtraPlugin de Symfony

Explicación del sfFormExtraPlugin

Hola a todos nuevamente. Este será el último artículo para este año y esperemos iniciar el 2011 con nuevas idea.

Estuve un buen tiempo ya probando los plugins de Symfony que dicho sea de paso son fantásticos. El que me llamo mucho la atención por su gran utilidad y sencillez fue el sfFormExtraPlugin que agrega unos widgets con jQuery muy útiles. Del widget que me gustaría hablar hoy es el de double list que permite generar dos combos a fin de ir pasando los datos de uno al otro.

Double list - sfFormExtraPlugin

Con solo un poco de configuración como lo podemos ver aquí, se puede llegar a generar esto de una forma muy rápida.

Para instalarlo no hay que hacer más que symfony plugin:install sfFormExtraPlugin para bajarlo y esto lo carga dentro de la carpeta plugins del proyecto, en el archivo config/ProjectConfiguration.class.php tenemos que habilitar el plugin agregandolo como parámetro al método enablePlugins que por defecto viene habilitando el Doctrine. Yo lo hice la siguiente manera:

class ProjectConfiguration extends sfProjectConfiguration
{
    public function setup()
    {
        $this->enablePlugins(
            'sfDoctrinePlugin',
            'sfFormExtraPlugin'
        );
    }
}

El último paso sería hacer que los javascripts y css del plugin sean publicados en la carpeta web ejecutando la tarea symfony plugin:publish-assets. Una cosa que hay que tener en cuenta es que la librería del jQuery debe estar importada aparte ya que no viene con el plugin. Para esto simplemente la descargamos y agregamos en el view.yml

default:
  javascripts:
    - /js/jquery-1.4.4.min.js

De esta manera simplemente nos faltaría crear el formulario:

class PruebaForm extends BaseForm
{
    public function configure()
    {
        $this->setWidgets(array(
            'secciones' => new sfWidgetFormChoice(array(
                'choices' => $this->defaults['unassociated'],
                'renderer_class' => 'sfWidgetFormSelectDoubleList',
                'renderer_options' => array(
                    'label_unassociated' => 'No asignadas',
                    'label_associated' => 'asignadas',
                    'associated_first' => false
                )
            ))
        ));
 
        $this->widgetSchema->setNameFormat('prueba[%s]');
    }
}

Ya tenemos nuestro formulario, lo que hacemos es agregar el widget con las siguientes opciones de configuración:

  1. choices: el array con los datos a mostrar
  2. renderer_class: aquí invocamos al widget que será usado
  3. renderer_options: las opciones de visualización donde vemos que se pueden modificar los labels «label_unassociated» y «label_associated» y la opción «associated_first» que permite poner primero el combo para los asociados o no

Para invocar a nuestro formulario es muy sencillo, directamente lo instanciamos en el actions:

//-- Al instanciar un formulario recibe como primer parámetro opcional
//-- un array de valores por defectos, haremos uso de este parametro y
//-- pasaremos el array con las opciones que se deben mostrar
$unassociated = array(1 => 'sección 1', 2 => 'sección 2', 3 => 'sección 3');
 
$this->pruebaForm = new PruebaForm(array('unassociated' => unassociated));

Ahora al mostrarlo en pantalla con un simple echo $pruebaForm en la vista podremos ver los dos combos y pasar de uno al otro. El valor real en el name va a ser asignado al combo para las opciones asociadas y el otro quedará con la palabra unassociated_ como prefijo así también lo podremos obtener al enviar el formulario si lo necesitamos.

De esta forma ya podremos elegir opciones para grabarlas.

El problema encontrado

Supongamos que queremos editar el registro. En el combo de opciones no asociadas volverán a aparecer todas pero no encuentro forma de pasarle al widget las opciones que ya están seleccionadas ya que dentro del widget si lo miramos encontramos que solo tiene un array choices que sirve para mostrar los no asociados. De esta manera puedo copiar el archivo plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormSelectDoubleList.class.php y lo puedo pegar dentro de la carpeta lib/widget/ dentro de mi proyecto.

Importante: no reemplazar el archivo original ya que perderíamos todo
si actualizamos el plugin. Lo ideal sería que se agregue esta
funcionalidad al widget original y esperemos que esté en las
siguientes versiones.

Una vez que hayamos copiado el archivo haremos unos cuantos cambios. Primero cambiarle el nombre para que no haya confusión tomando en cuenta que no estamos usando namespaces y segundo unas pocas líneas de código.

Dentro del método configure agregamos una línea.

protected function configure($options = array(), $attributes = array())
{
  $this->addRequiredOption('choices');
 
  //-- Agregamos una opción para pasarle las opciones asociadas
  $this->addOption('associated_choices', array());
 
  $this->addOption('class', 'double_list');
  $this->addOption('class_select', 'double_list_select');
  $this->addOption('associated_first', true);
  $this->addOption('label_unassociated', 'Unassociated');
  $this->addOption('label_associated', 'Associated');
  $associated_first = isset($options['associated_first']) ? $options['associated_first'] : true;

Dentro del método render agregamos unas pocas líneas más

$associated = array();
$unassociated = array();
 
//-- Inicio de los cambios
//-- Obtengo las opciones ya asociadas
$associated_choices = $this->getOption('associated_choices');
 
//-- Las cargo en el array $associated
foreach ($associated_choices as $key => $option)
{
    $associated[$key] = $option;
}
 
//-- Obtengo la diferencia para dejar solo los no seleccionados
//-- en el combo de no asociados así no se repetirán.
$choices = array_diff_key($choices, $associated_choices);
 
//-- Fin de los cambios
 
foreach ($choices as $key => $option)

Y eso es todo. Ahora simplemente en el formulario hacemos unos pequeños cambios:

class PruebaForm extends BaseForm
{
    public function configure()
    {
        //-- Obtenemos como nuevo parámetro las opciones asociadas
        $associated = !empty($this->defaults['associated']) ? $this->defaults['associated'] : array();
 
        //-- En el renderer_class ahora llamamos al nuevo widget
        //-- En el renderer_options pasamos la nueva opción associated_choices con las opciones asociadas
        $this->setWidgets(array(
            'secciones' => new sfWidgetFormChoice(array(
                'choices' => $this->defaults['unassociated'],
                'renderer_class' => 'sfWidgetFormSelectDoubleList2',
                'renderer_options' => array(
                    'label_unassociated' => 'No asignadas',
                    'label_associated' => 'asignadas',
                    'associated_first' => false,
                    'associated_choices' => $associated
                )
            ))
        ));
 
        $this->widgetSchema->setNameFormat('prueba[%s]');
    }
}

Por último en el action lo único diferente será pasarle como parámetro las opciones asociadas:

//-- Al instanciar un formulario recibe como primer parámetro opcional un array de valores por defectos, haremos uso de este parametro y pasaremos el array con las opciones que se deben mostrar
$unassociated = array(1 => 'sección 1', 2 => 'sección 2', 3 => 'sección 3');
$associated = array(2 => 'sección 2');
$this->pruebaForm = new PruebaForm(array('unassociated' => unassociated, 'associated' => $associated));

Ahora sí hemos solucionado este problema. Espero que les haya servido.

Les deseo a todos un muy buen inicio de año.

1 Comment

  1. Hola queria saber si hay forma de que la lista doble del sfFormExtraPlugin funcione con el model como lo hace la lista que trae por defecto symfony

Deja un comentario