2016-02-17 30 views
6

w moim projekcie Mam kilka formularzy z typami opcji z dużo opcji.Symfony 2.8 dynamic ChoiceType opcje

Postanowiłem więc zbudować autouzupełnianie typu wyboru na podstawie autouzupełniania jquery, które dodaje nowe elementy HTML <option> do oryginalnego <select> w środowisku wykonawczym. Po wybraniu są one przesyłane poprawnie, ale nie można ich obsługiwać domyślnie ChoicesToValuesTransformer, ponieważ nie istnieją one w mojej formie podczas ich tworzenia.

Jak mogę sprawić, by symfony akceptowało moje dynamicznie dodawane wartości?

Znalazłem tę odpowiedź Validating dynamically loaded choices in Symfony 2, gdzie przesłane wartości są używane do modyfikowania formularza na zdarzeniu formularza PRE_SUBMIT, ale nie można go uruchomić dla mojej sytuacji. Muszę zmienić wyborów znane bieżącego typu zamiast dodawać nowy widżet do formularza

Odpowiedz

19

aby poradzić sobie z dynamicznie wartości dodanych używać 'choice_loader' możliwość wyboru typu. It's new in symfony 2.7 i niestety nie ma żadnej dokumentacji.

Zasadniczo jest to usługa wdrożenia ChoiceLoaderInterface który definiuje trzy funkcje:

  • loadValuesForChoices(array $choices, $value = null)
    • jest wywoływana postaci kompilacji i otrzymuje zaprogramowanych wartości przedmiotu związane w postaci
  • loadChoiceList($value = null)
    • nazywa się na kompilacji widzenia i powinien zwrócić pełną listę wyborów w ogólnym
  • loadChoicesForValues(array $values, $value = null)
    • nazywa na formularzu złożyć i odbiera przedstawione dane

Teraz Pomysł polega na zachowaniu ArrayChoiceList jako własności prywatnej w ładowarce wyboru. W formularzu kompilacji loadValuesForChoices(...) jest wywoływana, tutaj dodajemy wszystkie wybrane ustawienia do naszej listy wyboru, aby mogły być wyświetlane użytkownikowi. W widoku budowy jest wywoływana loadChoiceList(...), ale nic nie ładujemy, po prostu zwracamy naszą prywatną listę wyboru wcześniej utworzoną.

Teraz użytkownik wchodzi w interakcję z formularzem, niektóre dodatkowe opcje są ładowane za pomocą autouzupełniania i umieszczane w HTML. Po przesłaniu formularza wybrane wartości są przesyłane, a w naszej akcji kontrolera najpierw formularz jest tworzony, a następnie na $form->handleRequest(..)loadChoicesForValues(...), ale przesłane wartości mogą być całkowicie różne od tych, które zostały uwzględnione na początku.Zastąpiliśmy więc naszą wewnętrzną listę wyboru nową, zawierającą tylko przedłożone wartości.

Nasza forma doskonale przechowuje dane dodane przez autouzupełnianie.

Trudnym elementem jest to, że potrzebujemy nowej instancji naszego programu ładującego do wyboru, gdy używamy typu formularza, w przeciwnym razie wewnętrzna lista wyboru zawierałaby mieszaninę wszystkich wyborów.

Ponieważ celem jest napisanie nowego typu wyboru autouzupełniania, zwykle należy użyć wtyczki zależności, aby przekazać wybrany program ładujący do usługi typu. Ale dla typów nie jest to możliwe, jeśli zawsze potrzebujesz nowej instancji, zamiast tego musimy ją uwzględnić za pomocą opcji. Ustawienie loadera wyboru w opcjach domyślnych nie działa, ponieważ są one również buforowane. Aby rozwiązać ten problem, trzeba napisać anonimową funkcję, która musi podjąć opcje parametrów:

$resolver->setDefaults(array(
    'choice_loader' => function (Options $options) { 
     return AutocompleteFactory::createChoiceLoader(); 
    }, 
)); 

Edit: Oto zmniejszona wersja klasy ładowarka wybór:

use Symfony\Component\Form\ChoiceList\ArrayChoiceList; 
use Symfony\Component\Form\ChoiceList\ChoiceListInterface; 
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; 

class AutocompleteChoiceLoader implements ChoiceLoaderInterface 
{ 
    /** @var ChoiceListInterface */ 
    private $choiceList; 

    public function loadValuesForChoices(array $choices, $value = null) 
    { 
     // is called on form creat with $choices containing the preset of the bound entity 
     $values = array(); 
     foreach ($choices as $key => $choice) { 
      // we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value 
      if (is_callable($value)) { 
       $values[$key] = (string)call_user_func($value, $choice, $key); 
      } 
      else { 
       $values[$key] = $choice; 
      } 
     } 

     // this has to be done by yourself: array(label => value) 
     $labeledValues = MyLabelService::getLabels($values); 

     // create internal choice list from loaded values 
     $this->choiceList = new ArrayChoiceList($labeledValues, $value); 

     return $values; 
    } 


    public function loadChoiceList($value = null) 
    { 
     // is called on form view create after loadValuesForChoices of form create 
     if ($this->choiceList instanceof ChoiceListInterface) { 
      return $this->choiceList; 
     } 

     // if no values preset yet return empty list 
     $this->choiceList = new ArrayChoiceList(array(), $value); 

     return $this->choiceList; 
    } 


    public function loadChoicesForValues(array $values, $value = null) 
    { 
     // is called on form submit after loadValuesForChoices of form create and loadChoiceList of form view create 
     $choices = array(); 
     foreach ($values as $key => $val) { 
      // we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value 
      if (is_callable($value)) { 
       $choices[$key] = (string)call_user_func($value, $val, $key); 
      } 
      else { 
       $choices[$key] = $val; 
      } 
     } 

     // this has to be done by yourself: array(label => value) 
     $labeledValues = MyLabelService::getLabels($values); 

     // reset internal choice list 
     $this->choiceList = new ArrayChoiceList($labeledValues, $value); 

     return $choices; 
    } 
} 
+0

Och, gdybym tylko wiedział, jakie powinny być klucze i wartości tablicy! –

+0

@IanPhillips zależy od tego, którą macie tablicę. Aby zwrócić wartości funkcji, spójrz na phpDoc 'ChoiceLoaderInterface'. Klucze są zawsze takie same jak w tablicy parametrów i wartości albo wyboru lub wartości. Zwróć uwagę, że nadal potrzebujesz [DataTransformer] (http://symfony.com/doc/current/cookbook/form/data_transformers.html), jeśli pracujesz z elementami! Tablica użyta do utworzenia wewnętrznej 'ArrayChoiceList' powinna zawierać późniejsze'

+0

@IanPhillips Dodałem skróconą wersję mojego autouzupełniania programu ładującego do wyboru – SBH

0

Podstawowym (i prawdopodobnie nie najlepszy) rozwiązaniem byłoby odmapować pola w formularzu jak:

->add('field', choiceType::class, array(
     ... 
     'mapped' => false 
    )) 

W regulatorze, po zatwierdzeniu, uzyskać dane i przesyła je do jednostki tak:

$data = request->request->get('field'); 
// OR 
$data = $form->get('field')->getData(); 
// and finish with : 
$entity = setField($data); 
+1

Ustawienie ''mapped' => false' nie rozwiązuje problemu. Również szukam ogólnego rozwiązania w mojej niestandardowej klasie typu bez potrzeby dodawania kodu do jakiegokolwiek kontrolera. – SBH

+0

Tak, masz rację, pomieszałem z inną rzeczą, która jest połączona z listą zdarzeń: [dynamiczna modyfikacja formularza] (http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#adding-an-event-listener-to-a-form-class), w której chodzi o to, aby uzyskać wszystkie pola wybrane w formularzu, i dodaj go do twojego chocie – Aridjar