I've got a problem with event orders, I have one method for getting data and another to open modal with data from previous method, and it works fine, but modal is rendered before getting data, so I see previous result for a sec. I do understand that I need my methods to be called in promise, but fail to realize how to do this.
App.vue
<v-app>
<v-main>
<h1 class="text-center text-uppercase">Gallery</h1>
<Cards
:images="images"
#addImage="updateImage($event)"
#showModal="showPopup($event)"
/>
<Card :image="image" v-if="modal" #closeImage="toggleImage($event)" />
</v-main>
</v-app>
</template>
<script>
import Cards from "./components/Cards.vue";
import Card from "./components/Card.vue";
export default {
name: "App",
components: { Cards, Card },
data: () => ({
images: {},
image: {},
modal: false,
}),
methods: {
updateImage: function (updatedImage) {
this.image = updatedImage;
},
showPopup: function (state) {
this.modal = state;
},
toggleImage: function (state) {
this.modal = state;
},
},
};
</script>
Cards.vue
<v-row>
<v-col
v-for="image in images"
:key="image.id"
class="d-flex"
cols="4"
#click="
getImage(image.id);
toggleWindow();
"
>
<v-img :src="image.url" :id="image.id">
<template v-slot:placeholder>
<v-row class="fill-height ma-0" align="center" justify="center">
<v-progress-circular
indeterminate
color="grey lighten-5"
></v-progress-circular>
</v-row>
</template>
</v-img>
</v-col>
</v-row>
</template>
<script>
export default {
name: "Cards",
props: ["images"],
methods: {
getImage(imageId) {
fetch(`https://boiling-refuge-66454.herokuapp.com/images/${imageId}`)
.then((res) => {
if (res.status == 200) {
return res.json();
} else {
throw new Error(res.status);
}
})
.then((data) => {
this.$emit("addImage", data);
});
},
toggleWindow() {
let toggle = true;
this.$emit("showModal", toggle);
},
},
};
</script>
<style></style>
I believe this is because getImage() is an async method. It should return an Promise that resolves itself after the last then(). The last then is the one containing this.$emit("addImage", data);.
In your #click you should then wait for getImage() to be resolved before calling toggleWindow().
So something like #click="async () => { await getImage(image.id); toggleWindow();}".
Related
I have a delete dialog component that when clicking Cancel or Delete nothing happens, not even errors. What am I missing to correctly delete or cancel
<template>
<v-dialog
v-bind="$attrs"
v-on="$listeners"
max-width="500px"
persistent
>
<v-card>
<v-card-title
class="headline"
>
{{ title }}
</v-card-title>
<v-card-text>
{{ message }}
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="grey darken-1"
text
#click="$emit('closeDeleteDialog')"
>
Cancel
</v-btn>
<v-btn
color="primary darken-1"
text
#click="$emit('deleteItem')"
>
Delete
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'DeleteDialog',
props: {
title: {
type: String,
required: true
},
message: {
type: String,
default: ''
}
},
emits: ['closeDeleteDialog', 'deleteItem']
}
</script>
This is how I use my component:
<DeleteDialog
v-model="dialogDelete"
title="Delete location"
message="Are you sure you want to delete this location?"
/>
On the same view as where I import my component I have my methods.
export default {
components: {
DeleteDialog: () => import('#/components/Shared/DeleteDialog'),
},
data: () => ({
locationId: null,
dialog: false,
dialogDelete: false,
})
},
methods: {
deleteItem () {
this.$store.dispatch('deleteFirebaseDoc', { docId: this.locationId, collection: 'locations' })
this.locationId = null
this.dialogDelete = false
},
deleteItemConfirm (item) {
this.locationId = item.docId
},
closeDeleteDialog () {
this.dialogDelete = false
}
}
}
</script>
How can I correctly access my component to delete and item or cancel the dialog?
In the parent component you need to to listen to those events you emit
<DeleteDialog
v-model="dialogDelete"
title="Delete location"
message="Are you sure you want to delete this location?"
#close-delete-dialog="dialogDelete = false" // or call closeDeleteDialog ()
#delete-item="deleteItem()"
/>
I've never used camelCase for events emitting so I'd rather write it like $emit('close-delete-dialog') and $emit('delete-item')
I've already searched what causes this error message to appear but could not really get my head around it in my code. I'm using Nuxt.js with Vuex. I just can't see where am i modifying the store without mutation. Can someone please tell me what am i doing wrong ? This error message appears when i pick some date from the date picker component. This solution works as intended, it's just the error message that bugs me and i dont really want to solve it by turning off the strict mode for store.
Component.vue
<template>
<v-col class="chart">
<v-card>
<v-card-title>Daily beta trend</v-card-title>
<v-row>
<v-col cols="4">
<b-form-group label="From" class="ml-3">
<b-form-datepicker id="datepicker-from" v-model="chartDataFrom" class="mb-2" />
</b-form-group>
</v-col>
<v-col cols="4">
<b-form-group label="To">
<b-form-datepicker id="datepicker-to" v-model="chartDataTo" class="mb-2" />
</b-form-group>
</v-col>
<v-col cols="4">
<b-form-group label="OS Version" class="mr-3">
<b-form-select v-model="selectedOS" :options="OSVersions">
<template #first>
<b-form-select-option :value="null" disabled>
-- Please select OS version --
</b-form-select-option>
</template>
</b-form-select>
</b-form-group>
</v-col>
</v-row>
<v-skeleton-loader
v-if="$fetchState.pending"
class="mx-auto"
max-width="300"
type="card"
/>
<p v-else-if="$fetchState.error">
An error occurred :(
</p>
<div v-else>
<Chart :data="chartData" />
</div>
</v-card>
</v-col>
</template>
<script>
import Chart from '~/components/Trends/Charts/Chart'
export default {
name: 'BetaTrend',
components: {
Chart
},
async fetch () {
await this.$store.dispatch('beta_trend/getChartData')
},
fetchOnServer: false,
computed: {
chartData: {
get () {
return this.$store.state.beta_trend.chartData
},
set (data) {
this.$store.commit('beta_trend/SET_CHART_DATA', data)
}
},
chartDataFrom: {
get () {
return this.$store.state.beta_trend.from
},
set (value) {
this.$store.commit('beta_trend/SET_FROM', value)
this.$fetch()
}
},
chartDataTo: {
get () {
return this.$store.state.beta_trend.to
},
set (value) {
this.$store.commit('beta_trend/SET_TO', value)
this.$fetch()
}
},
OSVersions: {
get () {
return this.$store.state.beta_trend.os
}
},
selectedOS: {
get () {
return this.$store.state.beta_trend.selectedOs
},
set (value) {
this.$store.commit('beta_trend/SET_SELECTED_OS', value)
this.$fetch()
}
}
}
}
</script>
<style scoped>
</style>
the store is defined like this:
export const state = () => ({
chartData: [],
from: null,
to: null,
os: [
'All',
'1803',
'19042'
],
selectedOs: null
})
export const mutations = {
SET_CHART_DATA (state, data) {
state.chartData = data
},
SET_FROM (state, from) {
state.from = from
},
SET_TO (state, to) {
state.to = to
},
SET_SELECTED_OS (state, os) {
state.selectedOs = os
}
}
export const actions = {
async getChartData ({ commit, state }) {
const data = await this.$axios.$get('api/frontend/trend/XXX', {
params: {
from: state.from,
to: state.to,
os: state.selectedOs
}
})
commit('SET_CHART_DATA', data)
if (state.from === null) {
commit('SET_FROM', data.dates[0])
}
if (state.to === null) {
commit('SET_TO', data.dates[data.dates.length - 1])
}
}
}
I fix this issue by using Lodash's cloneDeep method on the get() of my computed option. I do have the state that is deep copied, this way it prevents any mutation of the actual object, and can therefore be modified by the set() in the same way.
The only needed thing is
import { cloneDeep } from 'lodash-es'
export default {
[...]
chartData: {
get () {
return cloneDeep(this.$store.state.beta_trend.chartData)
},
[...]
},
PS: also worth mentioning that JSON.parse(JSON.stringify(object)) is not recommended: https://flaviocopes.com/how-to-clone-javascript-object/#wrong-solutions
I want to send a 'template' prop to a component then render it. If I send a plain HTML it works, but if I send a Vuetify tag like <v-btn>test</v-btn> the template does not get compiled.
I know i shouldn't pass a template via props, but this is a specific case: The parent component works as a "template builder" and the child components works as the "result viewer", so I have to pass the created template to the child so that it can compile and show it.
Here's what I've been trying:
main.js
import Vue from 'vue'
import App from './App.vue'
// Some imports here ...
import vuetify from './plugins/vuetify';
new Vue({
vuetify,
render: h => h(App)
}).$mount('#app')
App.vue
<script>
import Vue from 'vue'
// eslint-disable-next-line
var staticRenderFns = [];
var dynamic = {
props: ['template'],
data: () => ({
templateRender: null,
}),
render(h) {
if (!this.templateRender) {
return h('div', 'loading...');
} else { // If there is a template, I'll show it
return this.templateRender();
}
},
watch: {
// Every time the template prop changes, I recompile it to update the DOM
template:{
immediate: true, // makes the watcher fire on first render, too.
handler() {
var res = Vue.compile(this.template);
this.templateRender = res.render;
// staticRenderFns belong into $options,
// appearantly
this.$options.staticRenderFns = []
// clean the cache of static elements
// this is a cache with the results from the staticRenderFns
this._staticTrees = []
// Fill it with the new staticRenderFns
for (var i in res.staticRenderFns) {
//staticRenderFns.push(res.staticRenderFns[i]);
this.$options.staticRenderFns.push(res.staticRenderFns[i])
}
}
}
},
}
export default {
name: 'App',
data: () => ({
template:`
<v-row>
<v-col>
<v-btn class="pa-2 primary white--text">Test</v-btn>
</v-col>
<v-col>
<v-btn class="pa-2 primary white--text">Test</v-btn>
</v-col>
<v-col>
<v-btn class="pa-2 primary white--text">Test</v-btn>
</v-col>
</v-row>
`,
}),
components:{
dynamic,
},
};
</script>
<template>
<v-app id="app" style="padding-top: 64px;">
<v-app-bar
app
color="blue"
>
<v-btn depressed color="white" class="black--text" click="addBtn">Button</v-btn>
</v-app-bar>
<dynamic :template='template'></dynamic>
</v-app>
</template>
Inside the dynamic component try to render a vue component using the passed template :
var dynamic = {
props: ['template'],
data: () => ({
templateRender: null,
}),
render(h) {
if (!this.template) {
return h('div', 'loading...');
} else { // If there is a template, I'll show it
return h(Vue.component('dynamic-render', {template:this.template}));
}
},
}
Full Example
var dynamic = {
props: ['template'],
data: () => ({
templateRender: null,
}),
render(h) {
if (!this.template) {
return h('div', 'loading...');
} else { // If there is a template, I'll show it
return h(Vue.component('dynamic-render', {
template: this.template
}));
}
},
}
var app = new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
count: 1,
template: `
<v-row>
<v-col>
<v-btn class="pa-2 primary white--text">Test</v-btn>
</v-col>
<v-col>
<v-btn class="pa-2 primary white--text">Test</v-btn>
</v-col>
<v-col>
<v-btn class="pa-2 primary white--text">Test</v-btn>
</v-col>
</v-row>
`,
}),
components: {
dynamic,
},
methods: {
changeContent() {
this.count = this.count + 1
this.template = '';
setTimeout(() => { //simulate loading status
this.template = `<v-col>
<v-btn class="pa-2 primary white--text">Btn ${this.count}</v-btn>
</v-col>`
}, 2000);
}
}
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#5.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-btn depressed color="primary" class="black--text" #click="changeContent">change content</v-btn>
<dynamic :template='template'></dynamic>
</v-app>
</div>
I am trying to pass data to the UserModal. But the issue I am facing here is that the value of
user_clicked field is set when the openuserdialog method runs(checked in console: the value is assigned) but I am not able to pass it as an argument to the modal. Please help me solve the problem.
<v-data-table :items="users" :disable-initial-sort="true" :mustSort="true" hide-actions>
<template slot="items" slot-scope="props">
<td>{{ props.item.file_type.name }}</td>
<td>{{ props.item.created_at | moment }}</td>
<td><a #click="openUserDialog(props.item.id, props.item.user_type)" href='javascript:void(0);' class="details-link"><span class="hidden-xs-only">UserTypes</span><span class="hidden-sm-and-up">User Types</span></a></td>
</template>
</v-data-table>
<v-dialog v-model="userDialog" max-width="1275">
<UserModal :document="user_clicked" />
<div class="text-xs-right">
<v-btn class='vue-file-button text-right' #click="closeUserDialog" >Close</v-btn>
</div>
</v-dialog>
<script>
import UserModal from 'views/users/shortlisted_users.vue';
export default {
components: {
UserModal
},
data: function() {
return {
userDialog: false,
user_clicked: ''
}
}
methods: {
openUserDialog(document_id, user_type) {
this.userDialog = true;
this.user_clicked = user_type;
console.log(this.user_clicked);
},
closeUserDialog(document_id) {
this.userDialog = false;
}
}
</script>
Update 1
openUserDialog(document_id, user_type) {
this.user_clicked = user_type;
this.userDialog = true;
console.log(this.user_clicked);
}
Update 2
<template>
<div>
<v-card id="users-card">
<Users :users="users"></Users>
</v-card>
</div>
</template>
<script>
import 'vue-awesome/icons';
import Icon from 'vue-awesome/components/Icon';
import Users from 'views/user/_user_table.vue';
export default {
components: {
Icon,
Users
},
props: ['document'],
data: () => ({
users: [],
tab_view: 'tab-users-card'
}),
created: function() {
console.log(this.document);
this.fetchUsers(this.document);
},
methods: {
fetchUsers(document) {
this.$axios.get('/my_account/users/document_suggested_users.json', {
params: {
document: document.id
}
})
.then(response => {
this.users = response.data;
})
},
}
};
</script>
The problem is that you are trying to use document in the created handler of the component which is far too early in its life-cycle.
Instead, one approach is to use a watch handler in your UserModal like this:
watch: {
document: function () {
console.log(this.document);
if (this.document) {
this.fetchUsers(this.document);
}
}
}
Try to declare your prop like so:
props: {
document: Object
}
I am having one root component know as App.vue. and another component named as FileUploaderParent.vue. I am calling a dispatch promise to store the data in the store. I am calling dispatch under mounted lifecycle hook.
On the other side, I am trying to access stored user data in the mounted function of a different component. It shows the error of undefined
I know, a work around can be to call same dispatch promise on the mounted function. But, It feels like a hack and the dispatch call is getting redundant.
Here is the code for App.vue:
<template>
<v-app>
<v-navigation-drawer v-model="drawer" app temporary >
<v-img :aspect-ratio="16/9" src="https://image.freepik.com/free-vector/spot-light-background_1284-4685.jpg">
<v-layout pa-2 column fill-height class="lightbox gray--text">
<v-spacer></v-spacer>
<v-flex shrink>
<!-- <div class="subheading">{{this.$store.getters.userData.name}}</div> -->
<!-- <div class="body-1">{{this.$store.getters.userData.email}}</div> -->
</v-flex>
</v-layout>
</v-img>
<v-list>
<template v-for="(item, index) in items">
<v-list-tile :href="item.href" :to="{name: item.href}" :key="index">
<v-list-tile-action>
<v-icon light v-html="item.icon"></v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-html="item.title"></v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</template>
</v-list>
</v-navigation-drawer>
<v-toolbar color="white" light :fixed=true>
<v-toolbar-side-icon #click.stop="drawer = !drawer"></v-toolbar-side-icon>
<v-img :src="require('#/assets/mad_logo.png')" max-width="80px" max-height="41px" />
<v-toolbar-title class="black--text justify-center" > <h1>MeshApp</h1></v-toolbar-title>
<v-spacer></v-spacer>
<v-avatar color="primary">
<!-- <span class="white--text headline">{{this.avatar.slice(0,2)}}</span> -->
</v-avatar>
</v-toolbar>
<v-content style="margin-top: 60px;">
<v-fade-transition mode="out-in">
<router-view></router-view>
</v-fade-transition>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'app',
components: {
},
data() {
return {
avatar: '',
drawer: false,
items: [
{
href: 'home',
router: true,
title: 'home',
icon: 'grid_on',
},
{
href: 'view_volunteers',
router: true,
title: 'View Volunteer',
icon: 'group'
},
{
href: 'profile',
router: true,
title: 'profile',
icon: 'account_circle',
},
{
href: 'logout',
router: true,
title: 'Logout',
icon: 'toggle_off',
}
]
}
},
props: [],
mounted() {
this.$store.dispatch('getUserData').
then(() => {
let findAvatar = this.$store.getters.userData.name.split(" ")
let createAvatar = ''
for(let i = 0; i < findAvatar.length; i++) {
createAvatar = createAvatar + findAvatar[i].slice(0,1)
}
this.avatar = createAvatar
console.log(this.avatar)
// this.$store.dispatch('getUserId', id)
})
}
}
</script>
<style scoped>
v-content {
margin-top: 60px !important;
}
</style>
Here is the code for FileUploaderParent.vue:
<template>
<v-layout class="text-xs-center ">
<v-flex>
<image-input v-model="avatar">
<div slot="activator">
<v-avatar color = "primary" size="150px" v-ripple v-if="!avatar" class=" mb-3">
<h1 class="white--text"><span>{{this.defaultAvatar}}</span></h1>
</v-avatar>
<v-avatar size="150px" v-ripple v-else class="mb-3">
<img :src="avatar.imageURL" alt="avatar">
</v-avatar>
</div>
</image-input>
<v-slide-x-transition>
<div v-if="avatar && saved == false">
<!-- Stores the Image and changes the loader -->
<v-btn class="primary" #click="uploadImage" :loading="saving">Save Avatar</v-btn>
</div>
</v-slide-x-transition>
</v-flex>
</v-layout>
</template>
<script>
import ImageInput from './FileUploaderChild.vue'
export default {
name: 'app',
data () {
return {
defaultAvatar: '',
avatar: null,
saving: false,
saved: false
}
},
mounted() {
this.$store.dispatch('getUserData').
then(() => {
let findAvatar = this.$store.getters.userData.name.split(" ")
let createAvatar = ''
for(let i = 0; i < findAvatar.length; i++) {
createAvatar = createAvatar + findAvatar[i].slice(0,1)
}
this.defaultAvatar = createAvatar
})
},
components: {
ImageInput: ImageInput
},
watch:{
avatar: {
handler: function() {
this.saved = false
},
deep: true
}
},
methods: {
uploadImage() {
this.saving = true
setTimeout(() => this.savedAvatar(), 1000)
},
savedAvatar() {
this.saving = false
this.saved = true
}
}
}
</script>
<style>
</style>
This is how the store looks like:
store.js
actions: {
getUserData(context) {
return new Promise((resolve, reject) => {
axios.get('http://someurl.in/api/v1/users/'+this.state.userId, {
headers: {
'Content-Type': 'application/json'
},
auth: {
username: 'someusername',
password: 'pass'
}
}).
then(response => {
context.commit('storeUserData', response.data.data.users)
resolve(response); // Let the calling function know that http is done. You may send some data back
}).catch(error => {
reject(error);
})
})
}
},
mutations: {
storeUserData(state, data) {
state.userData = data
}
}
This is how the error looks like:
How do i access store data in FileUploaderParent.vue under mounted function?
The code you have written seems right, but if you still need an answer you can use watch on store
this.$store.watch(() => this.$store.state.userData, async () => {
//do your code here
});
How about adding a check that determines if an API call is required:
actions: {
getUserData(context) {
return new Promise((resolve, reject) => {
// Be sure to set the default value to `undefined` under the `state` object.
if (typeof this.state.userData === 'undefined') {
axios
.get('http://someurl.in/api/v1/users/' + this.state.userId, {
headers: {
'Content-Type': 'application/json'
},
auth: {
username: 'someusername',
password: 'pass'
}
})
.then(response => {
context.commit('storeUserData', response.data.data.users);
resolve(response.data.data.users);
})
.catch(error => {
reject(error);
});
}
else {
resolve(this.state.userData);
}
})
}
}
The this.$store.getters refers to a getter in your Vuex store. You have no getters in your Vuex store - at least none can be seen in your sample. (https://vuex.vuejs.org/guide/getters.html)