Skip to content

Commit 74f20e8

Browse files
[Filter] Fix category facet and add active filter list
1 parent fe5d462 commit 74f20e8

13 files changed

Lines changed: 275 additions & 51 deletions

File tree

src/Controller/Shop/FilterController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public function viewMore(Request $request, string $filterField): Response
8181
}
8282
}
8383
$currentLocalizedCatalog = $this->catalogProvider->buildLocalizedCatalog($currentChannel, $currentLocale);
84-
$request = new \Gally\Sdk\GraphQl\Request(
84+
$gallyRequest = new \Gally\Sdk\GraphQl\Request(
8585
$currentLocalizedCatalog,
8686
new Metadata('product'),
8787
false,
@@ -93,7 +93,7 @@ public function viewMore(Request $request, string $filterField): Response
9393
$gallyFilters,
9494
);
9595

96-
$aggregationOptions = $this->searchManager->viewMoreProductFilterOption($request, $filterField, $optionSearch);
96+
$aggregationOptions = $this->searchManager->viewMoreProductFilterOption($gallyRequest, $filterField, $optionSearch);
9797

9898
/** @var array<string, string>$option */
9999
foreach ($aggregationOptions as $option) {

src/Form/Type/Filter/GallyDynamicFilterType.php

Lines changed: 114 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
use Gally\SyliusPlugin\Event\GridFilterUpdateEvent;
1818
use Gally\SyliusPlugin\Grid\Filter\Type\SelectFilterType;
19+
use Gally\SyliusPlugin\Search\ActiveFilterResolver;
1920
use Gally\SyliusPlugin\Search\Aggregation\Aggregation;
2021
use Gally\SyliusPlugin\Search\Aggregation\AggregationOption;
2122
use Sylius\Bundle\GridBundle\Form\Type\Filter\BooleanFilterType;
@@ -24,6 +25,7 @@
2425
use Sylius\Component\Locale\Context\LocaleContextInterface;
2526
use Sylius\Component\Taxonomy\Model\TaxonInterface;
2627
use Symfony\Component\Form\AbstractType;
28+
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
2729
use Symfony\Component\Form\Extension\Core\Type\RangeType;
2830
use Symfony\Component\Form\FormBuilderInterface;
2931
use Symfony\Component\HttpFoundation\RequestStack;
@@ -49,6 +51,18 @@ public function __construct(
4951

5052
public function buildForm(FormBuilderInterface $builder, array $options): void
5153
{
54+
$isTaxonPage = $this->isTaxonPage();
55+
$categoryFieldAdded = false;
56+
57+
if ($isTaxonPage && !$this->hasCategoryAggregation()) {
58+
// Gally doesn't always return a "category" aggregation on taxon listing pages (e.g. a
59+
// leaf taxon with no sub-taxons), so there's no signal telling us where it would
60+
// normally sit in the facet order. Placed first, right below the active filters, in
61+
// that case.
62+
$this->addCategoryTaxonomyAnchor($builder);
63+
$categoryFieldAdded = true;
64+
}
65+
5266
foreach ($this->aggregations as $aggregation) {
5367
switch ($aggregation->getType()) {
5468
case 'slider':
@@ -95,32 +109,122 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
95109
/* @var AggregationOption $option */
96110
$choices[$option->getLabel()] = $option->getId();
97111
}
98-
$options = [
99-
'block_prefix' => 'sylius_gally_filter_checkbox',
100-
'label' => $aggregation->getLabel(),
101-
'choices' => $choices,
102-
'expanded' => true,
103-
'multiple' => true,
104-
'search_url' => $this->buildSearchUrl($aggregation->getField()),
105-
'has_more' => $aggregation->hasMore(),
106-
];
107112
$builder->add(
108113
$aggregation->getField(),
109114
SelectFilterType::class,
110-
$options
115+
[
116+
'block_prefix' => 'sylius_gally_filter_checkbox',
117+
'label' => $aggregation->getLabel(),
118+
'choices' => $choices,
119+
'expanded' => true,
120+
'multiple' => true,
121+
'search_url' => $this->buildSearchUrl($aggregation->getField()),
122+
'has_more' => $aggregation->hasMore(),
123+
]
111124
);
112125
break;
126+
case 'category':
127+
// A taxon-scoped request is still a category browsing context even when
128+
// Gally does return this aggregation (e.g. a parent taxon whose products
129+
// span several sub-taxons): direct taxon navigation (native component) is
130+
// used either way, never the Gally query-string filter, which would produce
131+
// links "filtering" the current taxon page instead of going to the sub-taxon
132+
// page directly.
133+
if ($isTaxonPage) {
134+
$this->addCategoryTaxonomyAnchor($builder);
135+
} else {
136+
$this->addCategoryField($builder, $aggregation);
137+
}
138+
$categoryFieldAdded = true;
139+
break;
113140
default:
114141
break;
115142
}
116143
}
144+
145+
// Gally excludes the category aggregation from the response as soon as a category filter is
146+
// active, so once selected the field above never reappears. Without this, submitting any
147+
// other filter would silently drop the category criteria.
148+
if (!$categoryFieldAdded) {
149+
$categoryId = $this->getCurrentCategoryId();
150+
if (null !== $categoryId) {
151+
$builder->add(ActiveFilterResolver::CATEGORY_FIELD, HiddenType::class, ['data' => $categoryId]);
152+
}
153+
}
117154
}
118155

119156
public function onFilterUpdate(GridFilterUpdateEvent $event): void
120157
{
121158
$this->aggregations = $event->getAggregations();
122159
}
123160

161+
private function isTaxonPage(): bool
162+
{
163+
$slug = $this->requestStack->getCurrentRequest()?->attributes->get('slug');
164+
165+
return \is_string($slug) && '' !== $slug;
166+
}
167+
168+
private function hasCategoryAggregation(): bool
169+
{
170+
foreach ($this->aggregations as $aggregation) {
171+
if ('category' === $aggregation->getType()) {
172+
return true;
173+
}
174+
}
175+
176+
return false;
177+
}
178+
179+
/**
180+
* On taxon listing pages Gally never returns a "category" aggregation at all (the taxon
181+
* already scopes the search), so browsing there is handled by the native Sylius taxonomy
182+
* component instead (proper direct links, plus a "go level up" link), inserted as a plain
183+
* form field so it renders through the same form_widget(form) call and keeps its position
184+
* in the facet order (see buildForm()). The block rendering it lives in checkbox.html.twig
185+
* (block "sylius_gally_filter_category_taxonomy_row").
186+
*/
187+
private function addCategoryTaxonomyAnchor(FormBuilderInterface $builder): void
188+
{
189+
$builder->add(ActiveFilterResolver::CATEGORY_FIELD, HiddenType::class, [
190+
'block_prefix' => 'sylius_gally_filter_category_taxonomy',
191+
]);
192+
}
193+
194+
/**
195+
* Rendered through the same SelectFilterType/autosubmit mechanism as the other facets (real
196+
* radio inputs, native browser change event), just themed to look like plain links via CSS
197+
* (see the "sylius_gally_filter_category" block and #searchbar .category-links rule in
198+
* filters.css) since a category is single-select, not a combinable criterion.
199+
*/
200+
private function addCategoryField(FormBuilderInterface $builder, Aggregation $aggregation): void
201+
{
202+
$choices = [];
203+
foreach ($aggregation->getOptions() as $option) {
204+
/* @var AggregationOption $option */
205+
$choices[$option->getLabel()] = $option->getId();
206+
}
207+
208+
$builder->add(ActiveFilterResolver::CATEGORY_FIELD, SelectFilterType::class, [
209+
'block_prefix' => 'sylius_gally_filter_category',
210+
'label' => $aggregation->getLabel(),
211+
'choices' => $choices,
212+
'expanded' => true,
213+
'multiple' => false,
214+
'data' => $this->getCurrentCategoryId(),
215+
]);
216+
}
217+
218+
private function getCurrentCategoryId(): ?string
219+
{
220+
$queryParameters = $this->requestStack->getCurrentRequest()?->query->all() ?? [];
221+
$criteria = \is_array($queryParameters['criteria'] ?? null) ? $queryParameters['criteria'] : [];
222+
$gallyCriteria = \is_array($criteria['gally'] ?? null) ? $criteria['gally'] : [];
223+
$categoryId = $gallyCriteria[ActiveFilterResolver::CATEGORY_FIELD] ?? null;
224+
225+
return \is_string($categoryId) && '' !== $categoryId ? $categoryId : null;
226+
}
227+
124228
private function buildSearchUrl(string $field): string
125229
{
126230
$request = $this->requestStack->getCurrentRequest();

src/Indexer/CategoryIndexer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public function getDocumentsToIndex(
119119

120120
private function formatTaxon(TaxonInterface $taxon, TaxonTranslationInterface $translation, TaxonInterface $menuTaxon): array
121121
{
122-
$parentId = '';
122+
$parentId = null;
123123
if (null !== $taxon->getParent() && $menuTaxon->getId() !== $taxon->getId()) {
124124
$parentId = str_replace('/', '_', (string) $taxon->getParent()->getCode());
125125
}

src/Resources/config/services/search.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
<service id="Gally\SyliusPlugin\Search\ActiveFilterResolver">
99
<argument type="service" id="request_stack" />
1010
<argument type="service" id="translator" />
11+
<argument type="service" id="sylius.repository.taxon" />
12+
<argument type="service" id="sylius.context.locale" />
1113
<tag name="kernel.event_listener" event="gally.grid.configure_filter" method="onFilterUpdate" />
14+
<tag name="kernel.reset" method="reset" />
1215
</service>
1316

1417
<service id="Gally\SyliusPlugin\Search\Finder" >

src/Resources/public/filters.css

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,31 +86,60 @@
8686
color: var(--bs-body-color);
8787
}
8888

89-
.active-filters .active-filter-chip
89+
/* Category is single-select and rendered as native radio inputs (so the existing autosubmit
90+
still applies unchanged), but themed to look like a plain list of links: a category is a
91+
navigation, not a combinable criterion like the other facets. */
92+
#searchbar .category-links .form-check
93+
{
94+
padding-left: 0;
95+
}
96+
97+
#searchbar .category-links .form-check-input
98+
{
99+
position: absolute;
100+
width: 1px;
101+
height: 1px;
102+
padding: 0;
103+
margin: -1px;
104+
overflow: hidden;
105+
clip: rect(0, 0, 0, 0);
106+
white-space: nowrap;
107+
border: 0;
108+
}
109+
110+
#searchbar .category-links .form-check-label
111+
{
112+
display: inline-block;
113+
padding: 0.25em 0;
114+
font-weight: normal;
115+
cursor: pointer;
116+
}
117+
118+
#searchbar .active-filters .active-filter-chip
90119
{
91120
text-decoration: none;
92121
font-weight: normal;
93122
padding: 0.5em 0.85em;
94123
}
95124

96-
.active-filters .active-filter-chip:hover
125+
#searchbar .active-filters .active-filter-chip:hover
97126
{
98127
background-color: var(--bs-secondary-bg-subtle, #e9ecef);
99128
}
100129

101-
.active-filters .active-filter-chip-remove
130+
#searchbar .active-filters .active-filter-chip-remove
102131
{
103132
font-weight: bold;
104133
}
105134

106-
.active-filters .active-filters-clear-all
135+
#searchbar .active-filters .active-filters-clear-all
107136
{
108137
font-size: 0.8em;
109138
color: var(--bs-secondary-color, rgba(0, 0, 0, 0.6));
110139
text-decoration: underline;
111140
}
112141

113-
.active-filters .active-filters-clear-all:hover
142+
#searchbar .active-filters .active-filters-clear-all:hover
114143
{
115144
color: var(--bs-body-color);
116145
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{#todo : make inclusion of scripts compatible with Webpack Encore and with the new strcuture of sylius 2#}
1+
{# TODO: make inclusion of scripts compatible with Webpack Encore and with the new structure of sylius 2 #}
22
{% for script in ['nouislider.min.js', 'range-slider.js', 'filters.js', 'search.js'] %}
33
<script src="{{ asset('bundles/gallysyliusplugin/' ~ script) }}"></script>
44
{% endfor %}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{#todo : make inclusion of scripts compatible with Webpack Encore and with the new strcuture of sylius 2#}
1+
{# TODO: make inclusion of scripts compatible with Webpack Encore and with the new structure of sylius 2 #}
22
{% for css in ['nouislider.min.css', 'slider.css', 'filters.css'] %}
33
<link rel="stylesheet" href="{{ asset('bundles/gallysyliusplugin/' ~ css) }}">
44
{% endfor %}

src/Resources/views/shop/form/checkbox.html.twig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,14 @@
1212
{% endif %}
1313
</div>
1414
{%- endblock sylius_gally_filter_checkbox_row %}
15+
16+
{% block sylius_gally_filter_category_taxonomy_row -%}
17+
{{ component('sylius_shop:product:show:taxonomy', {template: '@GallySyliusPlugin/shop/product/_shared/categories.html.twig'}) }}
18+
{%- endblock sylius_gally_filter_category_taxonomy_row %}
19+
20+
{% block sylius_gally_filter_category_row -%}
21+
<div class="{% if required %}required {% endif %}field category-links{% if (not compound or force_error|default(false)) and not valid %} error{% endif %}">
22+
{{- form_label(form) -}}
23+
{{- form_widget(form) -}}
24+
</div>
25+
{%- endblock sylius_gally_filter_category_row %}

src/Resources/views/shop/grid/filter/gally_dynamic_filter.html.twig

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
<div class="field mb-3">
88
<div class="filters-title">{{ 'gally_sylius.ui.filters.headline'|trans }}</div>
99
{{ component('gally_shop:product:active_filters', {template: '@GallySyliusPlugin/shop/product/_shared/active_filters.html.twig'}) }}
10-
{% if app.request.attributes.has('slug') %}
11-
{{ component('sylius_shop:product:show:taxonomy', {template: '@GallySyliusPlugin/shop/product/_shared/categories.html.twig'}) }}
12-
{% endif %}
1310
{{ form_widget(form) }}
1411
{{ form_help(form) }}
1512
{{ form_errors(form) }}

src/Resources/views/shop/shared/components/header/search/autocomplete/results.html.twig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@
6161
<small>{{ option_label }}</small>
6262
</a>
6363
{% endfor %}
64+
{% endif %}
6465

65-
<hr class="d-lg-none"/>
66+
{% if products|length %}
67+
<hr class="d-lg-none"/>
6668
{% endif %}
6769
</div>
6870

0 commit comments

Comments
 (0)