WIP Migrate searchbar
This commit is contained in:
@@ -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> </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)}">> 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> </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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user