-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 48c56e8
Showing
13 changed files
with
527 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
namespace Sherlockode\SonataSortableBundle\Controller; | ||
|
||
use Sherlockode\SonataSortableBundle\Manager\SortableManager; | ||
use Sonata\AdminBundle\Controller\CRUDController; | ||
use Symfony\Component\HttpFoundation\RedirectResponse; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Contracts\Translation\TranslatorInterface; | ||
|
||
class SortableController extends CRUDController | ||
{ | ||
public function __construct( | ||
private readonly TranslatorInterface $translator, | ||
private readonly SortableManager $sortableManager | ||
) { | ||
} | ||
|
||
/** | ||
* @param string $direction | ||
* | ||
* @return Response | ||
*/ | ||
public function moveAction(string $direction): Response | ||
{ | ||
if (!$this->admin->isGranted('EDIT')) { | ||
$this->addFlash('danger', $this->translator->trans( | ||
'You are not authorized to perform this action', | ||
[], | ||
'sherlockode_sonata_sortable' | ||
)); | ||
|
||
return new RedirectResponse($this->admin->generateUrl( | ||
'list', | ||
['filter' => $this->admin->getFilterParameters()] | ||
)); | ||
} | ||
|
||
$object = $this->admin->getSubject(); | ||
$newPosition = $this->sortableManager->getPosition($object, 'position', $direction); | ||
$object->setPosition($newPosition); | ||
|
||
$this->admin->update($object); | ||
|
||
return new RedirectResponse($this->admin->generateUrl( | ||
'list', | ||
['filter' => $this->admin->getFilterParameters()] | ||
)); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
DependencyInjection/SherlockodeSonataSortableExtension.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
namespace Sherlockode\SonataSortableBundle\DependencyInjection; | ||
|
||
use Symfony\Component\Config\FileLocator; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Extension\Extension; | ||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; | ||
|
||
class SherlockodeSonataSortableExtension extends Extension | ||
{ | ||
public function load(array $configs, ContainerBuilder $container): void | ||
{ | ||
$loader = new YamlFileLoader( | ||
$container, | ||
new FileLocator(__DIR__.'/../Resources/config') | ||
); | ||
$loader->load('services.yaml'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php | ||
|
||
namespace Sherlockode\SonataSortableBundle\Manager; | ||
|
||
use Doctrine\ORM\EntityManagerInterface; | ||
use Doctrine\ORM\NonUniqueResultException; | ||
use Doctrine\ORM\NoResultException; | ||
use Symfony\Component\PropertyAccess\PropertyAccess; | ||
|
||
class SortableManager | ||
{ | ||
public function __construct(private readonly EntityManagerInterface $em) | ||
{ | ||
} | ||
|
||
/** | ||
* @param object $object | ||
* @param string $positionProperty | ||
* @param string $direction | ||
* | ||
* @return int | ||
*/ | ||
public function getPosition(object $object, string $positionProperty, string $direction): int | ||
{ | ||
$propertyAccessor = PropertyAccess::createPropertyAccessor(); | ||
$currentPosition = $propertyAccessor->getValue($object, $positionProperty); | ||
$lastPosition = $this->getLastPosition(get_class($object), $positionProperty); | ||
|
||
if ('top' === $direction) { | ||
return 0; | ||
} | ||
|
||
if ('bottom' === $direction && $currentPosition < $lastPosition) { | ||
return $lastPosition; | ||
} | ||
|
||
if ('up' === $direction && $currentPosition > 0) { | ||
return $currentPosition - 1; | ||
} | ||
|
||
if ('down' === $direction && $currentPosition < $lastPosition) { | ||
return $currentPosition + 1; | ||
} | ||
|
||
return $currentPosition; | ||
} | ||
|
||
/** | ||
* @param string $className | ||
* @param string $positionProperty | ||
* | ||
* @return int | ||
*/ | ||
public function getFirstPosition(string $className, string $positionProperty): int | ||
{ | ||
try { | ||
$position = $this->em->createQueryBuilder() | ||
->select(sprintf('MIN(o.%s)', $positionProperty)) | ||
->from($className, 'o') | ||
->getQuery() | ||
->getSingleScalarResult(); | ||
} catch (NoResultException | NonUniqueResultException $e) { | ||
return 0; | ||
} | ||
|
||
return $position ?? 0; | ||
} | ||
|
||
/** | ||
* @param string $className | ||
* @param string $positionProperty | ||
* | ||
* @return int | ||
*/ | ||
public function getLastPosition(string $className, string $positionProperty): int | ||
{ | ||
try { | ||
$position = $this->em->createQueryBuilder() | ||
->select(sprintf('MAX(o.%s)', $positionProperty)) | ||
->from($className, 'o') | ||
->getQuery() | ||
->getSingleScalarResult(); | ||
} catch (NoResultException | NonUniqueResultException $e) { | ||
return 0; | ||
} | ||
|
||
return $position ?? 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
Installation | ||
============ | ||
|
||
Make sure Composer is installed globally, as explained in the | ||
[installation chapter](https://getcomposer.org/doc/00-intro.md) | ||
of the Composer documentation. | ||
|
||
Applications that use Symfony Flex | ||
---------------------------------- | ||
|
||
Open a command console, enter your project directory and execute: | ||
|
||
```console | ||
$ composer require sherlockode/sonata-sortable-bundle | ||
``` | ||
|
||
Applications that don't use Symfony Flex | ||
---------------------------------------- | ||
|
||
### Step 1: Download the Bundle | ||
|
||
Open a command console, enter your project directory and execute the | ||
following command to download the latest stable version of this bundle: | ||
|
||
```console | ||
$ composer require sherlockode/sonata-sortable-bundle | ||
``` | ||
|
||
### Step 2: Enable the Bundle | ||
|
||
Then, enable the bundle by adding it to the list of registered bundles | ||
in the `config/bundles.php` file of your project: | ||
|
||
```php | ||
// config/bundles.php | ||
|
||
return [ | ||
// ... | ||
Sherlockode\SonataSortableBundle\SherlockodeSonataSortableBundle::class => ['all' => true], | ||
]; | ||
``` | ||
|
||
Sortable behavior in admin listing | ||
================================== | ||
|
||
Pre-requisites | ||
-------------- | ||
|
||
You need to have a working Sonata admin and to have installed and configured `gedmo/doctrine-extensions` (check `stof/doctrine-extensions-bundle` for easy integration). | ||
|
||
The recipe | ||
---------- | ||
|
||
First of all, add a position property in your entity | ||
|
||
```php | ||
// src/Entity/Category.php | ||
|
||
namespace App\Entity; | ||
|
||
use Doctrine\ORM\Mapping as ORM; | ||
use Gedmo\Mapping\Annotation as Gedmo; | ||
|
||
/** | ||
* @ORM\Table(name="category") | ||
* @ORM\Entity | ||
*/ | ||
class Category | ||
{ | ||
/** | ||
* @var int | ||
* | ||
* @Gedmo\SortablePosition | ||
* @ORM\Column(name="position", type="integer") | ||
*/ | ||
private $position; | ||
} | ||
``` | ||
|
||
To change this position with Sonata, we need to add a new route in our admin: | ||
|
||
```php | ||
// src/Admin/CategoryAdmin.php | ||
|
||
namespace App\Admin; | ||
|
||
use Sonata\AdminBundle\Admin\AbstractAdmin; | ||
use Sonata\AdminBundle\Route\RouteCollectionInterface; | ||
|
||
class CategoryAdmin extends AbstractAdmin | ||
{ | ||
// ... | ||
|
||
/** | ||
* @param RouteCollectionInterface $collection | ||
* | ||
* @return void | ||
*/ | ||
protected function configureRoutes(RouteCollectionInterface $collection): void | ||
{ | ||
$collection->add('move', $this->getRouterIdParameter().'/move/{direction}'); | ||
} | ||
} | ||
``` | ||
|
||
Update the admin configuration to use our custom controller | ||
|
||
```yaml | ||
services: | ||
admin.category: | ||
class: 'App\Admin\CategoryAdmin' | ||
arguments: [ ~, App\Entity\Category, 'Sherlockode\SonataSortableBundle\Controller\SortableController' ] | ||
tags: | ||
- { name: sonata.admin, manager_type: orm, label: Categories } | ||
``` | ||
Then, add default sort by position in the admin: | ||
```php | ||
// src/Admin/CategoryAdmin.php | ||
|
||
namespace App\Admin; | ||
|
||
use Sonata\AdminBundle\Admin\AbstractAdmin; | ||
use Sonata\AdminBundle\Datagrid\DatagridInterface; | ||
|
||
class CategoryAdmin extends AbstractAdmin | ||
{ | ||
// ... | ||
|
||
protected function configureDefaultSortValues(array &$sortValues): void | ||
{ | ||
$sortValues[DatagridInterface::PAGE] = 1; | ||
$sortValues[DatagridInterface::SORT_ORDER] = 'ASC'; | ||
$sortValues[DatagridInterface::SORT_BY] = 'position'; | ||
} | ||
} | ||
``` | ||
|
||
Add controls to allow the user to change the position of items: | ||
|
||
```php | ||
// src/Admin/CategoryAdmin.php | ||
|
||
namespace App\Admin; | ||
|
||
use Sonata\AdminBundle\Admin\AbstractAdmin; | ||
use Sonata\AdminBundle\Datagrid\ListMapper; | ||
|
||
class CategoryAdmin extends AbstractAdmin | ||
{ | ||
// ... | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
protected function configureListFields(ListMapper $list): void | ||
{ | ||
$list | ||
// your other fields | ||
->add(ListMapper::NAME_ACTIONS, null, [ | ||
'actions' => [ | ||
'move' => [ | ||
'template' => '@SherlockodeSonataSortable/list__action_move.html.twig', | ||
], | ||
], | ||
]) | ||
; | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
sherlockode_sonata_sortable_move_item: | ||
path: /{id}/move/{direction} | ||
controller: Sherlockode\SonataSortableBundle\Controller\SortableController::move | ||
methods: GET | ||
requirements: | ||
id: '\d+' | ||
direction: up|down|top|bottom |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
services: | ||
_defaults: | ||
autowire: true | ||
autoconfigure: true | ||
|
||
# Controller | ||
Sherlockode\SonataSortableBundle\Controller\SortableController: | ||
tags: ['controller.service_arguments'] | ||
arguments: | ||
$sortableManager: '@sherlockode.sonata_sortable.sortable_manager' | ||
|
||
# Manager | ||
sherlockode.sonata_sortable.sortable_manager: | ||
class: Sherlockode\SonataSortableBundle\Manager\SortableManager | ||
arguments: | ||
$em: '@doctrine.orm.entity_manager' | ||
|
||
# Twig extension | ||
Sherlockode\SonataSortableBundle\Twig\SortableExtension: | ||
tags: ['twig.extension'] | ||
Sherlockode\SonataSortableBundle\Twig\SortableRuntime: | ||
arguments: | ||
$sortableManager: '@sherlockode.sonata_sortable.sortable_manager' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?xml version="1.0"?> | ||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> | ||
<file source-language="en" target-language="en" datatype="plaintext" original="file.ext"> | ||
<body> | ||
<trans-unit id="action_not_allowed"> | ||
<source>You are not authorized to perform this action</source> | ||
<target>You are not authorized to perform this action</target> | ||
</trans-unit> | ||
<trans-unit id="move_down"> | ||
<source>Move down</source> | ||
<target>Move down</target> | ||
</trans-unit> | ||
<trans-unit id="move_to_first_position"> | ||
<source>Move to first position</source> | ||
<target>Move to first position</target> | ||
</trans-unit> | ||
<trans-unit id="move_to_last_position"> | ||
<source>Move to last position</source> | ||
<target>Move to last position</target> | ||
</trans-unit> | ||
<trans-unit id="move_up"> | ||
<source>Move up</source> | ||
<target>Move up</target> | ||
</trans-unit> | ||
</body> | ||
</file> | ||
</xliff> |
Oops, something went wrong.