Skip to content

Better documentation for customize a Collection Prototype #6056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ghost opened this issue Dec 21, 2015 · 30 comments
Closed

Better documentation for customize a Collection Prototype #6056

ghost opened this issue Dec 21, 2015 · 30 comments
Labels
Form hasPR A Pull Request has already been submitted for this issue.
Milestone

Comments

@ghost
Copy link

ghost commented Dec 21, 2015

http://symfony.com/doc/master/cookbook/form/form_customization.html#how-to-customize-a-collection-prototype

There is a form field named tasks. But why is the form field (in the block) named task?

For example I have a form field named externalIds. Would the form in the block named externalId?
If yes: Why? Magic?

@ghost
Copy link
Author

ghost commented Dec 21, 2015

How to customize a Collection Prototype if the form field is named externalIds? What is the correct name of the block?

Already tried:

  • _externalIds_entry_widget
  • _external_ids_entry_widget
  • _external_id_entry_widget
  • externalIds_entry_widget
  • externalId_entry_widget
  • external_Ids_entry_widget
  • external_id_entry_widget

@wouterj
Copy link
Member

wouterj commented Dec 21, 2015

I don't see a block named task in the section about overriding the collection prototype.

@ghost
Copy link
Author

ghost commented Dec 21, 2015

{% block _tasks_entry_widget %}
    <tr>
        <td>{{ form_widget(task.task) }}</td>
        <td>{{ form_widget(task.dueDate) }}</td>
    </tr>
{% endblock %}

I mean task.task and task.dueDate. Why task? Where does it come from?

The block itself is named _tasks_entry_widget because the form field is tasks.

I don't get it working with a form field named externalId. What is the correct name of the block?

That because I think the documentation about this should be better.
Everybody should easily understand it.

@xabbuh xabbuh added the Form label Dec 23, 2015
@toomoi
Copy link

toomoi commented Dec 30, 2015

I totally agree with you. I read this documentation again and again, but no way to exploit it.. I'm trying to use Macros to reach my goal, but I am convinced that there is a solution provided by symfony...

@ghost
Copy link
Author

ghost commented Jan 4, 2016

I've found out how it works. You need to add the name of the form as prefix.

As example:

{% block _new_sms_recipientNumbers_entry_widget %}
{# ... #}
{% endblock %}

The form type class name is NewSmsType - that because _new_sms_ is important as prefix.

The collection type is named recipientNumbers:

$builder->add('recipientNumbers', CollectionType::class);

Now I need the inner variable names. recipientNumber is not working.

@ghost
Copy link
Author

ghost commented Jan 4, 2016

I found a way to get all available variables in a block to understand what is possible:

{% block _new_sms_recipientNumbers_entry_widget %}
    {{ dump() }}
{% endblock %}

@Vatvat99
Copy link

I am also having difficulties to understand that part of documentation.

The doc shows:

{% form_theme form _self %}

{% block _tasks_entry_widget %}
    <tr>
        <td>{{ form_widget(form.task) }}</td>
        <td>{{ form_widget(form.dueDate) }}</td>
    </tr>
{% endblock %}

I don't understand where does {{ form_widget(form.task) }} and {{ form_widget(form.dueDate) }} are coming from ?

In my case, I have a field offer_pictures that I display like this in my template:

{% block content %}
    {{ form_start(form) }}
    {{ form_widget(form.offer_pictures) }}
    {{ form_end(form) }}
{% endblock %}

If I try to customize the placeholder like that:

{% form_theme form _self %}

{% block _offer_offer_pictures_entry_widget %}
        {{ form_widget(form.offer_pictures) }}
{% endblock %}

Symfony returns this error : Method "offer_pictures" for object "Symfony\Component\Form\FormView" does not exist in :offer:add.html.twig at line 16

Don't we have access to form fields in the entry_widget block ? As I understand it, that's what the doc suggests.

@ghost
Copy link
Author

ghost commented May 14, 2016

@Vatvat99 Could you show us your form class and entity fields?

@xabbuh
Copy link
Member

xabbuh commented May 14, 2016

In this block you have access to the fields of the Form type that you embedded in the collection type. offer_pictures seems to be your collection and not the embedded type.

@ghost
Copy link
Author

ghost commented May 14, 2016

@Vatvat99 You should try out this code:

{% block _offer_offer_pictures_entry_widget %}
        {{ dump() }}
{% endblock %}

This will provide you a lot of information thanks to the VarDumper Component.

@HeahDude
Copy link
Contributor

@Vatvat99 in the block of your example form is the entry so you can only access fields of one offer_picture field. As suggested by @JHGitty {{ dump(form) }} should help.

@JHGitty if you have a collection field with a name externalIds, its default block name is the same then you have access to _externalIds_widget and _externalIds_entry_widget.

You can customize the block name only (without modifying the field's name) with the option block_name.
If the entry is also a collection you may have a scenario like this:

class TaskManagerType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = array())
    {
        // ...
        $builder->add('taskLists', CollectionType::class, array(
            'entry_type' => TaskListType::class,
            'block_name' => 'task_lists',
        ));
    }
}

class TaskListType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = array())
    {
        // ...
        $builder->add('tasks', CollectionType::class, array(
            'entry_type' => TaskType::class,
        ));
    }
}

class TaskType
{
    public function buildForm(FormBuilderInterface $builder, array $options = array())
    {
        $builder->add('name');
        // ...
    }
}

Leading to the lines below in a controller:

// ...
$form = $this->createFormBuilder(TaskManagerType::class)
    ->add('Save', SubmitType::class)
    ->getForm();
// ...
return $this->render('/form.html.twig', array('form' => $form->createView()));

Then you get the following customizable blocks (* is standing for "row", "widget", or "label"):

{% block _task_manager_task_lists_* %}{# the collection field of TaskManager #}
{% block _task_manager_task_lists_entry_* %}{# the inner TaskListType #}
{% block _task_manager_task_lists_entry_tasks_* %}{# the collection field of TaskListType #}
{% block _task_manager_task_lists_entry_tasks_entry_* %}{# the inner TaskType #}
{% block _task_manager_task_lists_entry_tasks_entry_name_* %}{# the field of TaskType #}

Hope that helps.

@ghost
Copy link
Author

ghost commented May 15, 2016

@HeahDude Do you like to optimize the documentation about this?

@HeahDude
Copy link
Contributor

@JHGitty Do you mean if I'm in favor of some changes or if I should try to send a PR myself?

First, definitely yes, the example provided in http://symfony.com/doc/master/cookbook/form/form_customization.html#how-to-customize-a-collection-prototype looks clearly inconsistent.

I think there is already some work in progress in #6321. Maybe you should give some feedback there to get it moving forward.

@Vatvat99
Copy link

@JHGitty @HeahDude @xabbuh Thanks for your help, and sorry for taking time to respond. Indeed, as xabbuh said, I can only access to the fields of the FormType, not the FormType itself.

@djuro
Copy link

djuro commented Dec 1, 2016

I think it is important to note that "entry" refers to a numeric key of a collection element.

I have a form named 'contact_details'. It has a collection named 'telephones'. Looking at form html source, I have seen that particular field has an ID: 'contact_details_telephones_0_number'.
But, to override the field, one would need to refer to it as:

{% block _contact_details_telephones_entry_number_widget %} ... {% endblock %}

@xaos01
Copy link

xaos01 commented Jan 17, 2017

@djuro THANK YOU!!! There is ZERO chance I ever would have stumbled upon what my block should be named from the documentation. Looking at what ID symfony gives a collection item is THE way to do this.

@spatialsparks
Copy link

spatialsparks commented Jan 21, 2017

Had the same issue here: ninsuo/symfony-collection#50 and wrote a short post on how I solved it, also used the ID of the DIV, maybe this helps someone else 😄

@alex-barylski
Copy link

I had the same issue - tried every prefix I could think of - ultimately it boiled down to missing the {% form_theme form _self %} immediately before the block override :)

@BenoitDuffez
Copy link

For those wondering how you can KNOW the prefix instead of guessing it, this SO answer tells that you need to go into the debug toolbar -> Forms -> click on your form field and then search for "unique_block_prefix".

Then it's just a piece of cake provided you read the awesome comments above.

@chhaytoch
Copy link

chhaytoch commented Jul 16, 2017

Hi, I am the newbie of symfony and I have read above comments but i still can't find the way out.
So i have created the sample one after i spent a half day on it as below:

Form Type :

class EventBackCreateType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = array())
    {
        // ...
        $builder->add('eventDateTimes', CollectionType::class, array(
            'type' => new EventDateTimeType(),
            'allow_add'  => true,
            'allow_delete' => true,
            'by_reference' => false
        ));
    }
}

class EventDateTimeType  extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = array())
    {
        parent::buildForm($builder, $options);

        $builder->add('eDate', TextType::class, array(
            'label' => 'Date',
            'attr' => array(
                'class' => 'datepicker'
            )
        ));
        $builder->add('eTimeFrom', TextType::class, array(
            'label' => 'From Time',
            'attr' => array(
                'class' => 'timepicker'
            )
        ));
        $builder->add('eTimeTo', TextType::class, array(
            'label' => 'To Time',
            'attr' => array(
                'class' => 'timepicker'
            )
        ));
    }
}

Twig :

{% extends 'CoreBundle:Crud:edit.html.twig' %}

{# Custom form collection prototype of eventDateTimes #}
{# If you write this '{% block _event_eventDateTimes_entry_widget %}' inside the {% block formbody %}, you will get the error 
'Neither the property "eDate" nor one of the methods "eDate()", "geteDate()"/"iseDate()" or "__call()" exist and have public access in class "Symfony\Component\Form\FormView".'
#}

{% block _event_eventDateTimes_entry_widget %}
    <td>{{ form_widget(form.eDate) }}</td>
    <td>{{ form_widget(form.eTimeFrom) }}</td>
    <td>{{ form_widget(form.eTimeTo) }}</td>
{% endblock %}

{% block formbody %}
    {#Important : Don't forget import form_theme as below #}
    {% form_theme form _self %}

    <table class="events-date-time" data-prototype="{{ form_widget(form.eventDateTimes.vars.prototype)|e('html_attr') }}">
      
    </table>
{% endblock %}

My purpose of create this sample just want to help the newbie as me to easy working and understand on customize form collection type. Please kindly feed me back if i have something wrong or still not clear.

Thank you all masters ! (bow)

  • Note : Sorry for my bad English.

@jpmmartin
Copy link

jpmmartin commented Dec 6, 2017

The simplest solution creates macros in twig, in fact, this is something that does not appear in the documentation. But now I explain.

@jpmmartin
Copy link

jpmmartin commented Dec 6, 2017

{% import _self as formMacros %}

{% macro printStepPrototype(defaultStep) %}    
        <div class="form-group form-float">
            <div class="form-line">
                {{ form_widget(defaultStep.name) }}
                {{ form_label(defaultStep.name) }}
            </div>
        </div>
{% endmacro %}

At the beginning of your twig template put something like this.

@jpmmartin
Copy link

jpmmartin commented Dec 6, 2017

{{ form_start(newStepForm, {'action': path('default_skill_create_trial'), 'method': 'POST', 'attr' : {'id' : 'newStepForm'} }) }}
    <div class="row clearfix">
        {{ form_errors(newStepForm) }}
        <div class="col-sm-12">
            <div class="form-group form-float">
                <div class="form-line">
                    {{ form_widget(newStepForm.name) }}
                    {{ form_label(newStepForm.name) }}
                </div>
            </div>
            {{ form_errors(newStepForm.name) }}
        </div>
        <div id="defaultSteps" data-prototype="{{ formMacros.printStepPrototype(newStepForm.defaultSteps.vars.prototype)|e('html_attr') }}">
            {% for defaultStep in newStepForm.defaultSteps %}
                {{ formMacros.printStepPrototype(defaultStep) }}
            {% endfor %}
        </div>
        <button type="submit" class="btn btn-link waves-effect">SAVE</button>                     
    </div>
{{ form_end(newStepForm) }}

Then, below, in the template, your form could be like that, then add the necessary javascript logic, I hope it works for you and do not waste unnecessary hours as it happened to me.

@javiereguiluz
Copy link
Member

javiereguiluz commented Dec 7, 2017

@juanpablomorenomartin I can understand your frustration with the Symfony Form component (and I share that frustration too), but your attitude and comments can't be tolerated. You can say anything you want and you can criticize anything you want ... but you can't be rude or insult to us or our project.

@olivier1208
Copy link

Fully agree with @javiereguiluz
I'm really struggling with that, and you help is welcomed, but not with rudeness

@jpmmartin
Copy link

jpmmartin commented Dec 11, 2017

I want to apologize for my comments to the truth that day I was very, very angry, but it is not a reason to make comments like that, so I apologize again.

@javiereguiluz
Copy link
Member

@juanpablomorenomartin we accept your apologies. But please, don't insult us again when you get angry 😠 in the future. Thanks!

@jbonnier
Copy link
Contributor

@BenoitDuffez thank you so much for pointing that out, I've been struggling thinking I was doing it wrong when the only problem was my identifier.

Cheers!

@javiereguiluz javiereguiluz added the hasPR A Pull Request has already been submitted for this issue. label Feb 20, 2019
@javiereguiluz javiereguiluz added this to the 4.2 milestone Feb 20, 2019
@KouloukLeGrand
Copy link

Hello!

I've been struggling with this too and as you guys mentionned, there is absolutely no way to figure all that out straight from the docs, either from https://symfony.com/doc/2.3/cookbook/form/form_customization.html#how-to-customize-a-collection-prototype or https://symfony.com/doc/current/form/form_themes.html#fragment-naming-for-collections

Maybe the solution would be to make a dedicated doc page for working with collections, including this advanced topic with a full example from the controller down to the template?

Maybe an even greater improvement would be to integrate collection handling in Symfony UX since we need boilerplate JavaScript for working with them? Thinking of https://github.com/ruano-a/symfonyCollectionJs among others which I'm working with and very happy of 😋

@miroslav-chandler
Copy link

Hi!
For me (symfony/twig-bundle:v4.4.15, twig/twig:v3.0.5), it's worked when I moved the collection block to the separate form_theme. In other cases, it doesn't work in the same template.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Form hasPR A Pull Request has already been submitted for this issue.
Projects
None yet
Development

No branches or pull requests