Dynamically changing state vue - vue.js

For my application I am using Vue-router and store to work together. In my state I am storing a few questions. The content of the questions is saved in my Store like so:
export default new Vuex.Store({
state: {
blabla: [
{
question: 'question1',
answer: false,
givenAnswer: null,
disabled: false,
},
{
question: 'question2',
answer: false,
givenAnswer: null,
disabled: false
}
],
score: null
},
getters: {
getQuestionList: state => {
return state.questions;
}
})
And of course with othe config, but this all works fine. I am getting this data through a Getter in my Vue template by using this code in my Component:
export default {
data() {
return {
questionId: this.$route.params.id - 1
}
},
computed: {
getQuestions () {
return this.$store.getters.getQuestionList
}
}
}
<p>{{ getQuestions[questionId].question }}</p>
Now I want to dynamically mutate the state of every question to disabled:false whenever someone answers the question in order to enable answering the next question. However I can not figure out how to exactly do it.
Can someone put me in the right direction? Thanks!
Edit for clarification: There is a list of 10 questions and when you start, only the first one is enabled at the start. When you answer question 1, question 2 should be enabled (question one also stays enabled), when you answer question 2, question 3 should be enabled, etc.

You can just commit a mutation that sets disabled: false for the specified element:
Cmponent
<button #click="submitQuestion(question.id)"></button>
methods: {
submitQuestion (id) {
// ...
this.$store.commit('ENABLE_QUESTION', id + 1) // passing id of the next question
}
}
Store
state: {
questions: [
{
id: 0,
question: 'question1',
answer: false,
givenAnswer: null,
disabled: false,
},
{
id: 1,
question: 'question2',
answer: false,
givenAnswer: null,
disabled: false
}
]
}
...
mutations: {
ENABLE_QUESTION (state, id) {
let questionIndex = state.questions.findIndex(item => item.id === id)
state.questions[questionIndex].disabled = false
}
}

in your store put this mutation code :
mutations: {
enable (state) {
state.questions.forEach(item => {
item.disabled = false
)}
}
}
and in your component you can commit this mutation easily as follows:
this.$store.commit('enable')

Related

Unlink state form store

I've had a problem
data() {
return {
list: [],
initState: { id: null, data: null }
}
},
computed: {
...mapState({
contacts: (state) => state.ContactBook.Contacts
})
},},
methods: {
setinitState(payload) {
this.initState.data = contacts[this.contactID]
},
How Can I make this.initState static? I need this.initState didnt changes when contacts[this.contactID] changes...
You may use the spread operator like this,
this.initState.data = {...contacts[this.contanctID]}
Spread Operator MDN
state.snapshot = JSON.parse(JSON.stringify(state.Contacts[payload]))

How to create getters and setters for all sub-properties of a Vuex state property efficiently?

I couldn't find the answer anywhere.
Let's say we have Vuex store with the following data:
Vuex store
state: {
dialogs: {
dialogName1: {
value: false,
data: {
fileName: '',
isValid: false,
error: '',
... 10 more properties
}
},
dialogName2: {
value: false,
data: {
type: '',
isValid: false,
error: '',
... 10 more properties
}
}
}
}
Dialogs.vue
<div v-if="dialogName1Value">
<input
v-model="dialogName1DataFileName"
:error="dialogName1DataIsValid"
:error-text="dialogName1DataError"
>
<v-btn #click="dialogName1Value = false">
close dialog
</v-btn>
</div>
<!-- the other dialogs here -->
Question
Let's say we need to modify some of these properties in Dialogs.vue.
What's the best practices for creating a getter and setter for every dialog property efficiently, without having to do it all manually like this:
computed: {
dialogName1Value: {
get () {
return this.$store.state.dialogs.dialogName1.value
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.value', value: value })
}
},
dialogName1DataFileName: {
get () {
return this.$store.state.dialogs.dialogName1.data.fileName
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.fileName', value: value })
}
},
dialogName1DataIsValid: {
get () {
return this.$store.state.dialogs.dialogName1.data.isValid
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.isValid', value: value })
}
},
dialogName1DataIsError: {
get () {
return this.$store.state.dialogs.dialogName1.data.error
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.error', value: value })
}
},
... 10 more properties
And this is only 4 properties...
I suppose I could generate those computed properties programmatically in created(), but is that really the proper way to do it?
Are there obvious, commonly known solutions for this issue that I'm not aware of?
getters can be made to take a parameter as an argument - this can be the 'part' of the underlying state you want to return. This is known as Method-style access. For example:
getFilename: (state) => (dialogName) => {
return state.dialogs[dialogName].data.fileName
}
You can then call this getter as:
store.getters.getFilename('dialogName1')
Note that method style access doesn't provide the 'computed property' style caching that you get with property-style access.
For setting those things in only one central function you can use something like this:
<input
:value="dialogName1DataFileName"
#input="update_inputs($event, 'fileName')">
// ...
methods:{
update_inputs($event, whichProperty){
this.$store.commit("SET_PROPERTIES", {newVal: $event.target.value, which:"whichProperty"})
}
}
mutation handler:
// ..
mutations:{
SET_PROPERTIES(state, payload){
state.dialogName1.data[payload.which] = payload.newVal
}
}
Let me explain more what we done above. First we change to v-model type to :value and #input base. Basically you can think, :value is getter and #input is setter for that property. Then we didn't commit in first place, we calling update_inputs function to commit because we should determine which inner property we will commit, so then we did send this data as a method parameter (for example above code is 'fileName') then, we commit this changes with new value of data and info for which property will change. You can make this logic into your whole code blocks and it will solved your problem.
And one more, if you want to learn more about this article will help you more:
https://pekcan.dev/v-model-using-vuex/

Set data field from getter and add extra computed field

I wanted to set fields inside data using getters:
export default {
data () {
return {
medications: [],
}
},
computed: {
...mapGetters([
'allMedications',
'getResidentsById',
]),
I wanted to set medications = allMedications, I know that we can user {{allMedications}} but my problem is suppose I have :
medications {
name: '',
resident: '', this contains id
.......
}
Now I wanted to call getResidentsById and set an extra field on medications as :
medications {
name: '',
resident: '', this contains id
residentName:'' add an extra computed field
.......
}
I have done this way :
watch: {
allMedications() {
// this.medications = this.allMedications
const medicationArray = this.allMedications
this.medications = medicationArray.map(medication =>
({
...medication,
residentName: this.getResidentName(medication.resident)
})
);
},
},
method: {
getResidentName(id) {
const resident = this.getResidentsById(id)
return resident && resident.fullName
},
}
But this seems problem because only when there is change in the allMedications then method on watch gets active and residentName is set.
In situations like this you'll want the watcher to be run as soon as the component is created. You could move the logic within a method, and then call it from both the watcher and the created hook, but there is a simpler way.
You can use the long-hand version of the watcher in order to pass the immediate: true option. That will make it run instantly as soon as the computed property is resolved.
watch: {
allMedications: {
handler: function (val) {
this.medications = val.map(medication => ({
...medication,
residentName: this.getResidentName(medication.resident)
});
},
immediate: true
}
}

Getting documents with ID from firstore collection

While using Firestore, vuefire, vue-tables-2, I stuck getting document's id.
My data structure is as below.
Here is my code.
<v-client-table :columns="columns" :data="devices" :options="options" :theme="theme" id="dataTable">
import { ClientTable, Event } from 'vue-tables-2'
import { firebase, db } from '../../firebase-configured'
export default {
name: 'Devices',
components: {
ClientTable,
Event
},
data: function() {
return {
devices: [],
columns: ['model', 'id', 'scanTime', 'isStolen'],
options: {
headings: {
model: 'Model',
id: 'Serial No',
scanTime: 'Scan Time',
isStolen: 'Stolen YN'
},
templates: {
id: function(h, row, index) {
return index + ':' + row.id // <<- row.id is undefined
},
isStolen: (h, row, index) => {
return row.isStolen ? 'Y': ''
}
},
pagination: {
chunk: 5,
edge: false,
nav: 'scroll'
}
},
useVuex: false,
theme: 'bootstrap4',
template: 'default'
}
},
firestore: {
devices: db.collection('devices')
},
};
My expectation is devices should id property as vuefire docs.
But array this.devices didn't have id field even if I check it exist it console.
Basically, every document already has id attribute, but it's non-enumerable
Any document bound by Vuexfire will retain it's id in the database as
a non-enumerable, read-only property. This makes it easier to write
changes and allows you to only copy the data using the spread operator
or Object.assign.
You can access id directly using device.id. But when passing to vue-tables-2、devices is copied and lost id non-enumerable attribute.
I think you can workaround using computed property
computed: {
devicesWithId() {
if (!this.devices) {
return []
}
return this.devices.map(device => {
...device,
id: device.id
})
}
}
Then, please try using devicesWithId in vue-tables-2 instead.

Binding an object from checkboxes

I need to bind an object from checkboxes, and in this example, a checkbox is its own component:
<input type="checkbox" :value="option.id" v-model="computedChecked">
Here's my data and computed:
data() {
return {
id: 1,
title: 'test title',
checked: {
'users': {
},
},
}
},
computed: {
computedChecked: {
get () {
return this.checked['users'][what here ??];
},
set (value) {
this.checked['users'][value] = {
'id': this.id,
'title': this.title,
}
}
},
....
The above example is a little rough, but it should show you the idea of what I am trying to achieve:
Check checkbox, assign an object to its binding.
Uncheck and binding is gone.
I can't seem to get the binding to worth though.
I assume you want computedChecked to act like an Array, because if it is a Boolean set, it will receive true / false on check / uncheck of the checkbox, and it should be easy to handle the change.
When v-model of a checkbox input is an array, Vue.js expects the array values to stay in sync with the checked status, and on check / uncheck it will assign a fresh array copy of the current checked values, iff:
The current model array contains the target value, and it's unchecked in the event
The current model array does not contain the target value, and it's checked in the event
So in order for your example to work, you need to set up your setter so that every time the check status changes, we can get the latest state from the getter.
Here's a reference implementation:
export default {
name: 'CheckBoxExample',
data () {
return {
id: 1,
title: 'test title',
checked: {
users: {}
}
}
},
computed: {
computedChecked: {
get () {
return Object.getOwnPropertyNames(this.checked.users).filter(p => !/^__/.test(p))
},
set (value) {
let current = Object.getOwnPropertyNames(this.checked.users).filter(p => !/^__/.test(p))
// calculate the difference
let toAdd = []
let toRemove = []
for (let name of value) {
if (current.indexOf(name) < 0) {
toAdd.push(name)
}
}
for (let name of current) {
if (value.indexOf(name) < 0) {
toRemove.push(name)
}
}
for (let name of toRemove) {
var obj = Object.assign({}, this.checked.users)
delete obj[name]
// we need to update users otherwise the getter won't react on the change
this.checked.users = obj
}
for (let name of toAdd) {
// update the users so that getter will react on the change
this.checked.users = Object.assign({}, this.checked.users, {
[name]: {
'id': this.id,
'title': this.title
}
})
}
console.log('current', current, 'value', value, 'add', toAdd, 'remove', toRemove, 'model', this.checked.users)
}
}
}
}