Embedding forms using sfPropel15Plugin

This article has moved to this location.


In my previous articles (Ajaxify symfony embedded form and Dynamic input using javascript and symfony embedded forms), I have shown you how to embed main form and it relations in symfony using propel 1.4 (sfPropelPlugin). In this article, I will explain to you how to do it by using propel 1.5 (sfPropel15Plugin). As a warm up :D, you can watch an interesting screencast here. Easy isn’t it? yes.. One line can do a lot of Things …

embedRelation and mergeRelation

mergeRelation in action

mergeRelation in action

embedRelation in action

embedRelation in action

As indicated by its name, both functions are useful for embedding the related object forms into the main form. All basic actions like Editing, adding, and deleting on embedded relation forms are already handled by propel. So, you don’t have to write a single line of code.

Customizing mergeRelation

Well, let us take an example from my previous article. I assume you’ve initiated the product module. Open ProductForm.class.php and ProductPhotoForm.class.php files, add the code below to the configure() function.

//ProductForm.class.php
public function configure()
{
   $this->mergeRelation('ProductPhoto', array(
              'empty_label' => 'New Photo',
              'item_pattern' => 'Photo %index%',));
}
//ProductPhotoForm.class.php
public function configure()
{
  $this->useFields(array('filename', 'caption'));
  $this->setWidget('filename', new sfWidgetFormInputFileEditable(array(
    'file_src'    => '/uploads/products/'.$this->getObject()->getFilename(),
    'edit_mode'   => !$this->isNew(),
    'is_image'    => true,
    'with_delete' => false,
  )));

  $this->setValidator('filename', new sfValidatorFile(array(
    'mime_types' => 'web_images',
    'path' => sfConfig::get('sf_upload_dir').'/products',
    'required' => false,
  )));
}

Now, try to create a new product, then click on the Add New link. Have a look at picture below. If you notice, there is one missing feature there, yes .. an action to delete the embedded relation forms. By default, this feature exist when you are editing an object, as shown by the picture above.

Creating new object

Creating new object


How to add the delete feature while we are creating a new object? Because the feature is intended for embedded relation forms, open ProductPhotoForm.class.php, add the code below to the configure function.

  if ($this->isNew()) {
    $deleteWidget = new sfWidgetFormDelete(array('parent_level' => 5));
    $this->setDeleteWidget('delete', $deleteWidget);
  }

Refresh your browser, the delete checkbox now appears as we expected.

Screenshot after adding delete widget

Screenshot after adding delete widget


In the code above, we have set the parent_level value by 5 and its value depends on the structure of your form (see the illustration below). What is the parent_level? parent_level is The number of times parentNode must be called to reach the parent.
Default form structure

Default form structure


In our case, the parent is the span tag (span (5) -> table (4) -> tbody (3) -> tr (2) -> td (1)), not the div tag, because the div tag is the container for all additional embedded relation forms.

One thing to notice, the delete action in the new mode is different from that in the edit mode. In the edit mode, when the user place a check on one of the embedded relation forms, then the user click one of submit buttons, the related object will be marked as deleted when it is being validated. Where as in the new mode, the related object does not exist yet and will be treated as a new object, so it will fail while it is being validated.
Error when saving a new object
To resolve the issue above, before entering the validation process, we have to get rid off the marked embedded relation form(s) from the main form.

Open ProductForm.class.php, add the code below:

  protected function doBind(array $values)
  {
    foreach ($this->optionalForms as $name => $form)
    {
      $i = 1;
      if (strpos($name, '/') === false) 
      {
        //mergeRelation
        while (array_key_exists($name . $i, $values))
        {
          $optional = $name . $i;
          if (isset($values[$optional]['delete']) && $values[$optional]['delete'])
          {
            unset($values[$optional]);
            $this->offsetUnset($optional);
          }
          $i++;
        }
      }
      else
      {
        //embedRelation
        list($parent, $name) = explode('/', $name);
        if (!isset($values[$parent]))
        {
          continue;
        }
        $valuesCopy = $values[$parent];
        $target = clone $this->embeddedForms[$parent];
        while (array_key_exists($name . $i, $valuesCopy))
        {
          $optional = $name . $i;
          if (isset($valuesCopy[$optional]['delete']) && $valuesCopy[$optional]['delete'])
          {
            unset($valuesCopy[$optional]);
            $target->offsetUnset($optional);
          }
          $i++;
        }
        //re-embed relation forms
        $this->offsetUnset($parent);
        $this->isBound = false;
        $this->embedForm($parent, $target);

        $this->isBound = true;
        $values[$parent] = $valuesCopy;
      }
    }
    parent::doBind($values);
  }

If you are still using propel 1.4, I recommend you to upgrade to propel 1.5. It is backwards compatible with propel 1.4 and offer many interesting features. For more information, you can read here.

Thanks you

Advertisements

6 responses to “Embedding forms using sfPropel15Plugin

  1. Kristin December 9, 2010 at 11:43 am

    Thanks so much for this tip. I noticed that this seems to only work if the product is new. If you try to cancel adding a photo for an existing product, you’ll still get the error message. Also, this appears to only work for MergeRelation, not EmbedRelation. Any idea why?

  2. Pingback: Embedding forms using sfPropel15Plugin « Creation site internet Lyon et Paris

  3. Panos Kyriakakis January 21, 2011 at 8:12 pm

    Nice post!
    I found that there is an issue (atleast when embeding forms) if you add 2 new photo forms, delete 1rst added and fill the second.
    After digging for some hours if found that sfPropelform starts the issue at bind. Expects as your bind method that sub forms numbering is continous, subrec1, subrec2 etc. But if you delete subrec1
    while( array_key_exists($name . $i, … want find subrec2 (it will never reach $i++).
    So i did a modification to sfFormPropel, changed:

            while (array_key_exists($name . $i, $taintedValuesCopy))
            {
             $target->embedForm($name . $i, clone $form);
             $target->getWidgetSchema()->moveField($name . $i, sfWidgetFormSchema::BEFORE, $name);
    

    to:

            $keys = array_keys($taintedValuesCopy);
            $keys = array_filter($keys, function ($element) use ($name) { return( strpos($element, $name)===0 ); });
            foreach($keys as $optional)
            {
              $target->embedForm($optional, clone $form);
              $target->getWidgetSchema()->moveField($optional, sfWidgetFormSche ma::BEFORE, $name);
    

    and binding works perfectly.
    So if make the same change to your bind it will work also.
    I am not using your bind because i cloned sfWidgetFormDelete and added javascript to remove the anwanted block from html, so no need to modify binding. Ok, did there some more so confirmation is made with jquery ui dialog.

    Keep rocking!
    Your posts are very-very helpful!

    Panos
    Greetings from Greece!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: