When I detect a specific width via class binding to make a mobile responsive web and swap between templates using display:none, the console keeps giving me an error.
I'm having trouble using 2 YouTube templates. If you use only one YouTube, the error disappears.
Error type: ReferenceError: YT is not defined. YouTube.vue?6574:118:25
If replacing the class binding template I used is a problem, is there another way?
<template>
<div class="container">
<div
class="youtube_container"
:class="{'youtube_container on' : WidthActive}">
<h3 class="youtube_title">
desktop video
</h3>
<!-- add -->
<component
is="script"
id="youtube-iframe-js-api-script"
src="https://www.youtube.com/iframe_api"
/>
<YouTube
src="https://youtu.be/GQmO52f26Ws"
width="540"
height="360" />
</div>
<div
class="youtube_container_mobile"
:class="{'youtube_container_mobile on' : WidthmobileActive}">
<h3 class="youtube_title">
mobile video
</h3>
<YouTube
src="https://youtu.be/GQmO52f26Ws"
width="280"
height="300"
class="youtube_padding" />
</div>
</div>
</template>
<script setup>
import YouTube from 'vue3-youtube'
import { ref } from 'vue'
const WidthActive = ref(true)
const WidthPostion = ref(0)
//mobile
const WidthmobileActive = ref(false)
const WidthmobilePostion = ref(0)
window.addEventListener('resize', () => {
WidthmobileActive.value = WidthmobilePostion.value > 590
WidthmobilePostion.value = window.innerWidth
})
window.addEventListener('resize', () => {
WidthActive.value = WidthPostion.value < 591
WidthPostion.value = window.innerWidth
})
</script>
<style>
.youtube_container{
display: grid;
}
.youtube_container.on{
display: none;
}
.youtube_container_mobile{
display: grid;
}
.youtube_container_mobile.on{
display: none;
}
</style>
Update
Reversing ref(false) and ref(true) solved the issue.
This is a known issue apparently, maybe give a try to that one
<component
is="script"
id="youtube-iframe-js-api-script"
src="https://www.youtube.com/iframe_api"
/>
Above your <YouTube> component.
Or maybe try to give it 2 differents refs aka ref="youtube" and ref="youtube2", not sure.
Otherwise, add the following to an apex file
<script id="youtube-iframe-js-api-script" src="https://www.youtube.com/iframe_api"></script>
Related
I create a component whose purpose is to display an arbitrary component within itself, with a changeable padding property. (The example is conditional for simplicity).
<script setup lang="ts">
defineProps<{
innerComponent: any;
settings: { padding: number; backgroundColor: string };
}>();
</script>
<template>
<div
:style="`
padding: ${settings.padding}px;
background-color: ${settings.backgroundColor}`"
>
<component :is="innerComponent" />
</div>
</template>
Let's create a simple component for the example.
<template>
<p style="background-color: beige">it's just a test.</p>
</template>
That's how we use it, and it works great.
<script setup lang="ts">
import ExternalComponent from "./components/ExternalComponent.vue";
import InnerComponent from "./components/InnerComponent.vue";
</script>
<template>
<ExternalComponent
:inner-component="InnerComponent"
:settings="{ padding: 200, backgroundColor: 'yellow' }"
/>
</template>
I wish it all looked even more aesthetically pleasing. For example, like this.
<InnerComponent v-inner="{ padding: 200, backgroundColor: 'yellow' }" />
Let's try using custom directives.
import { createApp, h } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.directive("inner", (el, binding, vnode) => {
//Here is supposedly expected to be something like
el.outerHTML = h("ExternalComponent", {
innerComponent: vnode,
settings: binding.value,
});
//or
vnode = h("ExternalComponent", {
innerComponent: vnode,
settings: binding.value,
});
//But, of course, something completely different :((
});
app.mount("#app");
Unfortunately, my knowledge is not enough to solve this problem. I would be glad to get any advice on what direction to dig.
In my case in desktop mode user can hover over the element to reveal the menu. User can also click the element that makes a request to the server.
In touchscreen mode, I would like the click to be revealing the menu instead of making a request.
It seems like impossible task to achieve unless I start going outside of vue and changing css directly from DOM level.
<script setup>
import { ref } from "vue";
const text = ref("whatever");
const isEditing = ref(false);
function sendToServer() {
console.log("Sending request of ", text.value, " to server.")
isEditing.value = false;
}
</script>
<template>
<div class="wrapper">
<span v-show="!isEditing" #click="sendToServer" #touchstart.prevent> {{ text }}</span>
<input v-show="isEditing" v-model="text">
<span #click="sendToServer" class="icon">➡️</span>
<span #click="isEditing = !isEditing" class="icon">✏️</span>
</div>
</template>
<style>
.icon {
opacity: 0;
}
.wrapper:hover .icon {
opacity: 1;
}
.wrapper > span {
cursor: pointer;
}
</style>
Vue SFC playground link
"It seems like impossible task to achieve unless I start going outside of vue and changing css directly from DOM level."
I would say it is impossible to achieve with CSS-only, but Vue (IMHO) seems cable and well suited to handle that.
Instead of using css :hover you will need to manage the visibility using state (isSelected in example)
then use #touchstart to toggle visibility - this will only be available to touch devices and mouse events #mouseover to show and #mouseout to hide.
there may be some more finessing to handle some edge cases, but this is how I'd implement it. For example, you may need a global touch event listener to hide when user clicks outside of the text.
<script setup>
import { ref } from "vue";
const text = ref("whatever");
const isEditing = ref(false);
const isSelected = ref(false);
function toggleIsSelected(){
isSelected.value = !isSelected.value
}
function unselect(){
isSelected.value = false;
}
function sendToServer() {
console.log("Sending request of ", text.value, " to server.")
isEditing.value = false;
}
</script>
<template>
<div class="wrapper">
<span v-show="!isEditing"
#click="sendToServer"
#touchstart=toggleIsSelected
#mouseover=toggleIsSelected
#mouseout=unselect
> {{ text }}</span>
<input v-show="isEditing" v-model="text">
<span v-show=isSelected #click="sendToServer" class="icon">➡️</span>
<span v-show=isSelected #click="isEditing = !isEditing" class="icon">✏️</span>
</div>
</template>
<style>
.wrapper > span {
cursor: pointer;
}
</style>
Because of non compatible dependencies, I have to downgrade from vue3 to vue2.
I have created a force directed graph with D3 library. Everything worked find with vue3 using composition api. I am not familiar with vue 2 and adapting my graph to vue2 has not been working out for me.
In vue3 it was very straitforward and ref() made it pretty easy to accomplish.
As for vue2, I have tried making good use of lifecycle hooks such as computed and watch
Any help is more than welcome
Here is a minimalistic representation of my working component in vue3. This component creates the graph in a svg and then renders it in the template.
<template>
<div class="col" style="position: absolute; width:100%; height:100%" >
<div class="main-map-container" style="overflow: hidden; width: 100%;">
<div ref="graph" class="canvas">
</div>
</div>
</div>
</template>
<script >
import {onMounted, onBeforeMount, ref} from 'vue'
export default {
setup(){
const graph = ref()
const links = [{src:"Amazon",target:"Aurora"},{src:"Amazon",target:"Aurora"},{src:"Amazon",target:"Zoox"},{src:"Amazon",target:"Rivian"}]
const nodes = [{id:"Amazon"},{id:"Aurora"},{id:"Zoox"},{id:"Rivian"}]
onBeforeMount( async ()=>{
const svgobj = ForceGraph(nodes, links)
graph.value.appendChild(svgobj);
})
function ForceGraph(
nodes,
links
){
// The code for the graph has been removed since it is much too long
return Object.assign( svg.node() );
}
return { graph }
}
}
</script>
<style></style>
This is the vue2 component that i have emptied for this post
<template>
<div class="col" style="position: absolute; width:100%; height:100%" >
<div class="main-map-container" style="overflow: hidden; width: 100%;">
<div ref="graph" class="canvas">
</div>
</div>
</div>
</template>
<script>
export default {
setup(){
},
methods: {
},
watch: {
},
props: {
},
computed: {
},
created() {
},
mounted() {
},
updated(){
},
data() {
return {
}}
}
</script>
<style>
</style>
You can use Vue3 composition API in vue 2. Install the composition api and then just keep your code the same, with the setup method exactly as it was.
The setup method, lifecycle hooks, and all the reactivity (refs and reactive objects) are made available to you, with very few incompatibilities.
We use d3 with Vue2 in this fashion all the time. 100% compatible.
https://github.com/vuejs/composition-api
Please see this minimum example
I have a Child.vue and Parent.vue which look like this
<!-- Child.vue -->
<template>
<div :class="'bg-gray text-white'">I'm Child</div>
</template>
<template>
<Child class="p-4" />
</template>
<script>
import Child from "./Child.vue";
export default {
components: {
Child,
},
};
</script>
<style>
.p-4 {
padding: 16px;
}
.bg-gray {
background: gray;
}
.text-white {
color: white;
}
</style>
The final class name for Child.vue wrapper element will be bg-gray text-white p-4, class names are all merged.
However, I would like to create a v-class directive and make my API looks like this
<!-- Child.vue -->
<template>
<div v-class="'bg-gray text-white'">I'm Child</div>
</template>
<script>
export default {
directives: {
class: {
inserted(el, binding) {
const css = String.raw; // Not doing anything right now.
el.className = css`
${binding.value}
`;
},
},
},
};
</script>
And Parent.vue stays the same.
Now the final merged class name is bg-gray text-white, p-4 is missing!
How can I preserve p-4 just like I'm using regular :class prop?
My use case is I want to use the tagged template literal function in my Vue template.
However, Vue doesn't support template literal function in the template, the compiler will yell!
For example
<div :class="css`text`"></div>
Try out to push the value to the class list :
el.classList.add( css`
${binding.value}
`);
This is my child element
<template lang="html">
<div class="row">
<div class="col-lg-8 col-md-8 col-sm-12 col-xs-12">
<bar-chart :v-if="this.barChartReadyToBeRendered" :chart-data='null' :height="340"></bar-chart>
</div>
<div class="flex-col-docs col-lg-3">
<div class="column" style="height: 150px">
<div class="col">
<q-select dark stack-label="Show Targets" class="select-notification"
v-model="selectTargetNotification"
:options="this.getTargetChangeOptions"
/>
</div>
<div class="col">
<q-select dark stack-label="Agency" class="select-notification"
v-model="selectOrgNotification"
:options="this.getOrganisationOptions"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import BarChart from '../../components/BarChart'
export default {
components: {
BarChart
},
.
.
/* Other code */
mounted () {
console.log('OUTSIDE MOUNTED')
this.$nextTick(() => {
console.log(this.$el)
let ctx = document.getElementById('bar-chart')
console.log('WWWWWWWWWWWWWWWWWW')
console.log(ctx)
console.log(this.$el)
this.createChart('bar-chart')
})
}
</script>
The bar chart chartjs is
<script>
import { Bar, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
this.renderChart(this.chartData, this.options)
}
}
</script>
<style>
</style>
In my parent element, the template is
<template>
<q-page padding class="row justify-center">
<div style="width: 80vw; max-width: 100vw;">
<div class="flex-row-docs">
<div class="doc-container">
<q-list no-border>
<div class="row justify-start">
<div class="col-6">
<target-changes-agency></target-changes-agency>
</div>
</div>
<div class="q-mb-md q-mt-md q-headline">Full coverage</div>
<span v-if="!isNewsByIdLoaded" class="row justify-center">
<q-spinner-mat :size="36" style="color: #027be3ff; text-align: justify; margin: 2rem;" />
</span>
<div class="row">
<article-cluster :isNewsByIdLoaded="isNewsByIdLoaded"></article-cluster>
</div>
</q-list>
</div>
</div>
</div>
</q-page>
</template>
I am expecting to console.log(ctx) and console.log(this.$el), however the output of those 2 is null and <!-- --> respectively.
I thought mounted and this.$nextTick() will allow me to have access to the DOM. What am i missing here? please help thank you
Why are you assuming that document.getElementById('bar-chart') would return any element? There is no element with that ID being created. What you're rather looking for is document.getElementsByTagName('bar-chart'), but that will also yield no result, because Vue does not internally create Web Components, but inserts the component's root element in place instead. So, what you can do is give your bar-chart component an id attribute, which will be passed to the root element automatically.
The next issue is that your bar-chart component is only visible when the condition in v-if is truthy. That's probably not the case when the component is first being loaded. In this working minimal example, I simply set v-if="false".
const { Bar, mixins } = VueChartJs
const { reactiveProp } = mixins
const BarChart = Vue.component('bar-chart', {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
//this.renderChart(this.chartData, this.options)
this.$nextTick(() => {
console.log('mounted bar-chart component:');
console.log(this.$el)
});
}
});
Vue.component('example-component', {
template: `<div><bar-chart v-if="false" id="barchart" chart-data="null" height="340"></bar-chart></div>`,
components: [BarChart],
mounted () {
this.$nextTick(() => {
console.log('mounted child component:');
let ctx = document.getElementById('barchart')
console.log(ctx)
console.log(this.$el)
})
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-chartjs#3.5.0/dist/vue-chartjs.min.js"></script>
<div id="app">
<example-component></example-component>
</div>
(The stack snippet console actually hides the <!-- -->, but you can see it in this codepen. Vue automatically inserts this empty HTML comment as a placeholder for a component that is not currently being displayed.)
The output is actually expected, as the bar-chart component is not being rendered, therefore this.$el (referring to the child component, not the bar-chart component) is empty.
Now here ist the same snippet with v-if="true" on the bar-chart component:
const { Bar, mixins } = VueChartJs
const { reactiveProp } = mixins
const BarChart = Vue.component('bar-chart', {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
//this.renderChart(this.chartData, this.options)
this.$nextTick(() => {
console.log('mounted bar-chart component:');
console.log(this.$el)
});
}
});
Vue.component('example-component', {
template: `<div><bar-chart v-if="true" id="barchart" chart-data="null" height="340"></bar-chart></div>`,
components: [BarChart],
mounted () {
this.$nextTick(() => {
console.log('mounted child component:');
let ctx = document.getElementById('barchart')
console.log(ctx)
console.log(this.$el)
})
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-chartjs#3.5.0/dist/vue-chartjs.min.js"></script>
<div id="app">
<example-component></example-component>
</div>
As you can see, the logs now return the correct elements, also in the mounted() hook of the bar-chart component.
Of course, you shouldn't use the id attribute in your component if you ever plan to have multiple instances of this component, because it would result in multiple elements having the same ID, which is invalid HTML and might lead to unexpected interferences. So, this was only for demonstration purposes in this minimal example. In your real code, you could use Vue's ref attribute instead, which you can then refer to via this.$refs inside the parent component.
There are two other issues in your code:
You don't need the colon in front of v-if, because it automatically binds to the expression given as its value.
You don't need this. in your expressions, you're in the components context automatically and can simply use the properties' names directly.