Symfony: попытка создать образец прототипа коллекции

У меня есть эта форма:

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 this:

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>

Как сказано в docs, чтобы получить собственный прототип, я должен добавить строки ниже в верхней части моего шаблона:

{% form_theme form _self %}

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

Но когда я нажимаю Add service, я не получаю текст I WILL WRITE MY CUSTOME PROTOTYPE HERE, но description, fields и quantity, связанные с классом ServicePerformedType, как и раньше..

ПРИМЕЧАНИЕ: возможно, есть другие способы создания прототипа формы, но я заинтересован в этом, поэтому очень благодарен тому, кто дал решение, связанное с этим, с прототипами пользовательских форм, спасибо.

Ответ 1

Я должен предупредить вас, что настройка прототипа может быть немного сложной. Если вы измените поля FormType, вам нужно будет пройти через шаблон и внести те же изменения, иначе ваша форма не будет отображаться.

Мне нравится создавать пользовательский шаблон для этого конкретного поля, а затем настраивать его соответствующим образом. Итак, глядя на ваш код, я бы сделал что-то вроде этого:

  • Создайте шаблон страницы - тот, который вы будете использовать для отображения всей страницы, включая форму.

    {# 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 %}
    
  • Теперь вам нужно создать шаблон MyBundle/Resources/views/form/fields/servicePerformed.html.twig. Он будет использоваться для настройки только поля servicePerformed. Шаблон должен выглядеть примерно так.

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

Надеюсь, это поможет вам.

Ответ 2

На самом деле формировать блоки тем, которые начинаются с подчеркивания _, относятся к полю с определенным именем.

Я имею в виду, что если ваша основная форма 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.

Ответ 3

У вашего шаблона для отображения формы есть некоторые проблемы. Первая - это строка:

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

Регулярное выражение должно быть __name__.

Затем вы извлекаете прототип, но затем сразу же перезаписываете его и заменяете HTML прототипа. Там нет ничего, что я могу видеть, что фактически добавляет новую форму к вашей существующей форме где угодно. Плюс, так как у вас просто строка текста, replace не найдет текст __name__ для замены.

Вы должны опубликовать полную информацию о своем Twig/Javascript, чтобы мы могли увидеть #bill_servicesPerformed, а также все остальное, что вы пытаетесь сделать. Прежде чем писать собственный прототип, вы должны получить форму, которая работает со стандартным прототипом, чтобы убедиться, что у вас нет ошибок там.

Ответ 4

В качестве примера, так я продолжаю это делать. Я не знаю, есть ли какие-то причины, поэтому будьте осторожны.

Форма для включения прототипа:

<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 необходимо учитывать для этих изменений, я думаю.

Ответ 5

Вы можете использовать макрос, посмотрите на приведенный ниже пример, он отлично работает даже для меня в 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 %}