Show a Component only once and never again VUEJS (current session) - javascript

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.

Related

Vue - Using localStorage to hold a theme not working as expected

This issue has confused me while developing a Vue site for some time now and I haven't been able to resolve my particular bug with other threads.
The idea should be simple, have a switch that toggles a value between 'light' and 'dark', and bind the class to that value.
<template>
<div id="app" :class='themeValue'>
<!-- Switch component from Buefy UI !-->
<b-switch size='is-large' type='is-dark' v-model='theme'></b-switch>
<!-- Other components !-->
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
theme: localStorage.getItem('theme') || false
}
},
watch: {
theme: function() {
localStorage.setItem('theme', this.theme)
}
},
computed: {
themeValue: function() {
return this.theme ? 'light' : 'dark'
}
}
}
</script>
<style lang="scss">
#import './_variables.scss';
.dark {
color: $light;
background-color: $background;
}
.light {
color: $dark;
background-color: $lightbackground;
}
</style>
I have tried what feels like a million different variations of the above, including using mounted() to set this.theme, holding all the logic in an external component and using $emit, etc.
The expected behavior is to default to dark theme unless localStorage holds a value for 'theme', in which case to default to that. The switch component should match the state of the theme always (i.e. if saved to light theme, the switch should default to true).
The behavior of the above code does is to always default to light theme, and the switch defaults to false. Those two being out of sync (light theme is applied when theme === true), the first switch press doesn't change the theme but does change itself to true, and subsequent presses work correctly (true applies .light class, false applies .dark)
EDIT: localStorage was storing the true/false values as strings. You could do a nice implementation with JSON.parse I think, but I ended up just using a mounted with if (localStorage.getItem('theme') === 'true') and it works fine. Needless to say I'm really mad at myself that this took 4 hours.
Okay so here is how i got it working locally, i switched around the or statement and changed the property name to themes.
<template>
<div id="app" :class='themeValue'>
<!-- Switch component from Buefy UI !-->
<button size='is-large' type='is-dark' #click='themes = !themes'>klik</button>
<!-- Other components !-->
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
themes: false || localStorage.getItem('theme')
}
},
watch: {
theme: function() {
localStorage.setItem('theme', this.theme)
}
},
computed: {
themeValue: function() {
return this.themes ? 'light' : 'dark'
}
}
}
</script>
<style lang="scss">
.dark {
color: white;
background-color: black;
}
.light {
color: black;
background-color: white;
}
</style>
So i suggest you change the name of your watch function to something like updateTheme, and switch the conditional statement in your data property and then it should work :)

Why does boostrap vue toaster dissapears immediately?

I am using Laravel + Vue.js to create a SPA. In the SPA, I am creating a form where user can write markdown content in it and click button to submit it. I want to show an error toaster at the bottom right corner of the screen if the user didn't input any content when they clicked the send button.
I am using boostrap vue toast to implement the error toaster.
However, when I clicked the send button, the error toaster will just blink for 1 second and dissapear immediately. Also, the error toaster blinks at top left corner which is different from what I wrote in the code below.
The mixin that contains the method to invoke the toast:
ErrorMessage.vue
# ErrorMessage.vue
<script>
export default {
methods: {
showErrorMessage (msg) {
this.$bvToast.toast(msg, {
title: ["Error!"],
variant: "danger",
toaster: "b-toaster-bottom-right"
});
}
}
};
</script>
I imported the above mixin in this vue component ArticleForm.vue.
ArticleForm.vue
<template>
<form #submit.prevent="passArticleData">
<div id="editor-markdown-editor">
<div id="editor-markdown">
<div
id="editor-markdown-tag-input"
#click="toggleTagModal"
>
<ul v-if="insertedTags.length">
<li v-for="tag in insertedTags"
:key="tag.id"
class="tag"
>
{{ tag.name }}
</li>
</ul>
<span v-else>タグを入力してください</span>
</div>
<div id="editor-markdown-textarea">
<textarea :value="input" #input="update" ref="textarea"></textarea>
<div id="drop-here"></div>
</div>
</div>
<div id="editor-preview">
<article v-html="compiledMarkdown(input)" class="markdown-render" ref="articleHtml"></article>
</div>
</div>
</form>
</template>
<script>
import _ from "lodash";
import ErrorMessage from "./mixins/ErrorMessage";
import { markdownTable } from "markdown-table";
export default {
props: {
article: Object,
tags: Array
},
mixins: [ErrorMessage],
data () {
return {
articleToPass: {
title: "",
body: "",
isDraft: true
},
input: "",
tagsToPass: [],
insertedTags: [],
tagModalOpen: false
};
},
methods: {
update: _.debounce(function (e) {
this.input = e.target.value;
}, 300),
passArticleData (e) {
// Get title from input
try {
this.articleToPass.title = this.$refs.articleHtml.getElementsByTagName("h1").item(0).innerHTML;
} catch (e) {
this.showErrorMessage(["Body md can't be blank"]);
}
// Remove first line(title) from users' input
this.articleToPass.body = this.input.substring(this.input.indexOf("\n") + 1);
// tag id of written article
const tagIds = this.insertedTags.map(obj => obj.id);
this.tagsToPass = tagIds;
this.$emit("handle-new-data", this.articleToPass, this.tagsToPass);
}
}
Parent component of the above vue component:
ArticleCreate.vue
<template>
<div id="content-area">
<header-component></header-component>
<main id="editor-area">
<article-form :article="article" :tags="tags" #handle-new-data="postArticle"></article-form>
</main>
</div>
</template>
<script>
import HeaderComponent from "./Header.vue";
import ArticleForm from "./ArticleForm.vue";
export default {
data () {
return {
article: {
title: "",
body: "",
is_draft: true
},
tags: []
};
},
components: {
HeaderComponent,
ArticleForm
},
methods: {
postArticle (articleObj, tagsData) {
const data = { article: articleObj, tags: tagsData };
axios.post("/api/articles", data)
.then((res) => {
this.$router.push({ name: "article.show", params: { article: res.data } });
});
}
}
};
</script>
I tried:
changed this.$bvToast to this.$root.$bvToast (based on this issue)
downgrade my bootstrap version from 4.6.0 to 4.5.3 (based on this question)
I have spent trying to solve this but failed. Does anyone know why is this happening and how can I make it work? Please let me know if you need any extra information. Thank you in advanced.
As stated in this comment on stackoverflow that is usually a sign of a bootstrap version mismatch.
I was actually able to reproduce that issue and also fix it with rolling back to bootstrap v4
Broken with bootstrap 5
https://codesandbox.io/s/bootstrap-vue-toasting-broken-with-bootstrap-5-bqe2c
Working with bootstrap 4
https://codesandbox.io/s/bootstrap-vue-toasting-working-with-bootstrap-4-jk2jl
I have figured out the problem.
The problem is that bootstrap-vue css is not loaded properly in my project.
Just add
import "bootstrap-vue/dist/bootstrap-vue.css";
to app.js and it works perfectly fine now.
Thank you

dynamic script loading Nuxt route change casing crushing

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.

Iframe load event fires twice

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>

Transition between two pages doesn't work with vue.js and GSAP

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>

Categories

Resources