{"id":1029,"date":"2015-01-19T06:07:39","date_gmt":"2015-01-19T06:07:39","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=1029"},"modified":"2024-09-30T08:49:20","modified_gmt":"2024-09-30T08:49:20","slug":"symfony-2-forms-and-ajax","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/symfony-2-forms-and-ajax\/","title":{"rendered":"Symfony2: Forms and Ajax"},"content":{"rendered":"<p>Making Ajax calls is a trivial task nowadays for any web developer, and I&#8217;m sure it&#8217;s no enigma for all of you how to do it using your favourite Javascript library or framework like jQuery, Angular, Backbone, you name it.<\/p>\n<p>&nbsp;<\/p>\n<h2>Forms and ajax<\/h2>\n<p>But how do we handle these requests in the backend? More specifically, how should we write our controllers\u00a0to take full advantage of our preferred framework, Symfony 2 in my case, and create a great UX for our consumers (with errors displayed nicely and effortless) and a great feeling for our colleagues with whom we share the code (by using status codes and promises).<\/p>\n<p><!--more--><\/p>\n<p>Firstly I prepare a basic controller, while trying to respect REST principles.\u00a0This will prove very valuable later on in the project and your app flow will be easier to follow. A good article on that can be found <a title=\"REST Symfony2\" href=\"http:\/\/williamdurand.fr\/2012\/08\/02\/rest-apis-with-symfony2-the-right-way\/\" target=\"_blank\" rel=\"noopener\">here<\/a>\u00a0.<code><br \/>\n<\/code><\/p>\n<p>\/Acme\/DemoBundle\/Controller\/DemoController.php<\/p>\n<pre class=\"lang:php decode:true\">[...]\r\nuse Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Route;\r\nuse Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Method;\r\n[...]\r\n\r\n\/**\r\n * Renders the \"new\" form\r\n * \r\n * @Route(\"\/\", \"demo_new\")\r\n * @Method(\"GET\")\r\n *\/\r\npublic function newAction(Request $request)\r\n{\r\n    $entity = new Demo();\r\n    $form = $this-&gt;createCreateForm($entity);\r\n\r\n    return $this-&gt;render('AcmeDemoBundle:Demo:new.html.twig',\r\n                    array(\r\n                'entity' =&gt; $entity,\r\n                'form' =&gt; $form-&gt;createView()\r\n                    )\r\n    );\r\n}\r\n\r\n\/**\r\n * Creates a new Demo entity.\r\n *\r\n * @Route(\"\/\", name=\"demo_create\")\r\n * @Method(\"POST\")\r\n *\r\n *\/\r\npublic function createAction(Request $request)\r\n{\r\n    \/\/This is optional. Do not do this check if you want to call the same action using a regular request.\r\n    if (!$request-&gt;isXmlHttpRequest()) {\r\n        return new JsonResponse(array('message' =&gt; 'You can access this only using Ajax!'), 400);\r\n    }\r\n\r\n    $entity = new Demo();\r\n    $form = $this-&gt;createCreateForm($entity);\r\n    $form-&gt;handleRequest($request);\r\n\r\n    if ($form-&gt;isValid()) {\r\n        $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n        $em-&gt;persist($entity);\r\n        $em-&gt;flush();\r\n\r\n        return new JsonResponse(array('message' =&gt; 'Success!'), 200);\r\n    }\r\n\r\n    $response = new JsonResponse(\r\n            array(\r\n        'message' =&gt; 'Error',\r\n        'form' =&gt; $this-&gt;renderView('AcmeDemoBundle:Demo:form.html.twig',\r\n                array(\r\n            'entity' =&gt; $entity,\r\n            'form' =&gt; $form-&gt;createView(),\r\n        ))), 400);\r\n\r\n    return $response;\r\n}\r\n\r\n\/**\r\n * Creates a form to create a Demo entity.\r\n *\r\n * @param Demo $entity The entity\r\n *\r\n * @return SymfonyComponentFormForm The form\r\n *\/\r\nprivate function createCreateForm(Demo $entity)\r\n{\r\n    $form = $this-&gt;createForm(new DemoType(), $entity,\r\n            array(\r\n        'action' =&gt; $this-&gt;generateUrl('demo_create'),\r\n        'method' =&gt; 'POST',\r\n    ));\r\n\r\n    return $form;\r\n}<\/pre>\n<p>Then I prepare my HTML:<\/p>\n<p>\/Acme\/DemoBundle\/Resources\/Demo\/new.html.twig<\/p>\n<pre class=\"lang:xhtml decode:true\">{% block body -%}\r\n    &lt;h1&gt;Demo creation&lt;\/h1&gt;\r\n\r\n    &lt;div class=\"form_error\"&gt;&lt;\/div&gt;\r\n    &lt;form method=\"POST\" class=\"ajaxForm\" action=\"{{path('demo_create')}}\" {{ form_enctype(form) }}&gt;\r\n        &lt;div id=\"form_body\"&gt;\r\n            {% include 'AcmeDemoBundle:Demo:form.html.twig' with {'form': form} %}\r\n        &lt;\/div&gt;\r\n\r\n        &lt;button type=\"submit\" class=\"btn btn-primary\"&gt;Submit&lt;\/button&gt;\r\n        {{form_rest(form)}}\r\n    &lt;\/form&gt;\r\n\r\n    &lt;ul class=\"record_actions\"&gt;\r\n        &lt;li&gt;\r\n            &lt;a href=\"{{ path('demo') }}\"&gt;\r\n                Back to the list\r\n            &lt;\/a&gt;\r\n        &lt;\/li&gt;\r\n    &lt;\/ul&gt;\r\n{% endblock %}\r\n\r\n\r\n{% block javascripts %}\r\n&lt;script&gt;\r\n    initAjaxForm();\r\n&lt;\/script&gt;\r\n{% endblock %}<\/pre>\n<p>And then I build the form; It&#8217;s always a good practice to put the fields of your form in a separate file\u00a0so that you can reuse them for the edit form for example.<\/p>\n<p>\/Acme\/DemoBundle\/Resources\/Demo\/form.html.twig<\/p>\n<pre class=\"lang:xhtml decode:true \">&lt;div class=\"row\" &gt;\r\n    &lt;div class=\"col-lg-4 col-md-4\"&gt;\r\n        &lt;div class=\"col-md-12\"&gt;{{ form_errors(form) }}&lt;\/div&gt;\r\n        &lt;div class=\"bg-danger\"&gt;{{ form_errors(form.name) }}&lt;\/div&gt;\r\n        &lt;div class=\"form-group input-group\"&gt;\r\n            {{ form_widget(form.name,  {'attr': {'placeholder':'Name', 'title':'Name', 'class':'form-control'}}) }}\r\n        &lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div class=\"col-md-4\"&gt;\r\n        &lt;div class=\"bg-danger\"&gt;{{ form_errors(form.isActive) }}&lt;\/div&gt;\r\n        &lt;div class=\"form-group\"&gt;\r\n            {{ form_widget(form.isActive,  {'attr': {'title':'Is active', 'class':'form-control'}}) }}\r\n        &lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n&lt;\/div&gt;<\/pre>\n<p>And to\u00a0tie the frontend with the backend we add a little Javascript (JQuery flavoured). This code simply listens to all submits from the page, and if the submitted form has the class ajaxForm then it submits it using Ajax.<\/p>\n<p>&nbsp;<\/p>\n<p>Thanks to the handy promises <code>.done<\/code> and <code>.fail<\/code>, and to the use of status codes in our response (400 \u00a0-&gt; Bad request, 200 -&gt; Ok, 404 -&gt; Not Found and so on) the script will automatically go to the right promise and will make handling the response a lot easier. Also, if an error occurs in your form (e.g. a field is invalid), then the form is re-rendered with the errors displayed.<\/p>\n<p>&nbsp;<\/p>\n<p>\/Acme\/DemoBundle\/Resources\/public\/main.js<\/p>\n<pre class=\"lang:js decode:true \">function initAjaxForm()\r\n{\r\n    $('body').on('submit', '.ajaxForm', function (e) {\r\n\r\n        e.preventDefault();\r\n\r\n        $.ajax({\r\n            type: $(this).attr('method'),\r\n            url: $(this).attr('action'),\r\n            data: $(this).serialize()\r\n        })\r\n        .done(function (data) {\r\n            if (typeof data.message !== 'undefined') {\r\n                alert(data.message);\r\n            }\r\n        })\r\n        .fail(function (jqXHR, textStatus, errorThrown) {\r\n            if (typeof jqXHR.responseJSON !== 'undefined') {\r\n                if (jqXHR.responseJSON.hasOwnProperty('form')) {\r\n                    $('#form_body').html(jqXHR.responseJSON.form);\r\n                }\r\n\r\n                $('.form_error').html(jqXHR.responseJSON.message);\r\n\r\n            } else {\r\n                alert(errorThrown);\r\n            }\r\n\r\n        });\r\n    });\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>Going a little further, you could put this form in a modal (easily done with bootstrap) and you can get a really nice user interface with forms in modals submitted with Ajax.<\/p>\n<p>Last but not least, don&#8217;t forget to check that your routings are correct in case you didn&#8217;t do it already:<\/p>\n<p>app\/config\/routing.yml<\/p>\n<pre class=\"lang:yaml decode:true \">demo:\r\n    resource: \"@AcmeDemoBundle\/Controller\/\"\r\n    type:     annotation\r\n    prefix:   \/<\/pre>\n<p>&nbsp;<\/p>\n<p>Have fun coding \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Making Ajax calls is a trivial task nowadays for any web developer, and I&#8217;m sure it&#8217;s no enigma for all [&hellip;]<\/p>\n","protected":false},"author":28,"featured_media":1173,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[77,82],"tags":[89,194,236,237],"yst_prominent_words":[394,569,571,1034,1116,1369],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/1029"}],"collection":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/users\/28"}],"replies":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/comments?post=1029"}],"version-history":[{"count":3,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/1029\/revisions"}],"predecessor-version":[{"id":133258,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/1029\/revisions\/133258"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media\/1173"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=1029"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=1029"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=1029"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=1029"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}