Added possibility to apply filters to search results.

This commit is contained in:
Jan Böhmer 2022-09-10 00:08:59 +02:00
parent c3f144447f
commit e96db21ceb
6 changed files with 61 additions and 30 deletions

View file

@ -35,6 +35,11 @@ export default class extends Controller {
{ {
const form = event.target.closest('form'); const form = event.target.closest('form');
for(const element of form.elements) { for(const element of form.elements) {
// Do not clear elements with data-no-clear attribute
if(element.dataset.noClear) {
continue;
}
element.disabled = true; element.disabled = true;
} }

View file

@ -127,13 +127,15 @@ class PartListsController extends AbstractController
/** /**
* Common implementation for the part list pages. * Common implementation for the part list pages.
* @param Request $request The request to parse
* @param string $template The template that should be rendered * @param string $template The template that should be rendered
* @param callable|null $filter_changer A function that is called with the filter object as parameter. This function can be used to customize the filter
* @param callable|null $form_changer A function that is called with the form object as parameter. This function can be used to customize the form
* @param array $additonal_template_vars Any additional template variables that should be passed to the template * @param array $additonal_template_vars Any additional template variables that should be passed to the template
* @param callable $filter_changer A function that is called with the filter object as parameter. This function can be used to customize the filter * @param array $additional_table_vars Any additional variables that should be passed to the table creation
* @param callable $form_changer A function that is called with the form object as parameter. This function can be used to customize the form
* @return Response * @return Response
*/ */
protected function showListWithFilter(Request $request, string $template, ?callable $filter_changer = null, ?callable $form_changer = null, array $additonal_template_vars = []): Response protected function showListWithFilter(Request $request, string $template, ?callable $filter_changer = null, ?callable $form_changer = null, array $additonal_template_vars = [], array $additional_table_vars = []): Response
{ {
$formRequest = clone $request; $formRequest = clone $request;
$formRequest->setMethod('GET'); $formRequest->setMethod('GET');
@ -149,7 +151,7 @@ class PartListsController extends AbstractController
$filterForm->handleRequest($formRequest); $filterForm->handleRequest($formRequest);
$table = $this->dataTableFactory->createFromType(PartsDataTable::class, ['filter' => $filter]) $table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge(['filter' => $filter], $additional_table_vars))
->handleRequest($request); ->handleRequest($request);
if ($table->isCallback()) { if ($table->isCallback()) {
@ -312,18 +314,18 @@ class PartListsController extends AbstractController
{ {
$searchFilter = $this->searchRequestToFilter($request); $searchFilter = $this->searchRequestToFilter($request);
$table = $dataTable->createFromType(PartsDataTable::class, [ return $this->showListWithFilter($request,
'Parts/lists/search_list.html.twig',
null,
null,
[
'keyword' => $searchFilter->getKeyword(),
'searchFilter' => $searchFilter,
],
[
'search' => $searchFilter, 'search' => $searchFilter,
])->handleRequest($request); ]
);
if ($table->isCallback()) {
return $table->getResponse();
}
return $this->render('Parts/lists/search_list.html.twig', [
'datatable' => $table,
'keyword' => $searchFilter->getQuery(),
]);
} }
/** /**

View file

@ -9,7 +9,7 @@ class PartSearchFilter implements FilterInterface
{ {
/** @var string The string to query for */ /** @var string The string to query for */
protected $query; protected $keyword;
/** @var boolean Whether to use regex for searching */ /** @var boolean Whether to use regex for searching */
protected $regex = false; protected $regex = false;
@ -49,7 +49,7 @@ class PartSearchFilter implements FilterInterface
public function __construct(string $query) public function __construct(string $query)
{ {
$this->query = $query; $this->keyword = $query;
} }
protected function getFieldsToSearch(): array protected function getFieldsToSearch(): array
@ -95,7 +95,7 @@ class PartSearchFilter implements FilterInterface
$fields_to_search = $this->getFieldsToSearch(); $fields_to_search = $this->getFieldsToSearch();
//If we have nothing to search for, do nothing //If we have nothing to search for, do nothing
if (empty($fields_to_search) || empty($this->query)) { if (empty($fields_to_search) || empty($this->keyword)) {
return; return;
} }
@ -115,27 +115,27 @@ class PartSearchFilter implements FilterInterface
//For regex we pass the query as is, for like we add % to the start and end as wildcards //For regex we pass the query as is, for like we add % to the start and end as wildcards
if ($this->regex) { if ($this->regex) {
$queryBuilder->setParameter('search_query', $this->query); $queryBuilder->setParameter('search_query', $this->keyword);
} else { } else {
$queryBuilder->setParameter('search_query', '%' . $this->query . '%'); $queryBuilder->setParameter('search_query', '%' . $this->keyword . '%');
} }
} }
/** /**
* @return string * @return string
*/ */
public function getQuery(): string public function getKeyword(): string
{ {
return $this->query; return $this->keyword;
} }
/** /**
* @param string $query * @param string $keyword
* @return PartSearchFilter * @return PartSearchFilter
*/ */
public function setQuery(string $query): PartSearchFilter public function setKeyword(string $keyword): PartSearchFilter
{ {
$this->query = $query; $this->keyword = $keyword;
return $this; return $this;
} }

View file

@ -65,6 +65,7 @@ use App\Services\MoneyFormatter;
use App\Services\SIFormatter; use App\Services\SIFormatter;
use App\Services\Trees\TreeViewGenerator; use App\Services\Trees\TreeViewGenerator;
use Brick\Math\BigDecimal; use Brick\Math\BigDecimal;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
@ -87,12 +88,14 @@ class AppExtension extends AbstractExtension
protected $FAIconGenerator; protected $FAIconGenerator;
protected $translator; protected $translator;
protected $objectNormalizer;
public function __construct(EntityURLGenerator $entityURLGenerator, MarkdownParser $markdownParser, public function __construct(EntityURLGenerator $entityURLGenerator, MarkdownParser $markdownParser,
SerializerInterface $serializer, TreeViewGenerator $treeBuilder, SerializerInterface $serializer, TreeViewGenerator $treeBuilder,
MoneyFormatter $moneyFormatter, MoneyFormatter $moneyFormatter,
SIFormatter $SIFormatter, AmountFormatter $amountFormatter, SIFormatter $SIFormatter, AmountFormatter $amountFormatter,
AttachmentURLGenerator $attachmentURLGenerator, AttachmentURLGenerator $attachmentURLGenerator,
FAIconGenerator $FAIconGenerator, TranslatorInterface $translator) FAIconGenerator $FAIconGenerator, TranslatorInterface $translator, ObjectNormalizer $objectNormalizer)
{ {
$this->entityURLGenerator = $entityURLGenerator; $this->entityURLGenerator = $entityURLGenerator;
$this->markdownParser = $markdownParser; $this->markdownParser = $markdownParser;
@ -104,6 +107,8 @@ class AppExtension extends AbstractExtension
$this->attachmentURLGenerator = $attachmentURLGenerator; $this->attachmentURLGenerator = $attachmentURLGenerator;
$this->FAIconGenerator = $FAIconGenerator; $this->FAIconGenerator = $FAIconGenerator;
$this->translator = $translator; $this->translator = $translator;
$this->objectNormalizer = $objectNormalizer;
} }
public function getFilters(): array public function getFilters(): array
@ -118,6 +123,8 @@ class AppExtension extends AbstractExtension
new TwigFilter('siFormat', [$this, 'siFormat']), new TwigFilter('siFormat', [$this, 'siFormat']),
new TwigFilter('amountFormat', [$this, 'amountFormat']), new TwigFilter('amountFormat', [$this, 'amountFormat']),
new TwigFilter('loginPath', [$this, 'loginPath']), new TwigFilter('loginPath', [$this, 'loginPath']),
new TwigFilter('toArray', [$this, 'toArray'])
]; ];
} }
@ -174,6 +181,11 @@ class AppExtension extends AbstractExtension
return json_encode($tree, JSON_THROW_ON_ERROR); return json_encode($tree, JSON_THROW_ON_ERROR);
} }
public function toArray($object): array
{
return $this->objectNormalizer->normalize($object, null);
}
/** /**
* This function/filter generates an path. * This function/filter generates an path.
*/ */

View file

@ -123,6 +123,14 @@
</div> </div>
</div> </div>
{# Retain the query parameters of the search form if it is existing #}
{% if searchFilter is defined %}
{% for property, value in searchFilter|toArray %}
<input type="hidden" name="{{ property }}" data-no-clear="true" value="{{ value }}">
{% endfor %}
{% endif %}
{{ form_end(filterForm) }} {{ form_end(filterForm) }}
</div> </div>
</div> </div>

View file

@ -1,11 +1,15 @@
{% extends "base.html.twig" %} {% extends "base.html.twig" %}
{% block title %} {% block title %}
{% trans %}parts_list.search.title{% endtrans %} {{ keyword }} {% trans %}parts_list.search.title{% endtrans %}: {{ keyword }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="accordion mb-3">
{% include "Parts/lists/_filter.html.twig" %}
</div>
{% include "Parts/lists/_parts_list.html.twig" %} {% include "Parts/lists/_parts_list.html.twig" %}
{% endblock %} {% endblock %}