How to use Ajax within Sonata Admin forms?
I was able to make this work a few months back. While what a.aitboudad has shared is accurate. There are a few gotcha's that first timers with Symfony/Sonata might face.
Here are the steps.
1> Extend Sonata CRUD's edit.html.twig
/ base_edit.html.twig
. For simplicity, I'll use only the latter. Copy vendor/bundles/Sonata/AdminBundle/Resources/views/CRUD/base_edit.html.twig
into the views folder corresponding to the MerchantAdminController - YourBundle/Resources/views/Merchant/base_edit.html.twig
2> We need to tell our MerchantAdmin class to use this template. So we override SonataAdmin's getEditTemplate
method like this:
public function getEditTemplate(){ return 'YourBundle:Merchant:base_edit.html.twig';}
3> Next we need to code the Ajax functionality in our base_edit.html.twig
. Standard Ajax comprises of the following:
3.1> -- Create an Action in the controller for the Ajax requestWe primarily want to get a list of category IDs corresponding to a particular tag. But most likely you are just using Sonata's CRUD Controller.
Define your MerchantAdminController which extends CRUDController
<?phpnamespace GD\AdminBundle\Controller;use Sonata\AdminBundle\Controller\CRUDController as Controller;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use GD\AdminBundle\Entity\Merchant;class MerchantAdminController extends Controller{}
3.2> -- Tell your Admin service to use this newly created controller instead of the default CRUDController by defining it in YourBundle/Resources/config/services.yml
gd_admin.merchant: class: %gd_admin.merchant.class% tags: - { name: sonata.admin, manager_type: orm, group: gd_merchant, label: Merchants } arguments: [null, GD\AdminBundle\Entity\Merchant, GDAdminBundle:MerchantAdmin]
Notice that the 3rd argument is the name of your controller. By default it would have been null.
3.3> -- Create an Action named getCategoryOptionsFromTagAction
in your controller. Your Ajax call will be to this Action.
// route - get_categories_from_tagpublic function getCategoryOptionsFromTagAction($tagId) { $html = ""; // HTML as response $tag = $this->getDoctrine() ->getRepository('YourBundle:Tag') ->find($tagId); $categories = $tag->getCategories(); foreach($categories as $cat){ $html .= '<option value="'.$cat->getId().'" >'.$cat->getName().'</option>'; } return new Response($html, 200); }
3.4> -- Create the corresponding route in app/config/routing.yml
. Remember to expose your route if you are using the FOSJsRoutingBundle (else you'll have to hardcode which is not a good idea).
get_categories_from_tag: pattern: /{_locale}/admin/gd/admin/merchant/get-categories-from-tag/{tagId} defaults: {_controller: GDAdminBundle:MerchantAdmin:getCategoryOptionsFromTag} options: expose: true
3.5> -- Make the Ajax Request and use the response
{% block javascripts %} {{ parent() }} <script type="text/javascript"> $(document).ready(function(){ var primaryTag = $("#{{ admin.uniqId }}_primaryTag"); primaryTag.change(updateCategories()); // Bind the function to updateCategories primaryTag.change(); // Manual trigger to update categories in Document load. function updateCategories(){ return function () { var tagId = $("#{{ admin.uniqId }}_primaryTag option:selected").val(); var primaryCategory = $("#{{ admin.uniqId }}_primaryCategory"); primaryCategory.empty(); primaryCategory.trigger("liszt:updated"); var locale = '{{ app.request.get('_locale') }}'; var objectId = '{{ admin.id(object) }}' var url = Routing.generate('get_categories_from_tag', { '_locale': locale, 'tagId': tagId, _sonata_admin: 'gd_admin.merchant', id: objectId }); $.post(url, { tagId: tagId }, function(data){ primaryCategory.empty().append(data); primaryCategory.trigger("liszt:updated"); },"text"); primaryCategory.val("option:first").attr("selected", true); }; } }); </script>{% endblock %}
Gotcha 1: How to get the Unique ID that is appended to all Sonata elements
Solution: Use the admin variable which will give you access to all the Admin Class's properties including uniqId. See code on how to use it.
Gotcha 2: How to get the Router in your JS.
Solution: By default Symfony2 Routing doesn't work in JS. You need to use a bundle called FOSJSRouting (explained above) and expose the route. This will give you access to the Router object within your JS too.
I have modified my solution slightly to make this example clearer. If you notice anything wrong, please feel free to comment.
At step 1 of Amit and Lumbendil answer you should change
{% extends base_template %}
into
{% extends 'SonataAdminBundle::standard_layout.html.twig' %}
if you get an error like
Unable to find template "" in YourBundle:YourObject:base_edit.html.twig at line 34.
Very detailed post, just to update the way of override and use the edit template in the Admin class.
Now, you should do it this way:
// src/AppBundle/Admin/EntityAdmin.php class EntityAdmin extends Admin{ public function getTemplate($name) { if ( $name == "edit" ) { // template 'base_edit.html.twig' placed in app/Resources/views/Entity return 'Entity/base_edit.html.twig' ; } return parent::getTemplate($name); }}
Or inject it in the service definition used the provided method, to keep the Admin class as cleaner as possible:
// app/config/services.yml app.admin.entity: class: AppBundle\Admin\EntityAdmin arguments: [~, AppBundle\Entity\Entity, ~] tags: - {name: sonata.admin, manager_type: orm, group: "Group", label: "Label"} calls: - [ setTemplate, [edit, Entity/base_edit.html.twig]]