Ajaxify your Symfony2 forms with jQuery

by Niki on January 30, 2013

Let’s start by creating a form type for a fictive blogging app

namespace Acme\BlogBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class PostType extends AbstractType
{
  public function buildForm( FormBuilderInterface $builder, 
                                            array $options )
  {
    $builder->add( 'title', 'text' );
    $builder->add( 'body',  'textarea' );
  }

  function getName() {
    return 'PostType';
  }
}

Now we need a controller that will use this form type to create a formview for use in our templates.

namespace Acme\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

use Acme\BlogBundle\Form\Type\PostType;

class PostController extends Controller
{
  /**
   * @Route( "/post/new", name="create_post" )
   * @Template()
   */
  public function createAction( Request $request )
  {
    $postform = $this->createForm( new PostType( ) );

    return array(
      'postform' => $postform->createView( );
    );
  }
}

Don’t forget to include the form type namespace in your controller!

Next we create a view to render our form in.

<form action="{{ path('create_post') }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(postform) }}
    <p>
        <button type="submit">Create</button>
    </p>
</form>

This wel generate something along the lines of following markup

<form action="/post/new" method="post">
  <div id="acme_blogbundle_posttype">
    <div>
      <label for="acme_blogbundle_posttype_title" class="required">
          Name
      </label>
      <input type="text" id="acme_blogbundle_posttype_title" name="acme_blogbundle_posttype[title]" required="required" maxlength="255">
    </div>
      <div>
      <label for="acme_blogbundle_posttype_body" class="required">
        Description
      </label>
      <textarea id="acme_blogbundle_posttype_body" name="acme_blogbundle_posttype[body]" required="required">
      </textarea>
    </div>
    <input type="hidden" id="acme_blogbundle_posttype__token" name="acme_blogbundle_posttype[_token]" value="82c82d6a2f52564f23c2437970f6192aa7102c08">
  </div>
  <p>
    <button type="submit">
        Create
    </button>
  </p>
</form>

Now the magic sits in the following 2 snippets of JavaScript code. First we create a function capable of reading out a form and submitting all the form data as a a regular form submit.

function postForm( $form, callback ){

  /*
   * Get all form values
   */
  var values = {};
  $.each( $form.serializeArray(), function(i, field) {
    values[field.name] = field.value;
  });

  /*
   * Throw the form values to the server!
   */
  $.ajax({
    type        : $form.attr( 'method' ),
    url         : $form.attr( 'action' ),
    data        : values,
    success     : function(data) {
      callback( data );
    }
  });

}

With that function in place the following code will bind the ajax submit to every form in the forms array. Notice I’m using twig variables in the script to get the name of the form.

$(document).ready(function(){

  var forms = [
    '[ name="{{ postform.vars.full_name }}"]'
  ];

  $( forms.join(',') ).submit( function( e ){
    e.preventDefault();

    postForm( $(this), function( response ){
    });

    return false;
  });

});

In the controller we can now handle the data as if it was a regular form submit

namespace Acme\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

use Acme\BlogBundle\Form\Type\PostType;

class PostController extends Controller
{
  /**
   * @Route( "/post/new", name="create_post" )
   * @Template()
   */
  public function createAction( Request $request )
  {
    $postform = $this->createForm( new PostType( ) );

    if ( $request->isMethod( 'POST' ) ) {

      $form->bind( $request );

      if ( $form->isValid( ) ) {

        /*
         * $data['title']
         * $data['body']
         */
        $data = $form->getData();

        $response['success'] = true;

      }else{

        $response['success'] = false;
        $response['cause'] = 'whatever';

      }

      return new JsonResponse( $response );
    }

    return array(
      'postform' => $postform->createView( );
    );
  }
}

Isn’t it nice how easy life can be ?

{ 8 comments… read them below or add one }

Joseph Myalla March 12, 2013 at 12:03 pm

Nice tutorial though I have not finished, where store the jquery library files and the two javascript files

Reply

Niki March 15, 2013 at 6:56 pm

You can store the JavaScript files in your bundle’s public directory (BUNDLE/Resources/public/js)

http://symfony.com/doc/2.1/cookbook/assetic/asset_management.html gives a good idea how asset management works in symfony2.

Reply

flip101 July 3, 2013 at 12:37 pm

Hi i tried the stuff you posted and i had to do some work to get it working. Below are the errors, improvements i made. Using symfony 2.3 and php 5.4.14

Snippet 2, Line 24: Parse: syntax error, unexpected ‘;’, expecting ‘)’ –> remove ;

Snippet 7, Line 48: Parse: syntax error, unexpected ‘;’, expecting ‘)’ –> remove ;

Snippet 7, Line 25 (and line 27, 33): Error: Call to a member function submit() on a non-object –> rename to $postform

Snippet 6, Line 4: Incorrect CSS selector –> changing to —‘[ name^=”{{ postform.vars.full_name }}”]’— Because the only html attributes with a name property are the inputs. They have names like “acme_blogbundle_posttype[title]”. The ‘[title]’ element is gonna mess up the selector

Snippet 6, Line 4 (again): Incorrect CSS selector –> how does a submit action on input elements even make sense?? Changing complete snippet to:
[code]
$(document).ready(function(){

// Array is not neccesary for the example, keep it simple !
// var forms = [
// '[ name="{{ postform.vars.full_name }}"]'
// ];

$( 'form' ).submit( function( e ) {
e.preventDefault();

postForm( $(this), function( response ) {
console.log(JSON.stringify(response)); // Good tutorial stuff
});

// return false; // no please http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/
});

});
[/code]

This is the controller using the new symfony 2.3 submit() method:
[code]
/**
* @Route( "/post/new", name="create_post" )
* @Template()
*/
public function postAction() { // renamed to postAction for consistency. Took out request as parameter (not sure if this method of injection is still supported)
$request = $this->getRequest(); // replacement for getting the request

$postform = $this->createForm( new PostType( ) );

if ( $request->isMethod( 'POST' ) ) {

$postform->submit( $request ); // renamed from bind to submit, renamed $form to $postform

if ( $postform->isValid( ) ) { // renamed $form to $postform

/*
* $data['title']
* $data['body']
*/
$data = $postform->getData(); // renamed $form to $postform

$response['success'] = true;

}else{

$response['success'] = false;
$response['cause'] = 'whatever';

}

return new JsonResponse( $response );
}

return array(
'postform' => $postform->createView( )
);
}
[/code]

In case anyone wants to try same and get things up and running, here is a complete template for the form you can use right away (including all javascripts). Notice that the form twig part is simplified with the new symfony 2.3 form
[code]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
{{ form(postform) }}
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<script type="text/javascript">
function postForm( $form, callback ){

/*
* Get all form values
*/
var values = {};
$.each( $form.serializeArray(), function(i, field) {
values[field.name] = field.value;
});

/*
* Throw the form values to the server!
*/
$.ajax({
type : $form.attr( 'method' ),
url : $form.attr( 'action' ),
data : values,
success : function(data) {
callback( data );
}
});

}

$(document).ready(function(){

// Array is not neccesary for the example, keep it simple !
// var forms = [
// '[ name="{{ postform.vars.full_name }}"]'
// ];

$( 'form' ).submit( function( e ) {
e.preventDefault();

postForm( $(this), function( response ) {
console.log(JSON.stringify(response)); // Good tutorial stuff
});

// return false; // no please http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/
});

});
</script>
</body>
</html>
[/code]

I actually got on this page because i am looking for a way how to get a json from a form. This could be done with JSON.stringify(values) … but then the format doesn’t match. Especially in combination with FOS Rest Bundle & Json decoder.

Reply

brian April 10, 2014 at 11:57 pm

Thanks, i was trying to get some quick gratification trying to see how others have done this. Personally i use fosjsrouter, but i like how you did this. The only problem i can see is when you dont use the submit as intended, you lose the auto validation features, i.e. the message that pops up when the field is left blank, etc… do you know a way around this?

Reply

Saman October 20, 2014 at 4:51 am

Thanks Niki, for you for to prepare this document. It helps me and I create my nice Ajax Symfony form.

Reply

krachleur December 6, 2014 at 11:30 am

Hi, thanks a lot , I used this tuto, it’s working well!

Reply

Jorge May 26, 2015 at 7:46 am

It’s good, but couldn’t make it with a file field in the form?????? help, please……..

Reply

Dragnucs June 30, 2015 at 2:45 pm

Thank you for your tutorial. It was very helpful to me. It is not magical as you say but just well structured and organized.

Reply

Leave a Comment

Previous post:

Next post: