2014-05-15 3 views
3

Je dois créer l'élément de forme avec la logique suivante:Comment créer (de la meilleure façon) un type d'élément de formulaire personnalisé spécifique Symfony 2?

  • utilisateur doit choisir son budget pour quelque chose
  • Il peut le sélectionner avec le bouton radio
  • ou sélectionnez « Autre » et entrez manualy

est ici le balisage HTML qui représente cette logique:

Choose your budget: 
<div id="briefing_budget" class="budget clearfix"> 
<label> 
    <input type="radio" id="briefing_budget_0" name="briefing[budget][selected]" required="required" value="9999"> 9 999 rubles 
</label> 
<label> 
    <input type="radio" name="briefing[budget][selected]" value="other"> other <input type="text" name="briefing[budget][number]"> 
</label> 
</div> 

Pour que cela fonctionne, j'ai créé un type de champ personnalisé avec un bloc Twig personnalisé. Finnaly, j'ai quelque chose que je n'aime vraiment ...

Voici le code de type personnalisé:

<?php 

namespace Company\Optimal\PromoAction\FreeCampaignFor10k; 

use Symfony\Component\Form\AbstractType; 
use Symfony\Component\Form\Exception\LogicException; 
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList; 
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer; 
use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener; 
use Symfony\Component\Form\Extension\Core\View\ChoiceView; 
use Symfony\Component\Form\FormBuilderInterface; 
use Symfony\Component\Form\FormEvent; 
use Symfony\Component\Form\FormEvents; 
use Symfony\Component\Form\FormInterface; 
use Symfony\Component\Form\FormView; 
use Symfony\Component\OptionsResolver\Options; 
use Symfony\Component\OptionsResolver\OptionsResolverInterface; 


/** 
* choices : [{"<value>": "<label>"}] 
*/ 
class NumberRadioType extends AbstractType 
{ 

    /** 
    * Caches created choice lists. 
    * @var array 
    */ 
    private $choiceListCache = array(); 

    public function buildForm(FormBuilderInterface $builder, array $options) 
    { 
     if (!$options['choice_list'] && !is_array($options['choices']) && !$options['choices'] instanceof \Traversable) { 
      throw new LogicException('Either the option "choices" or "choice_list" must be set.'); 
     } 

     $preferredViews = $options['choice_list']->getPreferredViews(); 
     $remainingViews = $options['choice_list']->getRemainingViews(); 

     if (null !== $options['empty_value'] && 0 === count($options['choice_list']->getChoicesForValues(array('')))) { 
      $placeholderView = new ChoiceView(null, '', $options['empty_value']); 

      $this->addSubForms($builder, array('placeholder' => $placeholderView), $options); 
     } 

     $this->addSubForms($builder, $preferredViews, $options); 
     $this->addSubForms($builder, $remainingViews, $options); 


     $builder->addViewTransformer(new ChoiceToBooleanArrayTransformer($options['choice_list'], $builder->has('placeholder'))); 
     $builder->addEventSubscriber(new FixRadioInputListener($options['choice_list'], $builder->has('placeholder')), 10); 

     $name = $builder->getName(); 
     $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($name) { 
      $data = $event->getData(); 
      $data = $data['selected'] == 'other' ? $data['number'] : $data['selected']; 
      $event->setData($data); 
     }); 

    } 


    /** 
    * {@inheritdoc} 
    */ 
    public function buildView(FormView $view, FormInterface $form, array $options) 
    { 
     $view->vars = array_replace($view->vars, array(
      'preferred_choices' => $options['choice_list']->getPreferredViews(), 
      'choices' => $options['choice_list']->getRemainingViews(), 
      'separator' => '-------------------', 
      'empty_value' => null, 
     )); 

     $view->vars['is_selected'] = function ($choice, $value) { 
      return $choice === $value; 
     }; 

     $view->vars['empty_value_in_choices'] = 0 !== count($options['choice_list']->getChoicesForValues(array(''))); 

     if (null !== $options['empty_value'] && !$view->vars['empty_value_in_choices']) { 
      $view->vars['empty_value'] = $options['empty_value']; 
     } 


    } 

    public function finishView(FormView $view, FormInterface $form, array $options) 
    { 
     foreach ($view as $childView) { 
      $childView->vars['full_name'] = $view->vars['full_name'] . '[selected]'; 
     } 
    } 


    public function setDefaultOptions(OptionsResolverInterface $resolver) 
    { 
     $choiceListCache =& $this->choiceListCache; 

     $choiceList = function (Options $options) use (&$choiceListCache) { 
      $choices = null !== $options['choices'] ? $options['choices'] : array(); 

      // Reuse existing choice lists in order to increase performance 
      $hash = hash('sha256', json_encode(array($choices, $options['preferred_choices']))); 

      if (!isset($choiceListCache[$hash])) { 
       $choiceListCache[$hash] = new SimpleChoiceList($choices, $options['preferred_choices']); 
      } 

      return $choiceListCache[$hash]; 
     }; 

     $emptyData = array(); 

     $emptyValue = function (Options $options) { 
      return $options['required'] ? null : ''; 
     }; 

     $emptyValueNormalizer = function (Options $options, $emptyValue) { 
      if (false === $emptyValue) { 
       return; 
      } elseif ('' === $emptyValue) { 
       return 'None'; 
      } 

      return $emptyValue; 
     }; 

     $resolver->setDefaults(array(
      'choice_list' => $choiceList, 
      'choices' => array(), 
      'preferred_choices' => array(), 
      'empty_data' => $emptyData, 
      'empty_value' => $emptyValue, 
      'error_bubbling' => false, 
      'compound' => true, 
      'data_class' => null, 
     )); 

     $resolver->setNormalizers(array(
      'empty_value' => $emptyValueNormalizer, 
     )); 

     $resolver->setAllowedTypes(array(
      'choice_list' => array('null', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'), 
     )); 
    } 


    /** 
    * Returns the name of this type. 
    * 
    * @return string The name of this type 
    */ 
    public function getName() 
    { 
     return 'number_radio'; 
    } 

    /** 
    * Adds the sub fields for an expanded choice field. 
    * 
    * @param FormBuilderInterface $builder The form builder. 
    * @param array $choiceViews The choice view objects. 
    * @param array $options The build options. 
    */ 
    private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options) 
    { 
     foreach ($choiceViews as $i => $choiceView) { 
      if (is_array($choiceView)) { 
       // Flatten groups 
       $this->addSubForms($builder, $choiceView, $options); 
      } else { 
       $choiceOpts = array(
        'value' => $choiceView->value, 
        'label' => $choiceView->label, 
        'translation_domain' => $options['translation_domain'], 
       ); 


       $choiceType = 'radio'; 


       $builder->add($i, $choiceType, $choiceOpts); 
      } 
     } 
    } 
} 

et modèle:

{% block number_radio_widget %} 
    {% spaceless %} 
      <div {{ block('widget_container_attributes') }}> 
       {% for child in form %} 
         <label>{{ form_widget(child) }}{{ child.vars.label }}</label> 
       {% endfor %} 
       <label><input type="radio" name="{{ form.vars.full_name }}[selected]" value="other"/> 
        other <input type="text" name="{{ form.vars.full_name }}[number]"/> 
       </label> 
      </div> 
    {% endspaceless %} 
{% endblock %} 

Je suis novice dans Symfony, donc j'ai copypassé beaucoup de la classe ChoiceType de Symfony, et je ne sais pas ce qui se passe ici. :)

Finnaly, la question est: Quel est le meilleur (ou au moins meilleur) moyen d'atteindre ce que j'ai obtenu en utilisant le composant de formulaire Symfony 2?

+0

Pensez à modifier cette question pour demander "Existe-t-il une manière spécifique à Symphony de gérer ce composant de formulaire avec moins de code? –

Répondre

1

Si vous heritate le FormType du "texte", il sera beaucoup plus simple à manipuler avec javascript ajoutée brindille

Régler le minimum requis dans le type de formulaire:

class NumberRadioType extends AbstractType 
{ 
    /** 
    * {@inheritdoc} 
    */ 
    public function buildForm(FormBuilderInterface $builder, array $options) 
    { 
     $builder->setAttribute('configs', $options['configs']); 
    } 

    /** 
    * {@inheritdoc} 
    */ 
    public function buildView(FormView $view, FormInterface $form, array $options) 
    { 
     $view->vars['configs'] = $form->getConfig()->getAttribute('configs'); 
    } 

    /** 
    * {@inheritdoc} 
    */ 
    public function setDefaultOptions(OptionsResolverInterface $resolver) 
    { 
     $resolver->setDefaults(array(
      'configs' => array() 
     )); 

     $resolver->setNormalizers(array(
      'configs' => function (Options $options, $value) { 
       return true; 
      } 
     )); 
    } 


    /** 
    * {@inheritdoc} 
    */ 
    public function getParent() 
    { 
     return 'text'; 
    } 

    /** 
    * {@inheritdoc} 
    */  
    public function getName() 
    { 
     return 'NumberRadio'; 
    } 

en html modèle brindille que vous avez fait:

{% block number_radio_widget %} 
    {% spaceless %} 
      <div {{ block('widget_container_attributes') }}> 
       {% for child in form %} 
         <label>{{ form_widget(child) }}{{ child.vars.label }}</label> 
       {% endfor %} 
       <label><input type="radio" {{id}}/> 
        other <input type="text" name="{{ form.vars.full_name }}[number]" value="{{value}}"/> 
       </label> 
      </div> 
    {% endspaceless %} 
{% endblock %} 

vous pouvez utiliser {{id}} pour obtenir le paramètre d'un InP ut comme le nom, l'identifiant et la valeur

vous pouvez utiliser {{value}} pour obtenir la valeur de l'entrée

javascript modèle brindille, vous pouvez personnaliser votre composant

{% block number_radio_javascript %} 
    <script type="text/javascript"> 
    var selectedInput = $('input[name=briefing[budget][selected]]:checked', '#myForm').val() 
if(selectedInput ==1){ 
//do something 
}else{ 
//do something else 
} 

</script> 
{% endblock %} 

Vous devrez ajouter du code pour inclure la partie javascript. Jetez un coup d'oeil à la « forme genemu » pour apprendre à faire, mais basiquement: vous devez créer une extension de brindille

https://github.com/genemu/GenemuFormBundle/blob/master/Twig/Extension/FormExtension.php

puis utilisez l'extension à chaque fois d'utiliser votre FormType

{% extends '::base.html.twig' %} 
{% block jsscript %} 
{{ vendor_type_javascript(form) }} 
{% endblock %} 
{% block content %} 
{{ form_widget(edit_form) }} 
     {{ form_errors(edit_form) }} 
     {{ form_rest(edit_form) }} 
{% endblock %} 
Questions connexes