Replace tooltips and page scrolling, migrate faq filter

This commit is contained in:
Björn Fromme
2021-07-27 14:06:11 +02:00
parent 7168e05852
commit 1f98ea5b2e
15 changed files with 173 additions and 225 deletions
+11
View File
@@ -4819,6 +4819,11 @@
"picomatch": "^2.2.3"
}
},
"microtip": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/microtip/-/microtip-0.2.2.tgz",
"integrity": "sha512-oah38eH5vSHVFP6yXjbKWOYt92mav++0j3zh844h1vhOscqEg7Rf4agDEIwUTFCcAPPdlhYUQMe6eZfHgD+4oQ=="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -7004,6 +7009,12 @@
"resolved": "https://registry.npmjs.org/slick-carousel/-/slick-carousel-1.8.1.tgz",
"integrity": "sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA=="
},
"smoothscroll-polyfill": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
"dev": true
},
"sockjs": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz",
+3 -1
View File
@@ -23,6 +23,7 @@
"jquery": "^1.12.4",
"lazysizes": "^4.1.8",
"leaflet": "^1.7.1",
"microtip": "^0.2.2",
"normalize.css": "^7.0.0",
"picturefill": "^3.0.3",
"sass-mq": "^3.3.2",
@@ -39,9 +40,9 @@
},
"devDependencies": {
"@babel/plugin-proposal-object-rest-spread": "^7.14.5",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.14.5",
"@symfony/webpack-encore": "^1.5.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"autoprefixer": "^9.4.6",
"core-js": "^3.15.0",
"file-loader": "^6.2.0",
@@ -49,6 +50,7 @@
"postcss": "^8.3.5",
"postcss-loader": "^5.3.0",
"sass-loader": "^9.0.3",
"smoothscroll-polyfill": "^0.4.4",
"vue-loader": "^15.9.7",
"vue-template-compiler": "^2.6.14"
},
@@ -6,6 +6,51 @@ const application = Application.start()
const context = require.context('./controllers', true, /\.js$/)
application.load(definitionsFromContext(context))
// Polyfills
import smoothscroll from 'smoothscroll-polyfill'
smoothscroll.polyfill()
// Function to parse config values from JSON data in HTML source
const parseAppConfig = () => {
const configElement = document.getElementById('appconfig');
if (null === configElement) {
return {}
}
return JSON.parse(configElement.innerHTML);
}
// Function to append pacode parameter to urls specified in config by domain
const appendPaCode = appConfig => {
const paCode = appConfig.paCode
const externalDomains = appConfig.externalDomains
if (!paCode) {
return
}
document.querySelectorAll('a').forEach(element => {
if (element.hostname === location.hostname) {
return
}
if (externalDomains.indexOf(element.hostname) === -1) {
return
}
let url = element.href
if (!url) {
return
}
if (url.indexOf('agenturcode=') !== -1) {
return
}
const separator = url.indexOf('?') === -1 ? '?' : '&'
url += separator + 'agenturcode=' + paCode
element.href = url
})
}
// Executed on document ready
document.addEventListener('DOMContentLoaded', () => {
const appConfig = parseAppConfig()
appendPaCode(appConfig)
})
@@ -16,12 +61,10 @@ import $ from 'jquery'
import dayjs from 'dayjs'
import 'dayjs/locale/de'
import '@fancyapps/fancybox'
import axios from 'axios'
global.$ = global.jQuery = $;
require('cookieconsent');
require('slick-carousel');
require('lazysizes');
require('lazysizes/plugins/unveilhooks/ls.unveilhooks');
require('bootstrap-sass/assets/javascripts/bootstrap');
@@ -44,9 +87,6 @@ import HotelList from './components/HotelList.vue'
// Define global constants
const $window = $(window);
const sliderPrevArrow = '<button class="slider-pagination__arrow slider-pagination__arrow--prev"><i class="fa fa-2x fa-chevron-left"></i></button>';
const sliderNextArrow = '<button class="slider-pagination__arrow slider-pagination__arrow--next"><i class="fa fa-2x fa-chevron-right"></i></button>';
const sliderDot = '<button class="slider-pagination__dot" data-role="none" role="button" tabindex="0" />';
// Configure fancybox
$.fancybox.defaults.iframe.css.width = '800px';
@@ -82,8 +122,6 @@ new Vue({
data () {
return {
maxWindowWidth: 991,
lastScrollPosition: 0,
scrollTopVisible: false,
searchBoxVisible: false,
navbarLocked: false,
searchbarFixed: false,
@@ -105,28 +143,16 @@ new Vue({
this.scroll(0);
},
scrollTo (anchorId) {
const top = $(anchorId).offset().top - this.getHeaderOffset();
const top = $(anchorId).offset().top;
this.scroll(top);
},
scroll (top) {
this.navbarLocked = true;
$('html, body').stop().animate(
{ scrollTop: top },
650,
() => {
this.navbarLocked = false;
}
);
},
getHeaderOffset () {
return $('.searchbar').hasClass('affix') ? 260 : 120;
window.scrollTo({ top: top, behavior: 'smooth' })
},
toggleSearchbox () {
this.searchBoxVisible = !this.searchBoxVisible;
},
hideSearchbox () {
this.searchBoxVisible = false;
},
toggleMobileNavigation () {
$('[data-mobile-menu]').collapse('toggle');
},
@@ -145,103 +171,6 @@ new Vue({
$(this).css('backgroundImage', 'url(' + imageSource + ')');
});
},
lazyLoadBackgroundImages () {
document.addEventListener('lazybeforeunveil', e => {
const bg = e.target.getAttribute('data-bg');
if (bg) {
e.target.style.backgroundImage = 'url(' + bg + ')';
}
});
},
initCarousels () {
// let $carouselItemWrappers = $('.slider--teasers > .slider__items').not('.slick-initialized');
// let initCallback = this.initTeaserSlider;
// $.each($carouselItemWrappers, function () {
// initCallback($(this));
// });
// $carouselItemWrappers = $('.slider--thirds > .slider__items').not('.slick-initialized');
// $.each($carouselItemWrappers, function () {
// let numberOfSlides = $(this).children().length;
// $(this).slick({
// accessibility: false, // This is a hack to avoid a slick carousel bug. see: https://github.com/kenwheeler/slick/issues/1407#issuecomment-325228683
// mobileFirst: true,
// infinite: false,
// slidesToShow: 1.15,
// slidesToScroll: 1,
// arrows: false,
// dots: false,
// responsive: [
// {
// breakpoint: 1200,
// settings: {
// slidesToShow: 3,
// arrows: numberOfSlides > 3,
// prevArrow: sliderPrevArrow,
// nextArrow: sliderNextArrow
// }
// },
// {
// breakpoint: 992,
// settings: {
// slidesToShow: 2.75,
// }
// },
// {
// breakpoint: 640,
// settings: {
// slidesToShow: 2.5,
// }
// },
// {
// breakpoint: 384,
// settings: {
// slidesToShow: 1.75,
// }
// }
// ]
// });
// });
},
initTeaserSlider ($element) {
let maxNumberOfSlides = $element.parent('.slider').hasClass('in-column') ? 3 : 4;
$element.slick({
accessibility: false, // This is a hack to avoid a slick carousel bug. see: https://github.com/kenwheeler/slick/issues/1407#issuecomment-325228683
mobileFirst: true,
infinite: false,
slidesToShow: 1.15,
slidesToScroll: 1,
arrows: false,
responsive: [
{
breakpoint: 1200,
settings: {
slidesToShow: maxNumberOfSlides,
arrows: true,
prevArrow: sliderPrevArrow,
nextArrow: sliderNextArrow
}
},
{
breakpoint: 992,
settings: {
slidesToShow: maxNumberOfSlides - 0.5,
}
},
{
breakpoint: 640,
settings: {
slidesToShow: 2.5,
}
},
{
breakpoint: 384,
settings: {
slidesToShow: 1.75,
}
}
]
});
},
initDropdownMenuBehavior () {
const $dropdownLinks = $('.navbar-main').find('a.dropdown-toggle');
if (this.mobileMode) {
@@ -254,11 +183,6 @@ new Vue({
});
}
},
initTooltips () {
if (!this.mobileMode) {
$('.tooltiptoggle').tooltip();
}
},
initFilterpanel () {
const $filterPanel = $('#filterpanel');
if (this.mobileMode) {
@@ -267,33 +191,9 @@ new Vue({
$filterPanel.addClass('in');
}
},
initPartnerIdSelect () {
$('[data-force-partnerid-select]').on('click', '.booking-link', function (e) {
e.preventDefault();
});
},
initFaqFilter () {
const $entries = $('[data-faq-entry]');
$('[data-faqfilter]').on('keyup', function () {
const query = $(this).val();
if (query.length < 3) {
$entries.fadeIn();
} else {
const pattern = new RegExp(query, 'i');
$entries.each(function () {
const question = $(this).children('[data-faq-question]').text();
const answer = $(this).children('[data-faq-answer]').text();
if (!question.match(pattern) && !answer.match(pattern)) {
$(this).fadeOut();
}
});
}
});
},
initListeners () {
$window.on('resize', () => {
this.setMobileMode();
this.initCarousels();
this.initDropdownMenuBehavior();
let newBackgroundImageMode = this.getBackgroundImageMode();
if (this.backgroundImageMode !== newBackgroundImageMode) {
@@ -301,12 +201,6 @@ new Vue({
this.setResponsiveBackgroundImages();
}
});
$window.on('scroll', () => {
this.scrollTopVisible = $window.scrollTop() > $window.height()/3;
if ($window.scrollTop() >= 0) {
this.lastScrollPosition = $window.scrollTop();
}
});
$(document).on('click', event => {
const $clickedElement = $(event.target);
const isNavExpanded = $('#ep-navbar-burger-menu').hasClass('collapse in');
@@ -322,48 +216,16 @@ new Vue({
this.searchbarFixed = false;
});
},
appendPaCode () {
const paCode = this.$store.state.config.paCode;
const externalDomains = this.$store.state.config.externalDomains;
if (!paCode) {
return;
}
$('a').each(function () {
if (this.hostname === location.hostname) {
return;
}
if (externalDomains.indexOf(this.hostname) === -1) {
return;
}
let url = $(this).attr('href');
if (!url) {
return;
}
if (url.indexOf('agenturcode=') !== -1) {
return;
}
const separator = url.indexOf('?') === -1 ? '?' : '&';
url += separator + 'agenturcode=' + paCode;
$(this).attr('href', url);
});
},
},
created () {
this.$store.dispatch('init');
EventBus.$on('ajaxContentLoaded', () => {
Vue.nextTick(() => {
this.initTooltips();
this.initFilterpanel();
this.appendPaCode();
});
});
EventBus.$on('tableLoaded', () => {
Vue.nextTick(() => {
this.initTooltips();
});
});
EventBus.$on('scrollTo', (anchorId, offset) => {
this.scrollTo(anchorId, offset);
document.getElementById(anchorId).scrollIntoView({ behavior: 'smooth' })
});
},
mounted () {
@@ -371,15 +233,9 @@ new Vue({
this.setMobileMode();
this.setBackgroundImageMode();
this.setResponsiveBackgroundImages();
this.lazyLoadBackgroundImages();
this.initListeners();
this.initFilterpanel();
this.initCarousels();
this.initTooltips();
this.initDropdownMenuBehavior();
this.initPartnerIdSelect();
this.initFaqFilter();
this.appendPaCode();
});
}
});
@@ -26,17 +26,17 @@
<td>{{ row.label }}</td>
<td style="white-space: nowrap">ab {{ calculatePrice(row.code) }} &euro;</td>
<td>
<i v-if="row.skipassIncluded" data-placement="top" title="Skipass inklusive" class="fa fa-tag tooltiptoggle"></i>
<i v-if="row.skipassIncluded" aria-label="Skipass inklusive" role="tooltip" data-microtip-position="top" class="fa fa-tag"></i>
<a href="#" data-toggle="modal"
:data-target="'#services' + index">
<i data-placement="top" title="Inklusivleistungen zeigen"
class="fa fa-info-circle tooltiptoggle"></i>
:data-target="'#services' + index"
data-microtip-position="top" aria-label="Inklusivleistungen zeigen" role="tooltip">
<i class="fa fa-info-circle"></i>
</a>
<div class="modal fade" :id="'services' + index" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button button--small" class="close" data-dismiss="modal">
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title">Inklusivleistungen</h5>
@@ -27,7 +27,11 @@
<thead>
<tr>
<th>Termin</th>
<th><i class="fa fa-bed tooltiptoggle" data-placement="top" title="Nächte"></i></th>
<th>
<span data-microtip-position="bottom" aria-label="Nächte" role="tooltip">
<i class="fa fa-bed"></i>
</span>
</th>
<th>Preis ab</th>
<th>inkl.</th>
<th class="hidden-xs"></th>
@@ -59,14 +63,16 @@
</td>
<td class="dates-table__cell dates-table__cell--icons">
<span class="icon-wrapper hidden-xs hidden-sm">
<i v-if="showBusIcon(row)" data-placement="top" title="Busfahrt inklusive"
class="fa fa-bus tooltiptoggle"></i>
<i v-if="row.dateSkipassIncluded" data-placement="top" :title="row.season === 's' ? 'Bergbahnticket inklusive' : 'Skipass inklusive'"
class="fa fa-tag tooltiptoggle"></i>
<span v-if="showBusIcon(row)" data-microtip-position="top" aria-label="Busfahrt inklusive" role="tooltip">
<i class="fa fa-bus"></i>
</span>
<span v-if="row.dateSkipassIncluded" data-microtip-position="top" :aria-label="row.season === 's' ? 'Bergbahnticket inklusive' : 'Skipass inklusive'" role="tooltip">
<i class="fa fa-tag"></i>
</span>
</span>
<button type="button" class="button button--light button--small hidden-xs tooltiptoggle"
data-toggle="modal" data-placement="top" :data-target="'#dateservices' + index"
title="Alle Inklusivleistungen anzeigen">Leistungen</button>
<button type="button" class="button button--light button--small hidden-xs"
data-toggle="modal" :data-target="'#dateservices' + index"
data-microtip-position="top" aria-label="Alle Inklusivleistungen anzeigen" role="tooltip">Leistungen</button>
<a href="#" data-toggle="modal" :data-target="'#dateservices' + index"
class="hidden-sm hidden-md hidden-lg"><i class="fa fa-info-circle"></i></a>
</td>
@@ -53,21 +53,25 @@
</td>
<td style="white-space: nowrap" :style="{'text-decoration': !row.roomAvailable && !row.isHideBookingButton ? 'line-through' : ''}">{{ calculatePrice(row) }} </td>
<td>
<i v-if="showBusIcon(row)" data-placement="top" title="Busfahrt inklusive" class="fa fa-bus tooltiptoggle"></i>
<i v-if="row.dateSkipassIncluded" data-placement="top" :title="row.season === 's' ? 'Bergbahnticket inklusive' : 'Skipass inklusive'" class="fa fa-tag tooltiptoggle"></i>
<span v-if="showBusIcon(row)" data-microtip-position="top" aria-label="Busfahrt inklusive" role="tooltip">
<i class="fa fa-bus"></i>
</span>
<span v-if="row.dateSkipassIncluded" data-microtip-position="top" :aria-label="row.season === 's' ? 'Bergbahnticket inklusive' : 'Skipass inklusive'" role="tooltip">
<i class="fa fa-tag"></i>
</span>
</td>
<td>
<button v-if="hasOptionalServices(row) && row.roomAvailable" type="button"
class="button button--light button--small hidden-xs tooltiptoggle"
data-toggle="modal" data-placement="top" :data-target="'#optservices' + index"
title="Alle Zusatzleistungen anzeigen">Optionen</button>
class="button button--light button--small hidden-xs"
data-toggle="modal" :data-target="'#optservices' + index"
data-microtip-position="top" aria-label="Alle Zusatzleistungen anzeigen" role="tooltip">Optionen</button>
<a href="#" v-if="hasOptionalServices(row) && row.roomAvailable" class="hidden-sm hidden-md hidden-lg"
data-toggle="modal" :data-target="'#optservices' + index"><i class="fa fa-info-circle"></i></a>
<div class="modal fade" :id="'optservices' + index" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button button--small" class="close" data-dismiss="modal"><span>&times;</span></button>
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
<h5 class="modal-title">Optionale Zusatzleistungen</h5>
</div>
<div class="modal-body">
@@ -1,7 +1,8 @@
<template>
<a href="#" @click.prevent="toggleWatchlist()">
<i class="fa tooltiptoggle" :class="{ 'fa-minus-circle': onWatchlist, 'fa-plus-circle': !onWatchlist }"
data-placement="auto" title="Merkliste +/-"></i>
<span data-microtip-position="top" :aria-label="onWatchlist ? 'von der Merkliste löschen' : 'auf die Merkliste setzen'" role="tooltip">
<i class="fa" :class="{ 'fa-minus-circle': onWatchlist, 'fa-plus-circle': !onWatchlist }"></i>
</span>
<span v-if="layout === 'link'">
{{ onWatchlist ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen' }}
</span>
@@ -9,7 +10,6 @@
</template>
<script>
import { mapGetters } from 'vuex'
import EventBus from '../_bus'
export default {
@@ -0,0 +1,24 @@
import { Controller } from 'stimulus'
export default class extends Controller {
static targets = [ 'entry' ]
apply(e) {
const query = e.target.value
if (query.length < 3) {
this.entryTargets.forEach(entry => {
entry.classList.remove('hidden')
})
return
}
const pattern = new RegExp(query, 'i')
this.entryTargets.forEach(entry => {
const question = entry.childNodes[0].textContent
const answer = entry.childNodes[1].textContent
if (!question.match(pattern) && !answer.match(pattern)) {
entry.classList.add('hidden')
}
});
}
}
@@ -0,0 +1,24 @@
import { Controller } from 'stimulus'
import {useDebounce} from '../mixins/useDebounce'
export default class extends Controller {
static values = { show: Boolean }
connect() {
useDebounce(this)
const handler = this.debounce(() => {
this.showValue = window.scrollY > window.innerHeight/3
}, 250)
window.addEventListener('scroll', handler, { passive: true })
}
scroll(e) {
e.preventDefault()
window.scroll({ top: 0, left: 0, behavior: 'smooth' })
}
showValueChanged() {
this.element.classList.toggle('hidden', false === this.showValue)
}
}
@@ -0,0 +1,11 @@
export const useDebounce = controller => {
Object.assign(controller, {
debounce: (callback, interval) => {
let debounceId
return (...args) => {
clearTimeout(debounceId)
debounceId = setTimeout(() => callback.apply(this, args), interval)
}
}
})
}
@@ -7,7 +7,7 @@ $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
// Reset and dependencies
@import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/print";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/print";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/glyphicons";
// Core CSS
@@ -44,7 +44,7 @@ $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
// Components w/ JavaScript
@import "~bootstrap-sass/assets/stylesheets/bootstrap/modals";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/tooltip";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/tooltip";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/popovers";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/carousel";
@@ -71,6 +71,8 @@
[data-rte-content] {
@import "rte-content";
}
@import "~microtip/microtip";
}
@layer components {
@@ -3,18 +3,18 @@
<div class="container-fluid ep-disturber faq">
<div>
<div>
<div class="container">
<div class="container" data-controller="faq-filter">
<span class="headline headline--3 headline--disturber">Häufig gestellte Fragen</span>
<f:if condition="{filter}">
<div class="faq-filter">
<input type="text" data-faqfilter class="form-control faq-filter__field" placeholder="Suchbegriff">
<input type="text" data-action="faq-filter#apply" class="form-control faq-filter__field" placeholder="Suchbegriff">
</div>
</f:if>
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<f:for each="{faqs}" as="faq" iteration="iteration">
<div class="panel panel-default" data-faq-entry>
<div class="panel panel-default" data-faq-filter-target="entry">
<a class="headline headline--4{f:if(condition: iteration.isFirst, else: ' collapsed')}"
role="button" data-toggle="collapse" data-parent="#accordion" data-faq-question
role="button" data-toggle="collapse" data-parent="#accordion"
href="#faq-{faq.uid}" {f:if(condition: iteration.isFirst, then: 'aria-expanded="true" ')}aria-controls="faq-{faq.uid}">{faq.question}</a>
<div id="faq-{faq.uid}" class="panel-collapse collapse{f:if(condition: iteration.isFirst, then: ' in')}" role="tabpanel" data-faq-answer>
{faq.answer -> f:format.html()}
@@ -91,6 +91,6 @@
</div>
</div>
</div>
<a @click.prevent="scrollTop()" v-show="scrollTopVisible" v-cloak href="#" class="arrowtop">
<button data-controller="scroll-top" data-action="scroll-top#scroll" class="arrowtop hidden">
<i class="fa fa-angle-up"></i>
</a>
</button>
@@ -44,8 +44,16 @@
</f:if>
</td>
<td>
<f:if condition="{row.dateBusIncluded}"><i class="fa fa-bus tooltiptoggle" data-placement="top" title="Busfahrt inklusive"></i></f:if>
<f:if condition="{row.dateSkipassIncluded}"> <i class="fa fa-tag tooltiptoggle" data-placement="top" title="Skipass inklusive"></i></f:if>
<f:if condition="{row.dateBusIncluded}">
<span data-microtip-position="top" aria-label="Busfahrt inklusive" role="tooltip">
<i class="fa fa-bus"></i>
</span>
</f:if>
<f:if condition="{row.dateSkipassIncluded}">
<span data-microtip-position="top" aria-label="Skipass inklusive" role="tooltip">
<i class="fa fa-tag"></i>
</span>
</f:if>
</td>
<td>
<f:if condition="{row.roomOptionalServices}">