2012-08-15 28 views
9

Estoy usando formularios Symfony 2.1 con PropelBundle y estoy tratando de refactorizar un formulario que tenía una lista desplegable de objetos (para seleccionar) para usar un campo de autocompletar de jquery (trabajando con AJAX). Para la lista desplegable que estaba usando el siguiente código (que funcionaba perfectamente para el menú desplegable) en mi tipo de formulario:¿Cómo agregar un campo de autocompletar en un formulario Symfony2 para una colección y usar Propel?

$builder->add('books', 'collection', array(
    'type'   => 'model', 
    'options'  => array(
     'class'  => 'MyVendor\MyBundle\Model\Book', 
     'property' => 'title', 
    ), 
    'allow_add'  => true, 
    'allow_delete' => true, 
    'by_reference' => false, 
    'required'  => false, 
)); 

En aras de dar un poco de contexto, digamos que estamos creando un nuevo "Lector "Objeto y que nos gustaría seleccionar los libros favoritos del lector de una lista de objetos" Libro "disponibles. Se usa un tipo de colección para que se puedan seleccionar muchos "libros favoritos" en el nuevo formulario "Reader". Ahora, me gustaría cambiar lo anterior para usar autocompletar. Para hacerlo, traté de implementar un Data Transformer to be able to get a Book object from a simple text field que podría usarse para que la función Autocompletar pase la ID del libro como se indica en the answer to this Question. Sin embargo, no pude averiguar cómo hacer que Data Transformer funcione con un tipo de colección y clases Propel. He creado una clase BookToIdTransformer como se indica en el Symfony libro de cocina y trató lo siguiente en el archivo "ReaderType":

$transformer = new BookToIdTransformer(); 
$builder->add(
     $builder->create('books', 'collection', array(
      'type'   => 'text', 
      'allow_add'  => true, 
      'allow_delete' => true, 
      'by_reference' => false, 
      'required'  => false, 
     ))->addModelTransformer($transformer) 
); 

Con lo anterior, me sale una "llamada a método no definido: getId" excepción (al parecer el transformador de espera una PropelCollection of Books, no un solo objeto Book ...). ¿Alguien sabe cómo hacerlo? o avíseme si hay otras formas de implementar la autocompletar en Symfony utilizando Propel y permitiendo seleccionar varios objetos (por ejemplo, una colección de libros).

Respuesta

14

La solución a la que finalmente fui es ligeramente diferente de mi respuesta anterior. Terminé usando un tipo de campo "texto" en lugar de un tipo de campo "colección" en mi formulario "ReaderType", ya que terminé usando el Loopj Tokeninput jQuery plugin que permite seleccionar varios objetos (por ejemplo, "Libro favorito") para asociarlo a mi principal objeto (por ejemplo, objeto "Reader") en el formulario. Teniendo eso en cuenta, creé un "Transformador de datos" para transformar los ids de los objetos pasados ​​(en una lista separada por comas en el campo de texto) en una colección de objetos Propel. El código se comparte de la siguiente manera, incluido un ejemplo de controlador de objetos ajax.

La parte clave de la "ReaderType" forma es el siguiente:

$transformer = new BooksToIdsTransformer(); 
$builder->add(
    $builder->create('books', 'text', array(
     'required' => false, 
    ))->addModelTransformer($transformer) 
); 

El archivo "transformador de datos" tiene el siguiente aspecto:

// src/MyVendor/MyBundle/Form/DataTransformer/BooksToIdsTransformer.php 
namespace MyVendor\MyBundle\Form\DataTransformer; 

use \PropelObjectCollection; 
use Symfony\Component\Form\DataTransformerInterface; 
use Symfony\Component\Form\Exception\UnexpectedTypeException; 
use MyVendor\MyBundle\Model\BookQuery; 

class BooksToIdsTransformer implements DataTransformerInterface 
{ 
    public function transform($books) 
    { 
     if (null === $books) { 
      return ""; 
     } 

     if (!$books instanceof PropelObjectCollection) { 
      throw new UnexpectedTypeException($books, '\PropelObjectCollection'); 
     } 
     $idsArray = array(); 
     foreach ($books as $book) { 
      $idsArray[] = $book->getId(); 
     } 
     $ids = implode(",", $idsArray); 
     return $ids; 
    } 

    public function reverseTransform($ids) 
    { 
     $books = new PropelObjectCollection(); 

     if ('' === $ids || null === $ids) { 
      return $books; 
     } 

     if (!is_string($ids)) { 
      throw new UnexpectedTypeException($ids, 'string'); 
     } 
     $idsArray = explode(",", $ids); 
     $idsArray = array_filter ($idsArray, 'is_numeric'); 
     foreach ($idsArray as $id) { 
      $books->append(BookQuery::create()->findOneById($id)); 
     } 
     return $books; 
    } 
} 

El controlador ajax que devuelve una colección JSON de "libros" a la función autocompletar Tokeninput es el siguiente:

namespace MyVendor\MyBundle\Controller; 

use Symfony\Bundle\FrameworkBundle\Controller\Controller; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use MyVendor\MyBundle\Model\BookQuery; 


class ClassAjaxController extends Controller 
{ 
    public function bookAction(Request $request) 
    { 
     $value = $request->get('q'); 

     $books = BookQuery::create()->findByName('%'.$value.'%'); 

     $json = array(); 
     foreach ($books as $book) { 
      $json[] = array(
       'id' => $book->getId(), 
       'name' => $book->getName() 
      ); 
     } 

     $response = new Response(); 
     $response->setContent(json_encode($json)); 

     return $response; 
    } 
} 

Y finalmente, th e enrutador en el archivo "routing.yml":

ajax_book: 
    pattern: /ajax_book 
    defaults: { _controller: MySiteBundle:ClassAjax:book } 
+0

Gracias por la explicación detallada. Si tiene muchas acciones del Controlador que deberían devolver respuestas JSON, recomiendo FOSRestBundle – Narretz

+0

¿puede agregar un fragmento de su vista/formulario, por favor? – timaschew

+0

@timaschew si recuerdo bien (terminé no necesitándolo), el código en la vista es solo el estándar usado para los formularios de Symfony. El campo es una entrada de texto regular. La funcionalidad de autocompletar se agrega a esa entrada mediante el complemento Tokeninput usando javascript usando el id de la entrada: $ ("# my-text-input"). TokenInput ("/ url/to/your/script /"); – RayOnAir

Cuestiones relacionadas