The function bound to (#load="myFunction") fires once when the iframe is created and once when it's actually loaded.
Why does it fire when the iframe is created, and how to avoid it?
<template>
<transition name="modal">
<div v-if="webviewOpen">
<transition name="content" appear>
<div v-if="webviewOpen">
<transition name="iframe">
<iframe
v-show="showIframe"
:src="webviewUrl"
#load="iframeIsLoaded"
/>
</transition>
</div>
</transition>
</div>
</transition>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
showIframe: false
}
},
computed: {
...mapState({
webviewOpen: state => state.webview.open,
webviewUrl: state => state.webview.url
})
},
watch: {
webviewOpen () {
setTimeout(() => {
this.showIframe = true
}, 1000)
}
},
methods: {
iframeIsLoaded () {
console.log('iframe loaded')
}
}
}
</script>
It seems it may be a web kit issue with firing twice ( safari/chrome ) as it fires when added to DOM (v-if on parent) and when the content is loaded. It may help to add .once modifier to the #load.once="myFunction()"
As #tao suggested something else was interefering, namely Nuxt Lazy Load package. So if anyone uses this package AND finds out iframes onload event mysteriously fires twice AND finds this thread:
Add iframes: false in your nuxt.config.js when importing the package inside the modules section. Problem solved!
We know from your linked answer that Chrome shows this issue unless you attach the listener after the iframe is appended to the DOM. To do this, we could take advantage of Vue's lifecycle hooks. We want it to happen after the iframe is added to the DOM, but before it has a chance to load, so we'll use the updated hook.
I don't experience the problem in any of my browsers, so I unfortunately can't really test it for you. Test for yourself and see if something like this fixes it for you:
<template>
<label for="show">Show iFrame</label>
<input id="show" type="checkbox" v-model="webviewOpen">
<div v-if="webviewOpen">
<iframe
src="https://motherfuckingwebsite.com/"
#load="iframeLoadHelper"
frameborder="0"
></iframe>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
webviewOpen: false,
iframeReady: false
};
},
methods: {
// Helper method, just to keep 'if' outside of the logic of your potentially
// complex #load method
iframeLoadHelper() {
if (this.iframeReady) return this.iframeLoaded();
else return; // do nothing
},
// The real load method
iframeLoaded() {
console.log('iframe loaded');
}
},
updated() {
console.log('changing ready-state');
this.iframeReady = this.webviewOpen;
}
};
</script>
<style>
:root { font-family: sans-serif; }
</style>
Related
I create a vue component to load scripts for ads dynamically, and when the route changes, the component should destroy herself and change back when the route enters.
So when the route leaves, there is no problem, but when I go to a page and then return to the same page, the ads do not appear anymore.
<template>
<div style="display: none;">
<slot />
</div>
</template>
<script>
export default {
props: {
async: { type: Boolean, default: true },
location: { type: String, default: '' }, // or elemnt id which will select the sapce
src: { type: String, required: false, default: '' }
},
data () {
return {
script: null
}
},
beforeDestroy () {
console.log('remove')
if (this.script) {
if (!this.location) {
this?.$el?.removeChild(this?.script)
}/** else {
const tag = document.querySelector(this.location)
tag?.parentNode?.removeChild(this.script)
} */
}
},
mounted () {
console.log('add loadjs')
this.loadJs()
},
methods: {
loadJs () {
const scriptTag = document.createElement('script')
console.log(this.$el)
scriptTag.async = this.async || true
// console.log(Object.keys(this.$slots.default[0]))
if (!this.src && this?.$slots?.default) { // when script is empty
scriptTag.text = this?.$slots?.default[0]?.text
} else { scriptTag.src = this.src }
if (!this.location) { // when location is not set load after element
this.$el.appendChild(scriptTag)
} else {
const location = document.querySelector(this.location)
location.appendChild(scriptTag)
}
this.script = scriptTag
}
}
}
</script>
the service for the ads is
<template>
<div>
ads
<div :id="id">
<script-component>
googletag.cmd.push(
function() {
googletag.display('{{ id }}');
}
);
</script-component>
</div>
</div>
</template>
<script>
const scriptLoadder = () => import('~/components/script/scriptLoadder')
export default {
components: {
'script-component': scriptLoadder
},
props: {
id: { type: String, required: true }
}
}
</script>
I have another similar component for another ads service that works on server load (when I enter the home page for the first time, this works fine). The issue is when the route changes, and then I go back to the same route. Both services of ads are just not appearing.
this is how I am using the component
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div>
<google-ads id="ATF_LB_1" :key="$route.fullPath + Math.random().toString(16) " />
or
<google-ads id="ATF_LB_1" :key="$route.fullPath" />
<script-component>
{{ pageScript.HP }}
</script-component>
or
<script-component :key="$route.fullPath">
{{ pageScript.HP }}
</script-component>
or
<script-component :key="$route.fullPath + Math.random().toString(16) ">
window.alert('test on page load works when going back not')
</script-component>
</div>
</template>
So the answer is incredibly annoying. The problem was not with my code but with the provider's code. The code they gave me was intended to run on SSR only site. The only thing to pay attention to this code to "fix" the behavior is to unmount the component or key="$router.fullpath" but this will cause another issue depend on your component place. When you are inside the component and change the page, Nuxt will run the following lifecycle destroy the component and the mount on the new page (which is a problem) and then destroyed it again. This will cause latency unless you add async or defer.
So to summarize, the problem was with the provider code; this component will load the script tag inside the template where you need it. I will open the npm repo for this component that works with Nuxt and create a git issue of the memory leak in a component lifecycle.
New to Vue and Nuxt. I am trying to show skeletons before image is loaded completely.
Here's my attempt, skeleton shows but image is never loaded and onImageLoad is never called.
<template>
<div>
<img v-if="isLoaded" #load="onImgLoad" :src="me.img">
<div v-else class="skeleton"></div>
</div>
</template>
<script lang="ts">
export default {
props: {
me: Object,
},
data() {
return {
isLoaded: false,
}
},
methods: {
onImgLoad() {
console.log(` >> isLoaded:`, this.isLoaded)
return this.isLoaded = true
},
},
}
</script>
I have some broken image url to test fallback src, is it a problem? But I tried removing those broken links and it's still not working.
Example data:
export const me = {
name: 'David',
img: 'https//david.png', // Example broken > https://no.jpg
},
Please help me, what I am doing wrong?
Because isLoaded is false on the initial render, the img element is removed from the DOM tree. If it’s not in the DOM, the image src won’t be requested, ergo— no load event.
Switch to v-show. The img element will remain in the DOM, so the #load event will fire.
I've started to learn vue.js, and I want to do a transition using GSAP (and not css) between two pages, so I found those properties : v-on:enter, v-on:leave.
It seems that my v-on:enter animation is only working on the first call of my app. I don't see the "leave" animation, plus I have some duplicated content when the new page appears.
I've two questions here :
What am I missing ?
How can I start my v-on:enter animation when the DOM is fully loaded? (so far my animation starts even if my DOM is not fully loaded)
Here's the code I use on my App.vue file, thank you very much.
<template>
<div id="app">
<transition
appear
v-on:enter="enter"
v-on:leave="leave"
v-bind:css="false"
>
<router-view/>
</transition>
</div>
</template>
<script>
import { TweenMax } from "gsap/TweenMax";
export default {
name: 'App',
components: {
},
methods: {
enter(el, done) {
TweenMax.to('body', 1, {opacity:1, onComplete:done});
},
leave(el, done) {
TweenMax.to('body', 1, {opacity:0, onComplete:done});
}
}
}
</script>
Use the out-in transition mode to transition the current view out first, then when complete, the new view transitions in.
Create a beforeEnter method in your component methods option to set the target element opacity to 0.
Listen to the beforeEnter JavaScript hook by adding v-on:before-enter="beforeEnter" to the
transition component.
Unless there is a good reason to use body as your target element, use the view component el instead.
Revised code:
<template>
<div id="app">
<transition
appear
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
<router-view/>
</transition>
</div>
</template>
<script>
import { TweenMax } from "gsap/TweenMax";
export default {
name: 'App',
components: {
},
methods: {
beforeEnter(el) {
TweenMax.set(el, { opacity: 0 });
},
enter(el, done) {
TweenMax.to(el, 1, { opacity:1, onComplete:done });
},
leave(el, done) {
TweenMax.to(el, 1, { opacity:0, onComplete:done });
}
}
}
</script>
When I v-bind a element-ref with :ref="testThis" it stops working it seems. Compare this version which works:
<template>
<div>
<q-btn round big color='red' #click="IconClick">
YES
</q-btn>
<div>
<input
ref="file0"
multiple
type="file"
accept=".gif,.jpg,.jpeg,.png,.bmp,.JPG"
#change="testMe"
style='opacity:0'
>
</div>
</div>
</template>
<script>
import { QBtn } from 'quasar-framework'
export default {
name: 'hello',
components: {
QBtn
},
data () {
return {
file10: 'file0'
}
},
methods: {
IconClick () {
this.$refs['file0'].click()
},
testMe () {
console.log('continue other stuff')
}
}
}
</script>
With this one which DOES NOT work:
<template>
<div>
<q-btn round big color='red' #click="IconClick">
YES
</q-btn>
<div>
<input
:ref="testThis"
multiple
type="file"
accept=".gif,.jpg,.jpeg,.png,.bmp,.JPG"
#change="testMe"
style='opacity:0'
>
</div>
</div>
</template>
<script>
import { QBtn } from 'quasar-framework'
export default {
name: 'hello',
components: {
QBtn
},
data () {
return {
file10: 'file0'
}
},
methods: {
IconClick () {
this.$refs['file0'].click()
},
testThis () {
return 'file0'
},
testMe () {
console.log('continue other stuff')
}
}
}
</script>
The first one works. The second one throws an error:
TypeError: Cannot read property 'click' of undefined
at VueComponent.IconClick
As I would like to vary the ref based on a list-index (not shown here, but it explains my requirement to have a binded ref) I need the binding. Why is it not working/ throwing the error?
In the vue docs I find that a ref is non-reactive: "$refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding."
I think that matches my case.
My actual problem 'how to reference an item of a v-for list' is NOT easily solved not using a binded ref as vue puts all similar item-refs in an array, BUT it loses (v-for index) order.
I have another rather elaborate single file component which works fine using this piece of code:
:ref="'file' + parentIndex.toString()"
in an input element. The only difference from my question example is that parentIndex is a component property.
All in all it currently is kind of confusing as from this it looks like binding ref was allowed in earlier vue version.
EDIT:
Triggering the method with testThis() does work.
If you want to use a method, you will need to use the invocation parentheses in the binding to let Vue know you want it to bind the result of the call and not the function itself.
:ref="testThis()"
I think the snippet below works as you expect it to. I use a computed rather than a method.
new Vue({
el: '#app',
data() {
return {
file10: 'file0'
}
},
computed: {
testThis() {
return 'file0';
}
},
methods: {
IconClick() {
this.$refs['file0'].click()
},
testMe() {
console.log('continue other stuff')
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<q-btn round big color='red' #click="IconClick">
YES
</q-btn>
<div>
<input :ref="testThis" multiple type="file" accept=".gif,.jpg,.jpeg,.png,.bmp,.JPG" #change="testMe" style='opacity:0'>
</div>
</div>
Code is in: VueJS
Hi,
I'm curious how to show a component once and then never again. I've tried v-if and v-model but none of them seem to work.
This is my code:
<template >
<div id="fakeLoader" v-if="show"></div>
</template>
<script>
import $ from 'jquery'
import '../../bower_components/fakeLoader/fakeLoader.min'
export default {
name: 'Fakeloader',
data() {
return {
show: true
};
},
methods: {
showLoader() {
this.show = false;
}
},
mounted () {
$(document).ready(function () {
// Init Fakeloader
$('#fakeLoader').fakeLoader({
timeToHide: 800,
spinner: 'spinner5',
bgColor: '#274156'
}
);
});
this.showLoader();
}
}
</script>
<style>
#import '../../bower_components/fakeLoader/fakeLoader.css';
#fakeLoader {
z-index: 999999 !important;
}
</style>
I'm setting a boolean called show and make it false whenever the jQuery function is called and make it permanently false so this component will not show again FOR THE CURRENT SESSION. So if the user opens another tab, the fakeloader should appear again at the beginning.
You might want to look into v-cloak. It allows you to segment areas of your site based on whether vue is loaded or not.
For your example, I'd have a CSS sheet comprised of
#app[v-cloak] {
display: none
}
#fakeloader {
display: none
}
#fakeloader[v-cloak] {
display: block
}
Then
<div id="app">
<div id="fakeloader" v-cloak>
This is my loader
</div>
<div id="content" v-cloak>
This is my page
</div>
</div>
This will let your app have preference when vue is loaded and the loader having preference when vue is loading.
EDIT: Just to show why a fiddle is a good thing to have before you answer. I have a fiddle up here which should demonstrate and I fixed the code.