How to use Ajax within Sonata Admin forms?

懵懂的女人 提交于 2019-11-26 23:53:20
Amit

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 request We 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

<?php

namespace 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_tag
public 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]]

in the block javascripts, you have to change "liszt:updated" to "chosen:updated"

hope it helps someone ;)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!