How to bind a dynamic <img> id to a JavaScript event? - vuejs2

I am learning how to use a Javascript image annotation library called Annotorious. I want to integrate it into a Vue app and it works fine if I do this:
<img :src="photo.url" id="dog" :alt="photo.title" />
However, I am getting a console error if I do it like so:
<img :src="photo.url" :id="photo.id" :alt="photo.title" />
My full code for the SFC named PhotoDetail.vue: Codesandbox link
<template>
<div>
<h1>Test Photo</h1>
<img :src="photo.url" :id="photo.id" :alt="photo.title" />
</div>
</template>
<script>
import { Annotorious } from '#recogito/annotorious'
export default {
name: 'photoDetail',
data() {
return {
photo: {}
}
},
methods: {
async getPhoto() {
this.photo.url = await 'https://picsum.photos/id/237/400/'
this.photo.id = 'dog'
this.photo.title = 'nice dog'
},
annotatePhoto() {
try {
const anno = new Annotorious({
image: this.photo.id // image element or ID
})
// Attach listeners to handle annotation events
anno.on('createAnnotation', function(annotation) {
console.log('Created!', annotation)
})
} catch (error) {
console.log('anno error', error)
}
}
},
async created() {
await this.getPhoto()
},
mounted() {
this.annotatePhoto()
}
}
</script>
Console error:
anno error TypeError: Cannot read property 'nodeType' of undefined
at new e (annotorious.min.js:1)
at VueComponent.annotatePhoto (cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/PhotoDetail.vue?vue&type=script&lang=js&:51)
at VueComponent.mounted (cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/PhotoDetail.vue?vue&type=script&lang=js&:84)
at invokeWithErrorHandling (vue.runtime.esm.js:1853)
at callHook (vue.runtime.esm.js:4213)
at Object.insert (vue.runtime.esm.js:3136)
at invokeInsertHook (vue.runtime.esm.js:6336)
at VueComponent.patch [as __patch__] (vue.runtime.esm.js:6555)
at VueComponent.Vue._update (vue.runtime.esm.js:3942)
at VueComponent.updateComponent (vue.runtime.esm.js:4060)
Any advice? Thank you!

First, you need to make sure the img component is rendered before calling annotatePhoto function. That is, after getPhoto function:
async mounted() {
await this.getPhoto()
this.annotatePhoto()
}
You also need to change the way you update the photo variable like this, or the DOM won't update:
this.photo = {
url: await 'https://picsum.photos/id/237/400/',
id: 'dog',
title: 'nice dog',
}
Working example: sandbox

Related

Getting "Cannot read properties of undefined (reading 'commit')"NUXT

I am new to Vue and stuck. I am trying to send user input data from a form into a vuex store. From that vuex store, an action will be called (fetching from API) and I would like that data back into my app and components.
<template>
<div>
<h1>APP NAME</h1>
<form action="submit" #submit.prevent="sendCityName()">
<label for="query"></label>
<input
type="text"
id="query"
v-model="cityName"
>
<button type="submit">Submit</button>
</form>
<h3>{{ lat }}</h3>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
cityName: ''
}
},
computed: {
coordinates () {
return this.$store.state.lat
}
},
methods: {
sendCityName() {
this.$store.commit('fetchCity', this.cityName)
}
},
}
</script>
Here is my index.vue and getting the error "Cannot read properties of undefined (reading 'commit')"
here is my store.js. I want to use the lat and lon across my app.
export const state = () => ({
lat: '',
lon: ''
})
export const mutations = {
SET_LAT(state, payload){
state.lat = payload
},
SET_LON(state, payload){
state.lon = payload
}
}
export const actions = {
async fetchCity({ commit }, cityName) {
// make request
axios.get(
`https://api.openweathermap.org/geo/1.0/direct`, {
params: {
appid: "xxxxxxx",
q: cityName,
}
}).then((response) => {
commit('SET_LAT', response.data[0].lat);
commit('SET_LON', response.data[0].lng);
});
},
};
When I button submit I get the error "Cannot read properties of undefined (reading 'commit')"
Here is my working repo with the fixes mentioned below.
There are 3 things in your code:
remove vuex from package.json and run yarn again, that one is already baked into Nuxt as stated in the official documentation, those are the only steps needed
all the files inside of store will be namespaced by default for you, since you do have store/store.js, the proper syntax will be
async sendCityName() {
await this.$store.dispatch('store/fetchCity', this.cityName) // 👈🏻 store prefix
}
since you do use the axios module, you should have the following in your action (using the async/await syntax since it's more modern and preferable)
async fetchCity({ commit }, cityName) {
const response = await this.$axios.get(
`https://api.openweathermap.org/geo/1.0/direct`, {
params: {
appid: "3d91ba5b3c11d13158a2726aab902a0b",
q: cityName,
}
})
commit('SET_LAT', response.data[0].lat)
commit('SET_LON', response.data[0].lng)
}
Looking at the browser's console, you also have some errors to fix.
I can also recommend an ESlint + Prettier configuration so that you keep your code error-proof + properly formatted at all times.

TypeError: Cannot read properties of undefined (reading '__ob__') in Nuxt

The error appears when navigating from one page with the ProductCard component to another.
I believe the error comes from the data fetching or the mounted(), but I haven't been able to solve it. The ProductCard component is just a visual one with some props. So the error must be here.
Full error:
client.js:228 TypeError: Cannot read properties of undefined (reading '__ob__')
at VueComponent.Vue.$destroy (vue.runtime.esm.js:4004:18)
at destroy (vue.runtime.esm.js:3175:27)
at invokeDestroyHook (vue.runtime.esm.js:6148:59)
at invokeDestroyHook (vue.runtime.esm.js:6153:9)
at invokeDestroyHook (vue.runtime.esm.js:6153:9)
at invokeDestroyHook (vue.runtime.esm.js:6153:9)
at VueComponent.patch [as __patch__] (vue.runtime.esm.js:6501:30)
at VueComponent.Vue.$destroy (vue.runtime.esm.js:4010:8)
at destroy (vue.runtime.esm.js:3175:27)
at invokeDestroyHook (vue.runtime.esm.js:6148:59)
My page .vue file template:
<template>
<main>
<ProductTabs></ProductTabs>
<div
v-if="productsLoading"
class="spinner-border"
style="width: 3rem; height: 3rem"
role="status"
>
<span class="sr-only">Loading...</span>
</div>
<v-container v-else fluid>
<v-row d-flex justify="center">
<ProductCard
v-for="product in products"
:key="product._id"
:product-title="product.productName"
:product-price="product.price"
:product-img1="product.img1"
:product-img2="product.img2"
></ProductCard>
<br />
</v-row>
</v-container>
</main>
</template>
My page .vue file script:
<script>
export default {
path: '/',
name: 'ProductsPage',
components: { ProductTabs },
// variables
data() {
return {
products: [],
productsLoading: false,
}
},
// call the get Poducts method
mounted() {
this.getAllProducts()
},
// get products from api and save into products array
methods: {
async getAllProducts() {
this.productsLoading = true
try {
const data = await this.$axios.$get('api/products')
this.products = data
this.productsLoading = false
return this.products
} catch (err) {
this.productsLoading = false
return err
}
},
},
}
</script>
I have the same issue, i resolved with keep-alive props on component:
<Nuxt keep-alive/>
More info on official doc
https://nuxtjs.org/docs/features/nuxt-components/
[Update]
You can also check if you have on some component:
data() {
return {
};
},
Seem it is the main problem
The issue was coming from a component hover event, nothing related to the code snippet above.
Maybe you can just use the created with the async functiion. or better still use the fetch provided by Nuxt.
asynce fetch() {
this.productsLoading = true
try {
const data = await this.$axios.$get('api/products')
this.products = data
this.productsLoading = false
} catch (err) {
this.productsLoading = false
return err
}
},
Also i dont think you will need a return in the promise

VueJS Loading template component via code?

I am trying to make a template component that I can use later on in my project. However I'm having a bit of a hard time showing it on the element I want via code.
The code I have so far is as such.
<template>
<div>
<b-alert show dismissible variant="danger" v-show="elementVisible">
<i class="mdi mdi-block-helper mr-2"></i>{{ text }}
</b-alert>
</div>
</template>
<script>
export default {
name: "alertDanager",
props: {
text: null
},
data() {
return {
elementVisible: true
};
},
created() {
setTimeout(() => (this.elementVisible = false), 5000);
}
};
</script>
I am trying to call this on an action by this
I import it
import dangerAlert from "#/components/Alerts/danger";
Then on the function I want to call it on I do this
const error = new dangerAlert({ propsData: { text: "Error message" } });
error.$mount("#error");
However it just gives me an error saying
_components_Alerts_danger__WEBPACK_IMPORTED_MODULE_3__.default is not a constructor
So I'm not sure how to fix this or do what I need to do. I've tried googling but can't seem to find an answer.
The Component imported is not a constructor and it should extends a constructor and to use that you should use Vue.extend()
Vue.extend() is a class inheritance method. Its task is to create a sub-class of Vue and return the constructor.
so instead of this
const error = new dangerAlert({ propsData: { text: "Error message" } });
error.$mount("#error");
make it like this
const DangerAlertExtended= Vue.extend(dangerAlert);
const error = new DangerAlertExtended({ propsData: { text: "Error message" } });
error.$mount("#error");

Vue Js show and hide a div with a Toggle

Hey all so I am brand new into the world of Vue.
I am building an app With a Rails API back end and Vue front end.
I am trying use a Toggle Switch (found in this repo Git Hub ) to send true attribute back to my model and open a hidden div on the page.
Right now I am getting these errors and i have no idea how to proceed. I dont think i am understanding their docs as I've read them but not really understanding what i need to do..:
vue.esm.js?efeb:1897 RangeError: Maximum call stack size exceeded
at VueComponent.Vue._render (vue.esm.js?efeb:3553)
at VueComponent.updateComponent (vue.esm.js?efeb:4069)
at Watcher.get (vue.esm.js?efeb:4482)
at new Watcher (vue.esm.js?efeb:4471)
at mountComponent (vue.esm.js?efeb:4076)
at VueComponent.Vue.$mount (vue.esm.js?efeb:9057)
at VueComponent.Vue.$mount (vue.esm.js?efeb:11953)
at init (vue.esm.js?efeb:3127)
at createComponent (vue.esm.js?efeb:5983)
at createElm (vue.esm.js?efeb:5930)
vue.esm.js?efeb:1897 RangeError: Maximum call stack size exceeded
at VueComponent.Vue._render (vue.esm.js?efeb:3553)
at VueComponent.updateComponent (vue.esm.js?efeb:4069)
at Watcher.get (vue.esm.js?efeb:4482)
at new Watcher (vue.esm.js?efeb:4471)
at mountComponent (vue.esm.js?efeb:4076)
at VueComponent.Vue.$mount (vue.esm.js?efeb:9057)
at VueComponent.Vue.$mount (vue.esm.js?efeb:11953)
at init (vue.esm.js?efeb:3127)
at createComponent (vue.esm.js?efeb:5983)
at createElm (vue.esm.js?efeb:5930)
Here is my AppToggle.vue
<template>
<div>
<AppToggle v-model="isToggleOn" onText="Hide Map" offText="Show Map"/>
</div>
</template>
<script>
export default {
name: "AppToggle",
data() {
return {
isToggleOn: true
};
}
};
</script>
and here is my Signup.vue component where the toggle is called:
<template>
... Some form stuff up here...
<app-toggle #click.prevent="toggleSmsDiv()"/>
<div id="smsDiv" v-if="isToggleOn">TEST DIV ITEMS</div>
... More form stuff down here...
</template>
<script>
import AppToggle from "#/components/AppToggle";
export default {
name: "Signup",
components: {
AppToggle
},
data() {
return {
isToggleOn: false,
first_name: "",
last_name: "",
email: "",
password: "",
password_confirmation: "",
error: ""
};
},
created() {
this.checkSignedIn();
},
updated() {
this.checkSignedIn();
},
methods: {
toggleSmsDiv() {
this.isToggleOn = !this.isToggleOn;
},
signup() {
this.$http.plain
.post("/signup", {
email: this.email,
password: this.password,
password_confirmation: this.password_confirmation
})
.then(response => this.signupSuccessful(response))
.catch(error => this.signupFailed(error));
},
signupSuccessful(response) {
if (!response.data.csrf) {
this.signupFailed(response);
return;
}
localStorage.csrf = response.data.csrf;
localStorage.signedIn = true;
this.error = "";
this.$router.replace("/products"); // Change this to User Dashboard
},
signupFailed(error) {
this.error =
(error.response && error.response.data && error.response.data.error) ||
"Something went wrong. Please try again.";
delete localStorage.scrf;
delete localStorage.signedIn;
},
checkSignedIn() {
if (localStorage.signedIn) {
this.$router.replace("/products"); //Change this to User Dashboard
}
}
}
};
</script>
<style>
</style>
You got Maximum call stack size exceeded because of you are using your AppToggle component inside AppToggle component which that cause the recursively call itself.
I'm not sure how do you import this package since I can't find it on npm. It seems the author of this package want us to copy TailwindToggle.vue manually.
So your AppToggle.vue would be:
// Same as TailwindToggle.vue
<template>
...
</template>
<script>
...
</script>
<style lang="postcss"> // Make sure you Vue config support postcss` language
...
</style>
And your Signup.vue would be:
<template>
...
<AppToggle v-model="isToggleOn" onText="Hide Map" offText="Show Map"/>
<div id="smsDiv" v-if="isToggleOn">TEST DIV ITEMS</div>
...
</template>
...
I'm not sure this will works since the style of TailwindToggle seems have to import some pieces from somewhere else (not sure). If it not working may be you can looking at its dist file and copy involved style and paste it into yours AppToggle.vue. But if it possible I would recommend you to another package instead.
Hope this helps.

Vue.js - Keep Alive Component - Error next Tick

Description
I'm trying to take advantage of the keep-alive functionality of vue-js 2.3 so my AJAX call is made only once.
Problem
The second time I try to open the popup component I get this error :
Error in nextTick: "TypeError: Cannot read property 'insert' of undefined"
TypeError: Cannot read property 'insert' of undefined
Steps
Click on the button to display the popup
Wait for one second
Close the popup
Click again on the button
https://jsfiddle.net/4fwphqhv/
Minimal reproduction example
<div id="app">
<button #click="showDialog = true">Show Component PopUp</button>
<keep-alive>
<popup v-if="showDialog" :show-dialog.sync="showDialog"></popup>
</keep-alive>
</div>
<template id="popup">
<el-dialog :visible.sync="show" #visible-change="updateShowDialog">{{asyncData}}</el-dialog>
</template>
Vue.component('popup', {
template: '#popup',
props : ['showDialog'],
data(){
return {
show: this.showDialog,
asyncData: "Loading please wait"
}
},
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
}
},
created:function (){
const _this = this
setTimeout(() => _this.asyncData = 'Async Data was loaded' , 1000)
},
});
var vm = new Vue({
el: '#app',
data: {
showDialog: false,
},
});
Real code of the popup component
<template>
<el-dialog title="Order in progress" size="large" :visible.sync="show" #visible-change="updateShowLoadOrder"></el-dialog>
</template>
<script>
let popUpData;
export default {
name: '',
data () {
return {
ordersInProgress: [],
show: this.showLoadOrder
}
},
props: ['showLoadOrder'],
methods: {
updateShowLoadOrder (isVisible) {
if (isVisible) return false;
this.$emit('update:showLoadOrder', false)
}
},
created () {
const _this = this;
if (!popUpData) {
axios.get('api/mtm/apiGetOrdersInProgress').then((response) => {
_this.ordersInProgress = popUpData = response.data;
});
} else {
this.ordersInProgress = popUpData;
}
}
}
</script>
Ok. So your problem here is the wrong life-cycle hook.
If you change created to activated... it should work. It did for me in your JS fiddle.
activated:function (){
setTimeout(() => this.asyncData = 'Async Data was loaded' , 1000)
}
There are two other hooks, activated and deactivated. These are for keep-alive components, a topic that is outside the scope of this article. Suffice it to say that they allow you to detect when a component that is wrapped in a tag is toggled on or off. You might use them to fetch data for your component or handle state changes, effectively behaving as created and beforeDestroy without the need to do a full component rebuild.
SOURCE: here