How to initialize code in mounted hook instead of in individual methods? - vuejs2

I am learning how to use a Javascript image annotation library called Annotorious. How can I initialize the library during the mounted hook (assuming that's the best place for it to happen) and not have it initialized in inidividual methods like the way I have it now?
I have two methods: (working sandbox here )
PhotoDetail.vue
annotatePhoto() {
try {
const anno = new Annotorious({ <-------- how to get this out of this scope and into the global scope of this SFC?
image: this.photo.id // image element or ID
})
// Attach listeners to handle annotation events
anno.on('createAnnotation', annotation => {
console.log('Created!', annotation)
})
} catch (error) {
console.log('anno error', error)
}
}
and
loadAnnotations() {
const anno = new Annotorious({ <-------- how to get this out of this scope and into the global scope of this SFC?
image: this.photo.id // image element or ID
})
anno.setAnnotations(annotations) < --- this is undefined because of this scope
})
}
I tried:
computed: {
anno() {
const anno = Annotorious.init({
image: this.photo.id
})
return anno
}
}
Thanks!

Here's what I did to get this to work:
data() {
return {
photo: {},
anno: {}
}
},
async mounted() {
await this.getPhoto()
this.anno = new Annotorious({ image: this.photo.id })
this.annotatePhoto()

Related

Using XState in Nuxt 3 with asynchronous functions

I am using XState as a state manager for a website I build in Nuxt 3.
Upon loading some states I am using some asynchronous functions outside of the state manager. This looks something like this:
import { createMachine, assign } from "xstate"
// async function
async function fetchData() {
const result = await otherThings()
return result
}
export const myMachine = createMachine({
id : 'machine',
initial: 'loading',
states: {
loading: {
invoke: {
src: async () =>
{
const result = await fetchData()
return new Promise((resolve, reject) => {
if(account != undefined){
resolve('account connected')
}else {
reject('no account connected')
}
})
},
onDone: [ target: 'otherState' ],
onError: [ target: 'loading' ]
}
}
// more stuff ...
}
})
I want to use this state machine over multiple components in Nuxt 3. So I declared it in the index page and then passed the state to the other components to work with it. Like this:
<template>
<OtherStuff :state="state" :send="send"/>
</template>
<script>
import { myMachine } from './states'
import { useMachine } from "#xstate/vue"
export default {
setup(){
const { state, send } = useMachine(myMachine)
return {state, send}
}
}
</script>
And this worked fine in the beginning. But now that I have added asynchronous functions I ran into the following problem. The states in the different components get out of sync. While they are progressing as intended in the index page (going from 'loading' to 'otherState') they just get stuck in 'loading' in the other component. And not in a loop, they simply do not progress.
How can I make sure that the states are synced in all my components?

VueJS $set not making new property in array of objects reactive

In my VueJS 2 component below, I can add the imgdata property to each question in the area.questions array. It works - I can see from the console.log that there are questions where imgdata has a value. But despite using $set it still isn't reactive, and the imgdata isn't there in the view! How can I make this reactive?
var componentOptions = {
props: ['area'],
data: function() {
return {
qIndex: 0,
};
},
mounted: function() {
var that = this;
that.init();
},
methods: {
init: function() {
var that = this;
if (that.area.questions.length > 0) {
that.area.questions.forEach(function(q) {
Util.HTTP('GET', '/api/v1/photos/' + q.id + '/qimage').then(function(response) {
var thisIndex = (that.area.questions.findIndex(entry => entry.id === q.id));
var thisQuestion = (that.area.questions.find(entry => entry.id === q.id));
thisQuestion.imgdata = response.data;
that.$set(that.area.questions, thisIndex, thisQuestion);
})
});
}
console.log("area.questions", that.area.questions);
},
Since area is a prop, you should not be attempting to make changes to it within this component.
The general idea is to emit an event for the parent component to listen to in order to update the data passed in.
For example
export default {
name: "ImageLoader",
props: {
area: Object
},
data: () => ({ qIndex: 0 }), // are you actually using this?
mounted () {
this.init()
},
methods: {
async init () {
const questions = await Promise.all(this.area.questions.map(async q => {
const res = await Util.HTTP("GET", `/api/v1/photos/${encodeURIComponent(q.id)}/qimage`)
return {
...q,
imgdata: res.data
}
}))
this.$emit("loaded", questions)
}
}
}
And in the parent
<image-loader :area="area" #loaded="updateAreaQuestions"/>
export default {
data: () => ({
area: {
questions: [/* questions go here */]
}
}),
methods: {
updateAreaQuestions(questions) {
this.area.questions = questions
}
}
}
Here that variable has a value of this but it's bound under the scope of function. So, you can create reactive property in data as below :
data: function() {
return {
qIndex: 0,
questions: []
};
}
Props can't be reactive so use :
that.$set(this.questions, thisIndex, thisQuestion);
And assign your API output to directly questions using this.questions.

VueJS How to access Mounted() variables in Methods

I'm new in Vue and would like assistance on how to access and use variables created in Mounted() in my methods.
I have this code
Template
<select class="controls" #change="getCatval()">
Script
mounted() {
var allcards = this.$refs.allcards;
var mixer = mixitup(allcards);
},
methods: {
getCatval() {
var category = event.target.value;
// I want to access mixer here;
}
}
I can't find a solution anywhere besides this example where I could call a method x from mounted() and pass mixer to it then use it inside my getCatval()
Is there an easier way to access those variables?
I will first suggest you to stop using var, and use the latest, let and const to declare variable
You have to first declare a variable in data():
data(){
return {
allcards: "",
mixer: ""
}
}
and then in your mounted():
mounted() {
this.allcards = this.$refs.allcards;
this.mixer = mixitup(this.allcards);
},
methods: {
getCatval() {
let category = event.target.value;
this.mixer
}
}
like Ninth Autumn said : object returned by the data function and props of your components are defined as attributes of the component, like your methods defined in the method attribute of a component, it's in this so you can use it everywhere in your component !
Here an example:
data() {
return {
yourVar: 'hello',
};
},
mounted() { this.sayHello(); },
method: {
sayHello() { console.log(this.yourVar); },
},
Update
you cannot pass any value outside if it's in block scope - Either you need to get it from a common place or set any common value
As I can see, var mixer = mixitup(allcards); is in the end acting as a function which does some operation with allcards passed to it and then returns a value.
1 - Place it to different helper file if mixitup is totally independent and not using any vue props used by your component
In your helper.js
const mixitup = cards => {
// Do some operation with cards
let modifiedCards = 'Hey I get returned by your function'
return modifiedCards
}
export default {
mixitup
}
And then in your vue file just import it and use it is as a method.
In yourVue.vue
import Helpers from '...path../helpers'
const mixitup = Helpers.mixitup
export default {
name: 'YourVue',
data: ...,
computed: ...,
mounted() {
const mixer = mixitup(allcards)
},
methods: {
mixitup, // this will make it as `vue` method and accessible through
this
getCatval() {
var category = event.target.value;
this.mixitup(allcards)
}
}
}
2- Use it as mixins if your mixitup dependent to your vue and have access to vue properties
In your yourVueMixins.js:
export default {
methods: {
mixitup(cards) {
// Do some operation with cards
let modifiedCards = 'Hey I get returned by your function'
return modifiedCards
}
}
}
And import it in your vue file:
import YourVueMixins from '...mixins../YourVueMixins'
const mixitup = Helpers.mixitup
export default {
name: 'YourVue',
mixins: [YourVueMixins] // this will have that function as vue property
data: ...,
computed: ...,
mounted() {
const mixer = this.mixitup(allcards)
},
methods: {
getCatval() {
var category = event.target.value;
this.mixitup(allcards)
}
}
}

Computed Getter causes maximum stack size error

I'm trying to implement the following logic in Nuxt:
Ask user for an ID.
Retrieve a URL that is associated with that ID from an external API
Store the ID/URL (an appointment) in Vuex
Display to the user the rendered URL for their entered ID in an iFrame (retrieved from the Vuex store)
The issue I'm currently stuck with is that the getUrl getter method in the store is called repeatedly until the maximum call stack is exceeded and I can't work out why. It's only called from the computed function in the page, so this implies that the computed function is also being called repeatedly but, again, I can't figure out why.
In my Vuex store index.js I have:
export const state = () => ({
appointments: {}
})
export const mutations = {
SET_APPT: (state, appointment) => {
state.appointments[appointment.id] = appointment.url
}
}
export const actions = {
async setAppointment ({ commit, state }, id) {
try {
let result = await axios.get('https://externalAPI/' + id, {
method: 'GET',
protocol: 'http'
})
return commit('SET_APPT', result.data)
} catch (err) {
console.error(err)
}
}
}
export const getters = {
getUrl: (state, param) => {
return state.appointments[param]
}
}
In my page component I have:
<template>
<div>
<section class="container">
<iframe :src="url"></iframe>
</section>
</div>
</template>
<script>
export default {
computed: {
url: function (){
let url = this.$store.getters['getUrl'](this.$route.params.id)
return url;
}
}
</script>
The setAppointments action is called from a separate component in the page that asks the user for the ID via an onSubmit method:
data() {
return {
appointment: this.appointment ? { ...this.appointment } : {
id: '',
url: '',
},
error: false
}
},
methods: {
onSubmit() {
if(!this.appointment.id){
this.error = true;
}
else{
this.error = false;
this.$store.dispatch("setAppointment", this.appointment.id);
this.$router.push("/search/"+this.appointment.id);
}
}
I'm not 100% sure what was causing the multiple calls. However, as advised in the comments, I've now implemented a selectedAppointment object that I keep up-to-date
I've also created a separate mutation for updating the selectedAppointment object as the user requests different URLs so, if a URL has already been retrieved, I can use this mutation to just switch the selected one.
SET_APPT: (state, appointment) => {
state.appointments = state.appointments ? state.appointments : {}
state.selectedAppointment = appointment.url
state.appointments = { ...state.appointments, [appointment.appointmentNumber]: appointment.url }
},
SET_SELECTED_APPT: (state, appointment) => {
state.selectedAppointment = appointment.url
}
Then the getUrl getter (changed its name to just url) simply looks like:
export const getters = {
url: (state) => {
return state.selectedAppointment
}
}
Thanks for your help guys.

Testing Methods within Vue Components using Jasmine

I have the following test which works great
it('does not render chapter div or error div', () => {
const payLoad = chapter;
const switcher = 'guild';
var vm = getComponent(payLoad, switcher).$mount();
expect(vm.$el.querySelector('#chapter-card')).toBeNull();
expect(vm.$el.querySelector('#error-card')).toBeNull();
});
To do this I wrote a helper method that mounts a component:
const getComponent = (prop1) => {
let vm = new Vue({
template: '<div><compd :payLoad="group" :index="index" "></compd ></div></div>',
components: {
compd,
},
data: {
payLoad: prop1,
},
})
return vm;
}
however, I have a method within my vue component compd. For simplicitys sake, lets call it
add(num,num){
return num+num;
}
I want to be able to write a test case similar to the following:
it('checks the add method works', () => {
expect(compd.add(1,2).toBe(3));
});
I cannot figure out how to do this. Has anyone any suggestions?
The documentation here:
https://v2.vuejs.org/v2/guide/unit-testing.html
Does not cover testing methods.
Source code from vue repo
As you can see the method gets called simply on the instance
const vm = new Vue({
data: {
a: 1
},
methods: {
plus () {
this.a++
}
}
})
vm.plus()
expect(vm.a).toBe(2)
You can also access the method via $options like in this case (vue source code)
const A = Vue.extend({
methods: {
a () {}
}
})
const vm = new A({
methods: {
b () {}
}
})
expect(typeof vm.$options.methods.a).toBe('function')
Update:
To test child components use $children to access the necessary child. Example
var childToTest = vm.$children.find((comp)=>comp.$options.name === 'accordion')` assuming name is set to `accordion`
After that you can
childToTest.plus();
vm.$nextTick(()=>{
expect(childToTest.someData).toBe(someValue)
done(); //call test done callback here
})
If you have a single child component and not a v-for put a ref on it
`
vm.$refs.mycomponent.myMethod()