Search code examples
symfony1

A better way to dynamically embed forms symfony?


I've been using Symfony's form framework for a while now. But would like to know if anyone has a better approach to embedding forms dynamically.

The problem arises when I embed a form (see bottom), I need to give it an array index, as Fabian explains how the sfForm object is like a multi-dimensional array in this article Advanced forms.

If I want to give the user the ability to click a button and embed another form, how can I achieve the following if they click the button multiple times:

<input type="parent[child][]" />
<input type="parent[child][]" />
<input type="parent[child][]" />

... repeated how many time user clicks a button. I can use fast javascript to copy and paste form elements in DOM.

Rather than this:

<input type="parent[child][1]" />
<input type="parent[child][2]" />
<input type="parent[child][3]" />

... repeated how many times user clicks a button. Requires javascript method to count how many times user clicks the button, ie to set correct array index. Also requires Ajax to call a PHP function that embeds form with this array index. I would like to avoid using this method if possible.

How I embed a form:

$parentForm = new ParentForm($parent)        

$child = new child();
$child->setParent($parent);

$sfForm = new sfForm();
$sfForm ->embedForm($someIndex, new ChildForm($child));

$parentForm->embedForm('child', $sfForm);

Solution

  • Hey, I found a way to do! The tricky part here is to override sfWidgetFormSchema::generateName method.

    class myWidgetFormSchema extends sfWidgetFormSchema
    {
    
      /**
       * Generates a name.
       *
       */
      public function generateName($name)
      {
        $name = parent::generateName($name);
        //match any [number] and replace it with []
        $name = preg_replace('/\[\d+\]/','[]', $name);
        return $name;
      }
    }
    

    Now, you only need to set this to your 'wrapper' form. Here's my example with "Master has many Slaves" schema:

      public function configure()
      {
        $this->getWidgetSchema()->setFormFormatterName('list');
        $this->widgetSchema->setNameFormat('master[%s]');
    
        $slavesForm = new sfForm();
        $slavesForm->setWidgetSchema(new myWidgetFormSchema);
        $slavesCount = $this->getOption('slaves_count', 2);
        for ($i = 0; $i < $slavesCount; $i++)
        {
          $slave = new Slave();
          $slave->Master = $this->getObject();
          $form = new SlaveForm($slave);
          $slavesForm->embedForm($i, $form);
        }
        $this->embedForm('new_slaves', $slavesForm);
      }
    

    Notice the 'slaves_count' option that I pass from executeCreate like this:

      public function executeCreate(sfWebRequest $request)
      {
        $schema = $this->getRequest()->getParameter('master');
    
        $this->form = new MasterNewForm(null, array('slaves_count'=> count($schema['new_slaves'])));
    
        $this->processForm($request, $this->form);
    
        $this->setTemplate('new');
      }
    

    Now you can easily use jQuery to clone rows and not bother about index! Cheers.