When working regularly with Sonata bundles, you’ve might come accross their MediaBundle, which integrates a media management module. You certainly recognized that there is something different in the list view: A tabbed navigation to filter media items. This is how it looks like in the current 2.3 branch:
Screenshot of the Sonata Demo
I think this is very useful, because you can filter very quickly – literally with one click – instead of wasting three steps on the datagrid (focus widget, select/type value, click filter button). Unfortunately the tab feature is not documented, so I tried to figure out how it works. Here is a tutorial how you can add filter tabs to your own Sonata Admin modules.
First thing to know: Filter tabs are accually not a feature of the Sonata Admin. What they did is, they “hacked” their own bundle to make it happen. So expect that the implementation can become a little bit dirty.
The example
Let’s say we have written a news module for Sonata Admin. Every news is assigned to a category. Categories are configured in config.yml
and provided as a configuration parameter. Now we want to add filter tabs to the list view, so we can quickly browse through our news categories. The bundle is called FooNewsBundle
, the admin class is Foo\NewsBundle\Admin\NewsAdmin
and the admin controller is Foo\NewsBundle\Controller\NewsAdminController
.
1. Template Work
Create a new template list.html.twig
and extend the original Sonata template. We exploit the unused block preview
to add some extra HTML for the filter tabs:
{% extends 'SonataAdminBundle:CRUD:base_list.html.twig' %} {% block preview %}{% endblock %}
At this point we have to decide how the query parameter should be called, which controls the category filter. In this case it is called cat
. Maybe you wonder where that variable categories
is coming from. It isn’t there by default. We have to add it with a “dirty hack”. Overwrite the render
method of the admin controller class and pass some extra variables to the template:
public function render($view, array $parameters = array(), Response $response = null) { // Get categories from parameters $parameters['categories'] = $this->container->getParameter("foo_news.categories"); // This one is also necessary. I'll explain in the next section ;) $parameters['persistent_parameters'] = $this->admin->getPersistentParameters(); return parent::render($view, $parameters); }
Warning: Those extra variables will be added to every template of the admin module. Make sure you don’t cause any naming-conflicts with other variables.
Personal note: I’m not very happy with that solution, but that’s how they did it in the MediaBundle. I think the CRUDController should be made more extensible at that point. If you have a different solution how to inject extra variables, I’d be glad if you’d leave a comment 🙂
Finally you have to tell the admin class to use your own template instead of the default one. This can be done in the configure
method of the admin class. (Make sure the template name matches your own setup).
public function configure() { $this->setTemplate("list", "FooNewsBundle:NewsAdmin:list.html.twig"); }
2. Adding Logic
Persistent parameters are query parameters that are kept in the URL while navigating in the admin panel. We have to make our cat
parameter persistent, so that it will not be lost when browsing (pagination, sorting) the list view. Therefore we have to implement the getPersistentParameters
method in our admin class. The method is responsible for fetching the persistent parameters from the request and returning them as an array.
public function getPersistentParameters() { if (!$this->hasRequest()) { return array(); } $cat = $this->getRequest()->get('cat'); return array('cat' => $cat); }
In the final step we have to connect the tab filter with the datagrid filter as it will do all the filter work for us. First define a filter option which filters for the same aspect as your tab filter and uses the same values. In our example we’ll add a filter option for the “category” attribute.
public function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper->add('category'); }
Then, if persistent parameters are present, set the value for the dategrid filter. We can do this quite easily by overwriting the getDatagrid
method in the admin class:
public function getDatagrid() { $datagrid = parent::getDatagrid(); $datagrid->setValue('category', null, $this->getPersistentParameter('cat')); return $datagrid; }
Personal note: The MediaBundle sets the filter value in the controller, but they had to overwrite the whole action. This may become a problem, when the default implementation of the listAction
method changes. I think it’s the more elegant and safer solution to do it in getDatagrid
method.
3. Bonus: Pre-selection on new entities
When a category filter is active in the list view and you click the “Add New” button, wouldn’t it be great if that category is pre-selected for the new entity? Since persistent parameters are automatically appended to every link, the information is already there – we just have to use it. The Admin
class has a method called getNewInstance
for creating new entity instances. We overwrite that method in the admin class and add some lines to pre-set the category:
public function getNewInstance() { $instance = parent::getNewInstance(); //Pre-set category if ($category = $this->getPersistentParameter('cat')) { $instance->setCategory($category); } return $instance; }
Conclusion
You’ve seen how to add filter tabs to a Sonata Admin module. I think it is a very handy feature to improve the usability of admin panels. What I don’t like is the fact, that you have to modify so many parts of the system. That’s why I’d love to see tabbed filters as a real feature in Sonata Admin, e.g. configurable like the datagrid filter. That would remove all the “hackyness” and make it more accessible to developers.
Very nice job!!
Thank you so much.. 😉