Parcourir la source

Add sort option

Thomas Rabaix il y a 14 ans
Parent
commit
6509540276

+ 27 - 15
Admin/Admin.php

@@ -135,7 +135,7 @@ abstract class Admin implements AdminInterface
     protected $translationDomain = 'AdminBundle';
 
     /**
-     * options to set to the form (ie, validation_groups)
+     * Options to set to the form (ie, validation_groups)
      *
      * @var array
      */
@@ -143,6 +143,15 @@ abstract class Admin implements AdminInterface
         'validation_groups' => 'Default'
     );
 
+    /**
+     * Default values to the datagrid
+     *
+     * @var array
+     */
+    protected $datagridValues = array(
+        '_page'       => 1,
+    );
+
     /**
      * The code related to the admin
      *
@@ -323,6 +332,7 @@ abstract class Admin implements AdminInterface
      */
     protected function configureListFields(ListMapper $list)
     {
+
     }
 
     /**
@@ -331,6 +341,7 @@ abstract class Admin implements AdminInterface
      */
     protected function configureDatagridFilters(DatagridMapper $filter)
     {
+
     }
 
     /**
@@ -349,6 +360,7 @@ abstract class Admin implements AdminInterface
     }
 
     /**
+     * @param string $code
      * @param string $class
      * @param string $baseControllerName
      */
@@ -361,7 +373,6 @@ abstract class Admin implements AdminInterface
 
     public function configure()
     {
-
         $this->uniqid = uniqid();
 
         if (!$this->classnameLabel) {
@@ -429,7 +440,6 @@ abstract class Admin implements AdminInterface
      */
     protected function buildListFieldDescriptions()
     {
-
         if ($this->loaded['list_fields']) {
             return;
         }
@@ -445,9 +455,10 @@ abstract class Admin implements AdminInterface
 
         if (!isset($this->listFieldDescriptions['_batch'])) {
             $fieldDescription = $this->modelManager->getNewFieldDescriptionInstance($this->getClass(), 'batch', array(
-                'label' => 'batch',
-                'code'  => '_batch',
-                'type'  => 'batch',
+                'label'    => 'batch',
+                'code'     => '_batch',
+                'type'     => 'batch',
+                'sortable' => false
             ));
 
             $fieldDescription->setTemplate('SonataAdminBundle:CRUD:list__batch.html.twig');
@@ -464,7 +475,6 @@ abstract class Admin implements AdminInterface
      */
     public function buildFilterFieldDescriptions()
     {
-
         if ($this->loaded['filter_fields']) {
             return;
         }
@@ -502,7 +512,6 @@ abstract class Admin implements AdminInterface
      */
     protected function buildFormFieldDescriptions()
     {
-
         if ($this->loaded['form_fields']) {
             return;
         }
@@ -956,7 +965,7 @@ abstract class Admin implements AdminInterface
 
         foreach ($this->getListFieldDescriptions() as $fieldDescription) {
 
-            // do not add field already set in the configureFormField method
+            // do not add field already set in the configureListFields method
             if ($mapper->has($fieldDescription->getFieldName())) {
                 continue;
             }
@@ -975,19 +984,25 @@ abstract class Admin implements AdminInterface
     public function getDatagrid()
     {
         if (!$this->datagrid) {
-            // retrieve the parameters
-            $parameters = $this->request->query->all();
+            // build the values array
+            $parameters = array_merge(
+                $this->getModelManager()->getDefaultSortValues($this->getClass()),
+                $this->datagridValues,
+                $this->request->query->all()
+            );
 
+            // always force the parent value
             if ($this->isChild() && $this->getParentAssociationMapping()) {
                 $parameters[$this->getParentAssociationMapping()] = $this->request->get($this->getParent()->getIdParameter());
             }
 
-            // build the datagrid filter
+            // initialize the datagrid
             $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $parameters);
             $this->datagrid->getPager()->setMaxPerPage($this->maxPerPage);
 
             $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
 
+            // build the datagrid filter
             $this->buildFilterFieldDescriptions();
             $this->configureDatagridFilters($mapper);
 
@@ -1236,7 +1251,6 @@ abstract class Admin implements AdminInterface
      */
     public function getListFieldDescriptions()
     {
-
         $this->buildListFieldDescriptions();
 
         return $this->listFieldDescriptions;
@@ -1250,7 +1264,6 @@ abstract class Admin implements AdminInterface
      */
     public function getListFieldDescription($name)
     {
-
         return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
     }
 
@@ -1298,7 +1311,6 @@ abstract class Admin implements AdminInterface
      */
     public function getFilterFieldDescription($name)
     {
-
         return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
     }
 

+ 14 - 0
Admin/AdminInterface.php

@@ -86,4 +86,18 @@ interface AdminInterface
      * @return void
      */
     function attachAdminClass(FieldDescriptionInterface $fieldDescription);
+
+    /**
+     * @abstract
+     * @return \Sonata\AdminBundle\Datagrid\DatagridInterface
+     */
+    function getDatagrid();
+
+    /**
+     * @abstract
+     * @param string $name
+     * @param array $parameters
+     * @return void
+     */
+    function generateUrl($name, array $parameters = array());
 }

+ 6 - 1
Builder/ORM/DatagridBuilder.php

@@ -17,6 +17,7 @@ use Sonata\AdminBundle\Admin\AdminInterface;
 use Sonata\AdminBundle\Datagrid\DatagridInterface;
 use Sonata\AdminBundle\Datagrid\Datagrid;
 use Sonata\AdminBundle\Datagrid\ORM\Pager;
+use Sonata\AdminBundle\Datagrid\ORM\ProxyQuery;
 use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
 
 use Doctrine\ORM\Mapping\ClassMetadataInfo;
@@ -192,8 +193,12 @@ class DatagridBuilder implements DatagridBuilderInterface
      */
     public function getBaseDatagrid(AdminInterface $admin, array $values = array())
     {
+        $queryBuilder = $admin->getModelManager()->createQuery($admin->getClass());
+
+        $query = new ProxyQuery($queryBuilder);
+
         return new Datagrid(
-            $admin->getModelManager()->createQuery($admin->getClass()),
+            $query,
             $admin->getList(),
             new Pager,
             $values

+ 7 - 4
Builder/ORM/ListBuilder.php

@@ -41,27 +41,30 @@ class ListBuilder implements ListBuilderInterface
      */
     public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription, array $options = array())
     {
-        if ($fieldDescription->getName() == '_action')
-        {
+        if ($fieldDescription->getName() == '_action') {
           $this->buildActionFieldDescription($fieldDescription);
         }
 
         $fieldDescription->mergeOptions($options);
         $fieldDescription->setAdmin($admin);
 
-        if($admin->getModelManager()->hasMetadata($admin->getClass()))
-        {
+        if($admin->getModelManager()->hasMetadata($admin->getClass())) {
             $metadata = $admin->getModelManager()->getMetadata($admin->getClass());
 
             // set the default field mapping
             if (isset($metadata->fieldMappings[$fieldDescription->getName()])) {
                 $fieldDescription->setFieldMapping($metadata->fieldMappings[$fieldDescription->getName()]);
+                if ($fieldDescription->getOption('sortable') !== false) {
+                    $fieldDescription->setOption('sortable', $fieldDescription->getOption('sortable', $fieldDescription->getName()));
+                }
             }
 
             // set the default association mapping
             if (isset($metadata->associationMappings[$fieldDescription->getName()])) {
                 $fieldDescription->setAssociationMapping($metadata->associationMappings[$fieldDescription->getName()]);
             }
+
+            $fieldDescription->setOption('_sort_order', $fieldDescription->getOption('_sort_order', 'ASC'));
         }
 
         if (!$fieldDescription->getType()) {

+ 1 - 4
Controller/CRUDController.php

@@ -15,17 +15,14 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\Form\Form;
-
 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 
 class CRUDController extends Controller
 {
-
     /**
      * The related Admin class
      *
-     * @var Admin
+     * @var \Sonata\AdminBundle\Admin\AdminInterface
      */
     protected $admin;
 

+ 20 - 1
Datagrid/Datagrid.php

@@ -12,6 +12,7 @@
 namespace Sonata\AdminBundle\Datagrid;
 
 use Sonata\AdminBundle\Datagrid\PagerInterface;
+use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
 use Sonata\AdminBundle\Filter\FilterInterface;
 
 class Datagrid implements DatagridInterface
@@ -31,7 +32,9 @@ class Datagrid implements DatagridInterface
 
     protected $bound = false;
 
-    public function __construct($query, ListCollection $columns, PagerInterface $pager, array $values = array())
+    protected $query;
+
+    public function __construct(ProxyQueryInterface $query, ListCollection $columns, PagerInterface $pager, array $values = array())
     {
         $this->pager    = $pager;
         $this->query    = $query;
@@ -39,11 +42,15 @@ class Datagrid implements DatagridInterface
         $this->columns  = $columns;
     }
 
+    /**
+     * @return \Sonata\AdminBundle\Datagrid\PagerInterface
+     */
     public function getPager()
     {
         return $this->pager;
     }
 
+
     public function getResults()
     {
         $this->buildPager();
@@ -64,6 +71,9 @@ class Datagrid implements DatagridInterface
             );
         }
 
+        $this->query->setSortBy(isset($this->values['_sort_by']) ? $this->values['_sort_by'] : null);
+        $this->query->setSortOrder(isset($this->values['_sort_order']) ? $this->values['_sort_order'] : null);
+
         $this->pager->setPage(isset($this->values['_page']) ? $this->values['_page'] : 1);
         $this->pager->setQuery($this->query);
         $this->pager->init();
@@ -71,6 +81,10 @@ class Datagrid implements DatagridInterface
         $this->bound = true;
     }
 
+    /**
+     * @param \Sonata\AdminBundle\Filter\FilterInterface $filter
+     * @return \Sonata\AdminBundle\Filter\FilterInterface
+     */
     public function addFilter(FilterInterface $filter)
     {
         return $this->filters[$filter->getName()] = $filter;
@@ -90,4 +104,9 @@ class Datagrid implements DatagridInterface
     {
         return $this->columns;
     }
+
+    public function getQuery()
+    {
+        return $this->query;
+    }
 }

+ 35 - 0
Datagrid/DatagridInterface.php

@@ -15,17 +15,52 @@ use Sonata\AdminBundle\Filter\FilterInterface;
 interface DatagridInterface
 {
 
+    /**
+     * @abstract
+     * @return \Sonata\AdminBundle\Datagrid\PagerInterface
+     */
     function getPager();
 
+    /**
+     * @abstract
+     * @return \Sonata\AdminBundle\Datagrid\ProxyQueryInterface
+     */
+    function getQuery();
+
+    /**
+     * @abstract
+     * @return array
+     */
     function getResults();
 
+    /**
+     * @abstract
+     * @return void
+     */
     function buildPager();
 
+    /**
+     * @abstract
+     * @param \Sonata\AdminBundle\Filter\FilterInterface $filter
+     * @return \Sonata\AdminBundle\Filter\FilterInterface
+     */
     function addFilter(FilterInterface $filter);
 
+    /**
+     * @abstract
+     * @return array
+     */
     function getFilters();
 
+    /**
+     * @abstract
+     * @return array
+     */
     function getValues();
 
+    /**
+     * @abstract
+     * @return array
+     */
     function getColumns();
 }

+ 0 - 1
Datagrid/ListCollection.php

@@ -10,7 +10,6 @@
  */
 namespace Sonata\AdminBundle\Datagrid;
 
-
 use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
 
 class ListCollection

+ 1 - 1
Datagrid/ListMapper.php

@@ -53,7 +53,7 @@ class ListMapper
 
             $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance(
                 $this->admin->getClass(),
-                $field->getKey(),
+                $name,
                 $fieldDescriptionOptions
             );
 

+ 11 - 19
Datagrid/ORM/Pager.php

@@ -19,10 +19,10 @@ use Doctrine\ORM\QueryBuilder;
  * Doctrine pager class.
  *
  * @author     Jonathan H. Wage <jonwage@gmail.com>
- * @version    SVN: $Id: sfDoctrinePager.class.php 28897 2010-03-30 20:30:24Z Jonathan.Wage $
  */
 class Pager extends BasePager
 {
+    protected $queryBuilder     = null;
 
     /**
      * Returns a query for counting the total results.
@@ -37,11 +37,12 @@ class Pager extends BasePager
             $countQuery->setParameters($this->getParameters());
         }
 
-        $countQuery->select(sprintf('count(%s.%s) as cnt', $countQuery->getRootAlias(), $this->getCountColumn()));
+        $countQuery->select(sprintf('DISTINCT count(%s.%s) as cnt', $countQuery->getRootAlias(), $this->getCountColumn()));
 
-        return $countQuery->getQuery()->getSingleScalarResult();
+        return $countQuery->getSingleScalarResult();
     }
 
+
     /**
      * Get all the results for the pager instance
      *
@@ -50,21 +51,16 @@ class Pager extends BasePager
      */
     public function getResults($hydrationMode = Query::HYDRATE_OBJECT)
     {
-        return $this->getQuery()->getQuery()->execute(array(), $hydrationMode);
+        return $this->getQuery()->execute(array(), $hydrationMode);
     }
 
     /**
      * Get the query for the pager.
      *
-     * @return Doctrine\ORM\Query
+     * @return \AdminBundle\Datagrid\ORM\ProxyQuery
      */
-
     public function getQuery()
     {
-        if (!$this->query) {
-            $this->query = $this->getQuery()->getQuery();
-        }
-
         return $this->query;
     }
 
@@ -74,14 +70,11 @@ class Pager extends BasePager
 
         $this->setNbResults($this->computeNbResult());
 
-        $query = $this->getQuery();
-
-        $query
-            ->setFirstResult(0)
-            ->setMaxResults(0);
+        $this->getQuery()->setFirstResult(0);
+        $this->getQuery()->setMaxResults(0);
 
         if (count($this->getParameters()) > 0) {
-            $query->setParameters($this->getParameters());
+            $this->getQuery()->setParameters($this->getParameters());
         }
 
         if (0 == $this->getPage() || 0 == $this->getMaxPerPage() || 0 == $this->getNbResults()) {
@@ -91,9 +84,8 @@ class Pager extends BasePager
 
             $this->setLastPage(ceil($this->getNbResults() / $this->getMaxPerPage()));
 
-            $query
-                ->setFirstResult($offset)
-                ->setMaxResults($this->getMaxPerPage());
+            $this->getQuery()->setFirstResult($offset);
+            $this->getQuery()->setMaxResults($this->getMaxPerPage());
         }
     }
 }

+ 82 - 0
Datagrid/ORM/ProxyQuery.php

@@ -0,0 +1,82 @@
+<?php
+/*
+ * This file is part of the symfony package.
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ * (c) Jonathan H. Wage <jonwage@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Datagrid\ORM;
+
+use Doctrine\ORM\QueryBuilder;
+use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
+
+/**
+ * This class try to unify the query usage with Doctrine
+ */
+class ProxyQuery implements ProxyQueryInterface
+{
+    protected $queryBuilder;
+
+    protected $sortBy;
+
+    protected $sortOrder;
+
+    public function __construct(QueryBuilder $queryBuilder)
+    {
+        $this->queryBuilder = $queryBuilder;
+    }
+
+    public function execute(array $params = array(), $hydrationMode = null)
+    {
+        // todo : check how doctrine behave, potential SQL injection here ...
+        if ($this->getSortBy()) {
+            $sortBy = $this->getSortBy();
+            if (strpos($sortBy, '.') === false) { // add the current alias
+                $sortBy = $this->queryBuilder->getRootAlias().'.'.$sortBy;
+            }
+            $this->queryBuilder->orderBy($sortBy, $this->getSortOrder());
+        }
+
+        return $this->queryBuilder->getQuery()->execute($params, $hydrationMode);
+    }
+
+    public function __call($name, $args)
+    {
+        return call_user_func_array(array($this->queryBuilder, $name), $args);
+    }
+
+    public function setSortBy($sortBy)
+    {
+        $this->sortBy = $sortBy;
+    }
+
+    public function getSortBy()
+    {
+        return $this->sortBy;
+    }
+
+    public function setSortOrder($sortOrder)
+    {
+        $this->sortOrder = $sortOrder;
+    }
+
+    public function getSortOrder()
+    {
+        return $this->sortOrder;
+    }
+
+    public function getSingleScalarResult()
+    {
+        $query = $this->queryBuilder->getQuery();
+
+        return $query->getSingleScalarResult();
+    }
+
+    public function __clone()
+    {
+        $this->queryBuilder = clone $this->queryBuilder;
+    }
+}

+ 33 - 0
Datagrid/ProxyQueryInterface.php

@@ -0,0 +1,33 @@
+<?php
+/*
+ * This file is part of the symfony package.
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ * (c) Jonathan H. Wage <jonwage@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Datagrid;
+
+
+/**
+ * Interface used by the Datagrid to build the query
+ */
+interface ProxyQueryInterface
+{
+
+    function execute(array $params = array(), $hydrationMode = null);
+
+    function __call($name, $args);
+
+    function setSortBy($sortBy);
+
+    function getSortBy();
+
+    function setSortOrder($sortOrder);
+
+    function getSortOrder();
+
+    function getSingleScalarResult();
+}

+ 9 - 2
Filter/FilterInterface.php

@@ -14,7 +14,7 @@ namespace Sonata\AdminBundle\Filter;
 interface FilterInterface
 {
     /**
-     * apply the filter to the QueryBuilder instance
+     * Apply the filter to the QueryBuilder instance
      *
      * @abstract
      * @param  $queryBuilder
@@ -26,10 +26,17 @@ interface FilterInterface
     function filter($queryBuilder, $alias, $field, $value);
 
     /**
-     * get the related form field filter
+     * Get the related form field filter
      *
      * @abstract
      * @return Field
      */
     function getFormField();
+
+    /**
+     * Returns the filter name
+     * @abstract
+     * @return void
+     */
+    function getName();
 }

+ 19 - 0
Model/ModelManagerInterface.php

@@ -11,6 +11,9 @@
 
 namespace Sonata\AdminBundle\Model;
 
+use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
+use Sonata\AdminBundle\Datagrid\DatagridInterface;
+
 interface ModelManagerInterface
 {
 
@@ -101,4 +104,20 @@ interface ModelManagerInterface
      * @return void
      */
     function getModelInstance($class);
+
+
+    /**
+     * Returns the parameters used in the columns header
+     *
+     * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
+     * @param \Sonata\AdminBundle\Datagrid\DatagridInterface $datagrid
+     * @return string
+     */
+    function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid);
+
+    /**
+     * @param sring $class
+     * @return array
+     */
+    function getDefaultSortValues($class);
 }

+ 39 - 1
Model/ORM/ModelManager.php

@@ -13,6 +13,8 @@ namespace Sonata\AdminBundle\Model\ORM;
 
 use Sonata\AdminBundle\Model\ModelManagerInterface;
 use Sonata\AdminBundle\Admin\ORM\FieldDescription;
+use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
+use Sonata\AdminBundle\Datagrid\DatagridInterface;
 use Doctrine\ORM\EntityManager;
 
 
@@ -144,7 +146,7 @@ class ModelManager implements ModelManagerInterface
      */
     public function getEntityIdentifier($class)
     {
-        return $this->getEntityManager()->getUnitOfWork()->getEntityIdentifier($class);
+        return $this->getMetadata($class)->identifier;
     }
 
     /**
@@ -180,4 +182,40 @@ class ModelManager implements ModelManagerInterface
         return new $class;
     }
 
+    /**
+     * Returns the parameters used in the columns header
+     *
+     * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
+     * @param \Sonata\AdminBundle\Datagrid\DatagridInterface $datagrid
+     * @return string
+     */
+    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
+    {
+        $values = $datagrid->getValues();
+
+        if ($fieldDescription->getOption('sortable') == $values['_sort_by']) {
+            if ($values['_sort_order'] == 'ASC') {
+                $values['_sort_order'] = 'DESC';
+            } else {
+                $values['_sort_order'] = 'ASC';
+            }
+        } else {
+            $values['_sort_order']  = 'ASC';
+            $values['_sort_by']     = $fieldDescription->getOption('sortable');
+        }
+
+        return $values;
+    }
+
+    /**
+     * @param sring $class
+     * @return array
+     */
+    public function getDefaultSortValues($class)
+    {
+        return array(
+            '_sort_order' => 'ASC',
+            '_sort_by'    => implode(',', $this->getEntityIdentifier($class))
+        );
+    }
 }

+ 29 - 0
Resources/public/css/layout.css

@@ -55,3 +55,32 @@ div.sonata-ba-modal-edit-one-to-one th.sonata-ba-list-field-header-batch
 div.sonata-ba-modal-edit-one-to-one div.sonata-ba-list-actions {
     display: none;
 }
+
+th.sonata-ba-list-field-header-order-desc:hover {
+    background: url(../famfamfam/bullet_arrow_up.png) no-repeat center left;
+}
+
+th.sonata-ba-list-field-header-order-asc:hover {
+    background: url(../famfamfam/bullet_arrow_down.png) no-repeat center left;
+}
+
+th.sonata-ba-list-field-header-order-desc,
+th.sonata-ba-list-field-header-order-asc {
+    padding-left: 15px;
+}
+
+th.sonata-ba-list-field-header-order-desc.sonata-ba-list-field-order-active {
+    background: url(../famfamfam/bullet_arrow_up.png) no-repeat center left;
+}
+
+th.sonata-ba-list-field-header-order-asc.sonata-ba-list-field-order-active {
+    background: url(../famfamfam/bullet_arrow_down.png) no-repeat center left;
+}
+
+th.sonata-ba-list-field-header-order-desc.sonata-ba-list-field-order-active:hover {
+    background: url(../famfamfam/bullet_arrow_down.png) no-repeat center left;
+}
+
+th.sonata-ba-list-field-header-order-asc.sonata-ba-list-field-order-active:hover {
+    background: url(../famfamfam/bullet_arrow_up.png) no-repeat center left;
+}

+ 18 - 3
Resources/views/CRUD/base_list.html.twig

@@ -26,20 +26,35 @@ file that was distributed with this source code.
         <form action="{{ admin.generateUrl('batch') }}" method="POST" >
             <table>
                 {% block table_header %}
-                    <tr>
+                    <tr class="sonata-ba-list-field-header">
                         {% for field_description in admin.list.elements %}
                             {% if field_description.getOption('code') == '_batch' %}
                                 <th class="sonata-ba-list-field-header sonata-ba-list-field-header-batch">
                                   <input type="checkbox" id="list_batch_checkbox" />
                                 </th>
                             {% else %}
-                                    {% spaceless %}<th class="sonata-ba-list-field-header sonata-ba-list-field-header-{{ field_description.type}}">
+                                {% set sortable = false %}
+                                {% if field_description.options.sortable is defined and field_description.options.sortable%}
+                                    {% set sortable             = true %}
+                                    {% set current              = admin.datagrid.values._sort_by == field_description.options.sortable %}
+                                    {% set sort_parameters      = admin.modelmanager.sortparameters(field_description, admin.datagrid) %}
+                                    {% set sort_active_class    = current ? 'sonata-ba-list-field-order-active' : '' %}
+                                    {% set sort_by              = current ? admin.datagrid.values._sort_order : field_description.options._sort_order %}
+                                {% endif %}
+
+                                {% 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 sortable %}<a href="{{ admin.generateUrl('list', sort_parameters) }}">{% endif %}
+
                                         {% if field_description.options.name is defined %}
                                             {% trans field_description.options.name from admin.translationdomain %}
                                         {% else %}
                                             {% trans field_description.name from admin.translationdomain %}
                                         {% endif %}
-                                    </th>{% endspaceless %}
+
+                                        {% if sortable %}</a>{% endif %}
+                                    </th>
+                                {% endspaceless %}
                             {% endif %}
                         {% endfor %}
                     </tr>