Stenciljs: Carousel third-party library destroyed when I change scope: 'true' for shadow: 'true' - stenciljs

I've a carousel based on the Swiperjs third-party library. The problem is that when I use scope: true, I have no problem and it works fine, however, when I try to configure the component with shadow: true, it is destroyed and nothing works. I am using the library as follows:
...
import Swiper, { SwiperOptions } from 'swiper';
...
#Component({
tag: 'my-swiper-slider',
styleUrl: './styles.css',
assetsDirs: ['assets'],
shadow: true,
})
export class Carousel {
#Prop() options: SwiperOptions = {
slidesPerView: 3,
spaceBetween: 40,
autoplay: false,
loop: false,
autoHeight: true,
breakpoints: {
640: {
slidesPerView: 5,
spaceBetween: 20,
},
768: {
slidesPerView: 5,
spaceBetween: 40,
},
1024: {
slidesPerView: 5,
spaceBetween: 50,
},
}
}
private setSwiper() {
this.swiper = new Swiper('.swiper-container', this.options);
}
componentDidLoad() {
this.setSwiper();
}
render() {
return (
<Host class={{"container": true}}>
<PrevBtn
goBack={this.goBack.bind(this)} />
<div class="swiper-container">
<div class="swiper-wrapper">
<slot></slot>
</div>
</div>
<NextBtn
goForward={this.goForward.bind(this)} />
</Host>
);
}
}
For some reason, when I configure the component as "shadow: true", it cannot access stencil or is what I think.

When you enable shadow, all child elements of the component will be moved into its shadow DOM and therefore be hidden as implementation detail. That means that you won't be able to find your container with the .swiper-container query selector anymore.
However, the Swiper constructor can also take an HTML element instead of a query selector, so you can use ref on the container element instead:
#Component({ tag: 'my-swiper-slider', shadow: true })
export class Carousel {
#Prop() options: SwiperOptions;
#State() swiper?: Swiper;
#State() swiperContainerRef?: HTMLDivElement;
componentDidLoad() {
if (this.swiperContainerRef) {
this.swiper = new Swiper(this.swiperContainerRef, this.options);
}
}
render() {
return (
<Host>
<div ref={el => this.swiperContainerRef = el)} />
</Host>
);
}
}
The container ref should always already be available in the componentDidLoad life-cycle hook, but you can also use requestAnimationFrame to wait for it to become available.

Related

Vue wait until external request finished

I have a page in my Vue application which does an animation on route enter. However because I embedded a Vimeo video in this page, the frames drop from 60 to 20fps during the animation. I think it's because the request to the Vimeo server has to be made and is not finished when the animation starts.
I would like to preload the embedded Vimeo video on the first site load or somehow wait until the request has finished and then start the animation. How do I do that?
The video is embedded like this in the parent component and the animation is in the projecttop child component:
<div>
<projecttop></projecttop>
<div class="vimeo-container">
<iframe src="https://player.vimeo.com/video/397648215" frameborder="0" allow="fullscreen"allowfullscreen></iframe>
</div>
</div>
export default {
name: 'parentcomponent',
components: {
projecttop
}
}
This is the child component with the animation:
<script>
import { gsap } from "gsap";
export default {
name: "projecttop",
methods: {
slideIn() {
var tl = gsap.timeline()
tl.from('#project-title', {
delay: 0.5,
duration: 0.8,
y: 100,
opacity: 0,
ease: "circ.out",
})
}
},
mounted () {
if(document.readyState === "complete") {
this.slideIn()
}
}
};
</script>
You could try something like this in your parent component:
<projecttop :start-animation="videoLoaded"></projecttop>
<div class="vimeo-container">
<iframe
src="https://player.vimeo.com/video/397648215"
frameborder="0"
allow="fullscreen"
allowfullscreen
#load="videoLoaded = true
>
</iframe>
</div>
...
data() {
return {
videoLoaded: false,
}
},
Then, in your child component, pass in the value of videoLoaded and add a watcher to monitor when this is updated to true:
props: {
startAnimation: {
type: Boolean,
required: true
}
},
watch: {
startAnimation(val) {
if (val) this.slideIn();
}
}

How to fix Cannot read property 'maps' of null" on Quasar Vue Google Maps?

How can I solve this Cannot read property 'maps' of null", when I call initMap to center the map with this line of code,
const map = new this.google.maps.Map(document.getElementById('map'), {
zoom: 13,
center: this.property
})
I already define this.google in compute also, but it doesn't work.
This is what i have done
<template>
<q-layout view="hhh lpr fff" class="main-layout">
<q-page-container>
<GmapMap
id="map"
ref="mapRef"
:center="{lat:-7.250445, lng:112.768845}"
:zoom="13"
map-type-id="terrain"
style="width: 1000px; height: 600px; margin-left:100px"
>
</GmapMap>
<router-view />
</q-page-container>
</q-layout>
</template>
<script>
import * as VueGoogleMaps from 'vue2-google-maps'
export default {
name: 'DefaultLayout',
data () {
return {
mapName: "map",
property: {
lat: 1.28237,
lng: 103.783098
},
}
},
computed: {
google: VueGoogleMaps.gmapApi
},
mounted () {
this.initMap();
},
methods: {
initMap() {
const map = new this.google.maps.Map(document.getElementById('map'), {
zoom: 13,
center: this.property
})
},
},
}
</script>
<style>
</style>
Anyone can help me solve this problem?
Computed property should be a getter function. I think you don't need a computed property here. You can make everything in the initMap method, e.g.
initMap() {
if(VueGoogleMaps) {
new VueGoogleMaps.gmapApi.maps.Map(document.getElementById('map'), {
zoom: 13,
center: this.property
})
}
}

Google Maps showing grey box in Vue modal

I have a <b-modal> from VueBootstrap, inside of which I'm trying to render a <GmapMap> (https://www.npmjs.com/package/gmap-vue)
It's rendering a grey box inside the modal, but outside the modal it renders the map just fine.
All the searching I've done leads to the same solution which I'm finding in some places is google.maps.event.trigger(map, 'resize') which is not working. Apparently, it's no longer part of the API [Source: https://stackoverflow.com/questions/13059034/how-to-use-google-maps-event-triggermap-resize]
<template>
<div class="text-center">
<h1>{{ title }}</h1>
<div class="row d-flex justify-content-center">
<div class="col-md-8">
<GmapMap
ref="topMapRef"
class="gmap"
:center="{ lat: 42, lng: 42 }"
:zoom="7"
map-type-id="terrain"
/>
<b-table
bordered
dark
fixed
hover
show-empty
striped
:busy.sync="isBusy"
:items="items"
:fields="fields"
>
<template v-slot:cell(actions)="row">
<b-button
size="sm"
#click="info(row.item, row.index, $event.target)"
>
Map
</b-button>
</template>
</b-table>
<b-modal
:id="mapModal.id"
:title="mapModal.title"
#hide="resetInfoModal"
ok-only
>
<GmapMap
ref="modalMapRef"
class="gmap"
:center="{ lat: 42, lng: 42 }"
:zoom="7"
map-type-id="terrain"
/>
</b-modal>
</div>
</div>
</div>
</template>
<script>
// import axios from "axios";
import { gmapApi } from 'gmap-vue';
export default {
name: "RenderList",
props: {
title: String,
},
computed: {
google() {
return gmapApi();
},
},
updated() {
console.log(this.$refs.modalMapRef);
console.log(window.google.maps);
this.$refs.modalMapRef.$mapPromise.then((map) => {
map.setCenter(new window.google.maps.LatLng(54, -2));
map.setZoom(2);
window.google.maps.event.trigger(map, 'resize');
})
},
data: function () {
return {
items: [
{ id: 1, lat: 42, long: 42 },
{ id: 2, lat: 42, long: 42 },
{ id: 3, lat: 42, long: 42 },
],
isBusy: false,
fields: [
{
key: "id",
sortable: true,
class: "text-left",
},
{
key: "text",
sortable: true,
class: "text-left",
},
"lat",
"long",
{
key: "actions",
label: "Actions"
}
],
mapModal: {
id: "map-modal",
title: "",
item: ""
}
}
},
methods: {
// dataProvider() {
// this.isBusy = true;
// let promise = axios.get(process.env.VUE_APP_LIST_DATA_SERVICE);
// return promise.then((response) => {
// this.isBusy = false
// return response.data;
// }).catch(error => {
// this.isBusy = false;
// console.log(error);
// return [];
// })
// },
info(item, index, button) {
this.mapModal.title = `Label: ${item.id}`;
this.mapModal.item = item;
this.$root.$emit("bv::show::modal", this.mapModal.id, button);
},
resetInfoModal() {
this.mapModal.title = "";
this.mapModal.content = "";
},
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1 {
margin-bottom: 60px;
}
.gmap {
width: 100%;
height: 300px;
margin-bottom: 60px;
}
</style>
Does anyone know how to get the map to display properly in the modal?
Surely, I'm not the first to try this?
Had this problem, in my case it was solved by providing the following options to google maps:
mapOptions: {
center: { lat: 10.365365, lng: -66.96667 },
clickableIcons: false,
streetViewControl: false,
panControlOptions: false,
gestureHandling: 'cooperative',
mapTypeControl: false,
zoomControlOptions: {
style: 'SMALL'
},
zoom: 14
}
However you can probably make-do with just center and zoom.
Edit: Try using your own google maps components, follow this tutorial:
https://v2.vuejs.org/v2/cookbook/practical-use-of-scoped-slots.html#Base-Example
You can use the package described in the tutorial to load the map, dont be scared by the big red "deprecated" warning on the npm package page.
However for production, you should use the package referenced by the author, which is the one backed by google:
https://googlemaps.github.io/js-api-loader/index.html
The only big difference between the two:
The 'google' object is not returned by the non-deprecated loader, it is instead attached to the window. See my answer here for clarification:
'google' is not defined Using Google Maps JavaScript API Loader
Happy coding!

How do I pass custom options into chartjs

I'm trying to be able to add notes to my charts but i'm stuck on how I would pass my individual photon object into my chartOptions variable to be used in the tooltips label function?
<template>
<swiper-slide v-for="(photon,key) in $store.state.photons" :key='key'>
<line-chart width="80vw" :dataset="dataset" :library="chartOptions" class="animated slideInLeft delay-0.01s"
v-else :data="photon.data.rbChannel" ytitle="R/B Channel" :download="true"></line-chart>
<line-chart width="80vw" :dataset="dataset" :library="chartOptions" class="animated slideInRight delay-0.01s"
v-else :data="photon.data.tempF" ytitle="Temperature F" :colors="['#ff0000']" :download="true"></line-chart>
</swiper-slide>
</template>
chartOptions variable
chartOptions: {
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
console.log(data.labels[tooltipItem.index])
console.log(tooltipItem)
return data.labels[tooltipItem.index]
}
}
},
height: '400px',
pan: {
enabled: false,
mode: 'xy',
},
zoom: {
enabled: true,
mode: 'x',
},
drag: true,
gridLines: {
zeroLineColor: "rgba(0,255,0,1)"
}
}

Trigger child component method from parent event

I am very new to VueJS and JavaScript and your help would be much appreciated.
My method "greet" is not working and I am unsure why. When I click on the button "change to bar" I get the error:
[Vue warn]: Property or method "greet" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <Chatjsvue> at src\components\vueChartjs\Chatjsvue.vue
<App> at src\App.vue
<Root>
chartjs.vue
<template src="../../views/chartjshtml/chartsjs.html"></template>
<script>
import LineChart from '#/assets/javascripts/chartjsline'
export default {
components: {
'line-chart': LineChart
}
}
</script>
chartsjs.html
<div class="wrapper">
<div>
<ul>
<li><button v-on:click="greet()">change to bar</button></li>
<li><line-chart></line-chart></li>
</ul>
</div>
</div>
chartsjsline.js
import { Line } from 'vue-chartjs'
export default {
extends: Line,
data() {
return {
datacollection: {
//Data to be represented on x-axis
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
datasets: [{
label: 'Activities ChartJS Line',
backgroundColor: '#f87979',
pointBackgroundColor: 'white',
borderWidth: 1,
pointBorderColor: '#249EBF',
//Data to be represented on y-axis
data: [40, 20, 30, 50, 90, 10, 20, 40, 50, 70, 90, 100]
}]
},
//Chart.js options that controls the appearance of the chart
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
display: true
}
}],
xAxes: [{
gridLines: {
display: false
}
}]
},
legend: {
display: true
},
responsive: true,
maintainAspectRatio: false
},
}
},
methods: {
greet() {
alert('hello');
}
},
mounted() {
//renderChart function renders the chart with the datacollection and options object.
this.renderChart(this.datacollection, this.options)
}
}
Your <line-chart> component (instance of LineChart) is a child component of your Chatjsvue component.
The child methods remain bound to those instances.
However it is extremely easy from the parent component to access its child component, and from there to execute their methods:
Keep a reference to your child component, using the ref special attribute: <line-chart ref="myLineChart"></line-chart>
Within a parent method, access the referred child using this.$refs.myLineChart.
Then you have access to everything on this child instance, including its methods: this.$refs.myLineChart.greet()
Working example:
Vue.component('chart-js', {
template: '#chartjs',
methods: {
lineChildGreet() {
// Access the child component through $refs.
// Then you can execute its methods.
this.$refs.myLineChart.greet();
},
},
});
Vue.component('line-chart', {
template: '#lineChart',
methods: {
greet() {
alert('hello');
},
},
});
var app = new Vue({
el: '#app',
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<chart-js></chart-js>
</div>
<template id="chartjs">
<div class="wrapper">
<div>
<ul>
<li><button v-on:click="lineChildGreet">greet on child component</button></li>
<!-- Keep a REFerence to the child component -->
<li><line-chart ref="myLineChart"></line-chart></li>
</ul>
</div>
</div>
</template>
<template id="lineChart">
<span>LineChart</span>
</template>
You need to add the greet() method to the chartjs.vue component, instead of chartsjsline.