Bind the vue to root element after plugin finishes ajax call - javascript

I am binding my application root element #app to vue and before that I am loading my custom plugin with Vue.use(myplugin). My plugin makes an ajax call, load the data and set it into Vue.$permission property.. so in short I want to load my user permission before mounting the app. but while my ajax call is fetching permission data, app is mounted and my page is getting rendered, which need the permission object.
is there a way I can bind the app root element to vue after my plugin finishes.. or any other alternate solution?

Yeah, that's quite simple actually:
const Vue = require('vue');
const vueInstance = new Vue({
// don't specify the 'el' prop there
});
doingAnAjaxCall() // assuming it returns a promise
.then(() => {
vueInstance.$mount('#root'); // only now the vue instance is mounted
});
If a Vue instance didn’t receive the el option at instantiation, it will be in “unmounted” state, without an associated DOM element. vm.$mount() can be used to manually start the mounting of an unmounted Vue instance.
See: https://v2.vuejs.org/v2/api/#vm-mount
So for your case you may use any asynchronous mechanism to detect the end of the ajax call. Maybe the simplest solution is to pass a callback function to your plugin object, and mount your vue instance inside.
/* Define plugin */
MyPlugin = {};
MyPlugin.install = function(Vue, options) {
doingAnAjaxCall()
.then(data => {
// do something with data
options.callback();
});
};
const Vue = require('vue');
/* Create the vue instance */
const vueInstance = new Vue({
// don't specify the 'el' prop there
});
/* Install the plugin */
Vue.use(MyPlugin, { // This object will be passed as options to the plugin install()
callback: () => {
vueInstance.$mount('#root'); // only now the vue instance is mounted
}
});

Related

How to render a native WebComponent inside an Vue mounted app ONLY ONCE

I created a native web-component(CampusList) which will request server.
Because I put it inside an element mounted by Vue, therefor Vue will createElementof that web-component second time
Because that component will require data from server, it will send the same request twice!
Of course it should not, what should I do?
Assuming you want the fetch to only happen once (even if you have multiple instances of the element on the page) you could declare a variable inside the custom element import, but outside of the definition, and check it before you fetch. In that way the fetch will only happen the first time it's encountered e.g.:
let fetchMade = false;
class CampusList extends HTMLElement {
connectedCallback() {
if (!fetchMade) {
console.log('fetch!');
fetchMade = true;
} else {
console.log('do not fetch');
}
}
}
customElements.define('campus-list', CampusList);

Stencil: call public method from another component via the DOM

I have a Stencil sidebar component with a refresh() method. I've exposed it with #Method():
#Method() async refresh() { ... }
and I can call it from my app:
const sidebar = document.querySelector('my-sidebar');
await sidebar.refresh();
However, I also have a popup component which is generated ad-hoc by the app via a separate API, and I want to make a button in the popup trigger the sidebar's refresh(). I've set the method as a Prop on the popup:
#Prop() refresh: Function;
and I've set the Prop in the app code as a reference to the method:
popup.refresh = sidebar.refresh;
...but when I try to execute it I get an error:
Uncaught (in promise) TypeError: ref.$lazyInstance$[memberName] is not a function
How can I get the popup to see the sidebar's method?
If you look at the value of sidebar.refresh in your app when you're trying to assign that Prop, you'll see something like this:
value(...args) {
const ref = getHostRef(this);
return ref.$onInstancePromise$.then(() => ref.$lazyInstance$[memberName](...args));
}
Stencil components are lazy-loaded by generating proxies with references pointing to methods, so you're just passing a reference to a proxy. The easiest way to fix this is to make a function which explicitly calls the method, forcing its hydration. (If the source component is not already hydrated, you'll need to wait for that first by using its lifecycle methods.)
Here's what that would look like in your app:
popup.refresh = () => sidebar.refresh();
// or, passing any arguments:
popup.refresh = args => sidebar.refresh(args);
You can also do it in your popup component:
async popupRefresh() {
let sidebar = document.querySelector('my-sidebar');
await sidebar.refresh();
}
If calling the method from inside another component in this way, you may see the TypeScript error Property 'refresh' does not exist on type 'Element'.
To avoid this, import the sidebar component into your popup component:
import { mySidebar } from 'stencil';
Then, use it as the type:
let sidebar: mySidebar = document.querySelector('my-sidebar');

Display custom third party html element inside a React component

I'm trying to show a custom HTML modal that is returned from a third-party library inside a functional React component.
This is the function that should load the modal, which is being called inside useEffect:
const modalRef = useRef()
const loadScript = async () => {
// custom script loader
const loader = new ScriptLoader({
src: 'checkout.reepay.com/checkout.js',
global: 'Reepay',
})
await loader.load()
// flag for displaying the div
setLoaded(true)
// Reepay.ModalCheckout(sessionId) should open a modal
modalRef.current.innerHTML = new window.Reepay.ModalCheckout(sessionId)
}
In my return:
return loaded ? <div ref={modalRef}></div> : <></>
When I try to display the component I just get a [Object object] inside the div. I don't know if using refs is the way to go, I'm a beginner and I didn't really understand how to integrate third-party code inside React.
From their documentation (https://docs.reepay.com/reference#overlay-checkout), it seems that instance of Reepay.ModalCheckout is an object, not HTML:
// Step 1: Initialize
var rp = new Reepay.ModalCheckout(); // No session id given
// ... Backend to backend call ...
// Step 2: Load the modal
rp.show(' YOUR SESSION ID HERE '); // Call the .show function with the session id
You do not have to inject it inside of a modal, since it comes with their own modal.
If you really want to inject it into a container, you should be using:
var rp = new Reepay.EmbeddedCheckout(' YOUR SESSION ID HERE ', { html_element: 'rp_container' } );

vueJS mixin trigger multiple times in laravel 5.7

I am new in Vue jS [version 2]. There are 3 component in my page. I want to use a axios get data available in all pages. I have done as follows in my app.js
const router = new VueRouter({mode: 'history', routes });
Vue.mixin({
data: function () {
return {
pocketLanguages: [],
}
},
mounted() {
var app = this;
axios.get("/get-lang")
.then(function (response) {
app.pocketLanguages = response.data.pocketLanguages;
})
}
})
const app = new Vue({
router,
}).$mount('#app');
and using this pocketLanguages in a component like
{{ pocketLanguages.login_info }} this. Its working fine but My Issue is axios.get('') triggering 4 times when page load [in console]
Now how can I trigger this only once or anything alternative suggestion will be appreciated to do this if explain with example [As I am new in Vue]
You are using a global mixin, which means that every component in your app is going to make that axios get call when it's mounted. Since your page has several components in it, no wonder the call is being made several times. What you need to do here is either:
Create a normal mixin and only use it in the master/container/page component in every route that actually needs to fetch the data by providing the option mixins: [yourMixinsName]. That component can then share the data with the other components in the page.
If your data is common between pages then it's better to use a global store such as Vuex to simplify state management.
On a side note: It is usually better to handle your data initialization in the created hook. Handling it in the mounted hook can lead to some pitfalls that include repeated calls, among other things, due to parent/child lifecycle hooks execution order. Please refer to this article for more information on the subject.
Finally problem solved
In resources/js/components/LoginComponent.vue file
<script>
import translator from '../translation';
export default {
mixins:[translator],
beforeCreate: function() {
document.body.className = 'login-list-body';
},
.....
mounted() {
this.langTrans();
}
and my translation.js file at /resources/js
export default {
data: function() {
return {
pocketLanguages: []
};
},
methods: {
langTrans: function() {
var self = this;
axios.get('/get-lang')
.then(function (response) {
self.pocketLanguages = response.data.pocketLanguages;
});
}
}
};

Vue Router - call function after route has loaded

I'm working on a project where I need to call a function AFTER the route has finished loading. However, when using the 'watch' functionality, it only loads on route change, but does so before route has finished loading. So when I attempt to run a script that targets DOM elements on the page, those elements don't exist yet. Is there any functionality in Vue Router that would allow me to wait until everything is rendered before running the script?
const app = new Vue({
el: '#app',
router,
watch: {
'$route': function (from, to) {
function SOMEFUNCTION()
}
},
data: {
some data
},
template: `
<router-view/>
`
})
You should use Vue.nextTick
In your case this would translate to:
const app = new Vue({
el: '#app',
router,
watch: {
$route() {
this.$nextTick(this.routeLoaded);
}
},
data() {
return {};
},
methods: {
routeLoaded() {
//Dom for the current route is loaded
}
},
mounted() {
/* The route will not be ready in the mounted hook if it's component is async
so we use $router.onReady to make sure it is.
it will fire right away if the router was already loaded, so catches all the cases.
Just understand that the watcher will also trigger in the case of an async component on mount
because the $route will change and the function will be called twice in this case,
it can easily be worked around with a local variable if necessary
*/
this.$router.onReady(() => this.routeLoaded());
},
template: `<router-view/>`
})
This will call the routeLoaded method every time the route changes (which I'm deducing is what you need since you are using the <router-view> element), if you also want to call it initially, I would recommend the mounted hook (like in the example) or the immediate flag on the watcher
In my opinion on this situation, you should use component life cycle method of the loaded component, either use mounted method or created method.
or if your script doesn't depend on any vue component (store) you can use router.afterEach hook
router.afterEach((to, from) => { if (to.name !== 'ROUTENAME'){ // do something }});
The solution for me was to set up a custom event in every page's mounted() hook with a mixin and listen for that event on the body for example. If you wanted to strictly tie it with the router's afterEach or the route watcher to ensure the route has indeed changed before the event was fired, you could probably set up a Promise in the afterEach and resolve it in the page's mounted() by either the event or sharing the resolve function through the window.
An example:
// Component.vue
watch: {
'$route': function (from, to) {
new Promise((resolve) => {
window.resolveRouteChange = resolve;
}).then(() => {
// route changed and page DOM mounted!
});
}
}
// PageComponent.vue
mounted() {
if(window.resolveRouteChange) {
window.resolveRouteChange();
window.resolveRouteChange = null;
}
}
In case of router-view, we can manually detect router-view.$el change after $route is changed
watch: {
'$route'(to, from) {
// Get $el that is our starting point
let start_el = this.$refs.routerview.$el
this.$nextTick(async function() { await this.wait_component_change(start_el)})
}
},
methods: {
on_router_view_component_changed: function() { }
wait_component_change: async function(start_el) {
// Just need to wait when $el is changed in async manner
for (let i = 0; i < 9; i++) {
console.log('calc_has_dragscroll ' + i)
if(start_el) {
if (!start_el.isSameNode(this.$refs.routerview.$el)) {
// $el changed - out goal completed
this.on_router_view_component_changed()
return
}
}
else {
// No start_el, just wait any other
if(this.$refs.routerview.$el) {
// $el changed - out goal completed too
this.on_router_view_component_changed()
return
}
}
await this.$nextTick()
}
},
}
You can accomplish this by hooking into VueJS lifecycle hooks:
Use VueJS Lifecycle Hooks:
Here is a summary of the major VueJS lifecycle hooks. Please consult the documentation for the full description.
i. beforeCreate: This function will be called before the component is created
ii. created: This function will be called after the component is created, but note although the component is created, it hasn't been mounted yet. So you won't be able to access the this of the component. However, this is a good place to make Network Requests that will update the data properties.
iii. mounted: This function is called once the component has been rendered and the elements can be accessed here. This is what you're looking for.
iv. beforeDestroy: This function is called before the component is destroyed. This can be useful to stop any listeners (setTimeout, setInterval..), that you created.
See the diagram below for the details.
const app = new Vue({
el: '#app',
router,
mounted(){
this.someFunction()
},
data: {
some data
},
template: `
<router-view/>
`
})
Use Vue Router Navigation Guards: Vue Router also expose some lifecycle hooks that can you hook into. However, as you will see below they do not fit your requirements:
i. beforeRouteEnter: called before the route that renders this component is confirmed. oes NOT have access to this component instance, because it has not been created yet when this guard is called!
ii. beforeRouteUpdate: called when the route that renders this component has changed, but this component is reused in the new route.
iii. beforeRouteLeave: called when the route that renders this component is about to be navigated away from.
References:
VueJS Documentation (LifeCycle): VueJS Instance
Vue Router Documentation (Navigation Guards): Navigation Guards

Categories

Resources