WIP Migrate to stimulus.js

This commit is contained in:
Björn Fromme
2021-08-16 13:07:33 +02:00
parent 01a9d6123f
commit 6db1cb23b3
14 changed files with 206 additions and 128 deletions
@@ -14,8 +14,6 @@ import Alpine from 'alpinejs'
import parseAppConfig from './components/appconfig' import parseAppConfig from './components/appconfig'
import initSliders from './components/sliders' import initSliders from './components/sliders'
import appendPaCode from './components/pacode' import appendPaCode from './components/pacode'
import watchlistToggle from './components/watchlist-toggle'
import watchlistButton from './components/watchlist-button'
import watchlist from './components/watchlist' import watchlist from './components/watchlist'
import productDetail from './components/product-detail' import productDetail from './components/product-detail'
import daytripDateSelect from './components/daytrip-date-select' import daytripDateSelect from './components/daytrip-date-select'
@@ -23,30 +21,10 @@ import searchBar from './components/search-bar'
import calendar from './components/calendar' import calendar from './components/calendar'
import contactForm from './components/contact-form' import contactForm from './components/contact-form'
import defaultFilterSettings from './_filtersettings'
import defaultFilterOptions from './_filteroptions'
const appConfig = parseAppConfig() const appConfig = parseAppConfig()
const storedWatchlist = JSON.parse(sessionStorage.getItem('myep-watchlist')) || []
appendPaCode(appConfig) appendPaCode(appConfig)
initSliders() initSliders()
Alpine.store('appconfig', appConfig) Alpine.store('appconfig', appConfig)
Alpine.store('show', {
searchBox: false,
mobileNav: false,
modal: null,
toggleMobileNav() {
this.mobileNav = !this.mobileNav
},
toggleSearchBox() {
this.searchBox = !this.searchBox
}
})
Alpine.store('watchlist', storedWatchlist)
Alpine.store('filterSettings', defaultFilterSettings)
Alpine.store('filterOptions', defaultFilterOptions)
Alpine.data('watchlistToggle', watchlistToggle)
Alpine.data('watchlistButton', watchlistButton)
Alpine.data('watchlist', watchlist) Alpine.data('watchlist', watchlist)
Alpine.data('productDetail', productDetail) Alpine.data('productDetail', productDetail)
Alpine.data('daytripDateSelect', daytripDateSelect) Alpine.data('daytripDateSelect', daytripDateSelect)
@@ -1,8 +0,0 @@
export default () => ({
show() {
return this.$store.watchlist.length > 0
},
label() {
return `Merkliste (${this.$store.watchlist.length})`
}
})
@@ -1,20 +0,0 @@
export default uid => ({
uid,
trigger: {
['@click.prevent']() {
const watchlistIndex = this.$store.watchlist.indexOf(this.uid)
if (-1 !== watchlistIndex) {
this.$store.watchlist.splice(watchlistIndex, 1)
} else {
this.$store.watchlist.push(this.uid)
}
sessionStorage.setItem('myep-watchlist', JSON.stringify(this.$store.watchlist))
}
},
onList() {
return -1 !== this.$store.watchlist.indexOf(this.uid)
},
label() {
return this.onList() ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen'
},
})
@@ -0,0 +1,32 @@
import { Controller } from 'stimulus'
import { useDispatch } from 'stimulus-use'
export default class extends Controller {
static values = {
id: String,
show: Boolean
}
static targets = [ 'overlay' ]
initialize() {
useDispatch(this)
}
invokeToggle() {
this.dispatch('toggle', this.idValue)
}
toggle(e) {
if (e.detail === this.idValue) {
this.showValue = !this.showValue
}
}
showValueChanged() {
if (this.hasOverlayTarget) {
this.overlayTarget.classList.toggle('hidden', this.showValue === false)
}
}
}
@@ -23,9 +23,19 @@ export default class extends Controller {
} }
filter() { filter() {
this.load({
method: 'POST',
body: new FormData(this.formTarget)
})
}
reset() {
this.load({ method: 'POST' })
}
load(options) {
this.loadingValue = true this.loadingValue = true
let data = new FormData(this.formTarget) fetch(this.uriValue, options)
fetch(this.uriValue, { method: 'POST', body: data })
.then(this.checkStatus) .then(this.checkStatus)
.then(this.parseHTML) .then(this.parseHTML)
.then(html => { .then(html => {
@@ -37,10 +47,6 @@ export default class extends Controller {
}) })
} }
reset() {
this.filter()
}
selectDateRange(e) { selectDateRange(e) {
this.dateFromTarget.value = e.detail.from this.dateFromTarget.value = e.detail.from
this.dateToTarget.value = e.detail.to this.dateToTarget.value = e.detail.to
@@ -0,0 +1,34 @@
import { Controller } from 'stimulus'
export default class extends Controller {
static values = { count: Number }
static targets = [ 'label' ]
connect() {
this.watchlist = JSON.parse(sessionStorage.getItem('myep-watchlist')) || []
this.countValue = this.watchlist.length
}
update(e) {
let key = e.detail.key
let index = this.watchlist.indexOf(key)
if (-1 !== index) {
this.watchlist.splice(index, 1)
} else {
this.watchlist.push(key)
}
this.countValue = this.watchlist.length
sessionStorage.setItem('myep-watchlist', JSON.stringify(this.watchlist))
}
countValueChanged() {
if (0 === this.countValue) {
this.element.classList.add('hidden')
} else {
this.element.classList.remove('hidden')
this.labelTarget.innerText = `Merkliste (${this.watchlist.length})`
}
}
}
@@ -0,0 +1,34 @@
import { Controller } from 'stimulus'
import { useDispatch } from 'stimulus-use'
export default class extends Controller {
static values = {
key: String,
onList: Boolean
}
static targets = [ 'label', 'icon' ]
initialize() {
useDispatch(this)
}
connect() {
let watchlist = JSON.parse(sessionStorage.getItem('myep-watchlist')) || []
this.onListValue = -1 !== watchlist.indexOf(this.keyValue)
}
toggle() {
this.onListValue = !this.onListValue
this.dispatch('toggle', {
key: this.keyValue,
onList: this.onListValue,
})
}
onListValueChanged() {
this.labelTarget.setAttribute('aria-label', true === this.onListValue ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen')
this.iconTarget.setAttribute('href', true === this.onListValue ? '#icon-minus-circle' : '#icon-plus-circle')
}
}
@@ -18,6 +18,7 @@
<f:render partial="ContactBar" arguments="{_all}"/> <f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/> <f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" arguments="{_all}"/> <f:render partial="Mobilenav" arguments="{_all}"/>
<f:cObject typoscriptObjectPath="lib.searchBox"/>
<f:render partial="Config" arguments="{_all}" /> <f:render partial="Config" arguments="{_all}" />
<f:render partial="Icons" arguments="{_all}"/> <f:render partial="Icons" arguments="{_all}"/>
</html> </html>
@@ -6,15 +6,13 @@
<a href="{f:uri.typolink(parameter: settings.watchlistPageUid)}" <a href="{f:uri.typolink(parameter: settings.watchlistPageUid)}"
class="contact-bar__item" class="contact-bar__item"
title="Merkliste" title="Merkliste"
x-data="watchlistButton" data-controller="watchlist-label"
x-show="show" data-action="watchlist-toggle:toggle@window->watchlist-label#update"
x-transition
x-cloak
rel="nofollow"> rel="nofollow">
<svg class="contact-bar__icon"> <svg class="contact-bar__icon">
<use href="#icon-heart"></use> <use href="#icon-heart"></use>
</svg> </svg>
<div class="contact-bar__label" x-text="label"></div> <div class="contact-bar__label" data-watchlist-label-target="label"></div>
</a> </a>
<a href="tel:{settings.phoneNumberLink}" <a href="tel:{settings.phoneNumberLink}"
class="contact-bar__item" class="contact-bar__item"
@@ -18,14 +18,20 @@
<div class="flex lg:hidden items-center space-x-4 pb-4" x-data> <div class="flex lg:hidden items-center space-x-4 pb-4" x-data>
<f:if condition="{data.tx_eptheme_hide_searchbar}"> <f:if condition="{data.tx_eptheme_hide_searchbar}">
<f:else> <f:else>
<button x-on:click.prevent="$store.show.toggleSearchBox()" class="focus:outline-none text-gray-800"> <button class="focus:outline-none text-gray-800"
data-controller="overlay-toggle"
data-overlay-toggle-id-value="search"
data-action="overlay-toggle#invokeToggle">
<svg class="w-8 h-8 text-gray-800"> <svg class="w-8 h-8 text-gray-800">
<use href="#icon-search"></use> <use href="#icon-search"></use>
</svg> </svg>
</button> </button>
</f:else> </f:else>
</f:if> </f:if>
<button x-on:click.prevent="$store.show.toggleMobileNav()" class="focus:outline-none text-gray-800"> <button class="focus:outline-none text-gray-800"
data-controller="overlay-toggle"
data-overlay-toggle-id-value="mobilenav"
data-action="overlay-toggle#invokeToggle">
<svg class="w-8 h-8 text-gray-800"> <svg class="w-8 h-8 text-gray-800">
<use href="#icon-menu"></use> <use href="#icon-menu"></use>
</svg> </svg>
@@ -3,19 +3,14 @@
xmlns:v="http://typo3.org/ns/FluidTYPO3/Vhs/ViewHelpers" xmlns:v="http://typo3.org/ns/FluidTYPO3/Vhs/ViewHelpers"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers"> xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<div class="fixed lg:hidden top-0 left-0 inset-0 w-screen h-screen z-100 overflow-y-scroll bg-cover bg-gray-700 transform" <div class="fixed lg:hidden top-0 left-0 inset-0 w-screen h-screen z-100 overflow-y-scroll bg-cover bg-gray-700 transform hidden"
x-data data-controller="overlay-toggle"
x-cloak data-overlay-toggle-id-value="mobilenav"
x-show="$store.show.mobileNav" data-overlay-toggle-target="overlay"
x-transition:enter="transition ease-out duration-300" data-action="overlay-toggle:toggle@window->overlay-toggle#toggle">
x-transition:enter-start="-translate-y-full"
x-transition:enter-end="translate-y-0"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="translate-y-0"
x-transition:leave-end="-translate-y-full">
<div class="relative bg-ep-primary"> <div class="relative bg-ep-primary">
<div class="fixed top-0 left-0 inset-x-0 px-8 py-2 flex justify-end bg-ep-primary"> <div class="fixed top-0 left-0 inset-x-0 px-8 py-2 flex justify-end bg-ep-primary">
<button class="focus:outline-none" x-on:click="$store.show.toggleMobileNav()"> <button class="focus:outline-none" data-action="overlay-toggle#invokeToggle">
<svg class="w-10 h-10 text-white"> <svg class="w-10 h-10 text-white">
<use href="#icon-close"></use> <use href="#icon-close"></use>
</svg> </svg>
@@ -142,10 +142,16 @@
</f:link.action> </f:link.action>
</div> </div>
</div> </div>
<button class="teaserbox-watchlist" x-data="watchlistToggle('{item.key}')" x-bind="trigger"> <button class="teaserbox-watchlist"
<span data-microtip-position="top" x-bind:aria-label="label" role="tooltip"> data-controller="watchlist-toggle"
<svg class="w-5 h-5"> data-watchlist-toggle-key-value="{item.key}"
<use x-bind:href="onList() ? '#icon-minus-circle' : '#icon-plus-circle'"></use> data-action="watchlist-toggle#toggle">
<span data-microtip-position="top"
role="tooltip"
aria-label="auf die Merkliste setzen"
data-watchlist-toggle-target="label">
<svg class="w-5 h-5 text-ep-secondary">
<use href="#icon-plus-circle" data-watchlist-toggle-target="icon"></use>
</svg> </svg>
</span> </span>
</button> </button>
@@ -23,15 +23,19 @@
<div class="teaserbox teaserbox--landscape teaserbox--searchresult searchresult-item"> <div class="teaserbox teaserbox--landscape teaserbox--searchresult searchresult-item">
<div class="teaserbox__image"> <div class="teaserbox__image">
<img src="{item.country.flag}" class="teaserbox__flag" alt="{item.country.name}"/> <img src="{item.country.flag}" class="teaserbox__flag" alt="{item.country.name}"/>
<f:link.action action="detail" controller="Product" extensionName="epproducts" pluginName="product_detail" <f:link.action action="detail" controller="Product" extensionName="epproducts"
pluginName="product_detail"
arguments="{product: item.product.uid, hotel: item.hotel.uid}" arguments="{product: item.product.uid, hotel: item.hotel.uid}"
additionalParams="{ref: referrer}" additionalParams="{ref: referrer}"
pageUid="{item.product.detailPage}" absolute="1" pageUid="{item.product.detailPage}" absolute="1"
rel="nofollow" title="Details &amp; Buchen"> rel="nofollow" title="Details &amp; Buchen">
<picture class="searchresult-item__image"> <picture class="searchresult-item__image">
<source media="(min-width: 1201px)" data-srcset="{item.hotel.images.resized.m.0.url} 260w"/> <source media="(min-width: 1201px)"
<source media="(max-width: 1200px)" data-srcset="{item.hotel.images.resized.l.0.url} 280w"/> data-srcset="{item.hotel.images.resized.m.0.url} 260w"/>
<source media="(max-width: 768px)" data-srcset="{item.hotel.images.resized.s.0.url} 220w"/> <source media="(max-width: 1200px)"
data-srcset="{item.hotel.images.resized.l.0.url} 280w"/>
<source media="(max-width: 768px)"
data-srcset="{item.hotel.images.resized.s.0.url} 220w"/>
<img class="scale lazyload" data-src="{item.hotel.images.resized.s[0].url}" <img class="scale lazyload" data-src="{item.hotel.images.resized.s[0].url}"
title="{item.hotel.images.resized.s.0.title}" title="{item.hotel.images.resized.s.0.title}"
alt="{item.hotel.images.resized.s.0.alt}"> alt="{item.hotel.images.resized.s.0.alt}">
@@ -102,7 +106,8 @@
<f:if condition="{item.dates -> f:count()} > 0"> <f:if condition="{item.dates -> f:count()} > 0">
<f:if condition="{item.daytrip}"> <f:if condition="{item.daytrip}">
<f:else> <f:else>
<f:link.action action="detail" controller="Product" extensionName="epproducts" pluginName="product_detail" <f:link.action action="detail" controller="Product"
extensionName="epproducts" pluginName="product_detail"
arguments="{product: item.product.uid, hotel: item.hotel.uid}" arguments="{product: item.product.uid, hotel: item.hotel.uid}"
additionalParams="{ref: referrer}" additionalParams="{ref: referrer}"
pageUid="{item.product.detailPage}" absolute="1" pageUid="{item.product.detailPage}" absolute="1"
@@ -121,7 +126,8 @@
</div> </div>
<div class="teaserbox__aside__bottom"> <div class="teaserbox__aside__bottom">
<span class="searchresult-item__price">ab {item.minPrice} €</span> <span class="searchresult-item__price">ab {item.minPrice} €</span>
<f:link.action action="detail" controller="Product" extensionName="epproducts" pluginName="product_detail" <f:link.action action="detail" controller="Product" extensionName="epproducts"
pluginName="product_detail"
arguments="{product: item.product.uid, hotel: item.hotel.uid}" arguments="{product: item.product.uid, hotel: item.hotel.uid}"
additionalParams="{ref: referrer}" additionalParams="{ref: referrer}"
pageUid="{item.product.detailPage}" absolute="1" pageUid="{item.product.detailPage}" absolute="1"
@@ -131,13 +137,19 @@
</f:link.action> </f:link.action>
</div> </div>
</div> </div>
<a href="#" class="teaserbox-watchlist" x-data="watchlistToggle('{item.key}')" x-bind="trigger"> <button class="teaserbox-watchlist"
<span data-microtip-position="top" x-bind:aria-label="label" role="tooltip"> data-controller="watchlist-toggle"
<svg class="w-5 h-5"> data-watchlist-toggle-key-value="{item.key}"
<use x-bind:href="onList() ? '#icon-minus-circle' : '#icon-plus-circle'"></use> data-action="watchlist-toggle#toggle">
<span data-microtip-position="top"
role="tooltip"
aria-label="auf die Merkliste setzen"
data-watchlist-toggle-target="label">
<svg class="w-5 h-5 text-ep-secondary">
<use href="#icon-plus-circle" data-watchlist-toggle-target="icon"></use>
</svg> </svg>
</span> </span>
</a> </button>
</div> </div>
</f:for> </f:for>
</div> </div>
@@ -5,15 +5,19 @@
<f:layout name="Default"/> <f:layout name="Default"/>
<f:section name="Main"> <f:section name="Main">
<div class="searchbox hidden-lg" x-show="$store.show.searchBox" x-transition x-data='searchBar({ <div class="fixed lg:hidden top-0 left-0 inset-0 w-screen h-screen z-100 overflow-y-scroll bg-cover bg-gray-700 transform hidden-lg hidden"
<f:format.raw> data-controller="overlay-toggle"
uri: "{ep:uri.ajax(controller: "AjaxSearchbar", action: "update", pageUid: settings.defaultAjaxUid)}", data-overlay-toggle-id-value="search"
initialSearchParams: {searchParams -> f:format.json()}, data-overlay-toggle-target="overlay"
initialFilterOptions: {filterOptions -> f:format.json()}, data-action="overlay-toggle:toggle@window->overlay-toggle#toggle">
datePresets: {settings.datePickerPresets -> f:format.json()} <div class="relative bg-ep-primary">
</f:format.raw> <div class="fixed top-0 left-0 inset-x-0 px-8 py-2 flex justify-end bg-ep-primary">
})'> <button class="focus:outline-none" data-action="overlay-toggle#invokeToggle">
<div class="container"> <svg class="w-10 h-10 text-white">
<use href="#icon-close"></use>
</svg>
</button>
</div>
<f:form action="processSearch" controller="Search" method="post" name="searchParams" <f:form action="processSearch" controller="Search" method="post" name="searchParams"
object="{searchParams}" pluginName="searchresult" extensionName="epproducts" object="{searchParams}" pluginName="searchresult" extensionName="epproducts"
pageUid="{settings.defaultSearchPageUid}" class="form-horizontal"> pageUid="{settings.defaultSearchPageUid}" class="form-horizontal">