Symfony2: Ajax and Full Page Templates

A quick look at one way to use the same controller action and Twig template (or just template) for displaying a full page and also just the inner content for an Ajax request in a Symfony2 application. This is using the ability to set which template a twig template extends from dynamically (which is shown here in the Twig documentation).

So assuming we have a current inner template that looks like this:

{# src/Acme/DemoBundle/Resources/views/Product/new.html.twig #}
{% extends '::full-layout.html.twig' %}

{% block title %}New product{% endblock %}

{% block body %}
<form action="{{ path('product_new') }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(form) }}

    <input type="submit" />
</form>
{% endblock body %}

This extends our main layout template that adds the rest of the page, the header, footer etc. Whenever we render it we will get the full page with all the content from the outer template as well. For an Ajax request we only want the content of the body block to be displayed, we do not want any of the outer content that is provided by the the main template and we are not interested in the title block either. We can do this by creating an alternative Ajax outer template to render just the body block that is as simple as:

{# app/Resources/views/ajax-layout.html.twig #}
{% block body %}{% endblock body %}

Then in our inner template we can use the app global to find out if it is an Ajax request:

{# src/Acme/DemoBundle/Resources/views/Product/new.html.twig #}
{% extends app.request.xmlHttpRequest 
         ? '::ajax-layout.html.twig'
         : '::full-layout.html.twig' %}

{# ... #}

Note: If you are using Twig outside of Symfony2 then you will need to add an alternative way of identifying whether it is an Ajax request.

It's a simple as that. However we can add an improvement in order to avoid having to repeat that check in multiple inner templates. By adding an intermediate template that contains just that logic and extending the inner template from it this can be kept in a single template:

{# app/Resources/views/layout.html.twig #}
{% extends app.request.xmlHttpRequest 
         ? '::ajax-layout.html.twig'
         : '::full-layout.html.twig' %}

{# src/Acme/DemoBundle/Resources/views/Product/new.html.twig #}
{% extends '::layout.html.twig' %}

{# ... #}

Another possible time you may need this sort of solution is where a template should sometimes be rendered as a main request and sometimes as a subrequest. The only real difference here is the logic in checking which template to extend. By passing a parameter when rendering a subrequest you can then check that:

{# ... #}

{% render url("inner_route", { 'partial': true }) with {}, { 'standalone': true } %}

{# ... #}
{# app/Resources/views/layout.html.twig #}
{% extends app.request.partial 
         ? '::full-layout.html.twig'
         : '::partial-layout.html.twig' %}

If you needed to combine these and other reasons for making the decision it would be worth moving the logic involved to a function using a Twig extension where it could then be tested outside of the template.