Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony: trying to customize a collection form prototype

Tags:

symfony

I have this form:

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

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

}

And this template to render the form:

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

As it is said in the docs, to get a custom prototype I should add the lines below at the top of my template:

{% form_theme form _self %}

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

But when I press Add service I dont get the text I WILL WRITE MY CUSTOME PROTOTYPE HERE, but the description, fields and quantity related to the ServicePerformedType class as before..

NOTE: maybe there are other ways to custom a form prototype, but I'm interested on this, so will be very thankful to someone who give a solution related to this way to custom form prototypes, thanks.

like image 206
ziiweb Avatar asked Feb 18 '16 18:02

ziiweb


1 Answers

I must warn you that customizing the prototype could be a bit tricky. If you change your FormType fields you'll need to go through the template and make the same changes as well or your form will fail to render.

What I like to do is to create a custom template for that specific field and then customize it appropriately. So, looking at your code I would do something like this:

  1. Create a page template - the one you'll use to render the entire page, including the form.

    {# 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. Now you'll need to create the template MyBundle/Resources/views/form/fields/servicePerformed.html.twig. It will be used to customize only the servicePerformed field. The template should look something like this

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

I want to note that in the field template I'm passing the original prototype _self.service_template(form.vars.prototype). By doing this, you can use the original fields and render them in your customized prototype. For example this code:

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

Will result in something like the following rendered prorotype:

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

You can then manipulate it using your javascript.

I hope this helps you.

like image 126
tftd Avatar answered Oct 19 '22 13:10

tftd