2

I need some help with prototypes that are within prototypes. Symfony is very clever with generating form prototypes, but once you are one layer down (i.e. prototypes within prototypes), it reuses $$name$$ for both prototypes.

This is what a prototype field looks like for my entity. JQuery swaps out $$name$$ with the correct index value (based on number of child nodes)

 <input type="text" id="entry_entities_$$name$$_contactFax" name="entry[entities][$$name$$][contactFax]" value="" />

So far so good. But when you go one level deeper, Symfony uses $$name$$ for the next level down too - here is a prototype for the entity property:

<div id="entry_entities_123_properties" data-prototype="    
    &lt;label for=&quot;entry_entities_$$name$$_properties_$$name$$_name&quot;&gt;Name&lt;/label&gt;
    &lt;input type=&quot;text&quot; id=&quot;entry_entities_$$name$$_properties_$$name$$_name&quot; name=&quot;entry[entities][$$name$$][properties][$$name$$][name]&quot; value=&quot;&quot; /&gt;

This means that (in this example with entity id 123) that all properties get ID 123:

name="entry[entities][123][properties][123][name]"
name="entry[entities][123][properties][123][name]"
name="entry[entities][123][properties][123][name]"

etc.

In my opinion the best way to solve the issue would be to use $$somethingelse$$ for the property - does anyone know where this is set - or does anyone have a complete example with JS on how to solve this? I embarked on a horrible find/replace of the second $$name$$ on each line, but it got very messy. I'm sure there is an easy way to do this, but I couldn't find any guides on the internet.

meze
  • 14,975
  • 4
  • 47
  • 52
mogoman
  • 2,286
  • 24
  • 28
  • You pass the '$$name$$' in prototype() function, can't you change it there? – meze Feb 24 '12 at 09:41
  • 2
    Actually I looked into Symfony code after posting this. $$name$$ is hard coded into CollectionType. Take a look at Symfony\Component\Form\Extension\Core\Type\CollectionType.php – mogoman Feb 24 '12 at 09:48
  • 2
    Right. [Not hardcoded in 2.1](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php). – meze Feb 24 '12 at 09:56
  • @meze thanks for that. I'm not sure if you've made my day better :-) Do I do some JS hacking or run my project on sf 2.1 beta? – mogoman Feb 24 '12 at 10:09
  • Depends on how much time you have ;) 2.1 beta isn't announced yet. You can also define your own collection type with 'prototype_name' option (see my answer below). – meze Feb 24 '12 at 10:19

2 Answers2

5

This code is for symfony 2.0 (in 2.1+ you can just pass the name to prototype() function):

You can create your own collection type with your required option:

<?php

namespace YourBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener;
use Symfony\Component\Form\Extension\Core\Type\CollectionType as BaseCollectionType;

class CollectionType extends BaseCollectionType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilder $builder, array $options)
    {
        if ($options['allow_add'] && $options['prototype']) {
            $prototype = $builder->create($options['prototype_name'], $options['type'], $options['options']);
            $builder->setAttribute('prototype', $prototype->getForm());
        }

        $listener = new ResizeFormListener(
            $builder->getFormFactory(),
            $options['type'],
            $options['options'],
            $options['allow_add'],
            $options['allow_delete']
        );

        $builder
            ->addEventSubscriber($listener)
            ->setAttribute('allow_add', $options['allow_add'])
            ->setAttribute('allow_delete', $options['allow_delete'])
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultOptions(array $options)
    {
        $defaultOptions = parent::getDefaultOptions($options);
        $defaultOptions['prototype_name'] =  '$$name$$';
        return $defaultOptions;
    }
}

Then just define a service with:

tags:
    - { name: form.type, alias: collection }

And use it as Symfony's collection but with prototype_name parameter.

meze
  • 14,975
  • 4
  • 47
  • 52
  • I saw this answer after 3 days trying rewriting and differenciating the placeholders. I wish I saw it earlier. +10000000 ! – renoirb Jan 26 '13 at 00:36
  • @renoirb Good! But keep in mind it's a hack for 2.0. Since v2.1 you can just pass the name to `prototype()` function. – meze Jan 26 '13 at 05:44
0

I'm not familiar with Symfony, but using String.replace() in Javascript replaces only the first occurrence by default, so you could use that pretty safely.

$('mydiv').html
(
    $('mydiv').html().replace('$$name$$', 123).replace('$$name$$', 456);
);

If things are split onto different lines, and you want to replace in those lines, you could use .split() to break it into an array, for() over it, then .join() it back together.

Joe
  • 15,669
  • 4
  • 48
  • 83
  • Thanks for the answer - I believe your idea would have worked, but the result would have been a lot of Javascript. Meze's answer fits into Symfony2 best practice. – mogoman Feb 24 '12 at 10:40
  • Fair enough :) Mainly posted as an option, expecting someone to come up with a Symfony-relevant solution. Glad you've got a way you're happy with – Joe Feb 24 '12 at 10:43