I am using regular Vue.js in my project. I store data in a store made from scratch and use it in a template:
<template>
<div>
<div class="row">
<div v-for="(picture, index) in storeState.pictures"
:key="index"
class="col-md-2 my-auto">
<div >
<img class="img-thumbnail img-responsive"
:src="picture.url"
#click="deleteMe">
</div>
</div>
</div>
</div>
</template>
<script>
import { store } from "../common/store.js"
export default {
name:"PictureUpload",
data() {
return {
storeState: store.state,
};
},
methods: {
deleteMe() {
let apiUrl = this.picture.url
console.log(apiUrl)
}
}
}
</script>
My pictures are rendering well but now I want to add a delete() function to the picture #click and whenever I click on the button I get:
TypeError: Cannot read property 'url' of undefined
So how can I access my picture data inside my method?
You should pass picture as parameter in the click handler :
#click="deleteMe(picture)">
and refer to it in the method like :
methods: {
deleteMe(picture) {
let apiUrl = picture.url //omit this
console.log(apiUrl)
}
}
the storeState should be a computed property :
export default {
name:"PictureUpload",
data() {
return {
};
},
computed:{
storeState(){
return store.state;
}
},
methods: {
deleteMe(picture) {
let apiUrl = picture.url
console.log(apiUrl)
}
}
}
Related
Here is a simple carousel:
<template>
<div ref="wrapperRef" class="js-carousel container">
<div class="row">
<slot></slot>
</div>
<div class="row">
<template v-for="n in noOfSlides" :key="n">
<span style="margin-right: 25px;" #click="console.log(n)">O</span>
</template>
</div>
</div>
</template>
This works using Options API. The noOfSlides is properly getting changed and the for-loop re-renders after mounting
export default {
name: 'carousel',
data() {
return {
noOfSlides: 0
}
},
mounted(){
this.noOfSlides = this.$refs.wrapperRef.querySelectorAll('.js-carousel-item').length;
}
}
It doesn't work while using composition API like below. The noOfSlides is not getting changed and thus for-loop not re-rendering
export default {
name: 'carousel',
setup() {
const wrapperRef = ref(null);
let noOfSlides = ref(0);
onMounted(function () {
noOfSlides = wrapperRef.value.querySelectorAll('.js-carousel-item').length;// this here properly returning the number of slides(more than 0 anyway)
})
return {
wrapperRef,
noOfSlides
}
}
}
What do I miss here?
This is not the Vue way to work with DOM, you could use slots :
export default {
name: 'carousel',
setup(props, { slots }) {
const wrapperRef = ref(null);
let noOfSlides = ref(0);
onMounted(function () {
console.log('slots ', slots.default());
noOfSlides.value = slots.default().length;
});
function log(n) {
console.log('n ', n);
}
return {
wrapperRef,
noOfSlides,
log,
};
},
};
DEMO
I want to call the method when the page is loading. So far I call the method when I click, but when I try to use mounted to automatically call the method, it keeps failing, do you have some suggestion?
Below is the code:
<template>
<div>
<AppHeader></AppHeader>
<div style="display: flex">
<ul>
<li
style="cursor: pointer"
v-for="exchange of profileExchanges"
:key="exchange.id"
#click="getImagesByExchange(exchange.id)"
>
<div style="padding: 20px; border: solid 2px red">
<h3>Brand: {{exchange.brand}}</h3>
<p>Description: {{exchange.description}}</p>
<p>Category: {{exchange.category}}</p>
</div>
</li>
</ul>
<div>
<span style="margin: 10px" v-for="url of exchangeImageUrls" :key="url">
<img width="250px" :src="url" />
</span>
</div>
</div>
<br />
</div>
</template>
<script>
import AppHeader from '#/components/Header5'
export default {
components: {
AppHeader
},
created() {
this.$store.dispatch('exchange/getExchangesByProfile', this.$store.state.auth.user.profile.user)
},
data() {
return {
selectedExchangeId: '',
exchanges: []
}
},
computed: {
user() {
return this.$store.state.auth.user
},
profile() {
return this.user.profile || {}
},
profileExchanges() {
return this.$store.getters['exchange/profileExchanges']
},
exchangeImageUrls() {
return this.$store.getters['exchange/imageUrls']
}
},
methods: {
getImagesByExchange(exchangeId) {
this.$store.dispatch('exchange/getImagesByExchange', exchangeId)
},
getListings() {
},
updateProfile(profile, closeModal) {
this.$store.dispatch('auth/updateProfile', profile)
.then(_ => {
closeModal()
})
}
}
}
</script>
I try to put mounted like this
mounted: function() {
this.getImagesByExchange() // Calls the method before page loads
},
But it keeps failing. I guess the problem is how to access the key, but not sure.
If the selectedExchangeId is already not set, you could get the first element of the profileExchanges array and pass the id to the getImagesByExchange function:
mounted() {
this.getImagesByExchange(this.profileExchanges[0].id) // Calls the method before page loads
},
EDIT
Looking again at your code one could suppose that, at the moment the component is mounted the profileExchanges property might not yet be set. One way to handle this is to call both of them inside the created like this:
async created() {
await this.$store.dispatch(
'exchange/getExchangesByProfile',
this.$store.state.auth.user.profile.user
);
if (this.profileExchanges && this.profileExchanges.length) {
this.getImagesByExchange(this.profileExchanges[0].id);
}
}
I have a Vue component with a tag input where I make an ajax call to the db to retrieve suggestions as the user is typing. I am using #johmun/vue-tags-input for this. Everything works fine except that instead of the autocomplete listing options including only the tag attribute of the Tag model, it includes the entire object.
I want to list only the tag attribute in the view, but I want to reference the array of entire Tag objects when it comes time to create the association with the user.
This what the current dropdown look like in the browser:
Here is my input component removing the irrelevant parts, so it meets SO's size constraints:
<template>
<div >
<b-container class="mt-8 pb-5">
<b-row class="justify-content-center">
<b-col lg="5" md="7">
<form>
...
<div v-if="step === 3">
<h2><strong>What topics are you interested in?</strong> (e.g tag1, tag2, etc...)</h2>
<h2>A few popular ones:
<button #click.prevent="addToTags(item)" class="btn btn-sm btn-success" v-for="item in existingTags.slice(0, 3)" :key="item.id">
{{ item.tag }}
</button>
</h2>
<vue-tags-input
v-model="tag"
v-on:keyup.native="getTags"
:tags="tags"
:autocomplete-items="filteredItems"
:autocomplete-min-length=3
#tags-changed="confirmedTags"
/>
</div>
...
</form>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import VueTagsInput from '#johmun/vue-tags-input';
import UsersService from '#/services/UsersService'
import TagsService from '#/services/TagsService'
import TagRelationsService from '#/services/TagRelationsService'
export default {
name: 'UserOnboard',
data() {
return {
tag: '',
tags: [],
...
}
};
},
components: {
VueTagsInput
},
computed: {
filteredItems() {
return this.existingTags.filter(i => {
return i.tag.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1;
});
},
...
user() {
return this.$store.state.auth.user
},
existingTags() {
return this.$store.state.tags.existingTags
}
},
...
methods:{
...
},
addToTags(newTag) {
if (!this.tags.includes(newTag)) {
this.tags.push(newTag)
}
// on button click add appropriate tag to tags array
// console.log('tag array is: ',tags)
},
confirmedTags(event) {
this.tags=event
console.log(event)
},
...
getTags() { //debounce need to be inside conditional
console.log('gettin tags')
// if (this.tag.length >2) {
this.$store.dispatch('debounceTags', this.tag)
// }
}
}
}
</script>
Also, here is the debounceTags method which runs via vuex:
import TagsService from '#/services/TagsService'
import { debounce } from "lodash";
export const state = {
existingTags: []
}
export const mutations = {
setTags (state, tags) {
state.existingTags = tags
}
}
export const actions = {
debounceTags: debounce(({ dispatch }, data) => {
console.log("Inside debounced function.");
dispatch("getTags" ,data);
}, 300),
async getTags ({ commit }, data) {
await TagsService.getTags(data)
.then(function (response) {
console.log('before setting tags this is resp.data: ', response)
commit('setTags', response);
});
}
}
export const getters = {
}
I'm studying vue.js and I create a little project: https://codesandbox.io/s/focused-gould-hs7k5?file=/src/components/Modal.vue
. I've created two components: Home and Modal. When I click on a button defined in the Home I opened a modal. In this component I used two dependencies: axios and infinite scroll. I create a function "loadMore" that I invoke on mounted property. This function load elements, that I stored in "photos" variable, from a GET call. After this I wanted to implementes a search filter bar. I created an input tag with a v-model "searchString". I defined this property in the data and I created a function "filteredResources" that filters the results.
<script>
import axios from "axios";
export default {
name: "Modal",
data() {
return {
photos: [],
results: [],
busy: false,
limit: 10,
searchString: ""
};
},
methods: {
loadMore() {
this.busy = true;
axios
.get("https://jsonplaceholder.typicode.com/photos")
.then(res => {
const append = res.data.slice(
this.photos.length,
this.photos.length + this.limit
);
this.photos = this.photos.concat(append);
this.busy = false;
})
.catch(function(error) {
console.log(error);
this.busy = false;
});
}
},
mounted() {
this.loadMore();
},
computed: {
filteredResources: () => {
if (!this.searchString) return this.photos;
let regex = new RegExp(this.searchString, "i");
return this.photos.filter(photo => regex.test(photo.title));
}
}
};
</script>
This is the template:
<section class="modal-body">
<slot name="body">
<input type="text" v-model="searchString" placeholder="Enter your search terms">
<ul>
<div
v-infinite-scroll="loadMore"
infinite-scroll-disabled="busy"
infinite-scroll-distance="limit"
class="productsContainer">
<li v-for="photo in photos" :key="photo.id">
<img :src="photo.url" height="200">
<p class="titleDescription">Title: {{photo.title}}</p>
</li>
</div>
</ul>
</slot>
</section>
If in the v-for directive I tried to put filteredResources instead of photos, for doing a search, I get an error:
"[Vue warn]: Error in render: "TypeError: Cannot read property 'searchString' of undefined"
But if I try to debug when filteredResources is invoked the 'searchString' variable is defined like an empty string.
Try this :
computed: {
filteredResources: (){
if (!this.searchString || this.searchString == '') return this.photos;
let regex = new RegExp(this.searchString, "i");
return this.photos.filter(photo => regex.test(photo.title));
}
}
I was wondering how can I move my items -> book, from one component to another. I took this books from api and I show them in the list, so I have an ID.
<template>
<v-flex v-for="(book, index) in allBooks">
<div>Title: {{ book.title }}</div>
<i #click="markAsFavorite(book)" :class="{isActive: isMark}" class="fas fa-heart"></i>
</template>
//component books
<script>
name: 'Books',
data () {
return {
allBooks: [],
isMark: false,
favouriteBooks: []
}
},
mounted() {
axios.get("https://www.googleapis.com/books/v1/volumes?q=id:" + this.id)
.then(response => {
console.log(response)
this.allBooks = response.data.items.map(item => ({...item, isMark: false}))
console.log(this.allBooks)
})
.catch(error => {
console.log(error)
})
},
methods: {
markAsFavorite(book) {
this.isMark = !this.isMark
let favouriteAllBooks = this.favouriteBooks.push(book => {
book.id = // i dont know what?
})
},
}
</script>
//component favourite
<template>
<div class=showFavouriteBook>
<p></p>
</div>
</template>
I tried to compare this marked book ID to something, and then this array with marked books show in second template favourite. But I have no idea how to do this. Maybe somebody can prompt me something?
You should use a global eventBus for that. An 'eventBus' is another instance of Vue which is used to pass data via components tied to the main application.
At the root script of your application append the following:
const eventBus = new Vue({
data: function() {
return {
some_var: null,
}
}
});
You can use Vue mixin to have your event bus accessible globally easily:
Vue.mixin({
data: function() {
return {
eventBus: eventBus,
}
}
});
Now when you want to pass data between components you can use the bus:
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
this.eventBus.some_var = 'it works!'
}
}
</script>
Component 2
<template>
<div>
{{ eventBus.some_var }} <!-- it works -->
</div>
</template>
In addition you can use $emit and $on.
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
// emit 'emittedVarValue' event with parameter 'it works'
this.eventBus.$emit('emittedVarValue', 'it works!')
}
}
</script>
Component 2
<template>
<div>
{{ some_var }} <!-- "it works" once eventBus receives event "emittedVarValue" -->
</div>
</template>
<script>
export default {
data: function() {
return {
some_var: null
}
},
mounted: function() {
// waiting for "emittedVarValue" event
this.eventBus.$on('emittedVarValue', (data)=>{
this.some_var = data;
})
}
}
</script>
Hope this answer helps you.