Skip to content
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

[Form] Collection prototype does not contain elements added by events. #4367

Closed
HelloGrayson opened this issue May 22, 2012 · 19 comments
Closed
Labels

Comments

@HelloGrayson
Copy link

I'm trying to setup a dynamic form collection using events based on the documentation.

First I add the collection to the form:

<?php

$builder->add('properties', 'collection', array(
    'type' => new Property(),
    'allow_add' => true,
    'allow_delete' => true,
    'prototype' => true,
    'label' => 'Properties',
));

Then in the Property form, I add an event subscriber:

<?php

$subscriber = new AddDynamicFieldsSubscriber($builder->getFormFactory());
$builder->addEventSubscriber($subscriber);

Then my subscriber class:

<?php

class AddDynamicFieldsSubscriber implements EventSubscriberInterface
{
    private $factory;

    public function __construct(FormFactoryInterface $factory)
    {
        $this->factory = $factory;
    }
    public static function getSubscribedEvents()
    {
        return array(FormEvents::PRE_SET_DATA => 'preSetData');
    }
    public function preSetData(DataEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data || false === $data->needsDynamicFields()) {
            return;
        }

        $form->add($this->factory->createNamed('choice', 'test', null, array(
            'choices' => array(1, 2, 3,),
            'expanded' => false,
            'label' => 'Test',
            'empty_value' => 'Select...',
        )));
    }
}

The form view from $form->createView() contains the elements that were added by the event subscriber. The prototype form view from form.properties.get('prototype') does not contain the elements elements that were added by the event subscriber.

Is there another supported method of getting an updated prototype in this scenario? If not - what would be the best way to customize to get this working?

@HelloGrayson
Copy link
Author

cc @bschussek

@webmozart
Copy link
Contributor

How could this ever work? The event subscriber modifies each form in the collection based on the specific data of that form. The prototype does not have any data though.

What you want to do should be done in JavaScript I suppose.

@ccorliss
Copy link
Contributor

I would expect the prototype to have all elements that were added to child forms. Then they could be removed via javascript as needed, vs needing to create elements in javascript.

@webmozart
Copy link
Contributor

You are talking about magic. How can the framework guess to what possible states your listeners might transform the collection rows considering all possible input states of the data? This is impossible.

@ccorliss
Copy link
Contributor

Isn't it more of a timing issue? It seems like the prototype is generated too early so it would have to guess dynamic fields as you suggest to make this work. If the prototype was generated later - either after data was populated, or generated on the fly - wouldn't that work as expected?

@webmozart
Copy link
Contributor

No. The prototype serves as HTML template for rows added by JavaScript. There is no round trip to the server, so the prototype cannot be adjusted to match your logic for the new data row that the server doesn't even know yet about (since it only exists in your browser).

@ccorliss
Copy link
Contributor

Don't think he gets it. I can do it with a custom collection type
that generates the prototype later - just was hoping for a supported
method.

Curtis Corliss
M: 805.722.7915
W: 805.284.9946

On May 22, 2012, at 9:54 AM, Grayson Koonce
reply@reply.github.com
wrote:

Ok sweet I'll reply

Sent from my iPhone

On May 22, 2012, at 9:38 AM, Curtis
reply@reply.github.com
wrote:

I would expect the prototype to have all elements that were added to child forms. Then they could be removed via javascript as needed, vs needing to create elements in javascript.


Reply to this email directly or view it on GitHub:
#4367 (comment)


Reply to this email directly or view it on GitHub:
#4367 (comment)

@ccorliss
Copy link
Contributor

I guess the inverse would work. Create all elements in the form, then
remove dynamic elements as needed using events.

On May 22, 2012, at 10:17 AM, Bernhard Schussek
reply@reply.github.com
wrote:

No. The prototype serves as HTML template for rows added by JavaScript. There is no round trip to the server, so the prototype cannot be adjusted to match your logic for the new data row that the server doesn't even know yet about (since it only exists in your browser).


Reply to this email directly or view it on GitHub:
#4367 (comment)

@webmozart
Copy link
Contributor

Since this is neither a feature request nor a bug, I'm closing this. You can post to the ML if you want to continue the discussion.

@samsamm777
Copy link

Im having exactly the same issue. I've got a form with an embedded collection, if the form type fields are hard coded in the form type the prototype works, but if you use event subscribers to show the fields then the prototype doesnt work. this ticket should be re-opened and a correct way suggested how to resolve the issue.

@samsamm777
Copy link

This needs to be re-opened. I can see no reason for it not to work. And my only solution to bypass it is to make new forms and a new controller function just to remove 1 field. its not right.

@samsamm777
Copy link

cc @bschussek

@advancingu
Copy link

I agree that this issue should be reopened. This appears to be an event timing issue.

At the time of rendering to HTML, the final state of the form must be known, so there is no reason that the prototype can't be rendered to reflect the fields that are actually present. In other words, rendering of the prototype field should simply be done after all FormEvents::PRE_SET_DATA events have been processed and not before.

@advancingu
Copy link

One helpful workaround that I found is to create a placeholder field directly using the $builder in buildForm() and then remove that field during FormEvents::PRE_SET_DATA handling, replacing it with the final field. This will only give you a barebones prototype without any field customizations done during the event, however it is much better than having the entire field missing.

@webmozart
Copy link
Contributor

I beg you to reread the comments. An event listener modifies a form based on the data of that form. The prototype does not have data. It's empty. What would you expect $event->getData() to be for that prototype?

@trsteel88
Copy link
Contributor

Is it possible to set a default object that the prototype will render using?

@docteurklein
Copy link
Contributor

You could modify CollectionType to pass a data value when creating the prototype form:
instead of what you see here https://github.com/symfony/Form/blob/master/Extension/Core/Type/CollectionType.php#L30,

you could do:

class CollectionType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if ($options['allow_add'] && $options['prototype']) {
            $prototype = $builder->getFormFactory()->createNamed(
                $options['prototype_name'],
                $options['type'], 
                 $data, // your data that will be used in your event listener to create your wanted prototype
                array_replace(array(
                     'label' => $options['prototype_name'] . 'label__',
                ), $options['options'])
            );

            $builder->setAttribute('prototype', $prototype);
        }

        // ...

What do you think @bschussek ?

PS: by modify, I mean: use a type extension, or create your own type that extends collection type.

@stof
Copy link
Member

stof commented Mar 19, 2013

@trsteel88 @docteurklein see #6910

@tlarsonlgdor
Copy link

I know this issue has been closed for quite a while, but I just experienced a similar issue. I had to dynamically add components to the embedded child form of a collectiontype. When I added the dynamic components to the embedded child form in the PRE_SET_DATA event, those components did not generate any prototype properties in the html like the others. Therefore, I simply added the components dynamically to the embedded child form at the same time as the others - during the buildForm function. How did I get the necessary data to the function to build the components dynamically? I sent the data via the $options array in the parent form, then sent it to each child form's $options array using the 'entry_options' property provided by symfony's CollectionType component.

It seems many people are using PRE_SET_DATA events to add components dynamically to forms (especially CollectionType components) unnecessarily. I think Symfony's documentation could be updated to include a cookbook description on how to add dynamic components to child forms of CollectionType by passing through data using their respective $options array and CollectionType's 'entry_options' property.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants