Make event handlers I need accessible method - vuejs2

In laravel 5.7 / vue 2.5.17 / vuex^3.1.0 I make event handlers in my container file MainApp.vue
with events:
mounted() {
bus.$on('dialog_confirmed', (paramsArray) => {
alert( "dialog_confirmed paramsArray::"+var_dump(paramsArray) )
if ( paramsArray.key == this.addToBookmarksKey(paramsArray.hostel_id) ) {
this.runAddToBookmarks(paramsArray.hostel_id, paramsArray.index);
}
if ( paramsArray.key == this.deleteFromBookmarksKey(paramsArray.hostel_id) ) {
this.runDeleteFromBookmarks(paramsArray.hostel_id, paramsArray.index);
}
})
}, // mounted() {
The idea is that runAddToBookmarks must be called from different pages and I need to set common method check which event is triggered.
I tried in resources/js/helpers/commonFuncs.js to add method :
export function addToBookmarksKey(hostel_id) {
return 'hostels_sorted__add_to_bookmarks_'+hostel_id;
}
and to use it in my vue file. like:
...
<template v-if="hostelsList.length">
<template v-for="nextHostel, index in hostelsList" >
<hostel-list-item
:currentLoggedUser="currentLoggedUser"
:nextHostel="nextHostel"
:index="index"
:hostelBookmarks="hostelBookmarks"
:delete_from_bookmarks_key="deleteFromBookmarksKey(nextHostel.id)"
:add_to_bookmarks_key="addToBookmarksKey(nextHostel.id)"
></hostel-list-item>
</template>
</template>
...
</template>
<script>
import {bus} from '../../../app';
import appMixin from '../../../appMixin';
import { addToBookmarksKey } from "../../../helpers/commonFuncs";
But I got error : property or method "addToBookmarksKey" is not defined on the instance but referenced during render.
Why addToBookmarksKey is not accessible in template of my vue file and which is simple way to work it?
I need to use addToBookmarksKey in many vue files , both in template and it javascript block ?
Thanks!

You need to define your helper function inside your component instance to use it within the template:
<script>
import { addToBookmarksKey } from "../../../helpers/commonFuncs";
export default {
//...
methods: {
addToBookmarksKey,
//...
}
}
You also can define it globally by adding the function in a mixin directly in main.js:
import Vue from "vue";
import App from "./App.vue";
import { addToBookmarksKey } from "path/to/the/helpers/commonFuncs";
Vue.mixin({
methods: {
addToBookmarksKey
}
})
new Vue({
render: h => h(App)
}).$mount("#app");
no need to import and define it inside your components this way.

Related

is it available to call the methods where in the vue component from the plugin?

I wanted to access the vue.data or methods in the plugin.
no matter what I tried several times, it didn't work.
such as eventBus, Mixin etc...
so I'm curious about the possibility to call the methods like that.
thank you for reading this question.
here is the custom component.
<template>
<div>
<v-overlay :value="isProcessing">
<v-progress-circular indeterminate size="64"></v-progress-circular>
</v-overlay>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
#Component
export default class ProgressCircular extends Vue {
private isProcessing: boolean;
startProcess() {
this.isProcessing = true;
}
}
</script>
and this is the plugin source.
import ProgressCircular from '#/components/ProgressCircular.vue';
import { VueConstructor } from 'vue';
import Vuetify from 'vuetify/lib';
import vuetify from './vuetify';
export default {
install(Vue: VueConstructor, options: any = {}) {
Vue.use(Vuetify);
options.vuetify = vuetify;
Vue.component('progress-circular', ProgressCircular);
Vue.prototype.$fireProgressing = function () {
// it didn't work
// I just wanted to access the method where in the Vue Component
// ProgressCircular.startProcess();
};
},
};
use the plugin syntax to extend vue like:
Vue.use({
install: Vue => {
Vue.prototype.$fireProgressing = () => {
};
}
});
or
Vue.use(YOURPLUGIN);
before you mount vue

Vue 2: How to unit test component that uses Chart.js (vue-chart-3)

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.

How to use vuex action in function

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>

User editable Vue template

In my app, I have a template for things like Invoice, Email etc. I'd like the user to be able to edit these templates by dragging and dropping elements. I'm currently using vue-loader along with webpack to pre-compile my vue files into pure JS.
Is it possible to load a vue template from the database on the fly? I've seen this post but this isn't using vue-loader so I'm not sure how to override the template on my component via the code. Something like:
created: function () {
this.$template = '<html><p>Loaded from the DB!</p></html>'
}
would be useful. Is this possible?
Edit: I've tried the following but I get an error Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.:
created: function () {
document.body.innerHTML = '<html><p>I AM FROM THE DB {{total}}</p></html>'
}
This would need to be modified to pass in the templates from your database, but this works in a very simple single file component. Obviously you will want to customize, but this demonstrates the concept.
Dynamic.vue
<script>
export default {
props:["template"],
data(){
return {
message:"hello"
}
},
created(){
this.$options.template = this.template
}
}
</script>
App.vue
<template>
<div>
<dynamic
v-for="template, index of templates"
:template="template" :key="index">
</dynamic>
</div>
</template>
<script>
import Vue from "vue"
import Dynamic from "./Dynamic.vue"
export default {
name: 'app',
data () {
return {
templates: [
"<h1>{{message}}</h1>",
"<h4>{{message}}</h4>"
]
}
},
components:{
Dynamic
}
}
</script>
main.js
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App)
})

Vue.js - Making helper functions globally available to single-file components

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)
})