Symfony:尝试自定义集合表单原型

20

我有一个表单:

class BillType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       $builder
        ->add('user')
        ->add('numberPlate')
        ->add('servicesPerformed', CollectionType::class, array(
             'label' => false,
             'entry_type' => ServicePerformedType::class,
             'allow_add' => true,
        ))
        ->add('Save', SubmitType::class)
    ;
    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'DefaultBundle\Entity\Bill'
        ));
    }

作为ServicePerformedType类,它是这样的:

class ServicePerformedType extends AbstractType
{
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
     $builder
         ->add('description', TextareaType::class, array('label' => false))
         ->add('price', TextType::class, array('label' => false))
         ->add('quantity', TextType::class, array('label' => false));
  }

}

同时,这是用于渲染表单的模板:

{{ form(form) }}
<a href="#">Add service</a>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script type="text/javascript">

    var index = 0;
    $('a').on('click', function() {

        var prototype = $('#bill_servicesPerformed').data('prototype');

        prototype = prototype.replace(/_name_/g, index.toString());

        $('#bill_servicesPerformed').html(prototype);

        index++;
    })
</script>

文档中所述,要获取自定义原型,我应该在模板顶部添加以下行:

{% form_theme form _self %}

{% block _servicesPerformed_entry_widget %}
I WILL WRITE MY CUSTOM PROTOTYPE HERE
{% endblock %}

但是当我点击“添加服务”时,我没有得到文本“I WILL WRITE MY CUSTOME PROTOTYPE HERE”,而是之前与类相关的、和。
注:也许有其他方法来自定义表单原型,但是我对此感兴趣,因此将非常感谢提供与这种方式相关的解决方案的人,谢谢。
5个回答

21

我必须警告你,定制原型可能有点棘手。如果更改FormType字段,则需要浏览模板并进行相同的更改,否则您的表单将无法呈现。

我喜欢做的是为特定字段创建自定义模板,然后进行适当的自定义。因此,看着你的代码,我会这样做:

  1. 创建一个页面模板 - 用于呈现整个页面,包括表单。

    {# MyBundle/Resources/views/myPage.html.twig #}
    {% extends "::base.html.twig" %}
    
    
    {# This will tell the form_theme to apply the 
       `MyBundle:form/fields/servicePerformed.html.twig` template to your "form" #}
    
    {% form_theme form with [
        'MyBundle:form/fields/servicePerformed.html.twig'
    ] %}
    
    {% block body %}
        <div>
            {{ form_start(form) }}
                {{ form_rest(form) }}
            {{ form_end(form) }}
        </div>
    {% endblock %}
    
  2. 现在你需要创建模板MyBundle/Resources/views/form/fields/servicePerformed.html.twig,它将用于仅定制servicePerformed字段。 模板应该看起来像这样:

  3. {% macro service_template(fields) %}
        <tr>
            <td>I WILL WRITE MY CUSTOM PROTOTYPE HERE</td>
        </tr>
    {% endmacro %}
    
    {# 
       The block name must match your field name!
       You can find it by going into the debug toolbar -> Forms -> 
       click on your form field and then search for "unique_block_prefix". 
       Copy that and add "_widget" at the end.
    #}
    
    {% block _service_performed_widget %}
        <table data-prototype="{{ _self.service_template(form.vars.prototype)|e }}">
            {% for fields in form.children %}
                {{ _self.service_template(fields) }}
            {% endfor %}
        </table>
    {% endblock %}
    
    我想指出,在字段模板中,我正在传递原始原型_self.service_template(form.vars.prototype)。通过这样做,您可以使用原始字段并将它们呈现在您的定制原型中。例如,此代码:

    {% macro service_template(fields) %}
        <tr>
            <td>{{ form_widget(fields.description) }}</td>
        </tr>
    {% endmacro %}
    

    将会得到类似以下渲染原型的结果:

    <tr>
        <td>
            <input type="text" id="service_performed___name___description" name="service[__name__][description]"/>
        </td>
    </tr>
    

    然后您可以使用JavaScript对其进行操作。

    我希望这可以帮助您。


是的,我也花了很长时间。幸运的是,一旦你搞清楚了,就可以很容易地重复使用它。只需更改字段名称,你就可以开始使用了。无论如何,很高兴我能帮上忙 :) - tftd
这似乎不再起作用了?原型定制也不在文档中了?我真的想以“干净的方式”使用表单主题来完成它,但是我尝试过的所有标签都没有起作用(即使是c/c unique_block_prefix + _widgetentry_widget等)。这可能是Symfony文档中最糟糕的部分之一(通常很好)。基本上,我真的觉得使用经典的form_theme应该影响正常和原型渲染,将它们分开并没有太多意义(但也许这是一种奇怪的技术限制)。 - Bonswouar
很奇怪 - 你使用的Symfony版本是什么?它在3.4+、4.5+和5.4上百分之百可行,因为我有项目使用了相同的方法(对于6.0+不确定--尚未测试)。你确定在尝试所做的更改之前清除了缓存吗? - tftd
FYI,原型文档位于此处,有关字段命名约定的更多信息可以在此处找到。我认为您可能没有使用正确的字段名称,或者没有清除缓存。 :) - tftd

5

实际上,以下划线 _ 开头的表单主题块与特定名称的字段相关。

我的意思是,如果您的主表单 BillType 被称为 my_form,您需要这样做:

{% form_theme form _self %}

{% block _my_form_servicesPerformed_entry_widget %}
I WILL WRITE MY CUSTOM PROTOTYPE HERE
{% endblock %}

这种方法的问题在于它只涉及到了特定的BillType迭代。如果你在其他地方使用这个表单类型并提供不同的名称my_form_2,那么你就需要添加一个相同的块,名为_my_form_2_servicesPerformed_entry_widget

1
文档对此事非常不清楚,但你是正确的。仅使用字段名称+下划线,例如“_servicesPerformed”,根本不起作用。 - baris1892

2

您可以使用宏,看下面的示例,即使在Symfony3中也可以正常工作。使用此示例,您还将能够格式化集合原型。

视图

{% macro widget_prototype(widget, remove_text) %}
{% if widget.vars.prototype %}
    {% set form = widget.vars.prototype %}
    {% set name = widget.vars.name %}
{% else %}
    {% set form = widget %}
    {% set name = widget.vars.full_name %}
{% endif %}

<div data-content="{{ name }}" class="panel panel-default">
    <div class="section row">
        <div class="col-md-12">
            <label class="field-label">Skill <span class="text-danger">*</span></label>
            <label class="field select">
                {{ form_widget(form.skill) }}
                <i class="arrow double"></i>                    
            </label>
        </div>
    </div>

    <div data-content="{{ name }}">
        <a class="btn-remove" data-related="{{ name }}">{{ remove_text }}</a>
        {{ form_widget(form) }}
    </div>
</div>
{% endmacro %}

这不是最完整/清晰/通用的答案,但实际上是唯一在Symfony 4中有效的答案。很遗憾form_theme不能(完全/容易地?)用于此。我建议首先阅读宏文档:https://twig.symfony.com/doc/3.x/tags/macro.html,但主要不要忘记`import`它! - Bonswouar

1
您的表单渲染模板存在一些问题。首先是这一行:
prototype = prototype.replace(/_name_/g, index.toString());

正则表达式应该是__name__
接下来,您正在检索原型,但随后立即覆盖它并替换原型的HTML。我看不到任何实际将新表单附加到现有表单的内容。此外,由于您只有一段文本字符串,因此replace不会找到要替换的任何文本__name__
您应该发布完整的Twig / Javascript范围,以便我们实际上可以看到#bill_servicesPerformed以及您尝试执行的其他所有操作。在编写自定义原型之前,您应该使用标准原型使表单正常工作,以确保没有任何错误。

0
作为一个例子,这是我继续进行的方式。我不知道是否有一些原因不这样做,所以要小心。

包含原型的表单:

<div class="modal-body" id="contactMehtods" data-prototype="
    {% filter escape %}
        {{ include(':organization/partials:prototype_contact_method.html.twig', { 'contact_method': contact_form.contactMethods.vars.prototype }) }}
    {% endfilter %}">
    <div class="form-group">
        {{ form_label(contact_form.name, null, { 'label_attr': { 'class': 'control-label' }}) }}
        {{ form_widget(contact_form.name, {'attr': {'class': 'form-control'}}) }}
    </div>
</div>

原型模板:

<div class="form-group">
    {{ form_label(contact_method.name, null, { 'label_attr': { 'class': 'col-sm-3 control-label' }}) }}
    <div class="col-sm-9">
        {{ form_widget(contact_method.name, {'attr': {'class': 'form-control'}}) }}
    </div>
</div>
<div class="form-group">
    {{ form_label(contact_method.value, null, { 'label_attr': { 'class': 'col-sm-3 control-label' }}) }}
    <div class="col-sm-9">
        {{ form_widget(contact_method.value, {'attr': {'class': 'form-control'}}) }}
    </div>
</div>

但需要注意的是,JavaScript 需要适应这些变化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接