Waiting for a script to load in VueJS? - javascript

So it seems Vue is not waiting for Stripe to load, natural behavior most probably. I tried coding a watcher to wait for it but it didn't block render until loaded, am I missing something?
<template>
<section>
<div class="px-8 pt-6 pb-12">
testing
</div>
</section>
</template>
<script>
export default {
head: {
script: [
{ src: 'https://js.stripe.com/v3/' },
],
},
data() {
return {
stripeReady: false,
stripeInterval: null,
elements: null,
}
},
watch: {
stripeReady: function(data) {
if (data) {
clearInterval(this.stripeInterval)
this.render()
}
}
},
created() {
let localThis = this
this.stripeInterval = setInterval(function() {
try {
Stripe('pk_test_');
localThis.stripeReady = true;
}
catch {}
}, 500)
},
mounted() {
let stripe = Stripe('pk_test_');
this.elements = stripe.elements();
}
}
</script>

Related

Global Vue component in plugin causes memory leak?

I'm using a Popup style UI component in a Nuxt.js base project. This is used by many pages and routes, so I declared and initiated as global component plugin when the app starts, like below:
// nuxt.config.js
plugins: [
{ src: '~/plugins/popup/index.js', mode: 'client' },
],
// plugins/toast/index.js
import Vue from 'vue';
import PopupComponent from './Popup.vue';
const PopupConstructor = Vue.extend(PopupComponent);
export default () => {
Vue.use({
install: () => {
let _popup = new PopupConstructor();
window.popup = Vue.prototype.popup = {
appear: _popup.appear,
disappear: _popup.disappear
};
_popup.vm = _popup.$mount();
_popup.dom = _popup.vm.$el;
document.body.appendChild(_popup.dom);
}
});
};
// Popup.vue
// some edit applied for the sake of simplicity
<template>
<div
class="popup"
:class="{
'--error': error,
'--visible': visible
}"
ref="popup"
>
<div class="content" ref="content">
<div class="title">{{title}}</div>
<div class="text">{{detail}}</div>
</div>
</div>
</template>
import gsap from 'gsap';
export default {
data: function () {
return {
visible: false,
title: '',
detail: '',
timer: 3000,
timeout: null,
animationTimeout: null,
};
},
created() {
},
mounted() {
this.$_appear = null;
this.$_disappear = null;
},
beforeDestroy() {
this.$_appear.kill();
this.$_appear = null;
this.$_disappear.kill();
this.$_disappear = null;
},
appear({ title, detail }) {
if (this.visible) {
this.clearTimeout();
}
this.visible = true;
this.$_appear.kill();
this.$_disappear.kill();
this.title = title;
this.detail = detail;
this.$_showAni = gsap.to(this.$refs.popup, 0.5, {
css: {
top: '100px',
opacity: 1
},
onComplete: () => {
this.$_appear = null;
}
});
this.timeout = window.setTimeout(() => {
this.disappear();
}, this.timer);
},
disappear() {
this.clearTimeout();
this.$_disappear.kill();
this.$_disappear = gsap.to(this.$refs.popup, 0.5, {
css: {
top: '100px',
opacity: 0
},
onComplete: () => {
this.$_disappear = null;
this.visible = false;
}
});
},
clearTimeout() {
if (this.timeout) {
window.clearTimeout(this.timeout);
this.timeout = null;
}
}
}
As you see, by this code the Popup vue component's methods(appear, disappear) will be accessible through window.popup, and the component itself will be created, mounted, attached on document.
This works just fine, but the problem is it seems this leads to memory leak. As I profile the memory allocation timeline using Chrome devtool, from some point of time memory allocated with window causes retained(dangling?; could be GC-ed but left due to reference using?) memory.
Is the usage of plugin like above okay? If not, to get the same utility while preventing memory leak, which part should be corrected?
EDIT:
I added the simple version implementation code for Popup which uses GSAP library for an animation. It uses the animation for appear and disappear sequentially.

GET json api data in datatable with axios and vuejs

I have a datatable and I want to pass the data according to the api that returns a json using findAll() from the sequelize..
But in console.log when I call the getUser method it returns the data with the data. But when you insert data into the datatable: it is informing you that it has no data.
Example datatable using in code: https://vuejsexamples.com/a-vue-plugin-that-adds-advanced-features-to-an-html-table/
<template>
<div>
<data-table v-bind="bindings"/>
</div>
</template>
<script>
import ActionButtons from "../Components/ActionButtons"
import axios from "axios"
export default {
name: 'Usuarios',
data(){
return {
user: this.user,
errors: []
}
},
computed: {
bindings() {
return {
data: this.user,
lang: "pt-br",
actionMode: "single",
columns: [
{
key:"code",
title:"Código"
},
{
key:"name",
title:"Nome"
},
{
key:"login",
title:"Login"
},
{
key:"cpf",
title:"CPF"
},
{
key:"actions",
title:"Ações",
component: ActionButtons,
},
],
}
}
},
methods:{
getUser() {
axios
.get("http://localhost:3005/users")
.then((res) => {
this.user = res.data;
})
.catch((error) => {
console.log(error);
});
},
}
};
</script>
I believe the reason it doesn't work is because the getUser() method is defined but not called.
If you move the async request into a created() lifecycle hook, the request will be made before the component is mounted, so the table should have access to the data. https://v3.vuejs.org/api/options-lifecycle-hooks.html#created
I think this will really help you.
<template>
<v-card>
<v-card-title>
Liste du Personnel
</v-card-title>
<v-card-text class="mt-3">
<main>
<data-table v-bind="listing_personnel" #actionTriggered="handleAction"/>
<br>
</main>
</v-card-text>
</v-card>
</template>
<script>
import axios from "axios";
import ActionButtons from "./ActionButtons"
export default {
data(){
return {
user: [],
errors: []
}
},
created() {
this.getPersonnel();
},
methods: {
handleAction(actionName, data) {
console.log(actionName, data);
window.alert("check out the console to see the logs");
},
async getPersonnel() {
try {
const response = await axios.get("SeletAllUsers");
this.user = response.data;
}
catch (error) {
console.log(error);
}
},
},
computed: {
listing_personnel() {
return {
data: this.user,
actionMode: "multiple",
columns: [
{
key: "user_matricule",
title: "Matricule"
},
{
key: "user_lastname",
title: "Noms"
},
{
key: "user_firstname",
title: "Prénoms"
},
{
key: "user_contact1",
title: "Contact"
},
{
key: "user_email",
title: "Email"
},
{
key:"actions",
title:"Actions",
component: ActionButtons,
},
]
};
}
},
};
</script>
/* preferably put this in its main.js
axios.defaults.baseURL = 'http://localhost:8080/';*/*

Scope of locally defined variables in vue

In this sample:
<template>
<div>
<p
v-for="prop in receivedPropsLocal"
:key="prop.id"
>
{{prop}}
</p>
</div>
</template>
<script>
export default {
name: "PropsReceiver",
props: {
receivedProps: {
required: true,
type: Array,
default() {
return [];
},
},
},
data() {
return {
receivedPropsLocal: Array,
};
},
methods: {
},
watch: {
receivedProps: {
deep: true,
handler(val) {
let tmp = Object.entries(Object.assign({}, val));
this.receivedPropsLocal = tmp;
},
},
},
computed: {
getReceivedPropsLocal: {
get() {
if (!this.receivedPropsLocal) {
let tmp = Object.entries(Object.assign({}, this.receivedProps));
this.receivedPropsLocal = tmp;
return this.receivedPropsLocal;
}
return this.receivedPropsLocal;
},
set(value) {
this.receivedPropsLocal = value;
},
},
},
};
</script>
what's the scope of tmp? Is it handled similarly to other entries in data() or not? or it doesn't matter.
I believe tmpis only accessible from inside the handler function since you used let to declare it.
You should declare it directly in the data object to use it anywhere in the component.

Child is not updated when boolean prop is changed

I have the following components:
Parent:
<template>
<Child path="instance.json"
v-bind:authenticated="authenticated"
v-bind:authenticator="authenticator"
/>
</template>
<script>
import { getAuthenticator } from '../auth';
export default {
data() {
return {
authenticated: false,
authenticator: null
};
},
beforeMount: async function () {
this.authenticator = getAuthenticator()
this.checkAccess();
},
methods: {
checkAccess() {
this.authenticated = this.authenticator.isAuthenticated();
},
async login() {
this.checkAccess();
await this.authenticator.signIn();
this.checkAccess();
}
}
};
</script>
Child:
<template>
<div id="swagger-ui"></div>
</template>
<script>
import swagger from "swagger-ui-dist";
import "swagger-ui-dist/swagger-ui.css";
export default {
props: ["path", "authenticated", "authenticator"],
mounted: async function() {
if (this.authenticated) {
let token = (await this.authenticator.getToken()).accessToken;
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: "#swagger-ui",
onComplete: function() {
ui.preauthorizeApiKey("token", token);
}
});
} else {
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: "#swagger-ui"
});
}
}
};
</script>
In the parent component, when the login method is called, the authenticated variable changes to true. Since authenticated is passed as a prop to the Child component, I'd expect the Child to be refreshed whenever authenticated is changed. However, the Child does not refresh.
I think that the problem might be caused by the fact that I am not using authenticated in the template of the child at all. Instead, I'm using it only in the mounted hook. In my case, I have no use for authenticated in the template.
I tried two solutions:
calling this.$forceUpdate() in the login method of Parent - that didn't work at all (nothing changed)
Adding :key to the Child, and changing the key each time the login is called - this works, however, it's a bit hacky. I'd like to understand how to do that properly.
what you need is to use a watcher.
Actually, your code is only run once (when de component is mounted), not at each prop change.
<template>
<div id="swagger-ui"></div>
</template>
<script>
import swagger from 'swagger-ui-dist';
import 'swagger-ui-dist/swagger-ui.css';
export default {
props: {
path: {
type: String,
default: '',
},
authenticated: {
type: Boolean,
default: false,
},
authenticator: {
type: Object,
default: () => {},
},
},
watch: {
async authenticated(newValue) {
await this.updateSwagger(newValue);
},
},
async mounted() {
await this.updateSwagger(this.authenticated);
}
methods: {
async updateSwagger(authenticated) {
if (authenticated) {
const token = (await this.authenticator.getToken()).accessToken;
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: '#swagger-ui',
onComplete: function () {
ui.preauthorizeApiKey('token', token);
},
});
} else {
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: '#swagger-ui',
});
}
},
},
};
</script>
It's fine that you're not using it in the template, the issue is that you only check authenticated in the child's mounted hook, which only runs once (and is false at that time).
You should use a watch to track changes to the authenticated prop instead of mounted:
watch: {
authenticated: {
handler(newValue, oldValue) {
this.setUi();
},
immediate: true // Run the watch when `authenticated` is first set, too
}
}
That will call a setUi method every time authenticated changes:
methods: {
async setUi() {
if (this.authenticated) {
let token = (await this.authenticator.getToken()).accessToken;
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: "#swagger-ui",
onComplete: function() {
ui.preauthorizeApiKey("token", token);
}
});
} else {
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: "#swagger-ui"
});
}
}
}

Vue.js. Data property undefined

I'm trying to access my data property in my Vue.js component. Looks like I'm missing something obvious.
Here is a short version of my code. StoreFilter.vue is a wrapper for matfish2/vue-tables-2.
<template>
<store-filter :selected.sync="storeIds"></store-filter>
</template>
<script>
import StoreFilter from './Filters/StoreFilter';
export default {
components: {
StoreFilter
},
data() {
return {
options : {
requestFunction(data) {
console.log(this.storeIds); //undefined
return axios.get('/api/orders', {
params: data
}).catch(function (e) {
this.dispatch('error', e);
}.bind(this));
},
},
storeIds: [],
}
},
watch : {
storeIds(storeIds) {
this.refreshTable();
}
},
methods : {
refreshTable() {
this.$refs.table.refresh();
}
}
}
</script>
How to get storeIds from requestFunction?
Use a closure, see rewrite below.
data() {
let dataHolder = {};
dataHolder.storeIds = [];
dataHolder.options = {
requestFunction(data) {
// closure
console.log(dataHolder.storeIds); // not undefined
return axios.get('/api/orders', {
params: data
}).catch(function (e) {
this.dispatch('error', e);
}.bind(this));
}
}
return dataHolder;
}
I recommend using the created() way to handle this.
export default {
// whatever you got here
data () {
return {
options: {}
}
},
created () {
axios.get('/api/orders', { some: params }).then(response => this.options = response.data)
}
}

Categories