2016-02-17 13 views
6

nel mio progetto ho alcune forme con tipi di scelta con un sacco di opzioni.Symfony 2.8 dynamic ChoiceType options

Quindi ho deciso di creare un tipo di scelta di completamento automatico basato su completamento automatico jQuery, che aggiunge nuovi elementi <option> HTML all'originale <select> in fase di runtime. Se selezionati, vengono inviati correttamente, ma non possono essere gestiti all'interno del valore predefinito ChoicesToValuesTransformer, dal momento che non esiste nel mio modulo quando lo creo.

Come posso far sì che symfony accetti i miei valori aggiunti dinamicamente?

Ho trovato questa risposta Validating dynamically loaded choices in Symfony 2, in cui i valori inviati vengono utilizzati per modificare il modulo sull'evento di modulo PRE_SUBMIT, ma non sono riusciti a farlo funzionare per la mia situazione. Ho bisogno di cambiare le scelte noti al tipo corrente, invece di aggiungere un nuovo widget per il modulo di

risposta

19

per far fronte a valori aggiunti dinamicamente utilizzano 'choice_loader' opzione di tipo scelta. It's new in symfony 2.7 e purtroppo non ha alcun documento.

Fondamentalmente un servizio di esecuzione ChoiceLoaderInterface che definisce tre funzioni:

  • loadValuesForChoices(array $choices, $value = null)
    • è chiamato forma costruttiva e riceve i valori preimpostati oggetto legati in forma
  • loadChoiceList($value = null)
    • è chiamato a vista di costruzione e dovrebbe restituire l'elenco completo delle scelte in generale
  • loadChoicesForValues(array $values, $value = null)
    • viene chiamato sulla forma inviare e riceve i dati presentati

Ora la l'idea è di mantenere un ArrayChoiceList come proprietà privata all'interno del loader di scelta. Sul modulo di costruzione loadValuesForChoices(...) viene chiamato, qui aggiungiamo tutte le scelte preimpostate nella nostra lista scelta in modo che possano essere visualizzate all'utente. Nella vista build viene chiamato il numero loadChoiceList(...), ma non viene caricato nulla, viene semplicemente restituito l'elenco delle scelte personali creato in precedenza.

Ora l'utente interagisce con il modulo, alcune opzioni aggiuntive vengono caricate tramite un completamento automatico e inserite nel codice HTML. Al momento dell'invio del modulo vengono inviati i valori selezionati e dapprima nel modulo di controllo viene creato il modulo e successivamente viene chiamato $form->handleRequest(..)loadChoicesForValues(...), ma i valori inviati potrebbero essere completamente diversi da quelli inclusi all'inizio.Quindi sostituiamo la nostra lista di scelta interna con una nuova contenente solo i valori inviati.

Il nostro modulo ora conserva perfettamente i dati aggiunti dal completamento automatico.

La parte difficile è che abbiamo bisogno di una nuova istanza del nostro caricatore scelto ogni volta che utilizziamo il tipo di modulo, altrimenti l'elenco di scelta interno terrebbe una combinazione di tutte le scelte.

Poiché l'obiettivo è scrivere un nuovo tipo di opzione di completamento automatico, in genere si utilizzerà l'iniezione di dipendenza per passare il caricatore scelto nel servizio di tipo. Ma per i tipi questo non è possibile se hai sempre bisogno di una nuova istanza, invece dobbiamo includerlo tramite le opzioni. L'impostazione del loader di scelta nelle opzioni predefinite non funziona, poiché sono anche memorizzati nella cache. Per risolvere questo problema è necessario scrivere una funzione anonima che ha bisogno di prendere le opzioni come parametri:

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

Edit: Qui è una versione ridotta della classe scelta loader:

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

Oh, se solo sapessi quali sono le chiavi ei valori dell'array! –

+0

@IanPhillips dipende da quale array intendi. Per i valori di ritorno delle funzioni dare un'occhiata al phpDoc di 'ChoiceLoaderInterface'. Le chiavi sono sempre come nell'array dei parametri e valutano scelte o valori. Nota che hai ancora bisogno di un [DataTransformer] (http://symfony.com/doc/current/cookbook/form/data_transformers.html) se lavori con entità! L'array utilizzato per creare l'interno 'ArrayChoiceList' dovrebbe contenere le successive etichette'

+0

@IanPhillips Ho aggiunto una versione abbreviata del mio caricatore di scelta automatica – SBH

0

A quella di base (e probabilmente non il migliore) opzione sarebbe quella di eliminare la mappatura del campo nel modulo come:

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

Nel controllore, dopo la convalida, ottenere i dati e li invia al soggetto come questo:

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

Impostazione ''mapped' => false' non risolve il problema. Inoltre sto cercando una soluzione generale all'interno della mia classe di tipo personalizzato senza la necessità di aggiungere codice a qualsiasi controller. – SBH

+0

Sì, hai ragione, ho mescolato con un'altra cosa che è combinata con un lister di eventi: [modifica dinamica del modulo] (http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#adding-an-event-listener-to-a-form-class) dove l'idea è di ottenere tutto il campo selezionato nel modulo, e aggiungilo al tuo chocie – Aridjar