I want to make my entity list/table searchable, but the docs are not helping. I created custom module that generates
I have entity Event and it has NAME variable. The result i want to get is like in domain/admin/people - Search by name
Not that complicated and you don't need Views
. Most of the functionality is right there, built-in, you only need to add your own bits.
In addition to EntityListBuilder
, create a form of the filter to be exposed. Just create a usual form extending FormBase
, building whatever UI you need (eg. a dropdown with items to select from, with Filter and Reset buttons). Let's call it DocumentFilterForm
, this is its form builder:
public function buildForm(array $form, FormStateInterface $form_state) {
$request = \Drupal::request();
$form['filter'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['form--inline', 'clearfix'],
],
];
$form['filter']['foo'] = [
'#type' => 'select',
'#title' => 'Which foo?',
'#options' => ...,
'#default_value' => $request->get('foo') ?? 0,
];
$form['filter']['bar'] = [
'#type' => 'select',
'#title' => 'Which bar?',
'#options' => ...,
'#default_value' => $request->get('bar') ?? 0,
];
$form['actions']['wrapper'] = [
'#type' => 'container',
'#attributes' => ['class' => ['form-item']],
];
$form['actions']['wrapper']['submit'] = [
'#type' => 'submit',
'#value' => 'Filter',
];
if ($request->getQueryString()) {
$form['actions']['wrapper']['reset'] = [
'#type' => 'submit',
'#value' => 'Reset',
'#submit' => ['::resetForm'],
];
}
return $form;
}
We have two dropdowns, as per usual, with the two buttons. The Reset only appears when needed, just like with the standard Views displays. submitForm()
redirects to the URL with the appropriate query (remember to change the route name), resetForm()
redirects to the original URL.
public function submitForm(array &$form, FormStateInterface $form_state) {
$query = [];
$foo = $form_state->getValue('foo') ?? 0;
if ($foo) {
$query['foo'] = $foo;
}
$bar = $form_state->getValue('bar') ?? 0;
if ($bar) {
$query['bar'] = $bar;
}
$form_state->setRedirect('entity.document.collection', $query);
}
public function resetForm(array $form, FormStateInterface &$form_state) {
$form_state->setRedirect('entity.document.collection');
}
Back in the list builder, we modify getEntityIds()
to apply our filters, when present:
protected function getEntityIds() {
$query = \Drupal::entityQuery($this->entityTypeId);
$request = \Drupal::request();
$foo = $request->get('foo') ?? 0;
if ($foo) {
$query->condition('foo', $foo);
}
$bar = $request->get('bar') ?? 0;
if ($bar) {
$query->condition('bar', $bar);
}
if ($this->limit) {
$query->pager($this->limit);
}
return $query->execute();
}
And finally, to actually include the filter form on the page:
public function render() {
$build['form'] = \Drupal::formBuilder()->getForm('Drupal\...\Form\DocumentFilterForm');
$build += parent::render();
return $build;
}
Sorting is even simpler, you only have to add to your list builder. First, set up the headers like with any other similar table:
public function buildHeader() {
$header = [
'foo' => [
'data' => 'Foo',
'field' => 't.foo',
],
'bar' => [
'data' => 'Bar',
'field' => 't.bar',
'sort' => 'asc',
],
];
return $header + parent::buildHeader();
}
Second, add the following to your previous getEntityIds()
:
protected function getEntityIds() {
$query = \Drupal::entityQuery($this->entityTypeId);
$request = \Drupal::request();
// ...
$order = $request->get('order');
if ($order) {
$sort = $request->get('sort');
foreach ($this->buildHeader() as $name => $field) {
if (is_array($field) && $field['data'] == $order) {
$header = [$name => $field + [
'specifier' => $name,
'sort' => $sort ?? $field['sort'] ?? 'asc',
]];
}
}
if ($header) {
$query->tableSort($header);
}
}
else {
$query->sort('bar', 'asc');
}
return $query->execute();
}
Note that this relies on the data
elements in the header being simple strings. If you use t()
, you need to compare to $field['data']->getUntranslatedString()
instead. It also assumes that your header items (foo
and bar
) use the same name as the underlying field. If not, modify accordingly. tableSort()
requires a simple nested array that has a specifier
pointing to the field name and a sort
to describe the sort direction, you can create it however you wish, it will work.
You are using an entity list builder? There is no standard support for filters/sort, if you want to implement something you have to do it yourself.
All content lists that use filters like admin/people and admin/content are views, expose your entity type to views if it is a content entity type.
One special case is admin/structure/views, which is a config entity list builder that has support for filters. But that's actually a client-side implementation done with javascript.