2010S

Easy Ajax Forms With Sfdoctrinedynamicformrelationsplugin

Here’s the case: you want (badly!) to add a form to an existing one through ajax. You know, like it wasn’t there the minute before but was only one click away! You want to make that click and have all the new form fields appear right here right now for your own pleasure! This new form of course is one of a related object. We’ll say for example that you have a table of customers and one to store addresses. Of course it happens that sometimes a customer might need to fill more than one address. Your form will ship with one, two or zero address forms, but a small link will say ‘add an address’ and the correct fields will appear. Then this link will stay and you’ll be able to add many more addresses!

My case was with companies to which I want to add some brands. In this case, the result would look like this:

Getting and installing the plugin

First go to http://github.com/kriswallsmith/sfDoctrineDynamicFormRelationsPlugin and download the plugin. Unpack it, and rename the folder in which it is contained to only ‘sfDoctrineDynamicFormRelationsPlugin’.

Add

$this->enablePlugins('sfDoctrineDynamicFormRelationsPlugin');

To the setup method of your ProjectConfiguration.class.php file. Now from the command line run:

symfony plugin:publish-assets

The most difficult is almost done!

Using the plugin

The plugin stays out of the way most of the time, but since it requires some ajax calls and some javascript stuff, it means you still have some work to do! For all the javascript needed, I’ll just show how I did it through jQuery, but anything else could be used. The case I was working on is that I have a company model to which I might have to add some brands. The model could be like this:

The Model

Company:
 columns:
   name:                           string(255)
   short_name:                     string(255)
   status_id:                      integer
   phone:                          string(255)
   email:                          string(255)

Brand:
 columns:
   name:                           string(255)
   short_name:                     string(255)
   company_id:                     integer
 relations:
   Company:
     foreignAlias:                 Brands

The main form class

In your CompanyForm class, just as the plugin’s readme suggests, you will add to the configure() method the following line:

$this->embedDynamicRelation('Brands');

That’s it for this class!

Showing the full form

Now let’s say you already had a company with some brands saved in your database. You can immediately show the form containing all those brands. This is done directly where you output the html for the company form. Which in my case is in a _form.php partial from the company module. In that form I just add the following lines somewhere between the form tags of the main form.

<ul id="brand_form_container">
 <?php foreach ($form['brands'] as $bookFields): ?>
  <li>
    <table><?php echo $bookFields ?></table>
    <a href="#">delete this brand </a >
  </li>
  <?php endforeach ?>
</ul>
<a href="#">Add a brand</a>

The javascript

As you can see, I have 2 links: delete this brand and add a brand that do not link to anything. Those are used through javascript to manipulate your form. The delete link is the easiest one as it does not require any ajax. When it’s clicked, we will simply remove the containing ‘li’ element, which will remove one brand form altogether. Therefore when we save the form, the brand in that form will be deleted in the database since the form wasn’t there anymore. The javascript to do this through jQuery is quite simple. Here is the whole javascript I used, including the delete function.

$(document).ready(getBrandForm);

var url = 'http://myserver.com/brand/new';
// Retrieve the form when the 'New' link is clicked
function getBrandForm(){
  $('a.get_ajax_form').click(function(){
     // load the form for Brands. Call back to attach js to the form
     $.get(url, function(data){
         $('#brand_form_container').append(data);
     })
     // return false to prevent the link's action
     return false;
   });
   // delete a form
   $('a.cleaner').click(function(event){
     $(this).parent().remove();
     return false;
   });
}

So when a link of the class ‘cleaner’ is clicked I remove the parent element. The other part is to get a new blank form to the page and add it to the list of existing forms. We simply load a form located at the /brand/new url. If you have generated your symfony module, you can almost directly use the generated form.

When you make an ajax call, symfony disables the layout by default, so you will only get the result of your newSuccess.php template. Which usually includes a _form.php partial.

In that partial, we need to do only 2 things: get rid off the

tags, since our form will be embedded into another one. Format it into a
  • so it appears correctly when we add it to the list. I’ll let you do that by yourselves! That’s it, you can try! And you’ll be disappointed that it doesn’t work… To make it work correctly you need to work a bit on the related form class.

    In the brand form class, we’ll do this:

    public function configure()
    {
      $this->useFields(array(
          'name',
          'short_name',
      ));
      $this->widgetSchema->setNameFormat('company[brands][$i][%s]');
      $this->disableLocalCSRFProtection();
    
      .....
    }
    
    1. The ‘company_id’ must absolutely be removed from the list of fields, otherwise the form save action will receive an empty company id and save a company not linked to anything.
    2. The widgets names must be correctly formatted to fit into the company form. This can also be done from within the action if you’re embedding let’s say addresses that could be related to a company, a person or a lot of different things. The $i is a number. It needs to be different from the ones already used in the form, but doesn’t need to follow them, you can jump from 3 to 67 if you will! Could be passed as a parameter of the /brand/new action from the javascript.
    3. Disable the CSRF protection on this form. Embedded forms in symfony are not supposed to have their own CSRF tokens. This again could be done from the action if you are using this form in other places and need to have your CSRF protection there.

    If you’ve done all of that, go get some rest, it now fully works!