En gros on souhaites faire une interface CRUD, 1N, avec laquelle,
lorsque l’on est sur l’édition d’un
élément, on ajoute un panel pour gérer tous
les éléments fils.
Ici nous avons une table
wtype, avec une table wconf qui contient une série
d’enregistrement reliés a un item wtype.
Tout comme pour l’exemple d’implémentation du
sortable avec drag’ndrop ( disponible ici ) nous allons utiliser les
composants suivants
:pixassociates/sortable-behavior-bundle
et
stof/doctrine-extensions-bundle
Vous devrez donc
avoir préalablement créé une entités
qui contient une liaison 1N avec un deuxième
entité.
La manipulation consiste uniquement à
injecter la deuxième entités dans le call de la
déclaration du service de la première avec
l’argument « addChild » et la
référence du service parent.
Lorsque l’on souhaites faire référence a un service on utilise la chaîne utilisée pour la déclaration (qui est libre), et on ajoute « @ » pour dire que c’est une référence.
#config/services.yaml
services:
admin.wconf:
class: App\Admin\WconfAdmin
tags:
- { name: sonata.admin, manager_type: orm, label: "Configuration des types" }
public: true
arguments: [~, App\Entity\Wconf, 'PixSortableBehaviorBundle:SortableAdmin']
calls:
- [setPositionService, ["@pix_sortable_behavior.position"]]
admin.wtype:
class: App\Admin\WtypeAdmin
arguments: [~, App\Entity\Wtype, ~]
calls:
- [addChild, ["@admin.wconf"]]
tags:
- { name: sonata.admin, manager_type: orm, label: "Types de contrats" }
public: true
Dans les 2 controller admin, il faudra rajouter les références au librairies suivantes :
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Route\RouteCollection;
use Pix\SortableBehaviorBundle\Services\PositionORMHandler as PositionHandler;
Ensuite dans l’interface Parent, on ajoute les
boutons.
Pour l’url de l’interface fils, il faut
lui donner la référence du service pour qu’il
créé les routes. Comme notre service est
« admin.wconf », la référence
pour la liste sera donc ‘admin.wconf.list’
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null): void
{
if (!$childAdmin && !\in_array($action, ['edit'], true)) {
return;
}
$admin = $this->isChild() ? $this->getParent() : $this;
$id = $admin->getRequest()->get('id');
$label=$this->hasSubject() && null !== $this->getSubject()->getLabel() ? $this->getSubject()->getLabel():null;
$menu->addChild(
'Configuration du contrat '.$label,
$admin->generateMenuUrl('edit', ['id' => $id])
);
$menu->addChild( 'Configuration des interfaces '.$label,
[
'uri' => $admin->generateUrl('admin.wconf.list', ['id' => $id])
]);
}
Puisque maintenant notre interface CRUD fils est restreinte au
scope de notre sélection parent, et que nous souhaitons
gérer l’ordre en drag’n drop, nous allons
supprimer la possibilité de trier la liste.
Il suffit
juste d’ajouter l’argument sortable=false (default
true), lors de la définition des champs de la liste.
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('_action', null, array(
'actions' => array(
'move' => array(
'template' => '@PixSortableBehavior/Default/_sort_drag_drop.html.twig',
'enable_top_bottom_buttons' => false, //optional
),
),
))
;
$listMapper->addIdentifier('etape', null, ['label' => 'Etape','sortable'=>false]);
$listMapper->addIdentifier('position', null, ['label' => 'position','sortable'=>false]);
//$listMapper->addIdentifier('id', null, ['label' => 'id','sortable'=>false]);
$listMapper->addIdentifier('label', null, ['label' => 'Label','sortable'=>false]);
$listMapper->addIdentifier('synchro_field', null, ['label' => 'Destination du champs','sortable'=>false]);
$listMapper->add('actif', null, ['editable' => true,'sortable'=>false]);
}
On souhaites aussi conserver l’ordre par défaut, pour
que l’utilisateur retrouve toujours le même ordre
à chaque rechargement.
Il y a deux manières.
La première, consiste à surcharger la
configuration par défaut :
public function __construct( $code, $class, $baseControllerName ) {
parent::__construct( $code, $class, $baseControllerName );
}
protected $datagridValues = array(
'_page' => 1,
'_sort_by' => 'position',
'_sort_order' => 'ASC',
);
La seconde méthode, permet de faire un trie plus fin, par exemple sur 2 champs en surchargeant la requête utilisée pour la liste.
public function createQuery($context = 'list')
{
$proxyQuery = parent::createQuery('list');
$proxyQuery->addOrderBy($proxyQuery->getRootAlias().'.etape', 'ASC');
$proxyQuery->addOrderBy($proxyQuery->getRootAlias().'.position', 'ASC');
return $proxyQuery;
}
Et la on se rends compte que si on désactive l’ordre
des colonnes, notre ordre configuré dans notre
requête ne fonctionne plus.
Du coup, ce que l’on va
faire, c’est surcharger le template pour cette liste
uniquement, et virer le code qui gère les entête de
colonnes, mais garder tout le reste.
La liste se trouve dans vendor/sonata-project/admin-bundle/src/Resources/views/CRUD/base_list.html.twig et notre block concerné est « table_header »
Nous allons ajouter notre template dans la déclaration de
notre service.
Pour cela nous devons prendre la
référence du template, décrit dans cette page
: https://symfony.com/doc/master/bundles/SonataAdminBundle/reference/templates.html#global-templates
Dans
notre cas c’est
Et on vas juste ajouter un call de type setTemplate avec notre configuration
admin.wconf:
class: App\Admin\WconfAdmin
tags:
- { name: sonata.admin, manager_type: orm, label: "Configuration des types" }
public: true
arguments: [~, App\Entity\Wconf, 'PixSortableBehaviorBundle:SortableAdmin']
calls:
- [setPositionService, ["@pix_sortable_behavior.position"]]
- [ setTemplate, [list, "Admin/wconf-list.html.twig"]]
On a mis :
« Admin/wconf-list.html.twig »
Ce qui
veux dire que le système vas aller chercher le fichier
/templates/Admin/wconf-list.html.twig
Ce fichier doit
étendre la vue initiale de la liste, et redéfinir le
block qui gère l’entête uniquement. On
souhaites vraiment garder tout le reste.
Du coup, on
démarre notre fichier en donnant la référence
du template maître, et on redéfinis ensuite notre
block
#/templates/Admin/wconf-list.html.twig
{% extends '@SonataAdmin/CRUD/base_list.html.twig' %}
{% block table_header %}
<thead>
<tr class="sonata-ba-list-field-header">
{% for field_description in admin.list.elements %}
{% if admin.hasRoute('batch') and field_description.getOption('code') == '_batch' and batchactions|length > 0 %}
<th class="sonata-ba-list-field-header sonata-ba-list-field-header-batch">
<input type="checkbox" id="list_batch_checkbox">
</th>
{% elseif field_description.getOption('code') == '_select' %}
<th class="sonata-ba-list-field-header sonata-ba-list-field-header-select"></th>
{% elseif field_description.name == '_action' and app.request.isXmlHttpRequest %}
{# Action buttons disabled in ajax view! #}
{% elseif field_description.getOption('ajax_hidden') == true and app.request.isXmlHttpRequest %}
{# Disable fields with 'ajax_hidden' option set to true #}
{% else %}
{% set sortable = false %}
{% apply spaceless %}
<th class="sonata-ba-list-field-header-{{ field_description.type }}{% if sortable %} sonata-ba-list-field-header-order-{{ sort_by|lower }} {{ sort_active_class }}{% endif %}{% if field_description.options.header_class is defined %} {{ field_description.options.header_class }}{% endif %}"{% if field_description.options.header_style is defined %} style="{{ field_description.options.header_style }}"{% endif %}>
{% if field_description.getOption('label_icon') %}
<i class="sonata-ba-list-field-header-label-icon {{ field_description.getOption('label_icon') }}" aria-hidden="true"></i>
{% endif %}
{{ field_description.label|trans({}, field_description.translationDomain) }}
</th>
{% endapply %}
{% endif %}
{% endfor %}
</tr>
</thead>
{% endblock %}
On a ensuite notre entête sans les liens pour pouvoir trier
le contenus.
On est sûr que l’utilisateur ne vas
pas gérer l’ordre en drag’n drop avec un trie
mal configuré et nous appeler toutes les 5 minutes pour
nous dire que ça ne fonctionne pas.