VueJS - use v-model with computed property? - javascript

I try to use a v-select input that uses the data I receive from my Vuex store. I have to use a computed property, since my data gets passed from an API.
My template looks like this.
<template>
<v-card v-if="isMounted">
<v-select
v-model="chartData.selected"
:items="chartData.items"
label="Select Item"
multiple
>
</v-select>
{{chartData.selected}}
</v-card>
</template>
<script>
import {mapState} from "vuex"
export default {
data: function () {
return {
isMounted: false,
value: []
}
},
computed: {
...mapState(["clusterValues"]),
chartData() {
return {
items: this.clusterValues.data,
selected: this.clusterValues.data,
}
}
}
}
</script>
I binded this computed property to v-model. However it does not update accordingly. I guess it does not work out with a computed property. (Vue.js - Input, v-model and computed property)
v-model="value". This works, but I it does not allow me to start with every item selected.
Starting like this does not work out: value: this.$store.state.clusterValues
How can I solve this problem?

Related

v-for with model vuejs

I need to execute a v-for, however I do not know how to add a v-model for each different component inside of v-for;
<template>
<ProfilePasswordField
v-for="(item, index) in profilePasswordItems"
:key="index"
:profilePasswordItem="item"
v-model="???"
>
</template>
This v-for will always be three items and I want to name the v-model's as: ['passaword', 'newPassword', confirmNewPassword']
How can I add those names dinamically for the v-model inside v-for?
I tried to do a list inside data() but it did not work. Something like that:
data (){
return{
listPassword: ['passaword', 'newPassword', 'confirmNewPassword']
}
},
methods: {
method1 () {
console.log(this.passaword)
console.log(this.newPassword)
console.log(this.confirmNewPassword)
}
}
The v-model directives cannot update the iteration variable itself therefore we should not use a linear array item in for-loop as the v-model variable.
There is another method you can try like this-
In the parent component-
<template>
<div>
<ProfilePasswordField
v-for="(item, index) in listPassword"
:key="index"
:profilePasswordItem="item"
v-model="item.model"
/>
<button #click="method1()">Click to see changes</button>
</div>
</template>
<script>
export default {
name: "SomeParentComponent",
data() {
return {
listPassword: [
{ model: "passaword" },
{ model: "newPassword" },
{ model: "confirmNewPassword" },
],
};
},
methods: {
method1() {
this.listPassword.forEach((item) => {
console.log(item.model);
});
},
},
}
</script>
And in your ProfilePasswordField you can emit the input event to listen to the respected changes in v-model binding. For example, the ProfilePasswordField component could be like this-
<template>
<v-text-field :value="value" #input="$emit('input', $event)"/>
</template>
<script>
export default {
name: "ProfilePasswordField",
props: {
value: {
required: true
}
}
</script>
In the parent component's console, you should see the changes made by the child component.

VUE change value only after clicking submit

I have a code like this. In this code, a user wants to change the current name. The current name is also displayed at top of the page.
// template
<div>{{ currentUser.name }}</div>
<v-text-field
required
v-model="currentUser.name"
class="mb-3"
></v-text-field>
<v-btn>submit</v-btn>
//script
data() {
currentUser: null
},
methods: {
getUser(id) {
//my GET method here
},
updateUser() {
//my PUT method here
}
The data is from an API. The current v-text-field is filled with the current name. Right now, if I change the value in the text field, the div value also changes. How to make it change only when the user has already clicked (let's say) a submit button and the process succeed?
This may work fine
<template>
<div>
<div>{{ currentUser.name }}</div>
<v-text-field required class="mb-3" ref="textField"></v-text-field>
<button #click="updateUsername">update user's name</button>
</div>
</template>
<script>
export default {
data() {
return {
currentUser: {
name: '',
},
}
},
methods: {
updateUsername() {
this.currentUser.name = this.$refs.textField.internalValue
},
},
}
</script>
You could also use a debounce, store it in another state but having to use a $refs is okay here.
Also, I'm not a Vuetify user, hence I'm not sure what all of those value are about but you have some nice choice overall.
Adding on to Kissu's answer, if you wish to change the value on blur (when you click away), you have to do the following.
Since Vuetify does not provide a lazy prop to only allow value update on change event, you have to do it yourself. Use the :value prop and bind it to a computed property and provide a getter setter to the computed property.
This will only trigger the change on blur, when you click away from the input, or press enter or press tab.
<template>
<div>{{ currentUserName }}</div>
<v-text-field
required
:value="currentUserName"
#change="onNameChange"
class="mb-3"
></v-text-field>
</template>
<script>
...
methods: {
onNameChange(event) {
this.currentUserName = event.target.value;
}
}
computed: {
currentUserName: {
get() {
return this.currentUser.name
},
set(newName) {
this.currentUser.name = newName;
}
}
}
...
</script>
<div>{{ currentUser.name }}</div>
<v-text-field
required
:value="currentUser.name"
#input="nameChanged" //here maybe #change="xxx"
class="mb-3"
></v-text-field>
<v-btn #click="updateUser">submit</v-btn>
//script
data() {
currentUser: null
},
methods: {
nameChanged(e) {
console.log(`e`,e) //The value is based on the printed value
this.tempName = e
},
updateUser() {
this.currentUser.name = this.tempName
}
}

How to pass old value to setter if nothing is changed in computed property vuejs?

I'm learning to use Vuejs so don't mind me!
I have set getter and setter inside computed property and use it inside form with v-model. My problem is, if I change something inside the v-text-field then I can make patch request but if I don't change anything and leave it the value I got from the state then I can't make the patch request as it said the field may not be null.
How can I leave default value (value I get from state) to the v-text-field and be able to make patch request if i don't want to change anything inside the field.
my vue component.vue
<template>
<div id="update-post">
<v-dialog v-model="updatePostModal" max-width="600">
<v-card class="px-5 py-5">
<v-form ref="form" v-on:submit.prevent="update">
<v-text-field
type="text"
v-model="title" <---
label="Title"
required
></v-text-field>
</v-form>
<v-card-actions>
<v-btn color="green darken-1 white--text" #click="update">
Update
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { getAPI } from "../axios-base";
export default {
name: "UpdatePostModal",
data() {
return {
updateTitle: null,
};
},
computed: {
title: {
get() {
var title = this.$store.state.APIData.postTitle;
return title;
},
set(value) {
this.updateTitle = value;
},
},
},
methods: {
update: function() {
var id = this.$store.state.APIData.postID;
getAPI
.patch(
`/api/posts/${id}/`,
{
title: this.updateTitle,
},
{
headers: {
Authorization: `Bearer ${this.$store.state.accessToken}`,
},
}
)
},
};
</script>
It seems like you have a few mistakes. First one, in here:
.patch(
`/api/posts/${id}/`,
{
title: this.updateTitle,
},
You are using the variable defined in data in your request. You should instead use the computed property so, it should be:
.patch(
`/api/posts/${id}/`,
{
title: this.title,
},
Next, the way you are using the state is also not right. If you are reading the computed property from the state you should always set it via the state as well. Otherwise, you'd end up with some unexpected behavior as your app grows. In order to do that you can do something like this:
get() {
// read from the state
},
set(value) {
// create an action to update the post title in the state
store.dispatch(
'updatePostTitle',
value
);
}
If you still don't want to do this, as a workaround you can address your problem like this (keeping in mind to fix the first issue mentioned above):
computed: {
title: {
get() {
return this.updateTitle || this.$store.state.APIData.postTitle;
},
set(value) {
this.updateTitle = value;
},
},
},

Vuejs: How to make this if else as Boolean?

I have a component with buttons that show some elements (v-autocomplete) when we click on them, but I should make it with a Boolean to simplify the code, how can I do that?
Because actually, it adds the index of the item in one array in showCompetence state, but I just would like a Boolean on each index Basically at the "openCompetence" function in "Methods".
import { mapGetters, mapActions } from "vuex";
export default {
name: "SkillCvCard",
data() {
return {
selectedCompetence: []
}
},
updated() {
this.addSelectSkill(this.userCompetences.competences.list);
console.log(this.selectedSkills)
},
props: {
userCompetences: {
type: Array
},
showCompetence: {
type: Array
}
},
computed: {
...mapGetters(["selectedSkills"]),
console: () => console,
},
methods: {
...mapActions(['addSelectSkill']),
openCompetence(index) {
if (this.showCompetence.includes(index)) {
console.log("close")
this.showCompetence.splice(this.showCompetence.indexOf(index), 1)
} else {
this.showCompetence.push(index)
console.log("open")
}
console.log(this.showCompetence)
}
}
about the template I will just add the minimum I hope it will be ok:
The first is where we click, to launch the "openCompetence" function.
<div v-for="(competences, index) of userCompetences.competences" :key="index">
{{ competences.category }} <v-btn #click="openCompetence(index)"> Add </v-btn>
</div>
the rest is a v-container with a v-for including multiple v-autocomplete, but the most important inside is the v-if:
<div class="skill-field" v-for="(skill, index) of userCompetences.competences" :key="index">
<template>
<v-flex md12 sm12 xs12 v-if="skill.list.length>0">
<v-autocomplete
v-if="showCompetence.includes(index)"
v-model="userCompetences.competences.list"
:items="skill.list"
chips
hide-selected
:label="skill.category"
item-text="name"
item-value="name"
multiple
>
</v-autocomplete>
</v-flex>
</template>
</div>
Normally, you would use a computed property to solve a problem like this. However, because you are inside of a v-for, a computed property is a little more difficult since you can't evaluate it in the context of each value of the v-for's index.
There are two common options:
Do exactly what you are doing. It's actually no less efficient since it will not re-evaluate unless data it depends on changes.
Add a computed called something like competencesDisplayState that returns an array of booleans that matches the ordering of the userCompetences.competences array in the v-for. Then your v-if can become something like:
v-if="competencesDisplayState[index]"
I normally just opt for the first approach as it is simpler and is easier to read and maintain.
If you want to go route 2, here's some code for the competencesDisplayState computed:
competencesDisplayState: () => {
var result = [];
for (var index=0;index < this.userCompetences.competences.length;++index) {
result(this.showCompetence.includes(index));
}
return result;
}

Why does V-select value changes on second click instead of first?

I have a V-select like below, and when I load the page it gets filled with data from my Vuex-store. I then have a computed property to get the currently selected company. My problem is that the value from the currently selected company only updates after I click on it Twice. Have I done something wrong in the implementation?
So when a user changes value in the V-select I want to update the list of users being shown, but this only works if the user clicks twice on the v-select selection.
<template>
<v-container fluid fill-height>
<v-layout child-flex>
<v-card>
<v-card-title>
<h3>Användare</h3>
<v-spacer></v-spacer>
<v-select
v-bind:items="listOfCompanys"
v-model="selectedCompany"
item-value="customerid"
item-text="name"
single-line
bottom
v-on:change="onChangeCompany"
autocomplete></v-select>
</v-card-title>
<UserTable></UserTable>
</v-card>
</v-layout>
</v-container>
</template>
<script>
import { FETCH_COMPANY_LIST, FETCH_USER_LIST } from '../store/actions.type'
import UserTable from './UserTable.vue'
export default {
name: 'Users',
data: () => ({
selectedCompany: 0
}),
components: {
UserTable
},
methods: {
onChangeCompany () {
this.$store.dispatch(FETCH_USER_LIST, this.currentCompany)
}
},
mounted: function () {
this.$store.dispatch(FETCH_COMPANY_LIST)
},
computed: {
listOfCompanys () {
return this.$store.state.users.companyList
},
currentCompany () {
return this.selectedCompany
}
}
}
</script>
Don't do both v-model and v-on:change. You're sending this.currentCompany, which I think is supposed to be selectedCompany. If the idea is to send the value when it changes, put a watch on the value, not on the widget. The widget feeds the value, the value feeds the store.

Categories

Resources