2012-08-15 13 views
9

Używam formularzy Symfony 2.1 z PropelBundle i próbuję refaktoryzować formularz, który miał listę rozwijaną obiektów (do wyboru), aby zamiast tego użyć pola autouzupełniania jquery (praca z AJAX). Na liście rozwijanej używałem następujący kod (który pracował idealnie dla rozwijanym) w swoim rodzaju forma:Jak dodać pole autouzupełniania w formularzu Symfony2 do gromadzenia i używania Propela?

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

W trosce o dając trochę kontekst, powiedzmy, że tworzymy nowy „Reader "obiekt i chcielibyśmy wybrać ulubione książki Czytelnika z listy dostępnych obiektów" Książki ". Używany jest typ kolekcji, dzięki czemu wiele "ulubionych książek" można wybrać w nowym formularzu "Czytnik". Teraz chciałbym zmienić powyższe, aby użyć autouzupełniania. W tym celu próbowałem zaimplementować Data Transformer to be able to get a Book object from a simple text field, który może być użyty do funkcji autouzupełniania w celu przekazania identyfikatora książki, jak wskazano w the answer to this Question. Jednak nie byłem w stanie wymyślić, jak sprawić, aby Transformator danych działał z typem kolekcji i klasami Propela. Stworzyłem klasę BookToIdTransformer jak wskazano w Symfony Cookbook i próbowałem następujące w „ReaderType” file:

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

z powyższym mam „wezwanie do metody niezdefiniowanej: getId” wyjątek (podobno Transformer spodziewa PropelCollection of Books, a nie pojedynczy obiekt Book ..). Czy ktoś wie, jak to zrobić? lub daj mi znać, czy istnieją inne sposoby implementacji autouzupełniania w Symfony za pomocą Propela i pozwalające na wybranie wielu obiektów (np. zbiór książek)?

Odpowiedz

14

Rozwiązanie, do którego ostatecznie dążyłem, różni się nieco od mojej poprzedniej odpowiedzi. W moim formularzu "ReaderType" użyłem typu pola "tekst" zamiast "pola kolekcji", ponieważ w końcu skorzystałem z Loopj Tokeninput jQuery plugin, który pozwala wybrać wiele obiektów (np. "Ulubiona książka"), aby powiązać się z moim głównym obiekt (np. obiekt "Reader") w formularzu. Biorąc to pod uwagę, stworzyłem "Transformator danych" do przekształcania przekazanych identyfikatorów obiektów (na liście rozdzielanej przecinkami w polu tekstowym) do zbioru Propel Object Collection. Kod jest udostępniany w następujący sposób, w tym przykładowy obiektowy kontroler obiektów ajax.

Kluczową częścią "ReaderType" postać wygląda następująco:

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

"Dane Transformer" plik wygląda tak:

// 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; 
    } 
} 

Kontroler ajax, która zwraca kolekcję JSON "books" do funkcji autouzupełniania Tokeninput są następujące:

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; 
    } 
} 

I na koniec e router w pliku "routing.yml":

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

Dzięki za dokładne wyjaśnienie. Jeśli masz wiele akcji kontrolera, które powinny zwracać odpowiedzi JSON, polecam FOSRestBundle – Narretz

+0

czy możesz dodać fragment widoku/formularza, proszę? – timaschew

+0

@timaschew jeśli dobrze pamiętam (nie potrzebowałem tego), kod w widoku jest po prostu standardowy dla formularzy symfony. To pole jest zwykłym wprowadzaniem tekstu. Funkcja autouzupełniania jest dodawana do tego wejścia przez wtyczkę Tokeninput za pomocą javascriptu z użyciem id input: $ ("# my-text-input"). TokenInput ("/ url/to/your/script /"); – RayOnAir