dynamic script loading Nuxt route change casing crushing - javascript

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.

Related

nuxtjs vuejs #error is not firing in a component when trying to load a fallback image

I have a component that I feed with props.
When an image throws a 404 I want to load a fallback image. So far so good.
However the #error function never fires when an image is broken and I can't figure out why! I never get 'hello event' in the console.
The component is on the first level of a nuxt page, my setup is a static SPA.
I tried to implement it as mentioned at the end of this github issue: https://github.com/vuejs/vue/issues/5404
<template>
<div class="channel">
<div class="img">
<img :src="imgUrl" :alt="name" :title="name" #error="getFallBackImage" />
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
chanCover: this.cover
}
},
props: {
name: {
type: String,
required: true
},
cover: {
type: String,
required: true
}
},
computed: {
imgUrl() {
console.log(this.chanCover)
return "channels/"+this.chanCover+".jpg"
},
},
methods: {
getFallBackImage(event) {
console.log("hello event")
this.chanCover = "default"
}
}
}
</script>

CSS of component balise doesn't load inside spefic component

I've a problem to load the css of my bloc component.
The webpage component allow to create an iframe and set some content inside easily.
It load correctly the template and script tag but not the css (it doesn't load).
Sometime it works, most of the time, it didn't.
I was thinking that it was a problem with the loading of the component but no.
If I load the component before or after the render of my "webpage" component : it don't load.
I've try with the auto import to true and after to false, but it solve nothing.
I have 2 components : webpage and bloc.
bloc.vue
<template>
<div class="bloc">
<p>Le texte</p>
</div>
</template>
<style>
.bloc {
background-color: blue;
padding: 20px;
}
</style>
webpage.vue
<script>
import Vue from "vue";
export default {
props: {
css: {
type: String,
required: false,
},
},
data() {
return {
load: false,
};
},
render(h) {
return h("iframe", {
on: { load: this.renderChildren },
});
},
beforeUpdate() {
//freezing to prevent unnessessary Reactifiation of vNodes
this.iApp.children = Object.freeze(this.$slots.default);
},
mounted() {
if (!this.load) this.renderChildren();
},
methods: {
// https://forum.vuejs.org/t/render-inside-iframe/6419/12
renderChildren() {
this.load = true;
const children = this.$slots.default;
const head = this.$el.contentDocument.head;
const body = this.$el.contentDocument.body;
let style = this.$el.contentDocument.createElement("style");
style.textContent += this.$props.css;
head.appendChild(style);
const iApp = new Vue({
name: "iApp",
// freezing to prevent unnessessary Reactifiation of vNodes
data: { children: Object.freeze(children) },
render(h) {
return h("body", this.children);
},
});
this.iApp = iApp; // cache instance for later updates
this.iApp.$mount(body); // mount into iframe
},
},
};
</script>
app.vue
<template>
<Webpage>
<component :is="name"></component>
</Webpage>
</template>
<script>
import bloc from "#/components/Bloc";
import Webpage from "#/components/Webpage";
export default {
components: {
bloc,
Webpage,
},
computed: {
name() {
return "bloc";
},
},
};
</script>
Do you have an idea where this might come from ?
The codesanbox : https://codesandbox.io/s/error-style-component-import-1t1hs?file=/pages/index.vue
Thank you.
Probably Webpage component overrides it.
Try moving your style to index and <Webpage class="bloc">

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

vue.js v-model not updating if vue-range-slider component on page

I have a problem with the structure of my Vue.js components, but I don't understand what it is.
This is my app.js:
require('./bootstrap');
window.Vue = require('vue');
Vue.component('search', require('./components/Search').default);
Vue.component('graph', require('./components/Graph').default);
Vue.component('account', require('./components/Account').default);
Vue.component('design-theme', require('./components/DesignTheme').default);
const app = new Vue({
el: '#app',
data: {
},
methods: {
},
mounted: function () {
},
computed: {
}
});
So I don't have any methods or anything here, it is all in the four individual components. Each component works fine on its own, but when there is more than one in a page, something is off. Consider the Search.vue component. It simply sends an axios request to the server on keyup and shows a list of results:
<template>
<div class="search basic-search">
<input type="text" v-model="search_string" v-on:keyup="search" class="form-control search" placeholder="Search" value=""/>
<div :class="['search-results', active === true ? 'active' : '']">
<div class="search-result" v-for="result in search_results" v-on:click="submit(result.id)">
{{ result.name }}
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
search_string : '',
search_results : [],
active : false
};
},
methods : {
search : function() {
const axios_data = {
_token : $('meta[name="csrf-token"]').attr('content'),
str : this.search_string
};
axios.post('/search', axios_data).then(response => {
if(response.data.success){
this.search_results = response.data.stocks;
this.active = true;
}
});
},
submit : function(stock_id) {
document.location = "/graphs/" + stock_id;
}
}
}
</script>
This works fine if the Graph.vue component is not included on the page. But, if it is, then search_str always remains empty, even though the search method is called on keyup.
There are no errors in the console - it's just that search_string remains empty when I type (as does the input field).
Perhaps I don't understand something on a conceptual level in Vue.js, but I can't figure out the relation here, or how to adapt the code to this situation.
This is the problematic part of the Graph component, if this is removed then the search works OK.
<vue-range-slider v-model="range" :min="0" :max="100" v-on:drag-end="updateRange"></vue-range-slider>
This is the component in question:
https://github.com/xwpongithub/vue-range-slider
Another interesting side effect (that I just noticed) is that, when this component is on the page, it is impossible to select text with the mouse. It seems like the component is somehow hijacking events, but I don't understand how.
As you identified correctly, the Vue Range Slider component is intercepting the events. There is an open merge request on their github page.
As suggested in the referenced issues, you should change this line in your package.json file:
"vue-range-component": "^1.0.3",
To this one:
"vue-range-component": "Anoesj/vue-range-slider#master",
However, since this is not the default branch of the plugin, you should frequently check the issue on github and switch back to the official branch as soon as the merge request passes.

How to access data property of vue component

I am new to Vue.js and I think its amazing. I have been tasked to start implementing some vue components in our non-greenfield web application and I thought I would start by implementing some self-contained "widgets" that have to deal heavily with state in my work's rails app.
Its working great as a self-contained component but I want to load it with a data attribute so the component know what it needs to deal with. My Vue file looks like (I have redacted parts of this due to IP concerns):
<template>
<div class="card">
<div class="card-body">
${{ b.id }}
</div>
<div class="card-footer bg--blue-sky">
${{ b.amount }}
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
errors: [],
b: {
id: null,
amount: null
}
}
},
// Fetches posts when the component is created.
created: function () {
jQuery.ajax({
url: "/api/b/" + '2' + ".json",
method: 'GET',
dataType: "json"
})
.then(response => {
this.b = response.b
})
.catch(e => {
this.errors.push(e)
});
}
}
</script>
<style scoped>
</style>
The component is registered with:
import FiDis from '../components/fi_dis.vue'
Vue.component('fi_dis', FiDis);
document.addEventListener('turbolinks:load', () => {
const fi_dis = new Vue({
el: '#bs',
components: { FiDis }
})
});
And in my html.erb code I create the components with:
<div id="bs" policy="2">
<fi_dis data-b-id="1"></fi_dis>
<fi_dis data-b-id="2"></fi_dis>
</div>
This all works flawlessly, and does exactly what I want it to do except for one thing. I want to access the data-b-id attribute within the created function of the component (i.e. replace the number '2' in the url of the ajax call above with the value form the attribute). In this way, I hope for the component to handle ANY "fi_dis" I choose, merely by specifying the b-id in the data attribute I want it to handle.
How can I achieve this?
You communicate data values passing props from parent component to child components.
So for example you should define which props your component is allowed to receive:
import FiDis from '../components/fi_dis.vue'
Vue.component('fi_dis', FiDis);
document.addEventListener('turbolinks:load', () => {
const fi_dis = new Vue({
el: '#bs',
components: { FiDis },
props['bId'],
created() { // This is a lifecycle method
this.printPropertyValue();
},
methods: {
// Your custom methods goes here separeted by commas
printPropertyValue() {
console.log(this.bId);
}
}
})
});
And the sintax for passing the data from the component implementation is using v-bind:propertyName or :propertyName (short hand).
<div id="bs" policy="2">
<fi_dis :bId="1"></fi_dis>
<fi_dis :bId="2"></fi_dis>
</div>

Categories

Resources