Как использовать Ajax в формах Sonata Admin?

У меня есть объект Merchant со следующими полями и ассоциациями: -

/**
 * @ORM\ManyToMany(targetEntity="Category", inversedBy="merchants")
 */
public $categories;

/**
 * @ORM\ManyToMany(targetEntity="Tag", inversedBy="merchants")
 */
public $tags;

/**
 * @ORM\ManyToOne(targetEntity="Category", inversedBy="merchants")
 */
protected $primaryCategory;

/**
 * @ORM\ManyToOne(targetEntity="Tag", inversedBy="merchants")
 */
protected $primaryTag;

Теги и категории также имеют сопоставление ManyToMany. Итак, у нас есть таблицы отображения Tag_Category, Merchant_Tag, Merchant_Category.

Теперь я хочу выполнить ajax для этих полей.

Я хочу, чтобы пользователь сначала выбрал основной тег. На основе основного тега ajax обновляет категории только до тех, которые принадлежат этому тегу, и выполняет некоторые другие операции.

Как я могу этого добиться?

Спасибо!


person Amit    schedule 12.04.2012    source источник


Ответы (4)


Я смог сделать эту работу несколько месяцев назад. Хотя то, что поделился a.aitboudad, является точным. Есть несколько ошибок, с которыми могут столкнуться новички в Symfony/Sonata.

Вот шаги.

1> Расширение edit.html.twig / base_edit.html.twig Sonata CRUD . Для простоты я буду использовать только последний. Скопируйте vendor/bundles/Sonata/AdminBundle/Resources/views/CRUD/base_edit.html.twig в папку представлений, соответствующую MerchantAdminController - YourBundle/Resources/views/Merchant/base_edit.html.twig

2> Нам нужно указать нашему классу MerchantAdmin использовать этот шаблон. Поэтому мы переопределяем метод getEditTemplate SonataAdmin следующим образом:

public function getEditTemplate()
{
    return 'YourBundle:Merchant:base_edit.html.twig';
}

3> Далее нам нужно кодировать функциональность Ajax в нашем base_edit.html.twig . Стандартный Ajax включает в себя следующее:

3.1> -- Создайте действие в контроллере для запроса Ajax. В первую очередь мы хотим получить список идентификаторов категорий, соответствующих определенному тегу. Но, скорее всего, вы просто используете CRUD-контроллер Sonata.

Определите свой MerchantAdminController, который расширяет 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> -- Сообщите службе администратора, что нужно использовать этот вновь созданный контроллер вместо CRUDController по умолчанию, определив его в 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]

Обратите внимание, что третий аргумент — это имя вашего контроллера. По умолчанию это было бы нулем.

3.3> -- Создайте действие с именем getCategoryOptionsFromTagAction в своем контроллере. Ваш вызов Ajax будет направлен на это действие.

// 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> -- Создайте соответствующий маршрут в app/config/routing.yml. Не забудьте открыть свой маршрут, если вы используете FOSJsRoutingBundle (иначе вам придется жестко кодировать, что не очень хорошая идея).

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> -- сделайте запрос Ajax и используйте ответ

{% 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 %}

Совет 1. Как получить уникальный идентификатор, добавляемый ко всем элементам Sonata

Решение. Используйте переменную администратора, которая предоставит вам доступ ко всем свойствам класса администратора, включая uniqId. См. код о том, как его использовать.

Совет 2. Как получить Router в вашем JS.

Решение. Маршрутизация Symfony2 по умолчанию не работает в JS. Вам нужно использовать пакет под названием FOSJSRouting (описанный выше) и предоставить маршрут. Это также даст вам доступ к объекту Router в вашем JS.

Я немного изменил свое решение, чтобы сделать этот пример более понятным. Если вы заметили что-то не так, пожалуйста, не стесняйтесь комментировать.

person Amit    schedule 28.05.2012
comment
Отличный ответ, отличная детализация, хороший! Один вопрос - зачем вам нужно было использовать материал FOSJSRouting в вашем ajax-вызове? Разве не было бы так же просто передать tagId как часть почтового запроса ajax? Таким образом, вы можете избежать загрузки дополнительного пакета - person lopsided; 19.06.2012
comment
Спасибо, чувак, FOSJSRouting должен получить доступ к объекту Router (Routing.generate(...)) для создания пути для маршрута. Если бы это был простой PHP-проект, мы бы просто дали имя файла - category_from_tag.php. Но здесь нам нужно вызвать действие внутри контроллера. - person Amit; 20.06.2012
comment
Я сделал это так, чтобы добавить маршрут в свой класс администратора (TagAdmin): $collection->add('addToGroup', $this->getRouterIdParameter().'/addToGroup');, использовать этот маршрут для URL-адреса ajax, например: url: '{{ admin.generateUrl('addToGroup', {'id': object.id}) }}', с дополнительным параметром данных в определении ajax: data: 'group='+$(this).prevAll('.group-id').val(), - person lopsided; 20.06.2012
comment
Конечно имеет смысл. Спасибо, что поделился. - person Amit; 21.06.2012
comment
Очень полезно ! Удалось ли вам фильтровать категории во время загрузки для существующего продавца? - person Pierre de LESPINAY; 13.05.2013
comment
@PierredeLESPINAY Я не уверен, но уверен, что категории могли быть отфильтрованы. У меня больше нет доступа к коду, иначе я бы проверил и ответил. - person Amit; 14.05.2013
comment
Я не думаю, что использование MerchantAdmin::getEditTemplate() больше не работает. Мне пришлось изменить это на MerchantAdmin::getTemplate($name), а затем вернуть свой собственный шаблон, когда $name === 'edit' - person caponica; 05.08.2013
comment
@Amit У вас есть идея, как получить uniqid родительского класса администратора, находясь внутри шаблона скрипта, как описано в моей проблеме? github.com/sonata-project/SonataDoctrineORMAdminBundle/issues/ - person webDEVILopers; 03.09.2014
comment
Я успешно сделал это для ajax, но поле entity всегда получает ошибку проверки This value is not valid. при отправке формы. Правильный путь может быть следующим: symfony.com /doc/current/поваренная книга/форма/ - person Sithu; 18.06.2015
comment
@lopsideed, не могли бы вы уточнить ту часть, где вы передаете tagId как часть почтового запроса ajax? data: 'group='+$(this).prevAll('.group-id').val(), Я не понимаю, что здесь происходит. Пожалуйста? - person Jack Brummer; 09.05.2017

На шаге 1 ответа Амита и Лумбендиля вы должны изменить

{% extends base_template %}

в

{% extends 'SonataAdminBundle::standard_layout.html.twig' %}

если вы получите сообщение об ошибке, например

Unable to find template "" in YourBundle:YourObject:base_edit.html.twig at line 34.  
person Paul Prijs    schedule 31.03.2014

Очень подробный пост, просто чтобы обновить способ переопределения и использовать шаблон edit в классе Admin.
Теперь вы должны сделать это следующим образом:

// 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);
    }
}

Или внедрить его в определение службы, используя предоставленный метод., чтобы сделать класс Admin максимально чистым:

// 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]]
person guillermogfer    schedule 24.06.2015

в блоке javascripts вы должны изменить "liszt:updated" на "chosen:updated"

надеюсь кому-то поможет ;)

person Hibatallah Aouadni    schedule 24.06.2016
comment
Добро пожаловать в SO :) Вы должны избегать задавать новый вопрос внутри ответа. Либо используйте комментарии для этого, либо откройте новый вопрос. - person Ivan Gabriele; 24.06.2016