Implement watchlist
This commit is contained in:
@@ -25,6 +25,7 @@ require('../images/favicon_uch.ico');
|
||||
|
||||
// Import dependencies
|
||||
import Vue from 'vue'
|
||||
import VueSession from 'vue-session'
|
||||
import $ from 'jquery'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/de'
|
||||
@@ -49,6 +50,8 @@ import Gmap from './components/Gmap.vue'
|
||||
import Calendar from './components/Calendar.vue'
|
||||
import EventPricetable from './components/EventPriceTable.vue'
|
||||
import ContactForm from './components/ContactForm.vue'
|
||||
import WatchlistToggle from './components/WatchlistToggle.vue'
|
||||
import Watchlist from './components/Watchlist.vue'
|
||||
|
||||
// Define global constants
|
||||
const $window = $(window);
|
||||
@@ -56,6 +59,9 @@ const sliderPrevArrow = '<button class="slider-pagination__arrow slider-paginati
|
||||
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" />';
|
||||
|
||||
// Enable session handling
|
||||
Vue.use(VueSession);
|
||||
|
||||
// Define date filter for vue templates
|
||||
Vue.filter('date', (value) => {
|
||||
return dayjs(value).format('DD.MM.YYYY');
|
||||
@@ -73,7 +79,9 @@ new Vue({
|
||||
Gmap,
|
||||
Calendar,
|
||||
EventPricetable,
|
||||
ContactForm
|
||||
ContactForm,
|
||||
WatchlistToggle,
|
||||
Watchlist
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -85,6 +93,12 @@ new Vue({
|
||||
searchbarFixed: false,
|
||||
mobileMode: false,
|
||||
backgroundImageMode: 'mobile',
|
||||
watchlist: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
watchlistCount () {
|
||||
return this.watchlist.length;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -366,6 +380,25 @@ new Vue({
|
||||
EventBus.$on('scrollTo', (anchorId, offset) => {
|
||||
this.scrollTo(anchorId, offset);
|
||||
});
|
||||
EventBus.$on('watchlistToggle', uid => {
|
||||
let list = this.watchlist;
|
||||
let watchlistIndex = list.indexOf(uid);
|
||||
if (watchlistIndex === -1) {
|
||||
list.push(uid)
|
||||
} else {
|
||||
list.splice(watchlistIndex, 1)
|
||||
}
|
||||
this.watchlist = list;
|
||||
this.$session.set('watchlist', this.watchlist);
|
||||
|
||||
});
|
||||
if (!this.$session.exists()) {
|
||||
this.$session.start();
|
||||
}
|
||||
if (!this.$session.has('watchlist')) {
|
||||
this.$session.set('watchlist', []);
|
||||
}
|
||||
this.watchlist = this.$session.get('watchlist');
|
||||
},
|
||||
mounted () {
|
||||
Vue.nextTick(() => {
|
||||
|
||||
@@ -73,6 +73,12 @@
|
||||
default () {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
watchlist: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -133,7 +139,6 @@
|
||||
this.loading = false;
|
||||
EventBus.$emit('ajaxContentLoaded');
|
||||
});
|
||||
|
||||
},
|
||||
resetFilterSettings () {
|
||||
this.filterSettings = this.defaultFilterSettings;
|
||||
|
||||
+11
@@ -59,14 +59,19 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<watchlist-toggle :uid="item.key" :watchlist="watchlist" class="teaserbox-watchlist"></watchlist-toggle>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/de'
|
||||
import WatchlistToggle from './WatchlistToggle.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WatchlistToggle
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@@ -75,6 +80,12 @@
|
||||
section: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
watchlist: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="watchlist">
|
||||
<template v-for="section in teasers">
|
||||
<h2 class="headline headline--secondary headline--underlined">{{ section.concept.name }}</h2>
|
||||
<div class="searchresult-section" v-bind:class="'searchresult-section--' + section.concept.code">
|
||||
<search-result-item
|
||||
v-for="item in section.items"
|
||||
v-bind:key="item.key"
|
||||
v-bind:item="item"
|
||||
v-bind:section="section"
|
||||
v-bind:watchlist="watchlist"></search-result-item>
|
||||
</div>
|
||||
</template>
|
||||
<div class="alert alert-info" v-if="total === 0">
|
||||
Deine Merkliste ist aktuell leer...
|
||||
</div>
|
||||
<div v-show="loading" class="loader">
|
||||
<img :src="loaderUri" alt="Loading...">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import EventBus from '../bus'
|
||||
import SearchResultItem from './SearchResultItem.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SearchResultItem
|
||||
},
|
||||
props: {
|
||||
watchlistUri: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
loaderUri: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
referringPageUid: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
watchlist: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
teasers: {},
|
||||
total: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadList () {
|
||||
if (this.watchlist.length === 0) {
|
||||
this.teasers = {};
|
||||
this.total = 0;
|
||||
return;
|
||||
}
|
||||
let query = {};
|
||||
query['tx_epproducts_ajax[watchlistUids]'] = this.watchlist;
|
||||
query['tx_epproducts_ajax[referringPageUid]'] = this.referringPageUid;
|
||||
this.loading = true;
|
||||
$.post(this.watchlistUri, query, (json) => {
|
||||
this.teasers = json.data;
|
||||
this.total = json.total;
|
||||
this.loading = false;
|
||||
EventBus.$emit('ajaxContentLoaded');
|
||||
});
|
||||
}
|
||||
},
|
||||
created () {
|
||||
EventBus.$on('watchlistToggle', () => {
|
||||
this.loadList();
|
||||
});
|
||||
},
|
||||
mounted () {
|
||||
Vue.nextTick(() => {
|
||||
this.loadList();
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<span v-on:click.prevent="toggleWatchlist()">
|
||||
<template v-if="onWatchlist">
|
||||
<i class="fa fa-minus tooltiptoggle" data-placement="auto" title="Merkliste"></i>
|
||||
</template>
|
||||
<template v-else>
|
||||
<i class="fa fa-plus tooltiptoggle" data-placement="auto" title="Merkliste"></i>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EventBus from '../bus'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
uid: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
watchlist: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleWatchlist () {
|
||||
EventBus.$emit('watchlistToggle', this.uid);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
onWatchlist () {
|
||||
return this.watchlist.indexOf(this.uid) !== -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -36,8 +36,8 @@ $output-bourbon-deprecation-warnings: false;
|
||||
@import "remix/forms";
|
||||
@import "remix/contactnav";
|
||||
@import "remix/faq";
|
||||
|
||||
@import "remix/destination-pulldown";
|
||||
@import "remix/watchlist";
|
||||
|
||||
// core config
|
||||
@import "variables";
|
||||
|
||||
@@ -33,6 +33,7 @@ $output-bourbon-deprecation-warnings: false;
|
||||
@import "remix/socialmedia";
|
||||
@import "remix/contactnav";
|
||||
@import "remix/faq";
|
||||
@import "remix/watchlist";
|
||||
|
||||
// core config
|
||||
@import "variables";
|
||||
|
||||
@@ -33,6 +33,7 @@ $output-bourbon-deprecation-warnings: false;
|
||||
@import "remix/socialmedia";
|
||||
@import "remix/contactnav";
|
||||
@import "remix/faq";
|
||||
@import "remix/watchlist";
|
||||
|
||||
// core config
|
||||
@import "variables";
|
||||
|
||||
@@ -33,6 +33,7 @@ $output-bourbon-deprecation-warnings: false;
|
||||
@import "remix/socialmedia";
|
||||
@import "remix/contactnav";
|
||||
@import "remix/faq";
|
||||
@import "remix/watchlist";
|
||||
|
||||
// core config
|
||||
@import "variables";
|
||||
|
||||
@@ -108,6 +108,9 @@
|
||||
&.youtube {
|
||||
top: 176px;
|
||||
}
|
||||
&.watchlist {
|
||||
top: 220px;
|
||||
}
|
||||
&:hover{
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@@ -100,3 +100,15 @@
|
||||
color: $color-label-low-contingent;
|
||||
}
|
||||
}
|
||||
|
||||
.product-detail {}
|
||||
|
||||
.product-detail__watchlist {
|
||||
display: inline-block;
|
||||
color: white;
|
||||
background-color: $color-brand-primary;
|
||||
font-size: 22px;
|
||||
line-height: 22px;
|
||||
padding: 4px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
text-decoration: none;
|
||||
color: $color-font-base;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: $color-font-base;
|
||||
@@ -57,6 +58,11 @@
|
||||
@include mq($from: tablet) {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid $color-grey;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -150,7 +156,7 @@
|
||||
|
||||
.teaserbox--landscape & {
|
||||
height: auto;
|
||||
border-style: none solid;
|
||||
border-style: none;
|
||||
|
||||
@include mq($from: tablet) {
|
||||
display: flex;
|
||||
@@ -158,7 +164,7 @@
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 8px 16px;
|
||||
border-style: solid none;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -189,7 +195,8 @@
|
||||
border-style: solid solid solid none;
|
||||
}
|
||||
|
||||
.teaserbox--searchresult & {
|
||||
.teaserbox--searchresult &,
|
||||
.teaserbox--landscape & {
|
||||
border: none;
|
||||
|
||||
@include mq($from: tablet) {
|
||||
@@ -341,3 +348,30 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.teaserbox-watchlist {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
@include size(48px);
|
||||
display: block;
|
||||
color: white;
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:visited,
|
||||
&:active {
|
||||
color: white;
|
||||
}
|
||||
cursor: pointer;
|
||||
padding: 4px 0 0 28px;
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
@include triangle(48px, $color-brand-secondary, up-right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
.watchlist {
|
||||
position: relative;
|
||||
|
||||
.loader {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(white, 0.75);
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
{namespace v=FluidTYPO3\Vhs\ViewHelpers}
|
||||
<div class="ep-contactnav">
|
||||
<a href="{f:uri.page(pageUid: settings.watchlistPageUid)}" class="bg-red watchlist" v-show="watchlistCount > 0" v-cloak>
|
||||
<span class="fa fa-list" aria-hidden="true"></span>
|
||||
<div>Merkliste ({{ watchlistCount }})</div>
|
||||
</a>
|
||||
<a href="tel:{settings.phoneNumberLink}" class="bg-red phone" title="Telefonische Beratung">
|
||||
<span class="fa fa-phone" aria-hidden="true"></span>
|
||||
<div>{settings.phoneNumber}</div>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
</div>
|
||||
<div class="col-sm-4 pull-left product-header__title">
|
||||
<div class="head">
|
||||
<!-- <a href="#" class="add-note" title="Dem Merkzettel hinzufügen"></a> -->
|
||||
{product.concept.name}
|
||||
</div>
|
||||
<div class="content">
|
||||
@@ -27,12 +26,17 @@
|
||||
<f:image class="flag" src="{ep:flagiconSource(countryCode: product.country.code)}" alt="{product.country.name}"/>
|
||||
<f:if condition="{hotel}">
|
||||
<f:then>
|
||||
<a href="{ep:gmapsUrl(hotel: hotel)}" target="_blank" class="ep-property ep-marker"><i class="fa fa-map-marker" aria-hidden="true"></i></a>
|
||||
<a href="{ep:gmapsUrl(hotel: hotel)}" target="_blank" class="ep-property ep-marker">
|
||||
<i class="fa fa-map-marker" aria-hidden="true"></i>
|
||||
</a>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<a href="{ep:gmapsUrl(region: product.region)}" target="_blank" class="ep-property ep-marker"><i class="fa fa-map-marker" aria-hidden="true"></i></a>
|
||||
<a href="{ep:gmapsUrl(region: product.region)}" target="_blank" class="ep-property ep-marker">
|
||||
<i class="fa fa-map-marker" aria-hidden="true"></i>
|
||||
</a>
|
||||
</f:else>
|
||||
</f:if>
|
||||
<watchlist-toggle :watchlist="watchlist" uid="{hotel.uid}:{product.uid}" class="product-detail__watchlist"></watchlist-toggle>
|
||||
<f:if condition="{product.skipassIncluded}">
|
||||
<a href="#" class="ep-property">inkl. Skipass</a>
|
||||
</f:if>
|
||||
@@ -72,3 +76,9 @@
|
||||
</f:if>
|
||||
{v:render.template(file: 'EXT:ep_theme/Resources/Private/Partials/Breadcrumb.html')}
|
||||
</div>
|
||||
<script>
|
||||
import WatchlistToggle from "../../Assets/js/components/WatchlistToggle";
|
||||
export default {
|
||||
components: {WatchlistToggle}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
<a href="{uriLink}" title="{teaser.product.name}" class="button button--full searchresult-item__button">Details & Buchen</a>
|
||||
</div>
|
||||
</div>
|
||||
<watchlist-toggle uid="{teaser.key}" v-bind:watchlist="watchlist" class="teaserbox-watchlist"></watchlist-toggle>
|
||||
</div>
|
||||
</f:section>
|
||||
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
|
||||
<f:section name="Content">
|
||||
<f:switch expression="{data.list_type}">
|
||||
<f:comment>Plugin watchlist</f:comment>
|
||||
<f:case value="epproducts_watchlist">
|
||||
<f:render section="Header" arguments="{_all}"/>
|
||||
<ep:container data="{data}">
|
||||
<f:cObject typoscriptObjectPath="tt_content.list.20.{data.list_type}" data="{data}"/>
|
||||
</ep:container>
|
||||
</f:case>
|
||||
<f:comment>Plugin snowreport</f:comment>
|
||||
<f:case value="epproducts_snowreport_list">
|
||||
<f:render section="Header" arguments="{_all}"/>
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
<div id="search">
|
||||
<search-result
|
||||
results-uri="{ep:uri.ajax(action: 'searchresult', controller: 'AjaxSearch', format: 'json', noCacheHash: 1)}"
|
||||
:initial-filter-settings='<v:format.json.encode>{filterSettings}</v:format.json.encode>'
|
||||
:initial-filter-options='<v:format.json.encode>{filterOptions}</v:format.json.encode>'
|
||||
:referring-page-uid="{referringPageUid}"
|
||||
v-bind:initial-filter-settings='<v:format.json.encode>{filterSettings}</v:format.json.encode>'
|
||||
v-bind:initial-filter-options='<v:format.json.encode>{filterOptions}</v:format.json.encode>'
|
||||
v-bind:referring-page-uid="{referringPageUid}"
|
||||
argument-prefix="{ep:argumentPrefix(pluginName: 'ajax')}"
|
||||
:date-presets='{settings.datePickerPresets -> v:format.json.encode()}'
|
||||
v-bind:date-presets='{settings.datePickerPresets -> v:format.json.encode()}'
|
||||
v-bind:watchlist="watchlist"
|
||||
inline-template>
|
||||
<div class="searchresult-wrapper">
|
||||
<div class="pagehead pagehead--searchresult">
|
||||
@@ -290,7 +291,8 @@
|
||||
v-for="item in section.items"
|
||||
v-bind:key="item.key"
|
||||
v-bind:item="item"
|
||||
v-bind:section="section"></search-result-item>
|
||||
v-bind:section="section"
|
||||
v-bind:watchlist="watchlist"></search-result-item>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"
|
||||
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers"
|
||||
xmlns:ep="http://typo3.org/ns/EP/EpTheme/ViewHelpers">
|
||||
|
||||
<f:layout name="Default"/>
|
||||
|
||||
<f:section name="Main">
|
||||
|
||||
<watchlist :watchlist="watchlist" :referring-page-uid="{referringPageUid}"
|
||||
loader-uri="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif')}"
|
||||
watchlist-uri="{ep:uri.ajax(action: 'list', controller: 'AjaxWatchlist', format: 'json', noCacheHash: 1)}"></watchlist>
|
||||
</f:section>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user