简体   繁体   English

如何制作(最佳方式)特定的自定义Symfony 2表单元素类型?

[英]How to make (best way) specific custom Symfony 2 form element type?

I need to create form element with following logic: 我需要使用以下逻辑创建表单元素:

  • User need to select his budget for something 用户需要选择他的预算
  • He can select it with radio button 他可以用单选按钮选择它
  • or select "Other" and enter it manualy 或选择“其他”并手动输入

Here is HTML markup that represents this logic: 这是表示此逻辑的HTML标记:

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>

To make this work, I've created custom field type with custom Twig block. 为了完成这项工作,我使用自定义Twig块创建了自定义字段类型。 Finnaly, I've got something that I don't really like... Finnaly,我有一些我不喜欢的东西......

Here is the code of custom type: 这是自定义类型的代码:

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

And template: 和模板:

{% 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 %}

I'm newbie in Symfony, so I copypasted a lot from Symfony's class ChoiceType , and actualy don't know purpose of half of stuff that happens there. 我是Symfony的新手,所以我从Symfony的类ChoiceType复制了很多ChoiceType ,并且ChoiceType并不知道那里发生的一半东西的目的。 :) :)

Finnaly, the question is: What is the best (or at least better) way to achieve what I have had achieved using Symfony 2 form component? Finnaly,问题是: 实现使用Symfony 2表单组件实现的目标的最佳(或至少更好)方法是什么?

If you heritate the FormType from "text", it will be much simple to handle with some javascript added in twig 如果你从“文本”中抄写FormType,那么在twig中添加一些javascript会很简单

Set the minimum required in the form type : 设置表单类型中所需的最小值:

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

in html twig template as you did : 在你做的html twig模板中:

{% 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 %}

you can use {{id}} to get the parameter of an input like the name, id and value 您可以使用{{id}}来获取输入的参数,例如名称,ID和值

you can use {{value}} to get the value of the input 您可以使用{{value}}来获取输入的值

in javascript twig template, you can customize your component 在javascript twig模板中,您可以自定义组件

{% 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 %}

You ll need to add some code to include the javascript part. 你需要添加一些代码来包含javascript部分。 Have a look to "genemu form" to learn how to do, but basicaly : you need to create a twig extension 看看“genemu form”学习如何做,但基本上:你需要创建一个枝条扩展

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

then use the extension each time to use your formType 然后每次使用扩展名来使用你的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 %}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM