Call method in App.vue from window/global scope

I need to call a method from outside Vue, I now it maybe isn’t a good practise, however I’m bound to the KioskProApi (https://docs.kioskproapp.com/), so I need to call functions in global scope and create a callback function that calls a Vue method.

I searched a lot on StackOverflow regarding calling methods from outside Vue, I couldn’t find a specific answer. Below as far as I got.

I work with the CLI (so my app is compiled with Webpack) and I’ve made a minimal demo.

My App.vue

<template>
  <div id="app">
  </div>
</template>

<script>
export default {
  name: 'app',
  created: function () {
    bla(); // function outside Vue scope called
  },
  methods: {
    callbackFunction() {
      console.log('bla');
    }
  }
}

function bla() {
  console.log('adfijdsaifoajdfioj');
  vm.$children[0].callbackFunction();
}

main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

window.vm = new Vue({
  render: h => h(App),
}).$mount('#app')

// vm.$children[0].callbackFunction();

I can call vm.$children[0].callbackFunction(); from main.js, however I can’t call it from my “app.vue”. How can I use this from within a function in App.vue?

I also found that you can use refs (see this StackOverflow) however that works with components. How would I use ‘$refs’ with the “main” component? For example like vm.$refs.app.callbackFunction();

I’ve solved it like this:

App.vue

<template>
  <div id="app">
  </div>
</template>

<script>
export default {
  name: 'app',
  created: function () {
    functionOutsideVue(this); // function outside Vue scope called
  },
  methods: {
    callbackFunctionInsideVueComponent() {
      console.log('callback inside Vue is called');
    }
  }
}

window.functionOutsideVue = function(vuecomponent) {
  console.log('functionOutsideVue is called : ');
  console.log(vuecomponent);
  vuecomponent.callbackFunctionInsideVueComponent();
}
2 Likes

Just a added note for anyone:

Refs are contextual. So you could expect something like this:

vm.$refs.parent.$refs.childrenRefs.$refs.childrensRefsRef and so on

You dont want that so you might as well just bind it yourself with something like:

// in your main.js or somwhere
window.vm = {}

//inside of your components on mount or created hooks you can bind them
vm.someComponent = this

// then you can access them anywhere with
vm.someComponent.someMethod()

*updated to match final answer

3 Likes

Any reason you can’t just pass the function as a callback? https://jsfiddle.net/jamesbrndwgn/pxsjy758/

1 Like

@JamesThomson That is not possible. Also my own solution didn’t work. The problem is that the callback doesn’t accept any arguments because it’s passed a string:
KioskPro works like window.takePhotoWithCountdownToFile("photo", "_takePhotoToFileWithCountdown_callback", 5, "Perfect!", 3);

My main struggle was calling a Vue method in a specific component outside of Vue (so in my case from that global callback function).

For example I can’t get vm.$refs.screentakephoto to work, it keeps giving me an undefined. While the component is registered <ScreenTakePhoto ref="screentakephoto">

@GreggOD Your solution works in my scenario with the line vm.someComponent.someMethod() however the vm.root.someMethod() doesn’t work. Did you forget something in that line, because where is the method connected to root?

Sorry I realised when reading this that you shouldn’t do the below because you have no methods on the root:

// wrong
vm.root = new Vue({
  render: h => h(App),
}).$mount('#app')

Instead in your App.vue file in a mounted or created hook you can do the same as mentioned for the other components:

vm.root = this

Technically the root we are setting above is not actually the root, its more a parent, but in almost any projects context it will be the root. In vue the $root is the registration instance which is the new Vue instantiated in main.js but you dont really need access to that because you never have any methods or properties on it.

1 Like

Ah, I see, that wasn’t apparent in your OP. Then yes, @GreggOD’s solution is a good one :+1:

2 Likes

Gracias :grinning: :+1:

1 Like

For completeness of the answer I’d add:

// in your main.js or somewhere
window.vm = {}

vm = new Vue({
  render: h => h(App),
}).$mount('#app')

Because by default I Vue init’s without the vm variable, like

new Vue({
  render: h => h(App),
}).$mount('#app')```

Im not so clear on what you are doing here? or saying?

Sorry, I get it. You don’t do anything with the Vue object. I was confused because in several examples you see that Vue object is initialised with a variable for later access.

In your case vm is just an empty object in which you can declare the “pointers” to the components.

So vm = new Vue, indeed doesn’t apply.

1 Like

Why not use a DOM event. Listen for it within the component, trigger it outside the component.

What is the benefit of using an event, instead of a call to a function? Do have an example of this idea?

The biggest benefit is you don’t have to figure out how to hook into the Vue app to get it to do something with data from outside of the Vue app. Also, using an event, you could have multiple listeners set up to do various tasks per event. The publisher of the event doesn’t have to know about any other code either: it simply publishes the event. If the work flow is reciprocal, i.e. the publisher needs to know when the Vue method is done, then you can do the reverse by having Vue publish another event that the original publisher listens for.

I don’t have an example handy, but if you think about 2 decoupled systems that use and affect the same data, this is a fairly common pattern to get them to communicate.

2 Likes

This Is the golden method for getting isolated scopes to communicate. Thank you for the awesome code example.

I used this same technique to allow A-frame and Quasar to co-exist, while also leveraging both of their respective strengths.