In my Vue project, I would like to load a script from a server (e. g. https://myurl.com/API.js).
The script contains a variable, which I would like to use in my Vue component (view).
The problem is that when I load that scrip using the loadScript module:
import Vue from 'vue'
import LoadScript from 'vue-plugin-load-script';
Vue.use(LoadScript);
Vue.loadScript('https://quvia.cz:4443/portalAPI.js')
It is then loaded after the Vue component, so when try to console.log(externalScriptVariable), it is undefined. If I would setTimeout for 1 second, it would output the variable just fine.
What can I do in Vue.js to "await" the script loading, so it would load before every other Vue component?
You can use async/await
import Vue from 'vue'
import LoadScript from 'vue-plugin-load-script';
Vue.use(LoadScript);
(async function() {
await Vue.loadScript('https://quvia.cz:4443/portalAPI.js');
// other things after script loaded
})();
Or promise's then
import Vue from 'vue'
import LoadScript from 'vue-plugin-load-script';
Vue.use(LoadScript);
Vue.loadScript('https://quvia.cz:4443/portalAPI.js').then(() => {
// other things after script loaded
})
.catch(() => {
// error
});
In my case, the problems were resolved by the "window" scope. Also, if you need to access any Vue element inside the "onload" function, you need a new variable for the "this" instance.
<script>
import { mapActions } from "vuex";
export default {
name: "Payment",
methods: {
...mapActions(["aVueAction"])
},
created() {
let paywayScript = document.createElement("script");
let self = this;
paywayScript.onload = () => {
// call to Vuex action.
self.aVueAction();
// call to script function
window.payway.aScriptFunction();
};
// paywayScript.async = true;
paywayScript.setAttribute(
"src",
"https://api.payway.com.au/rest/v1/payway.js"
);
document.body.appendChild(paywayScript);
}
};
</script>
I worked with this on Vue 2.6.
What you could do is use the beforeCreate() lifecycle that vue provides and load the script from there.
import LoadScript from 'vue-plugin-load-script';
export default {
name: "App",
beforeCreate() {
LoadScript('https://quvia.cz:4443/portalAPI.js')
}
};
there are also other lifecycles that might suit your needs which you can find here: https://v2.vuejs.org/v2/guide/instance.html
Also, calling the LoadScript this in the main.js would make sure it is done before any components load
Related
I am using React, and I am wondering how I can access the object that I have included as script in my html file within my own jsx file
This is an example that I got:
<script src="url-to-some-script"></script>
<div id="an-id-wrapper">
<div id="an-id"></div>
</div>
<script>
var settings = { config: "some-config", id: "an-id" };
TheObjectThatINeedToAccessFromScript.initialize(settings);
</script>
I want to do something like:
Add the script in my html file
Place the div in some React component
Be able to reach the TheObjectThatINeedToAccessFromScript so I can call initialize on it within my jsx file. Eg trough an import TheObjectThatINeedToAccessFromScript from "some-where";
How can I do an import on this script?
It is now available on the window object, so I can access it trough there:
window.TheObjectThatINeedToAccessFromScript.initialize(settings);
If I understand correctly, you just want to export your initialize() function from the script, and import it within your component. E.g:
function initialize() {
/*[...]*/
}
export { initialize };
import React, { useEffect } from 'react';
import { initialize } from './yourScriptFile';
/*[...]*/
function MyComponent() {
useEffect(() => {
initialize();
}, []);
return <div>{/*[...]*/}</div>
}
Unless it's absolutely vital, as a best practice, try to avoid binding things to the window. It can end in tears.
In Vue2, I was able to access my Vue instance to make use of components registered with Vue.
test.js
import Vue from 'vue'
export function renderLogin () {
Vue.toasted.show('Please login again', { type: 'error', duration: 2000 })
}
In the above code, I am able to access the toasted package as I have already registered it with Vue in my main.js. However, in Vue3 I'm unable to use the toasted package as I'm unable to access the Vue instance inside a js file.
Need help on how to access Vue instance('this') inside a js file.
After a day of searching, I was able to access the toasted component from the vue instance inside a js file.
First, we would have to export the app instance to be able to read it in a js file
main.js
export const app = createApp({
render() {
return h(AppWrapper);
},
});
Next, we would have to register our component in our globalProperties of our app's instance.
app.config.globalProperties.$toast = toast;
We can now import the app instance in our js file and access toast component
test.js
import { app } from '#/main.js'
app.config.globalProperties.$toast('Toast working fine', {
type: 'success',
duration: 2000,
})
Hope this helps someone out. Please let me know if there are other/better ways. Thank you
// Vue 3 Composition API
<script>
import { getCurrentInstance } from 'vue';
export default {
setup() {
const _instance = getCurrentInstance();
const vueInstance = _instance.appContext;
},
};
</script>
It's not exactly the way as in Vue2, but this will probably expose what you are looking for.
If you want to make a package globally available in Vue3 you probably need to add the following code to a plugin:
//* This will help for accessing the toasted instance in other files (plugins)
app.config.globalProperties.$toasted = toasted;
//* This will expose the toasted instance in components with this.$toasted
app.provide('$toasted', toasted);
With this you are able to get the toasted instance in the options api with: this.$toasted
And with the composition api:
const { $toasted } = _instance.appContext.app.config.globalProperties;
And in another plugin with:
constructor(app) { app.config.globalProperties; }
You can use provider/inject.
For example if you want to use axios across my components, provide axios in your main.js
import { createApp } from "vue";
import App from "./App.vue";
import axios from "axios";
const app = createApp(App);
app.provide("http", axios);
app.mount("#app");
Then in SFC component you could access by 2 ways:
// Composition API
<script>
import { inject } from 'vue'
export default {
setup() {
const http = inject("http");
http.get("https://jsonplaceholder.typicode.com/todos/1").then((response) => {
console.log(response.data);
});
}
}
</script>
// Vue 2 options API
<script>
export default {
inject: ["http"],
}
</script>
Original answer here.
I am trying to set up my Vuex store states to contain an array of Marker objects from Google Maps Javascript API. Currently, my createStore function looks like this:
import { createStore } from "vuex";
export default createStore({
state: {
GCPMarkers: {
basemapMarkers: [new google.maps.Marker()],
mosaicMarkers: [new google.maps.Marker()],
},
},
// other properties
});
The Google Maps Javascript API is loaded in a Vue component named Map in my Home page:
// In Map.vue
mounted() {
const loader = new Loader({
apiKey: "my key",
version: "weekly",
});
loader
.load()
.then(this.initMap)
.catch((e) => console.log(e));
},
And my main.ts is like so:
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
createApp(App).use(store).use(router).mount("#app");
When I run my app, it reports the following in the basemapMarkers: [new google.maps.Marker()], line:
Uncaught ReferenceError: google is not defined
I understand that the error is due to google not being loaded yet by the time that import store from "./store" is ran. However, I'm not sure what the right approach to solve this problem is.
The best I could come up with is moving the loader code to my main.ts so that only when the promise from loader is resolved do I start importing other files. However, that feels clunky to me and is probably not the way.
Since you need google to be defined (loaded by Loader), you should defer the app creation until after Loader has completed. Assuming the load() succeeds in assigning the google global, you could create the app in that Promise's callback:
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
const loader = new Loader({
apiKey: "my key",
version: "weekly",
});
loader
.load()
.then(() => {
const store = require('./store').default
createApp(App)
.use(store) // ok to use store now that `google` is loaded
.mount('#app');
})
.catch((e) => console.log(e));
I try to convert a HTML template (Bootstrap 5) to Gatsby template. CSS and pages working expected but in HTML template there is a main.js file and it need to load after page rendered.
I modify the main.js file like that;
import { Swiper } from "swiper/swiper-react.cjs.js";
import GLightbox from "glightbox/dist/js/glightbox.min.js";
import AOS from "aos";
AOS.init();
export const onClientEntry = () => {
window.onload = () => {
console.log("deneme");
/*rest of code*/
};
};
In here I try two way. One of them, I create main.js file inside src->components->assets->js folder. Then in layout.js I try to import that file.
import React from "react";
import PropTypes from "prop-types";
import { Breadcrumb } from "gatsby-plugin-breadcrumb";
import Header from "./partials/header";
import { Helmet } from "react-helmet";
import useSiteMetadata from "./hooks/siteMetadata";
import "./assets/css/style.css";
import "./assets/js/main.js"
However in here in debug not hit the any method inside onClientEntry. So I decide to change my way.
Secondly, I try to add code inside main.js to gatsby-browser.js. That's time again getting Cannot read property 'addEventListener' of null because of html is not ready yet.
My file structure:
window (and other global objects like document) are not available during the SSR (Server-Side Rendering) because this action is performed by the Node server (where for obvious reasons there's no window, yet) so you can't access directly to onload function. In addition, accessing these global objects outside the scope of React (without hooks) can potentially break React's hydration process.
That said, you have a few approaches:
Using React hooks. Specifically, useEffect with empty dependencies ([]) fits your specifications, since the effect will be fired once the DOM tree is loaded (that's what empty deps means):
const Layout = ({children}) => {
useEffect(()=>{
mainJs();
}, [])
return <main>{children}</main>
}
Assuming that your ./assets/js/main.js file has a mainJs() function exported, this approach will load it when the DOM tree is loaded. For example:
const mainJs= ()=> console.log("deneme");
The console.log() will be triggered when the HTML tree is built by the browser. Tweak it to adapt it to your needs.
Adding a window-availability condition like:
export const onClientEntry = () => {
if(typeof window !== 'undefined'){
window.onload = () => {
console.log("deneme");
/*rest of code*/
};
}
};
Alternatively, you can output the console.log directly in your onClientEntry, depending on your needs:
export const onClientEntry = () => {
console.log("deneme");
/*rest of code*/
};
You can even combine both approaches by adding a useEffect in your gatsby-browser if it works for you.
I currently have a strange Vue setup due to our websites all using an old system.
What we have had to do is create an instance of Vue for each component (usually not many). What I want to do for all components is to pass their name and reference to the element into an array, just for reference when debugging issues on live issues.
app.js
import Vue from "vue";
import Axios from 'axios';
import inViewportDirective from 'vue-in-viewport-directive';
window.components = [];
Vue.component( 'video-frame', () => import('./components/VideoFrame.vue' /* webpackChunkName: "video-frame" */) );
Vue.prototype.$event = new Vue();
Vue.prototype.$http = Axios;
Array.prototype.forEach.call(document.querySelectorAll(".app"), (el, index) => new Vue({el}));
Now i'm adding the following code to each component, is there not a way I can do this once within my app.js and have all the components automatically do the following:
mounted() {
window.components.push({
tag: this.$vnode.tag,
elm: this.$vnode.elm
});
},
You can use a global mixin like this:
Vue.mixin({
mounted: function() {
window.components.push({
tag: this.$vnode.tag,
elm: this.$vnode.elm
});
}
});
That will ensure that code will run on the mounted hook on every single one of your Vue instances.
Reference: https://v2.vuejs.org/v2/guide/mixins.html