@@ -2,22 +2,39 @@ | |||
namespace Lc\SovBundle\Controller\Admin; | |||
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection; | |||
use EasyCorp\Bundle\EasyAdminBundle\Config\Action; | |||
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; | |||
use EasyCorp\Bundle\EasyAdminBundle\Config\Assets; | |||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; | |||
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore; | |||
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\EA; | |||
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; | |||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController as EaAbstractCrudController; | |||
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterCrudActionEvent; | |||
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterEntityUpdatedEvent; | |||
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeCrudActionEvent; | |||
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityDeletedEvent; | |||
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent; | |||
use EasyCorp\Bundle\EasyAdminBundle\Exception\ForbiddenActionException; | |||
use EasyCorp\Bundle\EasyAdminBundle\Exception\InsufficientEntityPermissionException; | |||
use EasyCorp\Bundle\EasyAdminBundle\Factory\EntityFactory; | |||
use EasyCorp\Bundle\EasyAdminBundle\Factory\FilterFactory; | |||
use EasyCorp\Bundle\EasyAdminBundle\Factory\PaginatorFactory; | |||
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField; | |||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField; | |||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; | |||
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; | |||
use EasyCorp\Bundle\EasyAdminBundle\Security\Permission; | |||
use Lc\SovBundle\Doctrine\EntityManager; | |||
use Lc\SovBundle\Doctrine\Extension\DevAliasInterface; | |||
use Lc\SovBundle\Doctrine\Extension\SeoInterface; | |||
use Lc\SovBundle\Doctrine\Extension\SortableInterface; | |||
use Lc\SovBundle\Doctrine\Extension\TranslatableInterface; | |||
use Lc\SovBundle\Field\CollectionField; | |||
use Lc\SovBundle\Field\GalleryManagerField; | |||
use Lc\SovBundle\Form\Type\Crud\PositionType; | |||
use Symfony\Component\Form\Extension\Core\Type\CollectionType; | |||
use Symfony\Component\Form\Extension\Core\Type\TextType; | |||
use Symfony\Component\HttpFoundation\RequestStack; | |||
use Symfony\Component\HttpFoundation\Session\SessionInterface; | |||
@@ -50,7 +67,6 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
} | |||
); | |||
} | |||
/* Boutons des actions dans les listes */ | |||
$actionsArray[Crud::PAGE_INDEX] = [ | |||
Action::NEW => [ | |||
@@ -107,6 +123,14 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
$actions->add(Crud::PAGE_EDIT, Action::DELETE); | |||
$actions->add(Crud::PAGE_NEW, Action::INDEX); | |||
if (in_array(SortableInterface::class, class_implements($this->getEntityFqcn()))) { | |||
$sortAction = Action::new('sort', 'Ordonner', 'fa fa-sort') | |||
->linkToCrudAction('sort') | |||
->setCssClass('btn btn-info') | |||
->createAsGlobalAction(); | |||
$actions->add(Crud::PAGE_INDEX, $sortAction); | |||
} | |||
$actions->reorder(Crud::PAGE_EDIT, [Action::INDEX, Action::SAVE_AND_RETURN, Action::SAVE_AND_CONTINUE]); | |||
$actions->reorder(Crud::PAGE_NEW, [Action::INDEX, Action::SAVE_AND_RETURN, Action::SAVE_AND_ADD_ANOTHER]); | |||
@@ -182,6 +206,105 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
return $responseParameters; | |||
} | |||
public function sort(AdminContext $context) | |||
{ | |||
$event = new BeforeCrudActionEvent($context); | |||
$this->get('event_dispatcher')->dispatch($event); | |||
if ($event->isPropagationStopped()) { | |||
return $event->getResponse(); | |||
} | |||
if (!$this->isGranted(Permission::EA_EXECUTE_ACTION) || !in_array( | |||
SortableInterface::class, | |||
class_implements($this->getEntityFqcn()) | |||
)) { | |||
throw new ForbiddenActionException($context); | |||
} | |||
$fields = FieldCollection::new($this->configureFields(Crud::PAGE_INDEX)); | |||
$filters = $this->get(FilterFactory::class)->create( | |||
$context->getCrud()->getFiltersConfig(), | |||
$fields, | |||
$context->getEntity() | |||
); | |||
$queryBuilder = $this->createIndexQueryBuilder($context->getSearch(), $context->getEntity(), $fields, $filters); | |||
$paginator = $this->get(PaginatorFactory::class)->create($queryBuilder); | |||
$entities = $this->get(EntityFactory::class)->createCollection($context->getEntity(), $paginator->getResults()); | |||
$this->get(EntityFactory::class)->processFieldsForAll($entities, $fields); | |||
$sortableForm = $this->createFormBuilder(array('entities', $paginator->getResults())) | |||
->add( | |||
'entities', | |||
CollectionType::class, | |||
array( | |||
'required' => true, | |||
'allow_add' => true, | |||
'entry_type' => PositionType::class, | |||
) | |||
) | |||
->getForm(); | |||
$entityManager = $this->getDoctrine()->getManagerForClass($this->getEntityFqcn()); | |||
$repository = $entityManager->getRepository($this->getEntityFqcn()); | |||
$sortableForm->handleRequest($context->getRequest()); | |||
if ($sortableForm->isSubmitted() && $sortableForm->isValid()) { | |||
// $entityInstance = $event->getEntityInstance(); | |||
foreach ($sortableForm->get('entities')->getData() as $elm) { | |||
$entityInstance = $repository->find($elm['id']); | |||
$entityDto = $context->getEntity()->newWithInstance($entityInstance); | |||
if (!$entityDto->isAccessible()) { | |||
throw new InsufficientEntityPermissionException($context); | |||
} | |||
$event = new BeforeEntityDeletedEvent($entityInstance); | |||
$this->get('event_dispatcher')->dispatch($event); | |||
$entityInstance = $event->getEntityInstance(); | |||
$entityInstance->setPosition($elm['position']); | |||
$this->updateEntity($entityManager, $entityInstance); | |||
$this->get('event_dispatcher')->dispatch(new AfterEntityUpdatedEvent($entityInstance)); | |||
} | |||
$url = $this->get(AdminUrlGenerator::class) | |||
->setAction(Action::INDEX) | |||
->generateUrl(); | |||
$this->addFlash('success', 'Position modifié', array(), 'mweb'); | |||
return $this->redirect($url); | |||
} | |||
$responseParameters = $this->configureResponseParameters( | |||
KeyValueStore::new( | |||
[ | |||
'pageName' => Crud::PAGE_INDEX, | |||
'templatePath' => '@LcSov/adminlte/crud/sort.html.twig', | |||
'entities' => $entities, | |||
'paginator' => $paginator, | |||
'global_actions' => array(), | |||
'batch_actions' => array(), | |||
'filters' => $filters, | |||
'sortable_form' => $sortableForm, | |||
] | |||
) | |||
); | |||
$responseParameters->set('fields', $this->configureFields('index')); | |||
$event = new AfterCrudActionEvent($context, $responseParameters); | |||
$this->get('event_dispatcher')->dispatch($event); | |||
if ($event->isPropagationStopped()) { | |||
return $event->getResponse(); | |||
} | |||
return $responseParameters; | |||
} | |||
public function configureFields(string $pageName): iterable | |||
{ | |||
if (in_array(SeoInterface::class, class_implements($this->getEntityFqcn()))) { | |||
@@ -194,7 +317,9 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
'Affiché dans les résultats de recherche Google' | |||
)->hideOnIndex(), | |||
CollectionField::new('oldUrls') | |||
->setFormTypeOption('entry_type', TextType::class)->setLabel('Anciennes urls du document')->hideOnIndex() | |||
->setFormTypeOption('entry_type', TextType::class)->setLabel( | |||
'Anciennes urls du document' | |||
)->hideOnIndex(), | |||
]; | |||
} | |||
@@ -40,6 +40,7 @@ class DashboardController extends AbstractDashboardController | |||
$assets->addWebpackEncoreEntry('adminlte-common'); | |||
$assets->addWebpackEncoreEntry('adminlte-index'); | |||
$assets->addWebpackEncoreEntry('adminlte-form'); | |||
$assets->addWebpackEncoreEntry('adminlte-sort'); | |||
return $assets; | |||
} |
@@ -4,7 +4,7 @@ namespace Lc\SovBundle\Field; | |||
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface; | |||
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait; | |||
use Lc\SovBundle\Form\Type\FileManagerType; | |||
use Lc\SovBundle\Form\Type\Crud\FileManagerType; | |||
use Symfony\Component\Form\Extension\Core\Type\TextType; | |||
/** |
@@ -4,8 +4,7 @@ namespace Lc\SovBundle\Field; | |||
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface; | |||
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait; | |||
use Lc\SovBundle\Form\Type\FileManagerType; | |||
use Lc\SovBundle\Form\Type\GalleryManagerType; | |||
use Lc\SovBundle\Form\Type\Crud\FileManagerType; | |||
use Symfony\Component\Form\Extension\Core\Type\CollectionType; | |||
@@ -4,7 +4,7 @@ namespace Lc\SovBundle\Field; | |||
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface; | |||
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait; | |||
use Lc\SovBundle\Form\Type\FileManagerType; | |||
use Lc\SovBundle\Form\Type\Crud\FileManagerType; | |||
use Symfony\Component\Form\Extension\Core\Type\TextType; | |||
/** |
@@ -1,21 +1,14 @@ | |||
<?php | |||
namespace Lc\SovBundle\Form\Type; | |||
namespace Lc\SovBundle\Form\Type\Crud; | |||
use ArrayObject; | |||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; | |||
use EasyCorp\Bundle\EasyAdminBundle\Dto\AssetsDto; | |||
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; | |||
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto; | |||
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField; | |||
use EasyCorp\Bundle\EasyAdminBundle\Form\EventListener\EasyAdminTabSubscriber; | |||
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EaFormPanelType; | |||
use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser; | |||
use Symfony\Component\Form\AbstractType; | |||
use Symfony\Component\Form\FormBuilderInterface; | |||
use Symfony\Component\Form\FormInterface; | |||
use Symfony\Component\Form\FormView; | |||
use Symfony\Component\OptionsResolver\Options; | |||
use Symfony\Component\OptionsResolver\OptionsResolver; | |||
/** |
@@ -1,6 +1,6 @@ | |||
<?php | |||
namespace Lc\SovBundle\Form\Type; | |||
namespace Lc\SovBundle\Form\Type\Crud; | |||
use Lc\SovBundle\DataTransformer\FileManagerTypeToDataTransformer; | |||
use Lc\SovBundle\Entity\File\File; |
@@ -0,0 +1,25 @@ | |||
<?php | |||
namespace Lc\SovBundle\Form\Type\Crud; | |||
use Symfony\Component\Form\AbstractType; | |||
use Symfony\Component\Form\Extension\Core\Type\HiddenType; | |||
use Symfony\Component\Form\FormBuilderInterface; | |||
class PositionType extends AbstractType | |||
{ | |||
/** | |||
* @param FormBuilderInterface $builder | |||
* @param array $options | |||
*/ | |||
public function buildForm(FormBuilderInterface $builder, array $options) | |||
{ | |||
$builder->add('id', HiddenType::class); | |||
$builder->add('position', HiddenType::class); | |||
} | |||
} |
@@ -0,0 +1,4 @@ | |||
import 'jquery-ui' | |||
import './sort.js' | |||
import './sort.scss' |
@@ -0,0 +1,39 @@ | |||
jQuery(document).ready(function () { | |||
initSovSortableList(); | |||
}); | |||
function initSovSortableList() { | |||
if ($('.sov-sortable').length > 0) { | |||
$('.sov-sortable tbody').sortable({ | |||
placeholder: "ui-state-highlight" | |||
}); | |||
$('.sov-sortable tbody').on("sortupdate", function (event, ui) { | |||
prototype = $('#form_entities').data('prototype'); | |||
$('.sov-sortable tr.sov-draggable').each(function (index, li) { | |||
// instead be a number based on how many items we have | |||
var newForm = prototype.replace(/__name__/g, index); | |||
// Replace '__name__' in the prototype's HTML to | |||
$(li).find('div:last-child').remove(); | |||
$(li).append(newForm); | |||
$(li).find('#form_entities_' + index + '_id').val($(li).data('id')); | |||
if ($('.sov-sortable').data('parent-position') !== '') { | |||
//Ajout d'un 0 initial pour les nuémros <10 | |||
indexAsString = index.toString().padStart(2, '0'); | |||
val = $('.sov-sortable').data('parent-position') + '.' + indexAsString | |||
} else { | |||
val = index; | |||
} | |||
$(li).find('#form_entities_' + index + '_position').val(val); | |||
}); | |||
}); | |||
} | |||
} | |||
@@ -0,0 +1,45 @@ | |||
.form-sent { | |||
.form-control:invalid { | |||
border-color: #dc3545; | |||
padding-right: 2.25rem; | |||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E"); | |||
background-repeat: no-repeat; | |||
background-position: center right calc(.375em + .1875rem); | |||
background-size: calc(.75em + .375rem) calc(.75em + .375rem); | |||
} | |||
select.form-control:invalid + .select2 .select2-selection { | |||
border-color: #dc3545; | |||
} | |||
select.form-control:invalid + .select2 .select2-selection b { | |||
border-color: #dc3545 transparent transparent transparent; | |||
} | |||
.form-check-label input:invalid ~ .checkmark { | |||
border-color: #dc3545; | |||
} | |||
} | |||
.nav-item .nav-link { | |||
position: relative; | |||
.invalid-form { | |||
display: none; | |||
position: absolute; | |||
top: -7px; | |||
right: -6px; | |||
color: #dc3545; | |||
background: #fff; | |||
border-radius: 10px; | |||
font-size: 1.2rem; | |||
} | |||
} | |||
.nav-item.has-invalid .nav-link .invalid-form { | |||
display: inline-block; | |||
z-index: 2; | |||
} | |||
@@ -16,7 +16,7 @@ services: | |||
resource: '../../Controller/' | |||
tags: [ 'controller.service_arguments' ] | |||
Lc\SovBundle\Form\Type\CrudFormType: | |||
Lc\SovBundle\Form\Type\Crud\CrudFormType: | |||
decorates: EasyCorp\Bundle\EasyAdminBundle\Form\Type\CrudFormType | |||
arguments: ['@form.type_guesser.doctrine', '@.inner'] | |||
@@ -0,0 +1,142 @@ | |||
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||
{# @var entities \EasyCorp\Bundle\EasyAdminBundle\Collection\EntityDtoCollection #} | |||
{# @var paginator \EasyCorp\Bundle\EasyAdminBundle\Orm\EntityPaginator #} | |||
{% extends ea.templatePath('layout') %} | |||
{% trans_default_domain ea.i18n.translationDomain %} | |||
{% block body_id entities|length > 0 ? 'ea-index-' ~ entities|first.name : '' %} | |||
{% block body_class 'index' ~ (entities|length > 0 ? ' index-' ~ entities|first.name : '') %} | |||
{% block content_title %} | |||
Tri position | |||
{# {%- apply spaceless -%} | |||
{% set default_title = ea.crud.defaultPageTitle('index')|trans(ea.i18n.translationParameters, 'EasyAdminBundle') %} | |||
{{ ea.crud.customPageTitle is null ? default_title|raw : ea.crud.customPageTitle('index')|trans(ea.i18n.translationParameters)|raw }} | |||
{%- endapply -%} #} | |||
{% endblock %} | |||
{% set has_batch_actions = batch_actions|length > 0 %} | |||
{% block page_actions %} | |||
{# {% block global_actions %} | |||
<div class="global-actions"> | |||
{% for action in global_actions %} | |||
{{ include(action.templatePath, { action: action }, with_context = false) }} | |||
{% endfor %} | |||
</div> | |||
{% endblock global_actions %} | |||
{% block batch_actions %} | |||
{% if has_batch_actions %} | |||
<div class="batch-actions" style="display: none"> | |||
{% for action in batch_actions %} | |||
{{ include(action.templatePath, { action: action }, with_context = false) }} | |||
{% endfor %} | |||
</div> | |||
{% endif %} | |||
{% endblock %} #} | |||
{% endblock page_actions %} | |||
{% block main %} | |||
{# sort can be multiple; let's consider the sorting field the first one #} | |||
{% set sort_field_name = app.request.get('sort')|keys|first %} | |||
{% set sort_order = app.request.get('sort')|first %} | |||
{% set some_results_are_hidden = false %} | |||
{% set has_footer = entities|length != 0 %} | |||
{% set has_search = ea.crud.isSearchEnabled %} | |||
{% set has_filters = filters|length > 0 %} | |||
{% set has_datagrid_tools = has_search or has_filters %} | |||
{{ form_start(sortable_form) }} | |||
{% embed '@LcSov/adminlte/embed/card.html.twig' %} | |||
{% block css %}card-table card-outline card-primary{% endblock %} | |||
{% block header %} | |||
<span data-toggle="tooltip" class="badge badge-light" data-original-title="Total" title="Total"> | |||
{{ paginator.numResults }} résultats | |||
</span> | |||
{% endblock %} | |||
{% block body %} | |||
<div class="table-responsive"> | |||
<table class="table table-bordered table-hover table-striped sov-sortable" | |||
data-parent-position="{{ entity is defined and entity is not null ? entity.position : '' }}"> | |||
<thead> | |||
{% block table_head %} | |||
<tr> | |||
<th></th> | |||
{% for field in fields ?? [] %} | |||
{% set field = field.getAsDto() %} | |||
{% set is_sorting_field = ea.search.isSortingField(field.property) %} | |||
<th class="{{ is_sorting_field ? 'sorted' }} {{ field.isVirtual ? 'field-virtual' }} {% if field.textAlign %}text-{{ field.textAlign }}{% endif %}" | |||
dir="{{ ea.i18n.textDirection }}"> | |||
<span>{{ field.label|raw }}</span> | |||
</th> | |||
{% endfor %} | |||
</tr> | |||
{% endblock table_head %} | |||
</thead> | |||
<tbody> | |||
{% block table_body %} | |||
{% for entity in entities %} | |||
{% if not entity.isAccessible %} | |||
{% set some_results_are_hidden = true %} | |||
{% else %} | |||
<tr class="sov-draggable" data-id="{{ entity.primaryKeyValueAsString }}"> | |||
<td> | |||
<i class="fa fa-fw fa-sort"></i> | |||
</td> | |||
{% for field in entity.fields %} | |||
<td class="{{ field.property == sort_field_name ? 'sorted' }} text-{{ field.textAlign }} {{ field.cssClass }}" | |||
dir="{{ ea.i18n.textDirection }}"> | |||
{{ include(field.templatePath, { field: field, entity: entity }, with_context = false) }} | |||
</td> | |||
{% endfor %} | |||
</tr> | |||
{% endif %} | |||
{% else %} | |||
<tr> | |||
<td class="no-results" colspan="100"> | |||
{{ 'datagrid.no_results'|trans(ea.i18n.translationParameters, 'EasyAdminBundle') }} | |||
</td> | |||
</tr> | |||
{% endfor %} | |||
{% if some_results_are_hidden %} | |||
<tr class="datagrid-row-empty"> | |||
<td class="text-center" colspan="{{ entities|first.fields|length + 1 }}"> | |||
<span class="datagrid-row-empty-message"><i | |||
class="fa fa-lock mr-1"></i> {{ 'datagrid.hidden_results'|trans({}, 'EasyAdminBundle') }}</span> | |||
</td> | |||
</tr> | |||
{% endif %} | |||
{% endblock table_body %} | |||
</tbody> | |||
</table> | |||
<div style="display: none;"> | |||
{{ form_row(sortable_form.entities) }} | |||
{{ form_rest(sortable_form) }} | |||
</div> | |||
</div> | |||
{% endblock %} | |||
{% endembed %} | |||
{% embed '@LcSov/adminlte/embed/card.html.twig' %} | |||
{% block footer %} | |||
<div class="form-actions"> | |||
<div class="button-action"> | |||
{# <a class=" {{ _action.css_class|default('') }}" href="{{ path('easyadmin', _request_parameters|merge({ action: _action.name })) }}" target="{{ _action.target }}"> | |||
{{ _action.label is defined and not _action.label is empty ? _action.label|trans(_trans_parameters) }} | |||
</a> #} | |||
<button type="submit" class="btn btn-primary float-right"> Sauvegarder</button> | |||
</div> | |||
</div> | |||
{% endblock %} | |||
{% endembed %} | |||
{{ form_end(sortable_form) }} | |||
{% endblock main %} | |||