So I have a computed property which is an object of a type which I have defined in my Vuex Store. I'm trying to use this property on my component with v-model. I have defined getters and setters for this property and I'm pretty sure I have configured everything correctly unless I have overlooked something. I tried console logging inside of the setter to see if it ever fires up but nothing gets logged in the console so it isn't being called at all. Any help would be appreciated
code for Store:
import { Module, VuexModule, getModule, Mutation, Action } from 'vuex-module-decorators'
import { websocket } from 'src/boot/socket.io-client'
import store from 'src/store'
import { DataTablePagination } from '../types'
import { Course } from './types'
import { MessageModule } from 'src/store/message/index'
import { Notify } from 'quasar'
import { validate } from "class-validator"
export { Course } from './types'
export { DataTablePagination } from '../types'
export interface CourseState {
pagination: DataTablePagination
courses: Course []
filter: string,
disabled: boolean,
selected: Course [],
active: boolean,
editCourseOpened: boolean,
addCourseOpened: boolean,
addCourseData: Course,
editCourseData: Course,
addCourseDisabled: boolean
}
@Module({
name: 'course',
namespaced: true,
dynamic: true,
store
})
class CourseClass extends VuexModule implements CourseState {
public pagination: DataTablePagination = {
descending: false,
rowsNumber: 0,
rowsPerPage: 10,
page: 1,
sortBy: 'name'
}
public courses: Course [] = []
public filter = ''
public disabled = true
public selected: Course [] = []
public active = true
public editCourseOpened = false
public addCourseOpened = false
public addCourseData = new Course()
public editCourseData = new Course()
public addCourseDisabled = true
@Mutation
SET_ADDCOURSEDISABLED(disabled: boolean) {
this.addCourseDisabled=disabled
}
@Mutation
SET_EDITCOURSEDATA(data: Course) {
this.editCourseData.copy(data)
}
// public SET_ADDCOURSEDATA(payload: { key: string, value: number | string }) {
// const { key, value } = payload
// if (Object.prototype.hasOwnProperty.call(this, key)) {
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// (this as any)[key] = value
// }
// }
@Mutation
SET_ADDCOURSEDATA(data: Course) {
this.addCourseData.copy(data)
}
@Mutation
SET_EDITCOURSEOPENED(opened: boolean) {
this.editCourseOpened=opened
}
@Mutation
SET_ADDCOURSEOPENED(opened: boolean) {
this.addCourseOpened=opened
}
@Mutation
SET_ACTIVE(active: boolean) {
this.active=active
}
@Mutation
SET_PAGINATION(pagination: DataTablePagination) {
this.pagination=pagination
}
@Mutation
SET_SELECTED(selected: Course []) {
this.selected=selected
}
@Mutation
SET_FILTER(filter: string) {
this.filter=filter
}
@Mutation
SET_COURSES(courses: Course []) {
this.courses=courses
}
@Mutation
SET_DISABLED(disabled: boolean) {
this.disabled=disabled
}
@Action
public ValidateAddCourseData() {
validate(this.addCourseDisabled).then(errors => {
console.log(errors)
if(errors.length) {
this.SET_ADDCOURSEDISABLED(true)
} else {
this.SET_ADDCOURSEDISABLED(false)
}
})
}
@Action
public async addCourse(input: Course) {
websocket.emit('query', `mutation {
createCourse (
course: {
code: "${input.code}"
name: "${input.name}"
creditHours: ${input.creditHours}
numberOfLabs: ${input.numberOfLabs}
contactHours: ${input.contactHours}
chargeableCredits: 0
}
) {
ok
message
}
}`, (response: {
errors: any
data: {
createAcademicProgram: {
ok: boolean
message: String
}
}
}) => {
if(response.data) {
this.fetchCourses()
Notify.create({
timeout: 3000,
position: 'center',
color: 'primary',
message: 'Course Added Successfully'
})
this.SET_ADDCOURSEOPENED(!this.addCourseOpened)
}
else {
MessageModule.newMessage({title: 'Error: Addition Failed', icon: 'error', message: 'Addition failed. Ensure that the course code you have entered is unique or contact your system administrator'})
}
})
}
@Action
public async deleteCourse(input: Course) {
websocket.emit('query', `mutation {
deleteCourse (
courseId: "${input.id}"
) {
id
ok
message
}
}`, (response: {
errors: any;
data: {
deleteCourse: {
id: any;
ok: boolean;
message: String
}
}
}) => {
if(response.data) {
this.fetchCourses()
Notify.create({
timeout: 2000,
position: 'center',
color: 'primary',
message: 'Course Deleted Successfully'
})
}
else {
MessageModule.newMessage({ title: 'Deletion Failed', icon: 'error', message: 'Deletion Failed. Ensure that no course instances are still tied to this course or contact your system administrator' })
}
})
}
@Action
public async editCourse(input: Course) {
websocket.emit('query', `mutation {
updateCourse (
course: {
id: "${input.id}"
code: "${input.code}"
name: "${input.name}"
creditHours: ${input.creditHours}
numberOfLabs: ${input.numberOfLabs}
contactHours: ${input.contactHours}
chargeableCredits: 0
}
) {
id
ok
message
}
}`, (response: {
errors: any
data: {
updateCourse: {
id: string
ok: boolean
message: string
}
}
}) => {
if(response.data) {
this.fetchCourses()
this.SET_SELECTED([input])
this.SET_EDITCOURSEOPENED(!this.editCourseOpened)
Notify.create({
timeout: 3000,
color: 'primary',
message: 'Course Updated Successfully',
position: 'center'
})
}
else {
MessageModule.newMessage({ title: 'Update Failed', icon: 'error', message: 'Update Failed. Ensure that all course codes are unique or contact your system administrator'})
}
})
}
@Action
public async fetchCourses() {
const order = this.pagination.sortBy !== null ? `order: {
by: ${this.pagination.sortBy}
dir: ${this.pagination.descending ? 'DESC' : 'ASC'}
}` : ''
websocket.emit('query', `{
courses(
page: {
skip: ${(this.pagination.page - 1) * this.pagination.rowsPerPage},
first: ${this.pagination.rowsPerPage}
}
filter: {
ilike: {
name: "${this.filter}"
}
}
${order}
) {
pagination {
total
listTotal
}
list {
id
code
name
creditHours
numberOfLabs
contactHours
}
}
}`, (response: {
errors: any
data: {
courses: {
list: Course[]
pagination: {
total: number
listTotal: number
}
}
}
}) => {
this.SET_COURSES(response.data.courses.list)
this.pagination.rowsNumber = response.data.courses.pagination.total
})
}
}
export const CourseModule = getModule(CourseClass)
Code for component:
<template>
<q-dialog v-model="isOpened" :bordered="true">
<q-card style="width: 50vw;">
<q-toolbar class="bg-grey-5 text-center">
<q-toolbar-title>Add Course</q-toolbar-title>
<q-btn flat round dense icon="close" v-close-popup />
</q-toolbar>
<q-card-section class="col">
<q-input v-model.trim="course.name" label="Name" type="object"
:rules="[
val => val.length > 0 || 'Required field'
]"/>
<q-input v-model="course.code" label="Code"
:rules="[
val => val.length > 0 || 'Required field'
]"/>
<q-input v-model.number="course.creditHours" label="Credit Hours" type="number"
:rules="[
val => val >= 0 || 'No negatives allowed'
]"/>
<q-input v-model.number="course.numberOfLabs" label="Number of Labs" type="number"
:rules="[
val => val >= 0 || 'No negatives allowed'
]"/>
<q-input v-model.number="course.contactHours" label="Contact Hours" type="number"
:rules="[
val => val >= 0 || 'No negatives allowed'
]"/>
<q-card-actions align="right">
<q-btn color="primary" :disable="disabled" @click="submit();">
<q-icon name="save" />
<q-tooltip
anchor="top middle"
self="bottom middle"
:offset="[10, 10]"
>
<strong>Add Course</strong>
</q-tooltip>
</q-btn>
<q-btn color="red" @click="toggle()" icon="cancel">
<q-tooltip
anchor="top middle"
self="bottom middle"
:offset="[10, 10]"
>
<strong>Cancel</strong>
</q-tooltip>
</q-btn>
</q-card-actions>
</q-card-section>
</q-card>
</q-dialog>
</template>
<script lang="ts">
import { Vue, Component, Watch } from 'vue-property-decorator'
import { CourseModule, Course } from 'src/store/course/index'
@Component
export default class AddCourse extends Vue {
//private course: Course = new Course()
//private disabled = true
get disabled() {
return CourseModule.addCourseDisabled
}
get course() {
return CourseModule.addCourseData
}
set course(args: any) {
console.log(args)
//CourseModule.SET_ADDCOURSEDATA(newValue)
}
get isOpened() {
return CourseModule.addCourseOpened
}
set isOpened(newValue: boolean) {
CourseModule.SET_ADDCOURSEOPENED(newValue)
}
toggle() {
CourseModule.SET_ADDCOURSEDATA(this.course)
this.isOpened = !this.isOpened
// if(this.isOpened) {
// this.getdisabled()
// }
// else {
// this.course.clear()
// }
}
submit() {
CourseModule.addCourse(this.course)
//this.toggle()
}
// getdisabled() {
// validate(this.course).then(errors => {
// if(errors.length) {
// this.disabled=true
// } else {
// this.disabled=false
// }
// })
// return true
// }
}
</script>
As you've already noted, the standard solution here would be to have a computed property for each form field, with get
and set
for each one passing through to the store.
One viable alternative would be to use a Proxy
to intercept the setting of the object properties. I don't know how to write this in TypeScript but that shouldn't detract from the core idea:
const store = new Vuex.Store({
state: {
course: {
name: 'Algebra',
teacher: 'Smith'
}
},
mutations: {
course (state, course) {
state.course = course
}
}
})
new Vue({
el: '#app',
store,
computed: {
course () {
const store = this.$store
const course = store.state.course
return new Proxy(course, {
set (obj, key, value) {
store.commit('course', { ...obj, [key]: value })
return true
}
})
}
}
})
<script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script>
<script src="https://unpkg.com/vuex@3.1.1/dist/vuex.js"></script>
<div id="app">
<input v-model="course.name">
<br>
<input v-model="course.teacher">
<br>
<p>{{ course.name }}</p>
<p>{{ course.teacher }}</p>
</div>
If you need to support IE then you won't be able to use Proxy
but it should be possible to implement something similar using standard JavaScript properties. The idea is much the same as for the proper Proxy
but you'd have to explicitly create get
and set
handlers on the 'proxy' object.