Skip to content

Commit 48feca1

Browse files
[Searchbar] Avoid duplicating searchbar DOM Fixes #51
1 parent e035ca3 commit 48feca1

13 files changed

Lines changed: 202 additions & 62 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@
107107
"enabled": true,
108108
"fetch": "eager"
109109
},
110+
"search-bar-mover": {
111+
"main": "src/Resources/assets/shop/controllers/SearchBarMoverController.js",
112+
"enabled": true,
113+
"fetch": "eager"
114+
},
110115
"view-more": {
111116
"main": "src/Resources/assets/shop/controllers/ViewMoreController.js",
112117
"enabled": true,

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
"enabled": true,
1515
"fetch": "eager"
1616
},
17+
"search-bar-mover": {
18+
"main": "src/Resources/assets/shop/controllers/SearchBarMoverController.js",
19+
"enabled": true,
20+
"fetch": "eager"
21+
},
1722
"view-more": {
1823
"main": "src/Resources/assets/shop/controllers/ViewMoreController.js",
1924
"enabled": true,

src/Controller/Shop/SearchController.php

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
use Symfony\Component\HttpFoundation\JsonResponse;
2424
use Symfony\Component\HttpFoundation\RedirectResponse;
2525
use Symfony\Component\HttpFoundation\Request;
26-
use Symfony\Component\HttpFoundation\RequestStack;
2726
use Symfony\Component\HttpFoundation\Response;
2827

2928
class SearchController extends AbstractController
@@ -34,31 +33,6 @@ public function __construct(
3433
) {
3534
}
3635

37-
public function getForm(Request $renderRequest, RequestStack $requestStack): Response
38-
{
39-
/** @var string|null $query */
40-
$query = $requestStack->getMainRequest()?->get('query');
41-
if (null === $query || '' === $query) {
42-
/** @var array<string, array<string, string>> $query */
43-
$query = $requestStack->getMainRequest()?->get('criteria', []);
44-
$query = $query['search']['value'] ?? '';
45-
}
46-
47-
$searchForm = $this->createForm(
48-
SearchFormType::class,
49-
['query' => $query],
50-
['action' => $this->generateUrl('gally_search_result_page'), 'method' => 'POST']
51-
);
52-
53-
return $this->render(
54-
'@GallySyliusPlugin/shop/shared/components/header/search/form.html.twig',
55-
[
56-
'searchForm' => $searchForm->createView(),
57-
'mobileMode' => $renderRequest->get('mobile_mode'),
58-
]
59-
);
60-
}
61-
6236
public function getResults(Request $request): Response
6337
{
6438
$searchForm = $this->createForm(SearchFormType::class);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
3+
/**
4+
* Moves the single search bar element into the desktop or mobile anchor
5+
* depending on the Bootstrap lg breakpoint (992px), avoiding DOM duplication.
6+
*/
7+
export default class extends Controller {
8+
static targets = ['bar'];
9+
10+
/**
11+
* CSS selector used to locate the desktop anchor in the document.
12+
* @type {string}
13+
*/
14+
static DESKTOP_ANCHOR_SELECTOR = '[data-gally-search-bar-anchor="desktop"]';
15+
16+
/**
17+
* CSS selector used to locate the mobile anchor in the document.
18+
* @type {string}
19+
*/
20+
static MOBILE_ANCHOR_SELECTOR = '[data-gally-search-bar-anchor="mobile"]';
21+
22+
connect() {
23+
// Read the target once: after the first _place() call, the bar element
24+
// is moved outside this.element, so Stimulus no longer considers it part
25+
// of this controller's target scope and this.barTarget would throw.
26+
this._bar = this.barTarget;
27+
this._mql = window.matchMedia('(min-width: 992px)');
28+
this._handler = (e) => this._place(e.matches);
29+
this._mql.addEventListener('change', this._handler);
30+
this._place(this._mql.matches);
31+
}
32+
33+
disconnect() {
34+
this._mql.removeEventListener('change', this._handler);
35+
}
36+
37+
/**
38+
* Moves the bar element to the appropriate anchor.
39+
*
40+
* @param {boolean} isDesktop true when the viewport is ≥ lg (992 px)
41+
*/
42+
_place(isDesktop) {
43+
const selector = isDesktop
44+
? this.constructor.DESKTOP_ANCHOR_SELECTOR
45+
: this.constructor.MOBILE_ANCHOR_SELECTOR;
46+
47+
const anchor = document.querySelector(selector);
48+
const bar = this._bar;
49+
50+
if (!anchor) {
51+
// Anchor not yet in the DOM (e.g. Turbo page transition) — keep bar
52+
// in its current position and retry on the next breakpoint change.
53+
return;
54+
}
55+
56+
// Only move if not already in the right place to avoid layout thrashing.
57+
if (anchor.firstElementChild !== bar) {
58+
// Remove style="display:none" from controller element on first placement
59+
this.element.removeAttribute('style');
60+
this.element.removeAttribute('aria-hidden');
61+
anchor.appendChild(bar);
62+
}
63+
}
64+
}

src/Resources/config/app/twig_hooks_shop.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
sylius_twig_hooks:
22
hooks:
3+
# Desktop searchbar placeholder
34
'sylius_shop.base.header.content':
45
search_bar:
56
template: '@GallySyliusPlugin/shop/shared/components/header/search/search_bar.html.twig'
67
priority: 250
78

9+
# Mobile searchbar placeholder
810
'sylius_shop.base.header':
911
search_bar:
1012
template: '@GallySyliusPlugin/shop/shared/components/header/search/search_bar_mobile.html.twig'
1113
priority: 150
14+
# Real searchbar renderer
15+
gally_search_bar:
16+
component: 'gally_shop:search:search_bar'
17+
priority: 400
1218

1319
'sylius_shop.product.search.content':
1420
breadcrumbs:

src/Resources/config/services.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<import resource="services/search.xml"/>
99
<import resource="services/twig.xml"/>
1010
<import resource="services/twig/component/product.xml"/>
11+
<import resource="services/twig/component/search.xml"/>
1112
<import resource="services/tracking.xml"/>
1213
</imports>
1314

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
5+
<services>
6+
<service id="Gally\SyliusPlugin\Twig\Component\Search\SearchBarComponent" autoconfigure="true">
7+
<argument type="service" id="form.factory" />
8+
<argument type="service" id="router" />
9+
<argument type="service" id="request_stack" />
10+
<tag name="sylius.twig_component" key="gally_shop:search:search_bar" />
11+
</service>
12+
</services>
13+
</container>

src/Resources/config/shop_routing.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@ gally_filter_view_more_ajax:
1212
_controller: Gally\SyliusPlugin\Controller\Shop\FilterController::viewMore
1313
_format: json
1414

15-
gally_search_form:
16-
path: /search
17-
methods: [ GET ]
18-
defaults:
19-
_controller: Gally\SyliusPlugin\Controller\Shop\SearchController::getForm
20-
2115
gally_search_post_result:
2216
path: /search-results
2317
methods: [ POST ]

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

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
{{ render(path('gally_search_form')) }}
1+
{# Desktop searchbar placeholder — SearchBarMoverController moves the real search bar here #}
2+
<div
3+
data-gally-search-bar-anchor="desktop"
4+
class="col pt-1"
5+
></div>

0 commit comments

Comments
 (0)