feat: search bar on TYPO3 13, loaded into the static pages, searching by GET
The search bar in the page header is back (SearchbarController, lib.searchBar and lib.searchBarCollapsed), in the Subpage, SubpageSidebarRight and Detail layouts as on TYPO3 10; product pages get the collapsed bar and its toggle button in the main navigation again. - The bar takes no arguments, so it is the same on every page of a site and cacheable. Page types 1706/1707 (/searchbar, /searchbar-collapsed on the ep-reisen root, also under /en/) render it alone for the static pages, which replace their exported bar with it via htmx. The suffixes have no leading slash: on the root page the route path has none, so "/searchbar" would never match (as /form and /snowreport do not there either). - The form sends dateFrom, dateTo, pax, destinationType, destinationUid and priceRange by GET to the search page instead of POSTing to processSearch: no signed fields and no redirect, and URLs of about 100 characters instead of about 870. SearchController reads them (SEARCHBAR_PARAMETERS, excluded from the cHash; the results are not cached) and drops anything but a real Y-m-d date, which made DateUtility::formatDateRange() throw. processSearch is removed. - The date range label is empty without arguments, so the template no longer needs ep:dateRange. Compared with TYPO3 10: the bar's markup matches apart from the form (open, collapsed, /en/); seven searches by GET load the same filter settings and hit counts as the TYPO3 10 POST. The static pages get the island from the exporter in EP/website, see .ddev/plans/static-export-searchbar-island.patch (local only). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b5637ba86e
commit
48e049671e
11 files changed
+444
-49
No files matched your search
@@ -0,0 +1,14 @@
|
||||
# The search bar in the page header, open (sticky once scrolled past) or collapsed behind the
|
||||
# search icon of the main navigation (product and hotel pages)
|
||||
lib.searchBar = USER
|
||||
lib.searchBar {
|
||||
userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
|
||||
extensionName = EpProducts
|
||||
pluginName = searchbar
|
||||
settings =< plugin.tx_epproducts.settings
|
||||
persistence =< plugin.tx_epproducts.persistence
|
||||
view =< plugin.tx_epproducts.view
|
||||
}
|
||||
|
||||
lib.searchBarCollapsed < lib.searchBar
|
||||
lib.searchBarCollapsed.settings.collapsed = 1
|
||||
@@ -0,0 +1,25 @@
|
||||
# The search bar without the page around it, on the site root (/searchbar, /searchbar-collapsed).
|
||||
# The static pages of the hybrid setup load it with htmx where the export had the bar: it is the
|
||||
# same on every page of a site, and its options follow the imported products (ep:import-dates
|
||||
# flushes the page cache). The form sends its fields by GET to the search page.
|
||||
|
||||
tx_eptheme_searchbar = PAGE
|
||||
tx_eptheme_searchbar {
|
||||
typeNum = 1706
|
||||
config {
|
||||
disableAllHeaderCode = 1
|
||||
disablePrefixComment = 1
|
||||
additionalHeaders.10.header = Content-Type: text/html; charset=utf-8
|
||||
additionalHeaders.20.header = X-Robots-Tag: noindex, nofollow
|
||||
admPanel = 0
|
||||
debug = 0
|
||||
}
|
||||
|
||||
10 =< lib.searchBar
|
||||
}
|
||||
|
||||
tx_eptheme_searchbar_collapsed < tx_eptheme_searchbar
|
||||
tx_eptheme_searchbar_collapsed {
|
||||
typeNum = 1707
|
||||
10 =< lib.searchBarCollapsed
|
||||
}
|
||||
@@ -2,8 +2,11 @@
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
|
||||
|
||||
<f:render partial="Topnav" arguments="{_all}"/>
|
||||
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
|
||||
<f:cObject typoscriptObjectPath="lib.searchBarCollapsed"/>
|
||||
<f:alias map="{searchbarCollapsed: 1}">
|
||||
<f:render partial="Topnav" arguments="{_all}"/>
|
||||
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
|
||||
</f:alias>
|
||||
<main id="main-content">
|
||||
<f:render section="Content"/>
|
||||
<f:render section="Teasers"/>
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
|
||||
<header>
|
||||
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
|
||||
<f:if condition="{data.tx_eptheme_hide_searchbar} || {settings.hideSearchbar}">
|
||||
<f:else>
|
||||
<f:cObject typoscriptObjectPath="lib.searchBar"/>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</header>
|
||||
<main id="main-content">
|
||||
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
|
||||
<header>
|
||||
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
|
||||
<f:if condition="{data.tx_eptheme_hide_searchbar}">
|
||||
<f:else>
|
||||
<f:cObject typoscriptObjectPath="lib.searchBar"/>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</header>
|
||||
<main id="main-content">
|
||||
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||
data-namespace-typo3-fluid="true">
|
||||
|
||||
<f:layout name="Default"/>
|
||||
|
||||
<f:section name="Main">
|
||||
<div data-controller="searchbar"
|
||||
<f:if condition="{collapsed}">
|
||||
data-searchbar-collapsed-value="true"
|
||||
</f:if>
|
||||
data-action="datepicker:range-select->searchbar#setDateRange searchbar-toggle:toggle@window->searchbar#toggle">
|
||||
<f:comment>
|
||||
GET to the search page, read by SearchController::initializeSearchresultAction()
|
||||
(SEARCHBAR_PARAMETERS). searchbar_controller.js finds the fields by their target.
|
||||
</f:comment>
|
||||
<form action="{f:uri.page(pageUid: settings.defaultSearchPageUid)}" method="get" data-searchbar-target="form">
|
||||
<input type="hidden" name="dateFrom" value="{searchParams.dateFrom}" data-searchbar-target="dateFrom"/>
|
||||
<input type="hidden" name="dateTo" value="{searchParams.dateTo}" data-searchbar-target="dateTo"/>
|
||||
<input type="hidden" name="pax" value="{searchParams.pax}" data-searchbar-target="pax"/>
|
||||
<input type="hidden" name="destinationUid" value="{searchParams.destinationUid}" data-searchbar-target="destinationUid"/>
|
||||
<input type="hidden" name="destinationType" value="{searchParams.destinationType}" data-searchbar-target="destinationType"/>
|
||||
<input type="hidden" name="priceRange" value="{searchParams.priceRange}" data-searchbar-target="priceRange"/>
|
||||
<div class="searchbar{f:if(condition: collapsed, then: ' reduced hidden')}"
|
||||
<f:if condition="{collapsed}">
|
||||
<f:else>
|
||||
data-controller="sticky"
|
||||
data-sticky-offset-value="650"
|
||||
data-sticky-padding-value="7.5rem"
|
||||
data-sticky-disabled-value="false"
|
||||
data-sticky-classes-value="sticky reduced"
|
||||
</f:else>
|
||||
</f:if>
|
||||
data-searchbar-target="container">
|
||||
<div class="bg-brand-gradient">
|
||||
<div class="reduced:max-w-screen-xl reduced:mx-auto reduced:px-4 flex flex-col reduced:flex-row reduced:items-center">
|
||||
<div class="flex items-start justify-between reduced:flex-1 pt-8 px-8 reduced:px-0 reduced:py-4">
|
||||
|
||||
<div class="flex-1"
|
||||
data-controller="collapse datepicker"
|
||||
data-datepicker-inline-value="true"
|
||||
data-datepicker-date-from-value="{searchParams.dateFrom}"
|
||||
data-datepicker-date-to-value="{searchParams.dateTo}"
|
||||
data-datepicker-min-date-value="{f:format.date(date: 'now', format: 'Y-m-d')}"
|
||||
data-action="collapse:click:outside->collapse#close searchbar:input@window->collapse#close"
|
||||
data-collapse-collapsed-value="true">
|
||||
<div class="relative border-b border-r border-white/50 mr-8">
|
||||
<input type="text"
|
||||
data-action="click->collapse#toggle"
|
||||
class="w-full bg-transparent border-0 ring-0 focus:ring-0 text-white uppercase px-2 py-1 cursor-pointer placeholder-white"
|
||||
placeholder="Zeitraum"
|
||||
aria-label="Zeitraum"
|
||||
data-searchbar-target="dateRangeLabel"/>
|
||||
<svg class="w-6 h-6 text-white absolute right-0 top-0 mr-2 mt-1 cursor-pointer"
|
||||
data-action="click->collapse#toggle">
|
||||
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-calendar"></use>
|
||||
</svg>
|
||||
<div class="hidden absolute z-100 top-100 mt-2 left-0 min-w-full bg-white p-4 shadow-md rounded"
|
||||
data-collapse-target="container">
|
||||
<div class="flex justify-between divide-x divide-zinc-200">
|
||||
<div class="pr-2">
|
||||
<input type="hidden"
|
||||
aria-label="Zeitraum"
|
||||
data-datepicker-target="field">
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col justify-between pl-2">
|
||||
<f:if condition="{settings.datePickerPresets}">
|
||||
<div class="flex flex-col space-y-4">
|
||||
<f:for each="{settings.datePickerPresets}" as="preset">
|
||||
<button type="button"
|
||||
class="button bg-button bg-button--light button--small w-full"
|
||||
data-preset='{preset -> f:format.json() -> f:format.raw()}'
|
||||
data-action="datepicker#selectPreset">
|
||||
<span>
|
||||
{preset.label}
|
||||
</span>
|
||||
</button>
|
||||
</f:for>
|
||||
</div>
|
||||
</f:if>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<button type="button"
|
||||
class="button bg-button bg-button--light button--small w-full"
|
||||
data-action="collapse#close">
|
||||
schließen
|
||||
</button>
|
||||
<button type="button"
|
||||
class="button bg-button bg-button--light button--small w-full"
|
||||
data-action="datepicker#reset">
|
||||
zurücksetzen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-24"
|
||||
data-controller="collapse"
|
||||
data-action="collapse:click:outside->collapse#close searchbar:input@window->collapse#close"
|
||||
data-collapse-collapsed-value="true">
|
||||
<div class="relative border-b border-r border-white/50 mr-8">
|
||||
<input type="text"
|
||||
data-action="click->collapse#toggle"
|
||||
data-searchbar-target="paxLabel"
|
||||
aria-label="Personenzahl"
|
||||
value="{searchParams.pax}"
|
||||
class="w-full bg-transparent border-0 ring-0 focus:ring-0 text-white uppercase px-2 py-1 cursor-pointer"/>
|
||||
<svg class="w-6 h-6 text-white absolute right-0 top-0 mr-2 mt-1 cursor-pointer"
|
||||
data-action="click->collapse#toggle">
|
||||
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-user-group"></use>
|
||||
</svg>
|
||||
<div
|
||||
class="hidden absolute z-20 top-100 mt-2 left-0 bg-white p-2 shadow-md rounded w-full h-48 overflow-y-scroll text-sm leading-6"
|
||||
data-collapse-target="container">
|
||||
<ul class="m-0 p-0">
|
||||
<f:for each="{filterOptions.pax}" as="option">
|
||||
<li>
|
||||
<button type="button"
|
||||
data-action="searchbar#setPax"
|
||||
data-option="{option}"
|
||||
class="w-full text-right">
|
||||
{option}
|
||||
</button>
|
||||
</li>
|
||||
</f:for>
|
||||
<li>
|
||||
<a href="{f:uri.typolink(parameter: settings.groupsPageUid)}"
|
||||
class="block text-right">
|
||||
> 24
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1"
|
||||
data-controller="collapse"
|
||||
data-action="collapse:click:outside->collapse#close searchbar:input@window->collapse#close"
|
||||
data-collapse-collapsed-value="true">
|
||||
<div class="relative border-b border-r border-white/50 mr-8">
|
||||
<input type="text"
|
||||
data-action="click->collapse#toggle"
|
||||
data-searchbar-target="destinationLabel"
|
||||
aria-label="Reiseziel/Konzept"
|
||||
class="w-full bg-transparent border-0 ring-0 focus:ring-0 text-white uppercase px-2 py-1 cursor-pointer placeholder-white"
|
||||
placeholder="Reiseziel/Konzept"/>
|
||||
<svg
|
||||
class="w-6 h-6 text-white absolute right-0 top-0 mr-2 mt-1 transform rotate-45 cursor-pointer"
|
||||
data-action="click->collapse#toggle">
|
||||
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-airplane"></use>
|
||||
</svg>
|
||||
<div
|
||||
class="hidden absolute z-20 top-100 mt-2 left-0 bg-white p-2 shadow-md rounded min-w-full h-64 overflow-y-scroll"
|
||||
data-collapse-target="container">
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<f:for each="{filterOptions.destinations}" as="destination">
|
||||
<div class="col-span-2">
|
||||
<button type="button"
|
||||
class="flex items-center space-x-2"
|
||||
data-action="searchbar#setDestination"
|
||||
data-option-uid="{destination.country.uid}"
|
||||
data-option-type="country"
|
||||
data-option-label="{destination.country.label}">
|
||||
<svg class="block w-8 h-6">
|
||||
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-flag-{destination.country.code -> f:format.case(mode: 'lower')}"></use>
|
||||
</svg>
|
||||
<span>{destination.country.label}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-span-3 flex flex-col items-start space-y-1">
|
||||
<f:for each="{destination.regions}" as="region">
|
||||
<button type="button"
|
||||
data-action="searchbar#setDestination"
|
||||
data-option-uid="{region.uid}"
|
||||
data-option-type="region"
|
||||
data-option-label="{region.label}">
|
||||
{region.label}
|
||||
</button>
|
||||
</f:for>
|
||||
</div>
|
||||
</f:for>
|
||||
</div>
|
||||
<div class="grid grid-cols-5 gap-4 border-t py-2 my-2">
|
||||
<div class="col-span-2">
|
||||
<strong>
|
||||
Konzept
|
||||
</strong>
|
||||
</div>
|
||||
<div class="col-span-3 flex flex-col items-start space-y-1">
|
||||
<f:for each="{filterOptions.concepts}" as="concept">
|
||||
<button type="button"
|
||||
data-action="searchbar#setDestination"
|
||||
data-option-uid="{concept.uid}"
|
||||
data-option-type="concept"
|
||||
data-option-label="{concept.name}">
|
||||
{concept.name}
|
||||
</button>
|
||||
</f:for>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="button bg-button button--small"
|
||||
data-action="searchbar#resetDestination">
|
||||
zurücksetzen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-32"
|
||||
data-controller="collapse"
|
||||
data-action="collapse:click:outside->collapse#close searchbar:input@window->collapse#close"
|
||||
data-collapse-collapsed-value="true">
|
||||
<div class="relative border-r border-b border-white/50">
|
||||
<input type="text"
|
||||
aria-label="Preis"
|
||||
data-action="click->collapse#toggle"
|
||||
data-searchbar-target="priceRangeLabel"
|
||||
class="w-full bg-transparent border-0 ring-0 focus:ring-0 text-white uppercase px-2 py-1 cursor-pointer placeholder-white"
|
||||
placeholder="Preis"/>
|
||||
<svg class="w-6 h-6 text-white absolute right-0 top-0 mr-2 mt-1 cursor-pointer"
|
||||
data-action="click->collapse#toggle">
|
||||
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-euro"></use>
|
||||
</svg>
|
||||
<div
|
||||
class="hidden absolute z-20 top-100 left-0 bg-white p-2 shadow-md w-full h-48 overflow-y-scroll text-sm leading-7"
|
||||
data-collapse-target="container">
|
||||
<ul class="m-0 p-0">
|
||||
<li>
|
||||
<button type="button"
|
||||
data-action="searchbar#setPriceRange"
|
||||
data-option-value=""
|
||||
data-label-value="beliebig"
|
||||
class="w-full text-right">
|
||||
beliebig
|
||||
</button>
|
||||
</li>
|
||||
<f:for each="{filterOptions.priceRanges}" as="range" iteration="iteration">
|
||||
<li>
|
||||
<button type="button"
|
||||
data-action="searchbar#setPriceRange"
|
||||
data-option-value="{range.uid}"
|
||||
data-label-value="{range.formatted}"
|
||||
class="w-full text-right">
|
||||
{range.formatted}
|
||||
</button>
|
||||
</li>
|
||||
</f:for>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="hidden reduced:block reduced:ml-4" title="Suchen">
|
||||
<svg class="w-8 h-8 text-white bg-badge-gradient">
|
||||
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-chevron-right"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-end mt-6 reduced:hidden">
|
||||
<button type="submit"
|
||||
class="w-1/3 text-white uppercase py-2">
|
||||
Detailsuche
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="w-1/3 text-white bg-badge-gradient hover:bg-none hover:bg-ep-pink transition-colors uppercase py-2">
|
||||
Alle Reisen anzeigen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</f:section>
|
||||
|
||||
</html>
|
||||
Reference in new issue
Block a user