symfony / symfony-docs

The Symfony documentation
https://symfony.com/doc
Other
2.16k stars 5.11k forks source link

Better documentation for customize a Collection Prototype #6056

Closed ghost closed 5 years ago

ghost commented 8 years ago

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 commented 8 years ago

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

Already tried:

wouterj commented 8 years ago

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

ghost commented 8 years ago
{% 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.

toomoi commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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

xabbuh commented 8 years ago

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 commented 8 years ago

@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 commented 8 years ago

@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 commented 8 years ago

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

HeahDude commented 8 years ago

@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 commented 8 years ago

@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 commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

Had the same issue here: https://github.com/ninsuo/symfony-collection/issues/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 commented 7 years ago

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 commented 7 years ago

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_blockprefix".

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

chhaytoch commented 7 years ago

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)

jpmmartin commented 6 years ago

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

jpmmartin commented 6 years ago
{% 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 commented 6 years ago
{{ 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 commented 6 years ago

@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 commented 6 years ago

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

jpmmartin commented 6 years ago

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 commented 6 years ago

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

jbonnier commented 6 years ago

@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!

KouloukLeGrand commented 3 years ago

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 commented 2 years ago

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.