diff --git a/Admin/Admin.php b/Admin/Admin.php index 81fb3c307d..d22d844643 100644 --- a/Admin/Admin.php +++ b/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 @@ protected function configureFormFields(FormMapper $form) */ protected function configureListFields(ListMapper $list) { + } /** @@ -331,6 +341,7 @@ protected function configureListFields(ListMapper $list) */ protected function configureDatagridFilters(DatagridMapper $filter) { + } /** @@ -349,6 +360,7 @@ public function configureSideMenu(MenuItem $menu, $action, Admin $childAdmin = n } /** + * @param string $code * @param string $class * @param string $baseControllerName */ @@ -361,7 +373,6 @@ public function __construct($code, $class, $baseControllerName) public function configure() { - $this->uniqid = uniqid(); if (!$this->classnameLabel) { @@ -429,7 +440,6 @@ public function postRemove($object) */ protected function buildListFieldDescriptions() { - if ($this->loaded['list_fields']) { return; } @@ -445,9 +455,10 @@ protected function buildListFieldDescriptions() 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 @@ protected function buildListFieldDescriptions() */ public function buildFilterFieldDescriptions() { - if ($this->loaded['filter_fields']) { return; } @@ -502,7 +512,6 @@ public function getParentAssociationMapping() */ protected function buildFormFieldDescriptions() { - if ($this->loaded['form_fields']) { return; } @@ -956,7 +965,7 @@ public function getList(array $options = array()) 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 @@ public function getList(array $options = array()) 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 @@ public function removeFormFieldDescription($name) */ public function getListFieldDescriptions() { - $this->buildListFieldDescriptions(); return $this->listFieldDescriptions; @@ -1250,7 +1264,6 @@ public function getListFieldDescriptions() */ public function getListFieldDescription($name) { - return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null; } @@ -1298,7 +1311,6 @@ public function removeListFieldDescription($name) */ public function getFilterFieldDescription($name) { - return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null; } diff --git a/Admin/AdminInterface.php b/Admin/AdminInterface.php index 0a0b44e2b7..c9e5d98853 100644 --- a/Admin/AdminInterface.php +++ b/Admin/AdminInterface.php @@ -86,4 +86,18 @@ function getClass(); * @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()); } \ No newline at end of file diff --git a/Builder/ORM/DatagridBuilder.php b/Builder/ORM/DatagridBuilder.php index 6cc993deec..3ed60c25a3 100644 --- a/Builder/ORM/DatagridBuilder.php +++ b/Builder/ORM/DatagridBuilder.php @@ -17,6 +17,7 @@ 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 @@ public function addFilter(DatagridInterface $datagrid, FieldDescriptionInterface */ 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 diff --git a/Builder/ORM/ListBuilder.php b/Builder/ORM/ListBuilder.php index 6383a56ee5..cca9aa6a1c 100644 --- a/Builder/ORM/ListBuilder.php +++ b/Builder/ORM/ListBuilder.php @@ -41,27 +41,30 @@ public function addField(ListCollection $list, FieldDescriptionInterface $fieldD */ 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()) { diff --git a/Controller/CRUDController.php b/Controller/CRUDController.php index 89f31fdc5b..02f6b6f3ec 100644 --- a/Controller/CRUDController.php +++ b/Controller/CRUDController.php @@ -15,17 +15,14 @@ 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; diff --git a/Datagrid/Datagrid.php b/Datagrid/Datagrid.php index 4b38d587ec..55b4314d14 100644 --- a/Datagrid/Datagrid.php +++ b/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 @@ public function __construct($query, ListCollection $columns, PagerInterface $pag $this->columns = $columns; } + /** + * @return \Sonata\AdminBundle\Datagrid\PagerInterface + */ public function getPager() { return $this->pager; } + public function getResults() { $this->buildPager(); @@ -64,6 +71,9 @@ public function buildPager() ); } + $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 @@ public function buildPager() $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 @@ public function getColumns() { return $this->columns; } + + public function getQuery() + { + return $this->query; + } } \ No newline at end of file diff --git a/Datagrid/DatagridInterface.php b/Datagrid/DatagridInterface.php index 42e45962e0..963280b3ff 100644 --- a/Datagrid/DatagridInterface.php +++ b/Datagrid/DatagridInterface.php @@ -15,17 +15,52 @@ 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(); } \ No newline at end of file diff --git a/Datagrid/ListCollection.php b/Datagrid/ListCollection.php index d131d7ac3b..19e318bbe6 100644 --- a/Datagrid/ListCollection.php +++ b/Datagrid/ListCollection.php @@ -10,7 +10,6 @@ */ namespace Sonata\AdminBundle\Datagrid; - use Sonata\AdminBundle\Admin\FieldDescriptionInterface; class ListCollection diff --git a/Datagrid/ListMapper.php b/Datagrid/ListMapper.php index 539c32dad9..6bc179f7ff 100644 --- a/Datagrid/ListMapper.php +++ b/Datagrid/ListMapper.php @@ -53,7 +53,7 @@ public function add($name, array $fieldDescriptionOptions = array()) $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( $this->admin->getClass(), - $field->getKey(), + $name, $fieldDescriptionOptions ); diff --git a/Datagrid/ORM/Pager.php b/Datagrid/ORM/Pager.php index 230b7de56d..c2e7b801b3 100644 --- a/Datagrid/ORM/Pager.php +++ b/Datagrid/ORM/Pager.php @@ -19,10 +19,10 @@ * Doctrine pager class. * * @author Jonathan H. Wage - * @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 @@ public function computeNbResult() $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 @@ public function computeNbResult() */ 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 @@ public function init() $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 @@ public function init() $this->setLastPage(ceil($this->getNbResults() / $this->getMaxPerPage())); - $query - ->setFirstResult($offset) - ->setMaxResults($this->getMaxPerPage()); + $this->getQuery()->setFirstResult($offset); + $this->getQuery()->setMaxResults($this->getMaxPerPage()); } } } diff --git a/Datagrid/ORM/ProxyQuery.php b/Datagrid/ORM/ProxyQuery.php new file mode 100644 index 0000000000..abf2bb5ee9 --- /dev/null +++ b/Datagrid/ORM/ProxyQuery.php @@ -0,0 +1,82 @@ + + * (c) Jonathan H. Wage + * + * 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; + } +} diff --git a/Datagrid/ProxyQueryInterface.php b/Datagrid/ProxyQueryInterface.php new file mode 100644 index 0000000000..6732d3f4ff --- /dev/null +++ b/Datagrid/ProxyQueryInterface.php @@ -0,0 +1,33 @@ + + * (c) Jonathan H. Wage + * + * 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(); +} diff --git a/Filter/FilterInterface.php b/Filter/FilterInterface.php index 8db44902d7..1ecbdbe23e 100644 --- a/Filter/FilterInterface.php +++ b/Filter/FilterInterface.php @@ -14,7 +14,7 @@ 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(); } diff --git a/Model/ModelManagerInterface.php b/Model/ModelManagerInterface.php index 1a5bb1b340..663766ad22 100644 --- a/Model/ModelManagerInterface.php +++ b/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 @@ function getEntityIdentifier($class); * @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); } diff --git a/Model/ORM/ModelManager.php b/Model/ORM/ModelManager.php index 127452157e..62bc457db3 100644 --- a/Model/ORM/ModelManager.php +++ b/Model/ORM/ModelManager.php @@ -13,6 +13,8 @@ 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 @@ public function createQuery($class, $alias = 'o') */ public function getEntityIdentifier($class) { - return $this->getEntityManager()->getUnitOfWork()->getEntityIdentifier($class); + return $this->getMetadata($class)->identifier; } /** @@ -180,4 +182,40 @@ public function getModelInstance($class) 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)) + ); + } } diff --git a/Resources/public/css/layout.css b/Resources/public/css/layout.css index 4793b1c8d3..07899f5eff 100644 --- a/Resources/public/css/layout.css +++ b/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; +} \ No newline at end of file diff --git a/Resources/views/CRUD/base_list.html.twig b/Resources/views/CRUD/base_list.html.twig index 6f03f74232..a2fd265e6e 100644 --- a/Resources/views/CRUD/base_list.html.twig +++ b/Resources/views/CRUD/base_list.html.twig @@ -26,20 +26,35 @@ file that was distributed with this source code.
{% block table_header %} - + {% for field_description in admin.list.elements %} {% if field_description.getOption('code') == '_batch' %} {% else %} - {% spaceless %}{% endspaceless %} + + {% if sortable %}{% endif %} + + {% endspaceless %} {% endif %} {% endfor %}
+ {% 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 %} + + {% if sortable %}{% 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 %} -