Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Grid/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ class Filter
public string $formFieldName;
public $callback;
public bool $enabled;
public ?string $group;

public function __construct(
string $formFieldName,
callable $callback,
bool $enabled = true
bool $enabled = true,
?string $group = null
) {
$this->formFieldName = $formFieldName;
$this->callback = $callback;
$this->enabled = $enabled;
$this->group = $group;
}
}
8 changes: 8 additions & 0 deletions src/Grid/Grid.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Grid
private string $batchMethod;
private string $theme;
private $rowAttributesCallback = null;
private array $filterLayout;

private Request $request;
private PaginationInterface $pagination;
Expand All @@ -30,6 +31,7 @@ public function __construct(
string $batchMethod = 'POST',
string $batchActionsTokenId,
?callable $rowAttributesCallback = null,
array $filterLayout = [],
) {
$this->columns = $columns;
$this->request = $request;
Expand All @@ -39,6 +41,7 @@ public function __construct(
$this->batchActionsTokenId = $batchActionsTokenId;
$this->theme = $theme;
$this->rowAttributesCallback = $rowAttributesCallback;
$this->filterLayout = $filterLayout;
}

public function getColumns(): array
Expand Down Expand Up @@ -81,6 +84,11 @@ public function getTheme(): string
return $this->theme;
}

public function getFilterLayout(): array
{
return $this->filterLayout;
}

public function getRowAttributes($item, bool $keepAsArray = false): null|array|string
{
if (!is_callable($this->rowAttributesCallback)) {
Expand Down
52 changes: 50 additions & 2 deletions src/Grid/GridBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,60 @@ public function removeColumn(string $name): self
return $this;
}

public function addFilter(string $formFieldName, callable $callback, bool $enabled = true): self
public function addFilter(string $formFieldName, callable $callback, bool $enabled = true, ?string $group = null): self
{
$this->filters[] = new Filter($formFieldName, $callback, $enabled);
$this->filters[] = new Filter($formFieldName, $callback, $enabled, $group);

return $this;
}

/**
* Converts the flat list of filters into a layout array consumed by Twig.
*
* Each entry in the returned array represents one "slot" in the filter bar:
* - ungrouped filter → one slot with a single field
* - grouped filters → one slot for the whole group, listing all its fields
*
* Example output:
* [
* ['fields' => ['name'], 'group' => null], // standalone filter
* ['fields' => ['dateFrom','dateTo'], 'group' => 'date'], // grouped filters
* ['fields' => ['status'], 'group' => null], // standalone filter
* ]
*
* Groups are emitted in the order their first member appears, and $processedGroups
* prevents the same group from being added a second time when the loop reaches
* subsequent members of that group.
*/
private function buildFilterLayout(): array
{
$layout = [];
$processedGroups = [];

foreach ($this->filters as $filter) {
if (!$filter->enabled) {
continue;
}

if ($filter->group === null) {
// Ungrouped filter: occupies its own slot.
$layout[] = ['fields' => [$filter->formFieldName], 'group' => null];
} elseif (!in_array($filter->group, $processedGroups)) {
// First time we encounter this group: collect all enabled fields belonging
// to it (in declaration order) and emit a single slot for the whole group.
$processedGroups[] = $filter->group;
$groupFields = array_map(
fn(Filter $f) => $f->formFieldName,
array_filter($this->filters, fn(Filter $f) => $f->enabled && $f->group === $filter->group)
);
$layout[] = ['fields' => array_values($groupFields), 'group' => $filter->group];
}
// Subsequent members of an already-processed group are intentionally skipped.
}

return $layout;
}

public function removeFilter(string $formFieldName): self
{
foreach ($this->filters as $key => $filter) {
Expand Down Expand Up @@ -304,6 +351,7 @@ public function getGrid(bool $forceRecreate = false): Grid
$this->batchMethod,
$this::class,
$this->rowAttributesCallback,
$this->buildFilterLayout(),
);
}

Expand Down
18 changes: 17 additions & 1 deletion src/Resources/views/theme/bootstrap5/datagrid-filters.html.twig
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
{{ form_start(form) }}

{% set layoutFieldNames = [] %}
{% for row in grid.filterLayout %}
{% set layoutFieldNames = layoutFieldNames|merge(row.fields) %}
{% if row.fields|length > 1 %}
<div class="row datagrid-filter-group">
{% for fieldName in row.fields %}
<div class="col datagrid-filter-group-item">
{{ form_row(form[fieldName]) }}
</div>
{% endfor %}
</div>
{% else %}
{{ form_row(form[row.fields[0]]) }}
{% endif %}
{% endfor %}

{% for field in form %}
{% if field.vars.block_prefixes[0] != 'button' %}
{% if field.vars.block_prefixes[0] != 'button' and field.vars.name not in layoutFieldNames %}
{{ form_row(field) }}
{% endif %}
{% endfor %}
Expand Down