WIP Migrate searchbar

This commit is contained in:
Björn Fromme
2021-07-27 14:06:12 +02:00
parent 06c67b4fad
commit 1ef361e329
4 changed files with 328 additions and 65 deletions
@@ -25,6 +25,7 @@ import faq from './components/faq'
import osmMap from './components/osm-map'
import productDetail from './components/product-detail'
import daytripDateSelect from './components/daytrip-date-select'
import searchBar from './components/search-bar'
// Executed on document ready
document.addEventListener('DOMContentLoaded', () => {
@@ -50,6 +51,7 @@ document.addEventListener('DOMContentLoaded', () => {
Alpine.data('osmMap', osmMap)
Alpine.data('productDetail', productDetail)
Alpine.data('daytripDateSelect', daytripDateSelect)
Alpine.data('searchBar', searchBar)
Alpine.start()
Glightbox({
@@ -0,0 +1,150 @@
import axios from 'axios'
import flatpickr from 'flatpickr'
import { German } from 'flatpickr/dist/l10n/de'
export default props => ({
picker: null,
optionsUri: props.optionsUri,
resetUri: props.resetUri,
searchParams: props.initialSearchParams,
filterOptions: props.initialFilterOptions,
datePresets: props.datePresets,
show: null,
destinationName: 'beliebig',
conceptSelected: false,
destinationSelected: false,
dateRangeSelected: { from: null, to: null },
dateRangeLabel: null,
init() {
this.$watch('dateRangeSelected', () => {
let dateRangeLabel = ''
if (null === this.dateRangeSelected.from && null === this.dateRangeSelected.to) {
this.searchParams.dateFrom = null
this.searchParams.dateTo = null
} else {
let dateFrom = flatpickr.formatDate(this.dateRangeSelected.from, 'd.m.Y')
let dateTo = flatpickr.formatDate(this.dateRangeSelected.to, 'd.m.Y')
dateRangeLabel = `${dateFrom} - ${dateTo}`
this.searchParams.dateFrom = dateFrom
this.searchParams.dateTo = dateTo
}
this.dateRangeLabel = dateRangeLabel
this.loadOptions()
})
},
loadOptions() {
const data = { 'tx_epproducts_ajax[searchParams]': this.searchParams }
axios({
url: this.optionsUri,
method: 'post',
data
}).then(response => {
this.applyOptions(response.data)
this.show = null
})
},
applyOptions(json) {
this.filterOptions.totalResults = json.totalResults
const filterOptions = json.filterOptions
this.filterOptions.priceRanges = filterOptions.priceRanges
this.filterOptions.pax = filterOptions.pax
this.filterOptions.destinations = filterOptions.destinations
if (filterOptions.dateFrom !== null) {
this.filterOptions.selectableRangeFrom = filterOptions.dateFrom
}
if (filterOptions.dateTo !== null) {
this.filterOptions.selectableRangeTo = filterOptions.dateTo
}
},
updateSearchParam(param, value) {
this.searchParams[param] = value
this.loadOptions()
},
updateDestination(destination) {
if (destination.type === 'concept') {
this.searchParams.conceptUid = destination.uid
} else {
this.searchParams.destinationUid = destination.uid
this.searchParams.destinationType = destination.type
}
this.loadOptions()
},
resetDestination() {
axios({
url: this.resetUri,
method: 'post'
}).then(response => {
this.applyOptions(response.data)
this.searchParams = response.data.searchParams
this.show = null
})
},
selectDateRangePreset(preset) {
let dateFrom = new Date(preset.dateFrom)
let dateTo = new Date(preset.dateTo)
this.dateRangeSelected = { from: dateFrom, to: dateTo }
this.picker.setDate([ dateFrom, dateTo ])
},
resetDateRange () {
this.dateRangeSelected = { from: null, to: null }
this.picker.clear()
this.picker.jumpToDate()
},
initDatePicker() {
this.picker = flatpickr(this.$refs.picker, {
mode: 'range',
dateFormat: 'd.m.Y',
locale: German,
inline: true,
onChange: selectedDates => {
if (selectedDates.length === 2) {
this.dateRangeSelected = { from: selectedDates[0], to: selectedDates[1] }
}
}
})
if (this.filterOptions.minDate && this.filterOptions.maxDate) {
this.picker.set('minDate', new Date(this.filterOptions.minDate))
this.picker.set('maxDate', new Date(this.filterOptions.maxDate))
}
},
priceRangeLabel() {
if (this.searchParams.priceRange === 0) {
return 'beliebig'
}
let range = this.filterOptions.priceRanges[this.searchParams.priceRange]
return this.formatPriceRange(range)
},
formatPriceRange(range) {
let min = range[0];
let max = range[1];
if (min === 1) {
return 'bis ' + max + ' €'
}
if (max === 999) {
return 'ab ' + min + ' €'
}
return min + ' - ' + max + ' €'
},
selectDestination(uid, name, type) {
this.destinationName = name
this.destinationSelected = true
this.conceptSelected = false
this.searchParams.destinationUid = uid
this.searchParams.destinationType = type
this.loadOptions()
},
selectConcept(uid, name) {
this.destinationName = name
this.destinationSelected = false
this.conceptSelected = true
this.searchParams.destinationUid = uid
this.searchParams.destinationType = 'concept'
this.loadOptions()
},
resetDestinationSelect() {
this.destinationName = 'beliebig'
this.destinationSelected = false
this.conceptSelected = false
this.resetDestination()
},
})
@@ -1,3 +1,7 @@
.flatpickr-calendar {
box-shadow: none;
}
.daterange {
padding-bottom: 16px;
}
@@ -5,75 +5,182 @@
<f:layout name="Default"/>
<f:section name="Main">
<div id="searchbar" v-cloak>
<search-bar
options-uri='<ep:uri.ajax controller="AjaxSearchbar" action="options" pageUid="{settings.defaultAjaxUid}" />'
reset-uri='<ep:uri.ajax controller="AjaxSearchbar" action="reset" pageUid="{settings.defaultAjaxUid}" />'
argument-prefix='<ep:argumentPrefix pluginName="ajax" />'
:initial-search-params='{searchParams -> f:format.json() -> f:format.raw()}'
:initial-filter-options='{filterOptions -> f:format.json() -> f:format.raw()}'
:date-presets='{settings.datePickerPresets -> f:format.json() -> f:format.raw()}'
inline-template>
<div class="container searchbar{f:if(condition: fixed, then: ' affix')}"{f:if(condition: fixed, else: ' x-data="sticky(650)" x-bind:class="sticky ? \'affix\': \'\'"')}>
<div class="container">
<f:form action="processSearch" controller="Search" method="post" name="searchParams"
object="{searchParams}" pluginName="searchresult" extensionName="epproducts"
pageUid="{settings.defaultSearchPageUid}" additionalAttributes="{ref: 'form'}">
<f:form.hidden property="destinationUid" additionalAttributes="{v-model: 'searchParams.destinationUid', number: 'number'}"/>
<f:form.hidden property="destinationType" additionalAttributes="{v-model: 'searchParams.destinationType'}"/>
<f:form.hidden property="dateFrom" value="" additionalAttributes="{v-model: 'searchParams.dateFrom'}"/>
<f:form.hidden property="dateTo" value="" additionalAttributes="{v-model: 'searchParams.dateTo'}"/>
<f:form.hidden property="nights" additionalAttributes="{v-model: 'searchParams.nights', number: 'number'}"/>
<f:form.hidden property="pax" additionalAttributes="{v-model: 'searchParams.pax', number: 'number'}"/>
<f:form.hidden property="conceptUid" additionalAttributes="{v-model: 'searchParams.conceptUid', number: 'number'}"/>
<f:form.hidden property="priceRange" additionalAttributes="{v-model: 'searchParams.priceRange', number: 'number'}"/>
<f:form.hidden property="pageUid" additionalAttributes="{v-model: 'searchParams.pageUid', number: 'number'}"/>
<div class="row">
<div class="col-sm-3">
<daterange-select
class="form-group"
@selected="updateDateRange"
:presets="datePresets"
:min-date="filterOptions.dateFrom"
:max-date="filterOptions.dateTo"
></daterange-select>
</div>
<div class="col-sm-1">
<pax-select
@selected="updatePax"
:pax-options="filterOptions.pax"
:pax="searchParams.pax"
groups-uri="{f:uri.typolink(parameter: settings.groupsPageUid)}"
></pax-select>
</div>
<div class="col-sm-4">
<destination-select
class="form-group"
@selected="updateDestination"
@reset="resetDestination"
:destination-options="filterOptions.destinations"
:concept-options="filterOptions.concepts"
></destination-select>
</div>
<div class="col-sm-2">
<price-range-select
@selected="updatePriceRange"
:range-options="filterOptions.priceRanges"
:range="searchParams.priceRange"
></price-range-select>
</div>
<div class="col-sm-2">
<div class="form-group">
<label>&nbsp;</label>
<button type="submit" class="btn btn-warning">Suchen <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span></button>
<button type="submit" class="searchbar__link">Detailsuche</button>
<div id="searchbar" x-data='searchBar({
optionsUri: "<ep:uri.ajax controller="AjaxSearchbar" action="options" pageUid="{settings.defaultAjaxUid}" />",
resetUri: "<ep:uri.ajax controller="AjaxSearchbar" action="reset" pageUid="{settings.defaultAjaxUid}" />",
initialSearchParams: {searchParams -> f:format.json() -> f:format.raw()},
initialFilterOptions: {filterOptions -> f:format.json() -> f:format.raw()},
datePresets: {settings.datePickerPresets -> f:format.json() -> f:format.raw()}
})'>
<div class="container searchbar{f:if(condition: fixed, then: ' affix')}"{f:if(condition: fixed, else: ' x-data="sticky(650)" x-bind:class="sticky ? \'affix\': \'\'"')}>
<div class="container">
<f:form action="processSearch" controller="Search" method="post" name="searchParams"
object="{searchParams}" pluginName="searchresult" extensionName="epproducts"
pageUid="{settings.defaultSearchPageUid}" additionalAttributes="{ref: 'form'}">
<f:form.hidden property="destinationUid" additionalAttributes="{x-model: 'searchParams.destinationUid'}"/>
<f:form.hidden property="destinationType" additionalAttributes="{x-model: 'searchParams.destinationType'}"/>
<f:form.hidden property="dateFrom" value="" additionalAttributes="{x-model: 'searchParams.dateFrom'}"/>
<f:form.hidden property="dateTo" value="" additionalAttributes="{x-model: 'searchParams.dateTo'}"/>
<f:form.hidden property="nights" additionalAttributes="{x-model: 'searchParams.nights'}"/>
<f:form.hidden property="pax" additionalAttributes="{x-model: 'searchParams.pax'}"/>
<f:form.hidden property="conceptUid" additionalAttributes="{x-model: 'searchParams.conceptUid'}"/>
<f:form.hidden property="priceRange" additionalAttributes="{x-model: 'searchParams.priceRange'}"/>
<f:form.hidden property="pageUid" additionalAttributes="{x-model: 'searchParams.pageUid'}"/>
<div class="row">
<div class="col-sm-3">
<div x-init="initDatePicker()">
<label>
Zeitraum
</label>
<div class="relative">
<input type="text"
class="form-control cursor-pointer"
placeholder="von... bis"
x-bind:value="dateRangeLabel"
x-on:click.prevent="show = show === 'dateRange' ? null : 'dateRange'"/>
<div class="absolute top-100 left-0 bg-white p-4 shadow-md" x-show="show === 'dateRange'" x-transition x-cloak>
<template x-if="datePresets">
<div class="flex items-center justify-between pb-2">
<template x-for="preset in datePresets">
<a class="daterange__preset" href="#"
x-on:click.prevent="selectDateRangePreset(preset)">
<i class="fa fa-calendar"></i> <span x-text="preset.label"></span>
</a>
</template>
</div>
</template>
<input x-ref="picker" type="hidden">
<div class="flex items-center justify-between pt-2">
<button class="daterange__button focus:outline-none" x-on:click.prevent="show = null">
<i class="fa fa-times"></i> schließen
</button>
<button class="daterange__button focus:outline-none" x-on:click.prevent="resetDateRange()">
<i class="fa fa-trash-o"></i> zurücksetzen
</button>
</div>
</div>
</div>
</div>
</div>
</f:form>
</div>
<div class="col-sm-1">
<div class="form-group">
<label>
Personen
</label>
<div class="relative">
<input type="text"
class="form-control cursor-pointer"
x-bind:value="searchParams.pax"
x-on:click.prevent="show = show === 'pax' ? null : 'pax'"/>
<div class="absolute top-100 left-0 bg-white p-4 shadow-md w-full h-48 overflow-y-scroll" x-show="show === 'pax'" x-transition x-cloak>
<ul class="m-0 p-0">
<template x-for="paxOption in filterOptions.pax">
<li>
<a href="#" x-on:click.prevent="updateSearchParam('pax', paxOption)" x-text="paxOption"></a>
</li>
</template>
<li><a href="{f:uri.typolink(parameter: settings.groupsPageUid)}">&gt; 20</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="destinationselect">
<label class="destinationselect__label">
Reiseziel
</label>
<div class="relative">
<input type="text"
class="form-control cursor-pointer"
x-bind:value="destinationName"
x-on:click.prevent="show = show === 'destination' ? null : 'destination'"/>
<div class="absolute top-100 left-0 bg-white p-4 shadow-md min-w-full h-48 overflow-y-scroll" x-show="show === 'destination'" x-transition x-cloak>
<template x-if="filterOptions.destinations && !conceptSelected">
<template x-for="destination in filterOptions.destinations">
<div class="flex items-start justify-between">
<div class="w-1/2">
<a href="#" x-on:click.prevent="selectDestination(destination.country.uid, destination.country.label, 'country')">
<img class="destination-pulldown__flag" x-bind:src="destination.country.flag" x-bind:alt="destination.country.label">
<strong x-text="destination.country.label"></strong>
</a>
</div>
<div class="w-1/2">
<ul class="plain-list">
<template x-for="region in destination.regions">
<li>
<a href="#" x-on:click.prevent="selectDestination(region.uid, region.label, 'region')" x-text="region.label"></a>
</li>
</template>
</ul>
</div>
</div>
</template>
</template>
<template x-if="filterOptions.concepts && !destinationSelected">
<div class="flex items-start justify-between">
<div class="w-1/2">
<strong>Konzept</strong>
</div>
<div class="w-1/2">
<ul class="plain-list">
<template x-for="concept in filterOptions.concepts">
<li>
<a href="#" x-on:click.prevent="selectConcept(concept.uid, concept.name)" x-text="concept.name"></a>
</li>
</template>
</ul>
</div>
</div>
</template>
<template x-if="destinationSelected || conceptSelected">
<button x-on:click.prevent="resetDestinationSelect()">
<i class="fa fa-trash-o"></i>
zurücksetzen
</button>
</template>
</div>
</div>
</div>
</div>
<div class="col-sm-2">
<div class="form-group">
<label>
Preis
</label>
<div class="relative">
<input type="text"
class="form-control cursor-pointer"
x-bind:value="priceRangeLabel()"
x-on:click.prevent="show = show === 'priceRange' ? null : 'priceRange'"/>
<div class="absolute top-100 left-0 bg-white p-4 shadow-md w-full h-48 overflow-y-scroll" x-show="show === 'priceRange'" x-transition x-cloak>
<ul class="m-0 p-0">
<li>
<a href="#" x-on:click.prevent="resetPriceRange()">beliebig</a>
</li>
<template x-for="(range, index) in filterOptions.priceRanges">
<li>
<a href="#"
x-on:click.prevent="updateSearchParam('priceRange', index)"
x-text="formatPriceRange(range)"></a>
</li>
</template>
</ul>
</div>
</div>
</div>
</div>
<div class="col-sm-2">
<div class="form-group">
<label>&nbsp;</label>
<button type="submit" class="btn btn-warning">Suchen <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span></button>
<button type="submit" class="searchbar__link">Detailsuche</button>
</div>
</div>
</div>
</f:form>
</div>
</search-bar>
</div>
</div>
</f:section>