Updata value of a chart with data in computed part - vue.js

I have to make charts in vue js. But the data I get are in a vuex store. I can get access to them with the computed, but I didn't manage to update the chart with.
Here's the code:
<template>
<v-content>
<v-container grid-list-md text-xs-center>
<v-layout row wrap>
<v-flex v-for="(chart, id) in charts" :key="id" v-bind="{['xs'+chart.size]: true}">
<RedmineChart :name="chart.name" :type="chart.type" :getData="chart.getData"
:refresh="chart.refresh" :chartOptions="chart.chartOptions" :info="chart.info"
:context="chart.context"/>
</v-flex>
</v-layout>
</v-container>
</v-content>
<script>
import {mapGetters, mapState} from 'vuex'
export default {
name: 'globalView',
computed: {
...mapGetters('tickets', {
archiTickets: 'ticketsFromArchi',
}),
},
data() {
return {
temps: [
{
name: 'Assigned / Late (Archi)',
info: '<b>Assigned:</b> Number of <b>open</b> redmine assigned to Groupe Archi SOA</br><b>Late:</b> Number of redmine assigned to Groupe Archi SOA not delivered at due date',
type: 'Number',
refresh: 5,
size: 6,
getData() {
return new Promise((resolve, reject) => {
let res = []
res.push({label: 'Assigned', number: archiTickets.length})
})
}
}
],
}
</script>
When I debug, it says that archoTickets is undefined even with this.archiTickets.
Is there a way to get rid of this problem or should I change the struct of the application?
Thank you

Related

V-autocomplete not updating on user interaction

I am having 3 multi-select elements on a page and one button. On click on the button, I want whatever the value selected on the third i.e last multi-select element, shall get selected on all top two multi-select elements. On clicking on button, i do see the values are getting updated in vuex.state.vModelValues based on the given key, but the updated values are not reflecting on the first 2 multi-select.
Note: I need to get/set the value of the first 2 multi-select from a JSON object based on the key/value pair. I can use an individual array with v-model with first 2 multi-select element, but i don't want to go with that approach as i may have 100 multi-selects element and can't define 100 array in data section. So want to define a JSON which will hold the key/value for each multi-selects and should be reactive. If the value of one entry change, the key that mapped to multi-select shall get updated immediately.
I am not sure what i am doing wrong here. The value are updated on store but the updated value are not get selected on UI. Please help.
Below is my code:
1. STORE
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state() {
return {
vModelValues: {},
};
},
getters: {
getVModelPlaceholder(state) {
return state.vModelValues;
},
getVModelValue(state, key) {
return state.vModelValues[key];
},
},
mutations: {
setVModelValue(state, payload) {
state.vModelValues[payload.key] = payload.value;
},
},
actions: {
setVModelValue(context, payload) {
context.commit("setVModelValue", payload);
},
},
modules: {},
});
APP.VUE
<template>
<v-app>
<v-main>
<HelloWorld />
</v-main>
</v-app>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
HelloWorld,
},
data: () => ({}),
mounted: function () {
this.$store.dispatch("setVModelValue", {
key: "listbox_checkbox_ID1",
value: ["Three"],
});
this.$store.dispatch("setVModelValue", {
key: "listbox_checkbox_ID2",
value: ["Two"],
});
},
};
</script>
HelloWorld.vue
<template>
<v-container>
<div id="container_dummy_PID1" style="height: auto; width: 100%">
<v-autocomplete
id="listbox_checkbox_1"
:items="ranges"
v-model="$store.state.vModelValues['listbox_checkbox_ID1']"
hide-details
multiple
dense
return-object
cache-items
>
</v-autocomplete>
<v-autocomplete
id="listbox_checkbox_2"
:items="ranges"
v-model="$store.state.vModelValues['listbox_checkbox_ID2']"
hide-details
multiple
dense
return-object
cache-items
>
</v-autocomplete>
<v-autocomplete
v-model="selectedValues"
:items="ranges"
hide-details
multiple
dense
required
return-object
>
</v-autocomplete>
<v-btn color="primary" #click="applyToAll()">Apply to All</v-btn>
</div>
</v-container>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
selectedValues: "",
ranges: ["One", "Two", "Three", "Four", "Five"],
};
},
methods: {
applyToAll() {
this.$store.dispatch("setVModelValue", {
key: "listbox_checkbox_ID1",
value: this.selectedValues,
});
this.$store.dispatch("setVModelValue", {
key: "listbox_checkbox_ID2",
value: this.selectedValues,
});
},
},
};
</script>

Error: vuex do not mutate vuex store state outside mutation handlers With uploading image

i'm kinda stuck. i get this error every time i try to update my store. what i'm doing is, i fetch my product and store it. here on upload image section (component) at created fill the image with state to show the already uploaded images, then on each upload change my state via dispatch and mutation. i know it has something to do with created() when i fill my data with state, but don't know how to solve that.
here is my template:
<v-card flat class="pb-4" max-width="450px">
<v-img class="rounded-lg theme__main__color" :src="url"></v-img>
<v-list container inline class="transparent text-center pa-0">
<v-list-item class="px-0">
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__w text_main_color"
:loading="isSelecting"
#click="onChooseClick"
>{{lang.chooseimage}}</v-btn>
</v-list-item>
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__s text_main_color"
#click.prevent="uploadImage"
>{{lang.upload}}</v-btn>
<input
ref="uploader"
class="d-none"
type="file"
accept="image/*"
#change="onFileChanged"
>
</v-list-item>
</v-list-item>
</v-list>
</v-card>
and this is my script:
export default {
data(){
return{
isSelecting: false,
selectedFile: null,
url: null,
placeholder: '/images/placeholder/place-800.png',
section: 'img',
pimages: [],
pId: null
}
},
methods:{
onChooseClick(){
this.isSelecting = true
window.addEventListener('focus', () => {
this.isSelecting = false
}, { once: true })
this.$refs.uploader.click()
},
onFileChanged(e) {
this.selectedFile = e.target.files[0]
this.url = URL.createObjectURL(this.selectedFile)
// do something
},
async uploadImage(){
if(this.notEmpty(this.selectedFile) && this.notEmpty(this.pId)){
const data = new FormData()
data.append('image', this.selectedFile)
data.append('pId', this.pId)
// let response = await this.axiosPost('product/createproimg', data)
this.pimages.push({"url": this.url})
this.url = this.placeholder
this.setEditProductImg(this.pimages)
}
}
},
created(){
this.url = this.placeholder
this.pId = this.editProduct.pId
this.pimages = this.editProduct.images
this.$nuxt.$on('insert',(section)=>{
if(section === 'desc' && !this.pId){
this.pId = this.editProduct.pId
}
})
}
}
and of course my store:
state:
editProduct: {
pId: null,
images: []
}
getter:
editProduct(state){
return state.editProduct
}
mutation:
SET_EDITPRODUCT_IMG(state, img){
state.editProduct.images = img
},
action:
setEditProductImg({commit}, img){
commit('SET_EDITPRODUCT_IMG', img)
},
UPDATE
thanks to #skirtle the above problem has been solved! but got a new same error on something else. as advise by #skirtle used const to mutate my state but get error. to be more clear, my state is an empty array, it will be filled the first time, but i get the error if i even try to change my select!!! let alone send another mutation!! here is the codes:
<template>
<div class="pt-6">
<v-row class="ma-0">
<v-col cols="12" md="12" class="pa-0">
<v-row class="ma-0">
<!-- form 1 -->
<template v-for="(select, index) in selects">
<component
:is="select"
:key="select.name"
v-model="catId"
#changed="addComponent(index)"
:catid="catId"
:selectindex="index"
:pcat="productCat[index]"
:subcat="subCat"
></component>
</template>
<!-- btn -->
<v-col cols="12" sm="6" md="3" class="px-1">
<v-btn width="100%" class="theme__little__color2 text_main_color px-2" #click.prevent="addCatBtn()">{{lang.addcat}}</v-btn>
</v-col>
{{selectedCatArr}}
<!-- btn -->
<addproductbtn :section="section" />
</v-row>
</v-col>
</v-row>
</div>
</template>
<script>
import addproductbtn from '~/components/global/cms/addproductbtn'
import selectcategory from '~/components/global/cms/selectcategory'
export default {
components:{
'addproductbtn': addproductbtn
},
data(){
return{
section: 'cat',
selects: [selectcategory],
catId: 0,
subCat: true,
selectedCatArr: []
}
},
methods:{
addComponent(index){
this.selects.length = index + 1
setTimeout(() => {
this.selects.push(selectcategory)
}, 1);
},
addCatBtn(){
this.goToRedirect('/cms/category/insert', this.$route.path, this.ProductId)
},
async insertCategory(){
const data = {
pId: this.editProduct.pId,
catId: this.catId
}
// let response = await this.axiosPost('product/catupdate', data)
const productCategory = this.selectedCatArr
this.setEditProductCat(productCategory)
}
},
computed:{
productCat(){
return this.editProduct.categories
},
ProductId(){
return this.editProduct.pId
}
},
created(){
this.$nuxt.$on('insert', ()=>{
this.insertCategory()
})
this.$nuxt.$on('nextcat', (subCat)=>{
this.subCat = subCat
})
this.$nuxt.$on('nextpanel', ()=>{
this.insertCategory()
})
this.$nuxt.$on('selectedcat', (selected, index)=>{
delete selected.subCategory
this.selectedCatArr.length = index
this.selectedCatArr.push(selected)
})
}
}
</script>
and my select component:
<template>
<v-col cols="12" sm="6" md="3" class="px-1 text_details_color3" v-if="showCat">
<v-select
return-object
:items="items"
:label="lang.category"
v-model="selected"
#change="emitEvent"
item-text="title"
item-value="id"
outlined></v-select>
{{selected}}
</v-col>
</template>
<script>
export default {
props:['selectindex','catid','pcat','subcat'],
data(){
return{
selected:{},
items:[],
showCat: true
}
},
async fetch(){
// this.items = await this.axiosGet(`categories/${this.catid}/1`)
this.items = [
{id: this.catid + 1, title: this.catid+'title1', subCategory: true},
{id: this.catid + 2, title: this.catid+'title2', subCategory: true},
{id: this.catid + 3, title: this.catid+'title3', subCategory: false},
{id: this.catid + 4, title: this.catid+'title4', subCategory: true}
]
},
methods:{
emitEvent(){
this.$emit('input', this.selected.id)
this.$emit('changed')
$nuxt.$emit('nextcat', this.selected.subCategory)
$nuxt.$emit('selectedcat', this.selected, this.selectindex)
}
},
computed:{
//
},
created(){
},
mounted(){
this.selected = this.pcat
this.showCat = this.subcat
}
}
</script>
I believe the problem is this line:
this.pimages.push({"url": this.url})
The array this.pimages is the same array that's inside the store state and by calling push you're modifying it outside the store.
There are a couple of ways you could fix this.
One way would be to perform the push inside a mutation, e.g. by having an ADD_EDITPRODUCT_IMG mutation:
mutation:
ADD_EDITPRODUCT_IMG(state, img){
state.editProduct.push(img)
},
You'd then call that in much the same way as with your current mutation, except that you'd just pass it the new image to add rather than passing the full array.
An alternative approach that is a bit closer to what you currently have would be to take a copy of the array rather than modifying the original:
const newImages = [...this.pimages, {"url": this.url}]
this.setEditProductImg(newImages)
Using this approach you wouldn't need any changes to your existing store.
Update:
I also suggest making pimages a computed property. There doesn't seem to be any good reason to 'copy' the data out of the store state in a created hook.

VueJS/Vuetify: Change colwidth with animation/transition

this is my app component:
<template>
<v-app id="inspire">
<v-main>
<v-container fluid>
<v-row>
<v-col :cols="dynamicCol">
<worldmap></worldmap>
</v-col>
</v-row>
</v-container>
</v-main>
</v-app>
</template>
<script>
import WorldMap from "./components/WorldMap";
export default {
props: {
source: String
},
components: {
worldmap: WorldMap
},
computed: {
changeDynamicCol() {
return this.$store.state.lineData.data.length;
}
},
watch: {
changeDynamicCol(value) {
if (value > 0) {
this.dynamicCol = 9;
}
}
},
data: () => ({
dynamicCol: 12,
drawer: null
}),
created() {
this.$vuetify.theme.dark = true;
}
};
</script>
<style scoped></style>
My App starts with an empty array and when I receive data, the first container small shrink from a width of 12 to 9. The code itself works fine, however the chart inside reacts to changes in the viewport, so get´s currently just cut off. In general it looks much better when this is animated.
Does anyone know if and how I can change the width of v-col with a transition or other animation?
I think you use watchers in wrong way.
Here I show how I see your case, but I don't sure if it works fine.
If you have more question, I can answer in comments
<template>
<v-app id="inspire">
<v-main>
<v-container fluid>
<v-row>
<v-col :cols="columnCount">
<worldmap></worldmap>
</v-col>
</v-row>
</v-container>
</v-main>
</v-app>
</template>
<script>
import WorldMap from "./components/WorldMap";
import { mapState } from 'vuex'
export default {
props: {
source: String
},
components: {
worldmap: WorldMap
},
computed: {
...mapState({
lineData: state => state.lineData.data.length
}),
columnCount() {
if (this.lineData > 0) {
return 9
}
return 12
}
},
data: () => ({
drawer: null
}),
};
</script>

Call to database after successful login is not being made

I'm having difficulty in getting an axios call to my database being initiated after a user has logged into my SPA.
A route (gaplist/3) brings the user to a page (gaplist.vue) which
detects if the user is logged in or not.
If not logged in, a login form is presented.
Once the entered username/password combo is accepted, the user is "pushed" to the same page (gaplist/3)
Here, the logged in status is detected and - this is where it all falls down - a call to the database would return a bunch of records associated with the user and the parameter "3".
Unfortunately, the last step doesn't fully happen. The logged in status is detected, but the database call is not made. Only if I refresh the page is the call made and the results presented.
What concept am I not grasping here?
Thanks, Tom.
My code is as follows:
GapList.vue (route: gaplist/3)
<template>
<v-content>
<v-container fluid fill-height>
<v-layout justify-center>
<v-flex xs12 sm6>
<h1>Production and sale of produce</h1>
<v-card flat>
<div v-if="isIn">
<p v-for="(card, id) in cards">{{card.product}}</p>
<logout-button></logout-button>
</div>
<div v-else>
<gap-login :gapid=gapid></gap-login>
</div>
</v-card>
</v-flex>
</v-layout>
</v-container>
</v-content>
</template>
<script>
import GapLogin from '../components/gap/GapLogin';
import LogoutButton from '../components/gap/LogoutButton'
export default {
name: 'GapList',
components: {
GapLogin,
LogoutButton
},
data () {
return {
gapid: this.$route.params.id,
cards: [],
lang: this.$i18n.locale,
bNoRecords: false,
}
},
created(){
this.loadCrops(this.gapid,this.lang)
},
computed: {
isIn : function(){ return this.$store.getters.isLoggedIn},
},
methods: {
loadCrops(gapid,lang){
var vm = this;
if (this.isIn){
axios.get('/gapcroplist/' + gapid)
.then(function (resp) {
vm.cards = resp.data;
})
.catch(function (resp) {
vm.bNoRecords = true;
});
}
},
}
}
</script>
GapLogin.vue
<template>
<div class="formdiv">
<v-layout justify-center>
<h3>Login</h3>
<v-card flat>
<v-alert
v-if="loginError"
:value="true"
type="error"
transition="scale-transition"
dismissible
>
You didn't enter correct information
</v-alert>
<v-form class="login" #submit.prevent="login">
<v-text-field
v-model="form.email"
type="email"
label="Email"
required
autofocus
></v-text-field>
<v-text-field
v-model="form.password"
type="password"
label="Password"
required
></v-text-field>
<v-btn
type="submit"
>Login in </v-btn>
</v-form>
</v-card>
</v-layout>
</div>
</template>
<script>
export default {
name: "GapLogin",
props: ['gapid'],
data() {
return {
form: {
email: null,
password: null
},
loginError:false
}
},
methods: {
login: function () {
this.loginError = false
this.$store.dispatch('login', this.form)
.then(() =>
{this.$router.push({path: '/gaplist/' + this.gapid})
})
.catch(err => {
this.loginError = true
}
)
},
}
}
</script>
Updated answer:
created hook will not be called again. Using updated will result in an error as well, as you would trigger another update and have an endless loop.
Instead of pushing to same route I would suggest that you emit a completed event:
In your login method in then instead of $router.push:
this.$emit('completed')
And register the event on the gap-login-component:
<gap-login #completed="completed" :gapid=gapid></gap-login>
And add that method to the GapList.vue-file:
completed () {
this.loadCrops(this.gapid, this.lang)
}
You are using axios in a global context, but it doesn't exists, it seems.
Try using this.$axios:
this.$axios.get('/gapcroplist/' + gapid)

Vue emmitting component property appears to skip parent

I've been stuck on this for a long time now and I can't find anything remotely similar to this problem online.
Context: Simple restaurant review website.
My application needs to be able to take webcam photos using WebRTC or upload photos from storage. I built a component to do this called AddImageDialog that lets the user select either webcam or upload a stored image by storing the image URL in prop on this component and then emitting it. The parent then handles where to place the image.
One part of my program that requires this is the NewReview component, that allows 5 photos to be uploaded, all using the same AddImageComponent.
There is a page for users to upload a new restaurant They may also leave a review on this page, so the Review component with the AddImageDialog is imported onto this page. This page must also allow the user to upload an official photo for the venue, so I have reused my AddImagedDialog on this page.
The problem... When uploading review photos (on the new restaurant page) whenever uploading via webcam, everything works fine but when uploading a stored image, the image seems to be emmitted but skips the NewReview parent component and jumps right back to the grandparent - NewRestaurant.
AddImageDialog.vue
<template>
<div>
<v-layout row justify-center>
<v-dialog v-model="thisShowDialog" max-width="550px">
<v-card height="350px">
<v-card-title>
Take a new photo or choose an existing one...
</v-card-title>
<v-card-text>
<div>
<!-- Add a new photo option -->
<div class="clickBox left" #click="showCamera = true">
<v-container fill-height>
<v-layout align-center>
<v-flex>
<v-icon size="40pt" class="enlarge">add_a_photo</v-icon>
</v-flex>
</v-layout>
</v-container>
</div>
<!-- Add photo from library -->
<div class="clickBox right" #click="uploadImage">
<v-container fill-height>
<v-flex>
<input type="file" class="form-control" id="file-input" v-on:change="onFileChange">
<v-icon size="40pt" class="enlarge">photo_library</v-icon>
</v-flex>
</v-container>
</div>
</div>
</v-card-text>
</v-card>
</v-dialog>
</v-layout>
<Camera
:showCamera.sync="showCamera"
:photoData.sync="thisPhotoData">
</Camera>
</div>
</template>
<script>
import Camera from '#/components/Camera'
export default {
props: ['showDialog', 'photoData'],
data () {
return {
thisShowDialog: false,
showCamera: false,
thisPhotoData: null
}
},
methods: {
uploadImage () {
var fileUpload = document.getElementById('file-input') // createElement('input', {type: 'file'})
fileUpload.click()
},
onFileChange (event) {
let files = event.target.files || event.dataTransfer.files
if (!files.length) {
return
}
this.createImage(files[0])
this.thisShowDialog = false
this.thisShowDialog = false
},
createImage (file) {
let reader = new FileReader()
reader.onload = e => {
alert('reader loaded')
this.thisPhotoData = e.target.result
}
reader.readAsDataURL(file)
}
},
watch: {
showDialog () {
this.thisShowDialog = this.showDialog
},
thisShowDialog () {
this.$emit('update:showDialog', this.thisShowDialog)
},
showCamera () {
// when hiding the camera, leave this component too.
if (!this.showCamera) {
this.thisShowDialog = false
}
},
thisPhotoData () {
alert('emmiting from add image comp')
this.$emit('update:photoData', this.thisPhotoData)
}
},
components: {
Camera
}
}
</script>
Sorry to post so much code, but I literally have no idea what's causing this problem.
You will notice I have tried to get both webcam and image uploads to work in the same way by assigning to a `photoData' variable and emitting it whenever it's been changed. I'm confused as to why they behave differently.
NewReview.vue
(this is the component being skipped by stored imaged uploads)
<template>
<!-- LEAVE A REVIEW -->
<v-expansion-panel class="my-4" popout>
<v-expansion-panel-content expand-icon="mode_edit">
<div slot="header">Been here before? Leave a review...</div>
<v-card class="px-3">
<v-alert :value="isLoggedIn" type="warning">
You are not logged in. Log in.
</v-alert>
<v-form v-model="validReview">
<!-- Title -->
<v-text-field
name="title"
label="Review Title"
v-model="thisReview.title"
:rules="reviewTitleRules">
</v-text-field>
<!-- Body -->
<v-text-field
name="body"
label="Review Body"
multi-line
v-model="thisReview.reviewBody"
:rules="reviewBodyRules">
</v-text-field>
<v-card-actions>
<!-- Submit -->
<v-btn v-if="canSubmit" :disabled="!validReview" class="primary" #click="submitReview">Submit</v-btn>
<!-- Select Rating -->
<star-rating-input
class="ratingSelectStyle"
:star-size="25"
active-color="#E53935"
v-model="thisReview.reviewRating"
:show-rating="false">
</star-rating-input>
<v-spacer />
<!-- Add and View 5 review images -->
<div v-for="i in 5" :key="i" #click="addImage(i-1)">
<review-image
:photoData="reviewPhotos[i-1]"
:id="i"
></review-image>
</div>
<!-- Add Images Component -->
<AddImageDialog
:showDialog.sync="showCamera"
:photoData.sync="photoData"
></AddImageDialog>
</v-card-actions>
</v-form>
</v-card>
</v-expansion-panel-content>
</v-expansion-panel>
</template>
<script>
import RestaurantService from '../services/RestaurantService'
import ReviewImage from '#/components/ReviewImage'
import StarRatingInput from 'vue-star-rating'
import {mapGetters} from 'vuex'
import AddImageDialog from '#/components/AddImageDialog'
export default {
props: ['review', 'restaurantID'],
data () {
return {
showCamera: false,
photoData: '',
reviewPhotos: ['', '', '', '', ''],
currentReviewPhoto: 0,
thisReview: this.review || {
title: '',
reviewBody: '',
reviewRating: 5,
user: 'Anon'
},
validReview: true,
reviewTitleRules: [
s => !!s || 'Title is required',
s => s.length >= 5 || 'Title must be at least 5 characters'
],
reviewBodyRules: [
s => !!s || 'Body is required',
s => s.length >= 10 || 'Body must be at least 10 characters'
]
}
},
methods: {
addImage (i) {
this.currentReviewPhoto = i
this.showCamera = true
// the rest of this is handled by watching for photodata to be changed by the camera component.
},
setName () {
this.thisReview.user = this.$store.state.isLoggedIn ? this.$store.state.user.name : 'Anon'
},
submitReview: async function () {
try {
await RestaurantService.submitReview(this.restaurantID, this.thisReview).then(res => {
if (res.status === 200) {
this.$store.dispatch('setNoticeText', 'Review Submitted')
}
})
} catch (err) {
if (err.response.status === 404) {
// console.log('404 - Failed to submit')
this.$store.dispatch('setNoticeText', '404 - Failed to submit review')
} else {
this.$store.dispatch('setNoticeText', 'An unexpected problem has occured. [' + err.response.status + ']')
}
}
}
},
computed: {
...mapGetters({isLoaded: 'isLoaded'}),
canSubmit () {
if (this.restaurantID) {
return true
} else {
return false
}
},
isLoggedIn () {
if (this.isLoaded && !this.$store.state.isLoggedIn) {
return true
} else {
return false
}
}
},
watch: {
thisReview: {
handler () {
this.$emit('update:review', this.thisReview)
},
deep: true
},
isLoaded () {
if (this.isLoaded) {
this.setName()
}
},
photoData () {
if (this.photoData !== '') {
this.reviewPhotos[this.currentReviewPhoto] = this.photoData
this.photoData = ''
}
}
},
components: {
StarRatingInput,
ReviewImage,
AddImageDialog
},
mounted () {
this.setName()
}
}
</script>
This works by defining 5 images for a review and an array of 5 strings, then depending on what image was selected, assigning the emmitted image from AddImageDialog to the appropriate array by watching for photo data. As mention, this watcher function will work with the webcam, but is skipped when uploading images.
I'm not even sure if whatever is causing this problem is in this code, but any suggestions would be greatly appreciated :)