pixassociates/sortable-behavior-bundle
Installation:
composer require pixassociates/sortable-behavior-bundle
Enable the bundle in app/AppKernel.php:
new Pix\SortableBehaviorBundle\PixSortableBehaviorBundle(),
Basic Configuration:
Add to app/config/config.yml:
pix_sortable_behavior:
db_driver: orm # or 'mongodb'
position_field:
default: sort
entities:
AppBundle\Entity\YourEntity: position
Entity Setup:
Add a position field to your entity (e.g., YourEntity):
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $position;
Admin Integration:
Extend SonataAdminBundle and configure the list fields:
protected function configureListFields(ListMapper $listMapper) {
$listMapper
->add('_action', 'actions', [
'actions' => [
'move' => ['template' => 'PixSortableBehaviorBundle:Default:_sort.html.twig']
]
]);
}
First Use Case: Clear cache and visit your admin listing. The default up/down arrows will appear next to each row, allowing manual sorting.
Manual Sorting (Default):
_sort.html.twig template for up/down buttons.Drag-and-Drop Sorting:
move action template in your admin class:
protected function configureListFields(ListMapper $listMapper) {
$listMapper
->add('_action', null, [
'actions' => [
'move' => [
'template' => 'PixSortableBehaviorBundle:Default:_sort_drag_drop.html.twig'
]
]
]);
}
base.html.twig for includes).Grouped Sorting:
sortable_groups in config.yml to enable sorting by groups:
pix_sortable_behavior:
sortable_groups:
entities:
AppBundle\Entity\Baz: [group_field_name]
AppBundle:Admin:_sort_grouped.html.twig).Custom Position Field:
position field per entity:
pix_sortable_behavior:
position_field:
entities:
AppBundle\Entity\CustomEntity: custom_sort_order
Doctrine Events:
Listen for preUpdate/prePersist to auto-generate position values if needed:
$entity->setPosition($entity->getPosition() ?: $this->getMaxPosition() + 1);
Batch Updates:
Use Doctrine’s createQueryBuilder() to update positions in bulk when reordering multiple items:
$qb = $em->createQueryBuilder()
->update('AppBundle:YourEntity', 'e')
->set('e.position', ':position')
->where('e.id IN (:ids)')
->setParameter('ids', $ids)
->setParameter('position', $position);
AJAX Handling:
Extend the move action in your admin controller to handle AJAX requests:
public function moveAction(Request $request) {
$response = parent::moveAction($request);
return $this->render('AppBundle:Admin:_sort_ajax.html.twig', [
'admin' => $this,
'response' => $response
]);
}
Template Overrides:
Copy templates from vendor/pixassociates/sortable-behavior-bundle/Resources/views/Default/ to app/Resources/PixSortableBehaviorBundle/views/Default/ for customization.
Cache Issues:
php bin/console cache:clear
_action field is rendered in the list template.Position Field Conflicts:
position field is nullable (nullable=true) if you’re using auto-increment logic.sort, order, position).Drag-and-Drop Dependencies:
base.html.twig:
{{ sonata_block_render_event('sonata.admin.extend.javascript', { 'group': 'sonata_admin' }) }}
Or manually include:
{{ sonata_jqueryui_bundle_jqueryui() }}
MongoDB Quirks:
position field is indexed for performance:
/**
* @MongoDB\Index(keys={"position"="asc"})
*/
*/
Sonata Admin Version Mismatch:
ListMapper changes).CSRF Token Errors:
move action may fail with CSRF errors if the route is not properly configured. Extend the route in your admin class:
protected function configureRoutes(RouteCollection $collection) {
$collection->add('move', $this->getRouterIdParameter() . '/move');
}
Check Database:
position field is being updated:
SELECT id, position FROM your_entity ORDER BY position;
Log AJAX Requests:
// config_dev.yml
framework:
profiler: { only_exceptions: false }
Template Debugging:
_sort.html.twig and add debug output:
<pre>{{ dump(admin) }}</pre>
Custom Sort Logic:
move action in your admin controller to implement custom sorting logic:
public function moveAction(Request $request) {
$id = $request->request->get('id');
$direction = $request->request->get('direction');
$entity = $this->getModelManager()->find($this->getClass(), $id);
if ($direction === 'up') {
$this->swapPositions($entity, 'down');
} else {
$this->swapPositions($entity, 'up');
}
return $this->render('PixSortableBehaviorBundle:Default:_sort.html.twig', [
'admin' => $this,
'object' => $entity
]);
}
private function swapPositions($entity, $direction) {
$em = $this->getModelManager()->getEntityManager();
$repo = $em->getRepository($this->getClass());
if ($direction === 'up') {
$currentPos = $entity->getPosition();
$entity->setPosition($currentPos - 1);
$sibling = $repo->findOneBy(['position' => $currentPos - 1]);
$sibling->setPosition($currentPos);
} else {
// 'down' logic
}
$em->flush();
}
Event Listeners:
sonata.admin.action.move events to add pre/post-processing:
// services.yml
services:
app.sortable_listener:
class: AppBundle\EventListener\SortableListener
tags:
- { name: kernel.event_listener, event: sonata.admin.action.move, method: onMove }
Custom Templates:
_sort_drag_drop.html.twig to support multi-item drag..dragging).API Integration:
move action to return JSON:
public function moveAction(Request $request) {
// ... existing logic ...
return new JsonResponse(['success' => true, 'position' => $entity->getPosition()]);
}
How can I help you explore Laravel packages today?