How to access Vue $refs in a plugin? - vue.js

methods: {
async create () {
this.disableSubmit = true;
await this.$firestore
.collection('collectionName')
.add(this.item)
.then(() => {
this.$refs.createForm.reset();
this.$notify('positive', 'Item successfully created!');
})
.catch(error => {
this.$notify('negative', 'ERROR! Try again later!', error);
});
this.disableSubmit = false;
},
}
If I use the code above inside the methods property, then everything works fine, but I would like to access that ref from outside the Vue component, for example a plugin, but it gives me an error.
TypeError: "_this.$refs is undefined"
Even when I just import it as a function, the error is the same, so I would like to know how to access the ref outside vue?
Bellow is the code for my plugin, and I would also like to point that I am using the quasar framework.
export let plugin = {
install (Vue, options) {
Vue.prototype.$plugin = async (collection, item) => {
return await firestore
.collection(collection)
.add(item)
.then(() => {
this.$refs.createFrom.reset();
notify('positive', 'Booking successfully created!');
})
.catch(error => {
notify('negative', 'ERROR creating booking! Try again later!', error);
});
};
}
};
I hope my question makes sense, and thanks in advance for any help

you could pass the context of your component, to apply the reset form from your plugin:
// plugin declaration
Vue.prototype.$plugin = async (collection, item, ctx) {
...
ctx.$refs.createFrom.reset()
...
}
then when u call to your plugin from yours components can do it like this:
// your component
methods: {
myFunction () {
this.$plugin(collection, item, this)
}
}
this is the reference of the context of your current component that will be used inside of your plugin
for example:
Vue.component('my-form', {
methods: {
resetForm() {
console.log('the form has been reset')
}
}
})
Vue.prototype.$plugin = (item, ctx) => {
console.log('item passed:', item)
ctx.$refs.refToMyForm.resetForm()
}
new Vue({
el: '#app',
data: {
item: 'foo'
},
methods: {
submit() {
this.$plugin(this.item, this)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<my-form ref="refToMyForm"></my-form>
<button #click="submit">submit</button>
</div>

Related

Vue 3 - How to execute a function on binging instance in custom directive

Im creating a custom directive as follows in main.ts .
let handleOutsideClick: any;
app.directive("closable", {
mounted: (el, binding, vnode) => {
handleOutsideClick = (e: any) => {
e.stopPropagation();
const payload = binding.value;
console.log(`instance: ${Object.getOwnPropertyNames(binding.instance)}`);
};
document.addEventListener("click", handleOutsideClick);
},
unmounted: (el) => {
document.removeEventListener("click", handleOutsideClick);
},
});
Inside the event handler i want to make a call to a function on the component that triggered this directive.
With Vue 2 you could do it with vnode.context'myfunction' but this does not seem to work with binding.instance.
How can i call the function using the binding instance?
Passing the function to be called as the binding's value and then calling it seems to work:
if (typeof binding.value === 'function') {
binding.value()
}
Working example:
const { createApp } = Vue;
const app = createApp({
setup() {
return { test: () => console.log('here') }
}
})
app.component('demo', {
template: `<div>test</div>`
})
let handleOutsideClick;
app.directive("closable", {
mounted: (el, binding, vnode) => {
handleOutsideClick = (e) => {
e.stopPropagation();
const payload = binding.value;
console.log(`instance: ${Object.getOwnPropertyNames(binding.instance)}`);
if (typeof binding.value === 'function') {
binding.value()
}
};
document.addEventListener("click", handleOutsideClick);
},
beforeUnmount: (el) => {
document.removeEventListener("click", handleOutsideClick);
},
});
app.mount('#app')
<script src="https://unpkg.com/vue#3.2.41/dist/vue.global.prod.js"></script>
<div id="app">
<demo v-closable="test"></demo>
</div>
Notes:
vue internal method names change based on environment. For example, if you remove .prod from the vue link in the example above, you'll get more data out of Object.getOwnPropertyNames(binding.instance).
If your app's business logic relies on vue internals method naming:
  a) you're doing it wrong™;
  b) it won't work in production
if the above is not helpful, please provide more details on your specific use-case via a runnable minimal, reproducible example.
If you need a multi-file node-like online editor, note codesandbox.io allows importing local projects using their CLI utility.

Vuex + Jest + Composition API: How to check if an action has been called

I am working on a project built on Vue3 and composition API and writing test cases.
The component I want to test is like below.
Home.vue
<template>
<div>
<Child #onChangeValue="onChangeValue" />
</div>
</template>
<script lang="ts>
...
const onChangeValue = (value: string) => {
store.dispatch("changeValueAction", {
value: value,
});
};
</scirpt>
Now I want to test if changeValueAction has been called.
Home.spec.ts
...
import { key, store } from '#/store';
describe("Test Home component", () => {
const wrapper = mount(Home, {
global: {
plugins: [[store, key]],
},
});
it("Test onChangeValue", () => {
const child = wrapper.findComponent(Child);
child.vm.$emit("onChangeValue", "Hello, world");
// I want to check changeValueAction has been called.
expect(wrapper.vm.store.state.moduleA.value).toBe("Hello, world");
});
});
I can confirm the state has actually been updated successfully in the test case above but I am wondering how I can mock action and check if it has been called.
How can I do it?
I have sort of a similar setup.
I don't want to test the actual store just that the method within the component is calling dispatch with a certain value.
This is what I've done.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}
Hope this will help you further :)

Vue2 create component based on data

I want to create a component based on ajax api response or data which include:
template
data
methods - there may be several methods
Remark: response or data is dynamic and it is not saved in file.
I have tried to generate and return result like :
<script>
Vue.component('test-component14', {
template: '<div><input type="button" v-on:click="changeName" value="Click me 14" /><h1>{{msg}}</h1></div>',
data: function () {
return {
msg: "Test Componet 14 "
}
},
methods: {
changeName: function () {
this.msg = "mouse clicked 14";
},
}
});
</script>
and do compile above code :
axios.get("/api/GetResult")
.then(response => {
comp1 = response.data;
const compiled = Vue.compile(comp1);
Vue.component('result-component', compiled);
})
.catch(error => console.log(error))
I got error on Vue.compile(comp1) -
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as
<script>, as they will not be parsed.
Thanks in advance
Your Api should return a JSON with every property required by a Vue component (name, data, template, methods), note that methods needs to be converted into an actual js function (check docs about that)
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
data() {
return {
apiComponent: { template: '<div>Loading!</div>' }
};
},
methods: {
loadApiComponent() {
setTimeout(() => {
this.buildApiComponent(JSON.parse('{"name":"test-component14","template":"<div><input type=\\\"button\\\" v-on:click=\\\"changeName\\\" value=\\\"Click me 14\\\" /><h1>{{msg}}</h1></div>","data":{"msg":"Test Componet 14 "},"methods":[{"name":"changeName","body":"{this.msg = \\\"mouse clicked 14\\\";}"}]}'));
}, 2000);
},
buildApiComponent(compObject) {
const {
name,
template,
data,
methods
} = compObject;
const compiledTemplate = Vue.compile(template);
this.apiComponent = {
...compiledTemplate,
name,
data() {
return { ...data
}
},
methods: methods.reduce((c, n) => {
c[n.name] = new Function(n.body);
return c;
}, {})
};
}
},
mounted() {
this.loadApiComponent();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component :is="apiComponent" />
</div>

ag grid not retrieving data when mounted with vue using axios

I have this strange case when trying to retrieve data from mongoDB using axios not showing on grid. It should be already successful given the data can already loaded into the view (already tested it), but it's nowhere inside beforeMount, mounted, or ready hook.
I already tried with
this.gridOptions.onGridReady = () => {
this.gridOptions.api.setRowData(this.ticketData)
}
but only yields partial success (unreliable),
here's a code snippet to show what I mean,
<template>
<div class="ticketing">
<ag-grid-vue style="width: 100%; height: 350px;"
class="ag-fresh"
:gridOptions="gridOptions"
>
</ag-grid-vue>
{{testData}} <!--testData can be loaded-->
<input type="button" #click.prevent="showData" value="test"> </div>
</template>
<script>
//import stuff
//header and url stuff
export default {
//component stuff
data () {
return {
gridOptions: null,
ticketData: [],
testData: [] // only for testing purpose
}
},
methods: {
showData () {
console.log('data shown')
this.testData = this.ticketData // this is working
}
},
beforeMount () {
var vm = this
axios.get(ticketingAPIURL, {'headers': {'Authorization': authHeader}})
.then(function (response) {
vm.ticketData = response.data
}) // this is working
.catch(function (error) {
console.log(error)
})
this.gridOptions = {}
this.gridOptions.rowData = this.ticketData // this is not working
this.gridOptions.columnDefs = DummyData.columnDefs
}
// mount, ready also not working
}
</script>
To be more specific, I still can't determine what really triggers onGridReady of ag-grid in conjunction with Vue component lifecycle, or in other words, how can I replace button to show testData above with reliable onGridReady/Vue component lifecycle event?
You define vm.ticketData and after you call it like this.ticketData
You can change it by: this.rowData = vm.ticketData
You are setting this.gridOptions.rowData outside of the axios callback, so this.ticketData is still empty.
Set it inside the callback:
mounted() {
var vm = this
axios.get(ticketingAPIURL, {'headers': {'Authorization': authHeader}})
.then(function (response) {
vm.ticketData = response.data
vm.gridOptions = {}
vm.gridOptions.rowData = vm.ticketData
vm.gridOptions.columnDefs = DummyData.columnDefs
})
.catch(function (error) {
console.log(error)
})
}
it is due to overlapped intialization between axios, ag-grid, and vue.
after much tinkering, I am able to solve it with using Vue's watch function:
watch: {
isAxiosReady(val) {
if (val) {
this.mountGrid() // initiate gridOptions.api functions
}
}
}

this.$http.get is not working inside methods vue js

I am working with Laravel + spark + vue js.
Blade file
<draggable class="col-md-12" :list="emergencies" :element="draggableOuterContainer" #end="onEnd">
Js file
import draggable from 'vuedraggable'
module.exports = {
data() {
return {
emergencies:[]
};
},
components: {
draggable,
},
created() {
this.getEmergencies();
},
methods: {
getEmergencies() {
this.$http.get('/ajax-call-url')
.then(response => {
this.emergencies = response.data;
});
},
onEnd: function(evt){
var counter = 1;
this.emergencies.forEach(function(user, index) {
this.$http.get('/ajax-call-url/')
.then(response => {
});
counter++;
});
}
}
};
Here I have drag and Drop, On Drop, I call "onEnd" function and getting following error.
TypeError: this is undefined
Here this.emergencies.forEach is working but it is giving error on this.$http.get
Any suggestions, what can be the solutions?
Instead of using function syntax, use arrow functions, as scope of this changes inside function:
onEnd: function(evt){
var counter = 1;
this.emergencies.forEach((user, index) => {
this.$http.get('/ajax-call-url/')
.then(response => {
});
counter++;
});
}
Check this for explanation.