I have trouble importing an external class into a single file Vue component.
The component looks like this:
<style>
</style>
<template>
<div>
A page
</div>
</template>
<script>
import AClass from './js/aclass.js';
// -----------------------------------------
// I have also tried all the below options
// -----------------------------------------
// import default from '/js/aclass.js';
// import * from '/js/aclass.js';
// import '/js/aclass.js';
// import default from '/js/aclass';
// import AClass from '/js/aclass';
// import * from '/js/aclass';
// import { AClass } from '/js/aclass.js';
// const AClass = require('/js/aclass.js');
module.exports = {
data: function () {
return {
}
},
mounted: function(){
// console.log('ACLass', AClass);
},
destroyed: function(){
},
methods: {
},
}
</script>
and the class file looks like this:
class AClass {
constructor() {
return this
}
}
export AClass
or
export default class AClass {
constructor() {
return this
}
}
None of the options seem to work: all the options based on import generate an error that says ReferenceError: "responseText is not defined" and the ones based on require one that says ÀClass is not defined` (when the console.log line is uncommented).
I have resorted to loading the class in the window global in the Vue.js base app - but everyone says that's barbaric, and I would like to be able to delay the loading of the library (the real one is heavier, and somewhat rarely used).
I have checked the various answers such as this and this and this but it looks like the mere use of import in the component makes things go wrong.
The component itself is loaded like this
var app = new Vue({
el: '#app',
router: (new VueRouter({
routes: [
{ path: '/view/grid/:md5', component: httpVueLoader('components/view/grid.vue'), name: 'grid_md5' },
]
})),
});
I am sure the solution must be there somewhere, but after about three hours of banging my head against this, I will throw myself at the mercy of the StackOverflow crowd.
Related
I have a vue2 project that uses ClassComponents and Chart.js (via vue-chart-3). I now have a simple component that wraps a DoughnutChart to manage data and stuff.
DBOverviewDoughnut.vue
<template>
<div>
<p>test</p>
<DoughnutChart ref="doughnutRef" :chartData="sharesData"></DoughnutChart>
</div>
</template>
<script lang="ts">
import Component from 'vue-class-component';
import Vue from 'vue';
import { DoughnutChart, ExtractComponentData } from 'vue-chart-3';
import { Prop, Ref } from 'vue-property-decorator';
import { ChartData } from 'chart.js';
#Component({ components: { DoughnutChart } })
export default class DBOverviewDoughnut extends Vue {
#Prop()
private sharesData!: ChartData<'doughnut'>;
#Ref()
private readonly doughnutRef!: ExtractComponentData<typeof DoughnutChart>;
created(): void {
this.assignColors();
}
mounted(): void {
if (this.doughnutRef.chartInstance) {
console.log(this.doughnutRef.chartInstance.data);
}
}
assignColors(): void {
this.sharesData.datasets[0].backgroundColor = [
'#77CEFF',
'#0079AF',
'#123E6B',
'#97B0C4',
'#A5C8ED',
];
}
}
</script>
Starting the program it will work fine and I can access the chartInstance inside the mounted hook.
But now I want to unit test my component. I thought on setting the propsData which will be the input data for the chart.
DBOverviewDoughnut.spec.ts
import DBOverviewDoughnut from '#/components/dashboard/DBOverviewDoughnut.vue';
import { mount, Wrapper } from '#vue/test-utils';
import { Share } from '#/Share';
describe('DBOverviewDoughnut', () => {
let cut: Wrapper<DBOverviewDoughnut>;
it('should render the correct amount of sections', () => {
cut = mount(DBOverviewDoughnut, {
propsData: {
sharesData: {
labels: ['TestShare1', 'TestShare2', 'TestShare3'],
datasets: [{ data: [11, 22, 33] }]
}
}
});
const chart = cut.findComponent({ ref: 'doughnutRef' });
console.log(chart);
});
});
Using shallowMount() doesn't seem to work, because I only get this from logging (no chartInstance and its properties as in the production code):
VueWrapper {
isFunctionalComponent: undefined,
_emitted: [Object: null prototype] {},
_emittedByOrder: [],
selector: { ref: 'doughnutRef' }
}
So I thought maybe I have to mount the component because the DoughnutChart is also a wrapper around the Chart.js charts. But when using mount() I get the following error:
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: `createElement()` has been called outside of render function.
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Error in render: "Error: [vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function."
found in
---> <DoughnutChart>
<DBOverviewDoughnut>
<Root>
I don't really know what I'm doing wrong. I registered the VueCompostionAPI in my main.ts:
import Vue from 'vue';
import { Chart, registerables } from 'chart.js';
import App from './App.vue';
import router from './router';
import store from './store';
import VueCompositionAPI from '#vue/composition-api';
Chart.register(...registerables);
Vue.use(VueCompositionAPI);
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
Following this post doesn't solve the problem either.
Anyone got an idea what's going wrong? I'm a bit confused if the error has to do with my test setup or with the installation of chart.js or compositionApi.
You need to use VueCompositionAPI inside your spec as well when you mount the component. You can do this by creating a local Vue instance inside your spec, adding VueCompositionAPI as a plugin to the instance and using the instance when you mount the component. https://vue-test-utils.vuejs.org/api/options.html#localvue
Using localVue is really what I should have thought about. This and installing the canvas-package works, that I get additional information about my Ref-Element. However I still have to figure out what to do with it.
#AdriHM I want to test if the rendered chat gets the correct data I guess. Or if it displays it correctly (e.g. display the correct amount of sections) But the longer I think about it the less I'm sure it's the right thing to test. I don't want to test the Chart.js API though.
I'm new to Vue, so it's likely I misunderstand something. I want to call a vuex action inside a local function in App.vue like so:
<template>
<div id="app">
<button #click="runFunction(1)">Test</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default{
data() { return { } },
methods: {
...mapActions(['doAction']),
buttonClicked: (input) => { runFunction(input) }
}
}
function runFunction(input){
doAction({ ID: input });
}
</script>
The action calls a mutation in store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
IDs: []
},
mutations: {
doAction: (state, id) => { state.IDs.push(id) }
},
actions: {
doAction: ({ commit }, id) => { commit('doAction', id) }
}
})
I also have a main.js that sets up the vue:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
The error I'm getting is:
ReferenceError: doAction is not defined
at runFunction
How can I call the mapped action inside a function? Version is Vue 2.6.10
There are several problems with defining runFunction as a 'local function':
function runFunction(input){
doAction({ ID: input });
}
Firstly, this is just a normal JavaScript function and the usual scoping rules apply. doAction would need to be defined somewhere that this function can see it. There is no magic link between this function and the component defined in App.vue. The function will be accessible to code in the component, such as in buttonClicked, but not the other way around.
The next problem is that it won't be available within your template. When you write runTemplate(1) in your template that's going to be looking for this.runTemplate(1), trying to resolve it on the current instance. Your function isn't on the current instance. Given your template includes #click="runFunction(1)" I'm a little surprised you aren't seeing a console error warning that the click handler is undefined.
mapActions accesses the store by using the reference held in this.$store. That reference is created when you add the store to your new Vue({store}). The store may appear to be available by magic but it's really just this.$store, where this is the current component.
It isn't really clear why you're trying to write this function outside of the component. The simplest solution is to add it to the methods. It'll then be available to the template and you can access doAction as this.doAction.
To keep it as a separate function you'd need to give it some sort of access to the store. Without knowing why you want it to be separate in the first place it's unclear how best to achieve that.
Of course it is not defined outside your instance .... you have to import the exported store from store.js on your function component :
<script>
import { mapActions } from 'vuex'
import store from 'store.js'
export default{
data() { return { } },
methods: {
...mapActions(['doAction']),
buttonClicked: (input) => { runFunction(input) }
}
}
function runFunction(input){
store.commit({ ID: input });
}
</script>
I have this vue js component:
<template>
<div>
hello world
</div>
</template>
<script>
export default {
name: 'mycomp',
data: function () {
console.error("window.google_recaptcha_public_key", window.google_recaptcha_public_key);
return {
}
},
mounted() {
let app = this;
console.error("window.google_recaptcha_public_key2", window.google_recaptcha_public_key);
},
}
</script>
<style scoped lang="scss">
</style>
returns:
window.google_recaptcha_public_key undefined
window.google_recaptcha_public_key2 undefined
where can I leave painless and happy all global configuration?
notice this configuration lives in my laravel backend. So I wont copy paste all values from the backend to the front end
U can use Vue.prototype in main.js file, or in file you import Vue
Vue.prototype.Hereanyname = window.hereanyname;
and in your Vue application, you can use it
Hereanyname.thefunction
Real example on Laravel
in main.js
import Vue from 'vue';
Vue.prototype.Routes = window.routes;
new Vue({
el: '#app',
template: '<App/>',
components: {App}
});
in your application
:href="Routes.route('laravel.route.here')"
So for your case
import Vue from 'vue';
Vue.prototype.GoogleRecaptcha = window.google_recaptcha_public_key;
new Vue({
el: '#app',
template: '<App/>',
components: {App}
});
inside application
mounted() {
console.log(this.GoogleRecaptcha)
}
In Vue3, you no longer have the global Vue instance, so you need to assign the window as a global property on your app...
// main.js
app.config.globalProperties.window = window
Then in your components, window will just work.
This info is from an impeccable source.
You should save your window variable in Vue.prototype
main.js
Vue.prototype.$authUser = window.authUser;
After that, you can access your data as follows:
Vue template
<div v-text="$authUser.name"></div>
Vue script
let name = this.$authUser.name;
window is available in the vuex store. This may help if you need to mutate the window property synchronously with other actions/mutations, give you a chance to validate what goes into it, or catch an error if the variable you intend to put there isn't available.
export default new Vuex.store({
state: {
windowFoo: window.foo,
googleRecaptcha: window.google_recaptcha_public_key
},
getters: {
windowFoo: (state) => state.windowFoo,
googleRecaptcha: (state) => state.googleRecaptcha
},
actions: {
barThenFooThenBaz({ commit }, { foo }) {
// ... do some other required actions first
commit("setWindowFoo", foo);
// ... do some dependent actions next
}
},
mutations: {
setWindowFoo(state, foo) {
state.windowFoo = foo;
}
}
});
Then from your Single File Component...
//....
computed: {
windowFoo() {
return this.$store.getters.windowFoo;
},
googleRecaptcha() {
return this.$store.getters.googleRecaptcha;
}
},
methods: {
async barThenFooThenBaz(foo) {
await this.$store.dispatch({
type: "barThenFooThenBaz",
foo: foo
});
// ... do something dependent on windowFoo being set
}
}
//....
Although the other answers here are totally acceptable, I've had issues using the Vue instance with Vue.prototype in main.js as our project has gotten larger, so I hope this helps!
Provide/Inject works nicely. Here's an example with Vue 3:
main.js
const app = createApp(App)
app.provide('recaptcha_key', window.google_recaptcha_public_key)
app.mount('#app')
MyComponent.vue
<script setup>
const { inject } from 'vue'
const recaptchaKey = inject('recaptcha_key')
</script>
I'm trying to call a function inside 'method' from outside. However, it isn't working.
Github issue reporting the same: https://github.com/vuejs/vue/issues/329
vm.test(); // call a function in method, not working
this.vue.test() // not working
export default {
methods: {
test: function() {
alert('test fuction called');
}
}
}
It is not very clear what the actual goal of the original poster is, however this is how you can call a method on a Vue instance, after creating it:
var viewModel = new Vue({
el: "#app",
data: {
msg: "Hello there"
},
methods: {
test: function() {
alert('test fuction called');
}
}
});
viewModel.test();
Working example: https://jsfiddle.net/Daryn/Lja7pake/3/
If you are exporting a single file component then try this:
example.js
<script>
export default {
methods: {
test: function() {
alert('test fuction called');
}
}
}
</script>
main.js
<script>
import Thing from './example.js';
Thing.test();
</script>
Reference: https://v2.vuejs.org/v2/guide/single-file-components.html
What you are trying to achieve is fundamentally flawed. You can't call a method of a component unless you have a reference to an instance of that particular component. In your code, which particular component is vm referring to?
All you're doing is exporting a Vue component definition from your module; there's no component being instantiated here.
We'll need to see more of your code or a complete explanation of what exactly you're trying to achieve so we can provide an alternative solution. (Why are you trying to call the component's method outside of its definition?)
export default {
...
methods: {
...
},
mounted () {
EventBus.$on(‘EVENT_NAME’, function (payLoad) {
...
});
}
}
This is the way I solved that problem.
For the purpose of this demonstration, we create a new project using Vue/CLI. After installation finished, we make the vm exposed to global. Open src/main.js and edit like so:
src/main.js
import Vue from 'vue';
import App from './App.vue';
var vm = new Vue({
router,
render: h => h(App)
}).$mount('#app');
// Add this line (tambahkan baris berikut):
window.vm = vm;
Leave the generated App.vue like it is. So the first child of vm (vm.$children[0]) is App.vue.
We see that App.vue have a child. That makes HelloWorld.vue component as a grand children of vm (vm.$children[0].$children[0]). Knowing this, we can call the methods from outside 'export default' like this:
src/components/HelloWorld.vue
<template>
<div class="hello">
<button
id="sebuahButton"
class="btn btn-outline-secondary btn-sm"
type="button"
>Click Me, Jose!</button>
<h1>{{ msg }}</h1>
<!-- and some stuff, vue cli default generated code -->
<div>
</template>
<script>
(function() {
// wait for the DOM ready event in plain JavaScript
document.addEventListener("DOMContentLoaded", event => {
document.getElementById("sebuahButton").onclick = function() {
vm.$children[0].$children[0].someAction();
};
});
})();
export default {
name: "HelloWorld",
props: {
msg: String
}
methods: {
someAction () {
// do something (lakukan sesuatu masbro!)
console.log("It's been called from outer space, Luke!");
}
}
}
</script>
I have a Vue 2 project that has many (50+) single-file components. I use Vue-Router for routing and Vuex for state.
There is a file, called helpers.js, that contains a bunch of general-purpose functions, such as capitalizing the first letter of a string. This file looks like this:
export default {
capitalizeFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
}
My main.js file initializes the app:
import Vue from 'vue'
import VueResource from "vue-resource"
import store from "./store"
import Router from "./router"
import App from "./components/App.vue"
Vue.use(VueResource)
const app = new Vue({
router: Router,
store,
template: '<app></app>',
components: { App },
}).$mount('#app')
My App.vue file contains the template:
<template>
<navbar></navbar>
<div class="container">
<router-view></router-view>
</div>
</template>
<script>
export default {
data() {
return {
// stuff
}
}
}
</script>
I then have a bunch of single-file components, which Vue-Router handles navigating to inside the <router-view> tag in the App.vue template.
Now let's say that I need to use the capitalizeFirstLetter() function inside a component that is defined in SomeComponent.vue. In order to do this, I first need to import it:
<template>Some Component</template>
<script>
import {capitalizeFirstLetter} from '../helpers.js'
export default {
data() {
return {
myString = "test"
}
},
created() {
var newString = this.capitalizeFirstLetter(this.myString)
}
}
</script>
This becomes a problem quickly because I end up importing the function into many different components, if not all of them. This seems repetitive and also makes the project harder to maintain. For example if I want to rename helpers.js, or the functions inside it, I then need to go into every single component that imports it and modify the import statement.
Long story short: how do I make the functions inside helpers.js globally available so that I can call them inside any component without having to first import them and then prepend this to the function name? I basically want to be able to do this:
<script>
export default {
data() {
return {
myString = "test"
}
},
created() {
var newString = capitalizeFirstLetter(this.myString)
}
}
</script>
inside any component without having to first import them and then prepend this to the function name
What you described is mixin.
Vue.mixin({
methods: {
capitalizeFirstLetter: str => str.charAt(0).toUpperCase() + str.slice(1);
}
})
This is a global mixin. with this ALL your components will have a capitalizeFirstLetter method, so you can call this.capitalizeFirstLetter(...) from component methods or you can call it directly as capitalizeFirstLetter(...) in component template.
Working example: http://codepen.io/CodinCat/pen/LWRVGQ?editors=1010
See the documentation here: https://v2.vuejs.org/v2/guide/mixins.html
Otherwise, you could try to make your helpers function a plugin:
import Vue from 'vue'
import helpers from './helpers'
const plugin = {
install () {
Vue.helpers = helpers
Vue.prototype.$helpers = helpers
}
}
Vue.use(plugin)
In your helper.js export your functions, this way:
const capFirstLetter = (val) => val.charAt(0).toUpperCase() + val.slice(1);
const img2xUrl = (val) => `${val.replace(/(\.[\w\d_-]+)$/i, '#2x$1')} 2x`;
export default { capFirstLetter, img2xUrl };
or
export default {
capFirstLetter(val) {
return val.charAt(0).toUpperCase() + val.slice(1);
},
img2xUrl(val) {
return `${val.replace(/(\.[\w\d_-]+)$/i, '#2x$1')} 2x`;
},
};
You should then be able to use them anywhere in your components using:
this.$helpers.capitalizeFirstLetter()
or anywhere in your application using:
Vue.helpers.capitalizeFirstLetter()
You can learn more about this in the documentation: https://v2.vuejs.org/v2/guide/plugins.html
Create a new mixin:
"src/mixins/generalMixin.js"
Vue.mixin({
methods: {
capitalizeFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
}
})
Then import it into your main.js like:
import '#/mixins/generalMixin'
From now on you will be able to use the function like this.capitalizeFirstLetter(str) within your component script or without this in a template. i.e.:
<template>
<div>{{ capitalizeFirstLetter('hello') }}</div>
</template>
You have to use this because you mixed a method into the main Vue instance. If there are ways of removing this it will probably involve something unconventional, this at least is a documented way of sharing functions which will be easy to understand for any future Vue devs to your project.
Using Webpack v4
Create a separate file for readability (just dropped mine in plugins folder).
Reproduced from #CodinCat and #digout responses.
//resources/js/plugins/mixin.js
import Vue from 'vue';
Vue.mixin({
methods: {
capitalizeFirstLetter: str => str.charAt(0).toUpperCase() + str.slice(1),
sampleFunction() {
alert('Global Functions');
},
}
});
Then, import in your main.js or app.js file.
//app.js
import mixin from './plugins/mixin';
USAGE:
Call this.sampleFunction() or this.capitalizeFirstLetter().
Use a global filter if it only concerns how data is formatted when rendered. This is the first example in the docs:
{{ message | capitalize }}
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
Great question. In my research I found vue-inject can handle this in the best way. I have many function libraries (services) kept separate from standard vue component logic handling methods. My choice is to have component methods just be delegators that call the service functions.
https://github.com/jackmellis/vue-inject
Import it in the main.js file just like 'store' and you can access it in all the components.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
router,
render: h => h(App)
})