Computed properties with v-model by index - vue.js

Let's say I have the following vuex store...
state: {
someObj: {
someList: [
{ key:'a', someSubList: [] },
{ key:'b', someSubList: [] },
{ key:'c', someSubList: [] },
]
}
}
How would I bind a separate v-model to each someSubList? As an example, after I check some checkboxes, I would expect to see some Ids be populated into the someSubList like this:
someList: [
{ key:'a', someSubList: [1, 13, 17, 19] },
{ key:'b', someSubList: [1, 2, 3, 4] },
{ key:'c', someSubList: [4, 16, 20] },
]
In other words, If I check a checkbox an associated id would be added to someSubList. If I uncheck the box, the id associated with that checkbox would be removed from the someSubList. Keep in mind that each someList has a different someSubList.
I'm thinking it would be similar to below, but I'm not sure what to use for the v-model param and how to pass the index to the set method
ex.
<span v-for="(someListRow.someSubList, index2) in someList" v-bind:key="index2">
<v-checkbox v-model="myModel" />
</span>
computed: {
someList: {
get() {
return this.$store.state.someObj.someList;
},
set(value) {
this.$store.commit('someCommit', value)
}
}
}
UPDATE:
For anyone interested I got it solved using the tips provided in the posts below and ended up doing this:
<v-checkbox #change="myChangeMethod($event, myObj)" label="MyLabel"
:input-value="isMyObjSelected(myObj)" />
myChangeMethod(event, myObj) {
if (event) {
this.$store.commit('AddToMyList', {myObj});
} else {
this.$store.commit('RemoveFromMyList', {myObj});
}
}
isMyObjSelected(myObj){
this.$store.getters.isMyObjSelected(myObj});
}

I believe you want to map your inputs to some value in your store?
For this to work you cannot use v-model. Instead work with a input="updateStore($event, 'pathToStoreField')" (or #change="...") listener and a :value="..." binding. In case of a checkbox you need to use :checked="..." instead of value.
For example:
<input type="checkbox" :checked="isChecked" #input="updateField($event.target.checked, 'form.field')">
...
computed:
...
isChecked() {
return this.$store.state.form.field;
},
...
methods: {
...
updateField(value, path) {
const options = { path, value };
this.$store.commit('setFieldByPath', options);
},
...
},
Then in your store you will need a mutation setFieldByPath that resolves the string-path to a property in the state object (state.form.field) and sets this property to value.
You can also place the updateField() method as setter of a computer property.
There is library that makes this a bit more convenient: https://github.com/maoberlehner/vuex-map-fields
Just look out for checkboxes: to set them checked the checked property needs to be true not the value property.

I think, unsure but Computed values usually become like a local variable available, you need to v-model what other variable is being posted through form, so if myModel is the variable passed through then set the value of the setter once received:
<span v-for="(myList, index2) in someList" v-bind:key="index2">
<v-checkbox v-model="myModel" />
</span>
Then in script
computed: {
myList() {
return this.$store.state.someObj.someList;
}
}

I guess u ask for this:
<span v-for="(someSubList, index2) in someList" v-bind:key="index2">
<v-checkbox v-model="myModel" />
</span>
data() {
return {
myModel: ''
}
},
computed: {
someList: {
get() {
return this.$store.state.someObj.someList;
},
set(value) {
this.$store.commit('someCommit', value)
}
}
}
properties inside data() can be used to bind your v-model's on your template.

For anyone interested I got it solved using the tips provided in the posts below and ended up doing this:
<v-checkbox #change="myChangeMethod($event, myObj)" label="MyLabel"
:input-value="isMyObjSelected(myObj)" />
myChangeMethod(event, myObj) {
if (event) {
this.$store.commit('AddToMyList', {myObj});
} else {
this.$store.commit('RemoveFromMyList', {myObj});
}
}
isMyObjSelected(myObj){
this.$store.getters.isMyObjSelected(myObj});
}

Related

passing object of handlers to v-on and accessing methods

I have a v-for loop in which I build up a row of buttons from my data. I also dynamically bind handlers to them and here is the part I am struggling with.
<component
is="button.component"
v-for="(button, index) in group"
:key="index"
v-on="button.handlers"
>
Text
</component>
As you can see, I pass button.handlers to the v-on function. My data looks like this:
data: {
group: [
{
component: BButton
value: 'foo',
handlers: { click: 'update(button.value)' }
},
{
component: CustomFilePickerButton
value: 'bar',
handlers: { change: 'uploadFile($event.files)' }
}
]
}
Vue complains that the string 'update(value)' is not a function but I dont know how I can get the equivalent to v-on:click="update(button.value)" which would work in the template.
Any ideas on how to solve this?
The use of v-on with object introduces limitations, 'update(button.value)' string cannot be compiled and make use of local button.value value because it's defined outside the template.
It would be possible to provide this functionality with custom directive similar to v-on.
Otherwise all necessary data needs to be provided to handlers with helper function:
<button
v-for="(button, index) in group"
:key="index"
v-on="mapHandlers(button, { value: button.value })"
>
...
methods: {
mapHandlers(button, data) {
let remappedHandlers = {};
for (let eventName in button.handlers) {
let handler = button.handlers[eventName];
remappedHandlers[eventName] = e => handler(e, data);
}
return remappedHandlers;
},
update(e, { value }) { ... }
},
data () {
return {
group: [
{
value: 'foo',
handlers: { click: this.update }
}
]
}
}

VueJS: how to trigger 'change' on <input> changed programmatically

I'm going to build a customized virtual keyboard, so that's the first problem I've encountered.
I have an input element, whose value is changed from outside, in my case by pressing a button. The problem is that there seems to be no way to trigger the normal 'change' event.
Neither clicking outside the input, nor pressing Enter gives any result. What might be the correct way of solving this problem?
<template>
<div class="app-input">
<input #change="onChange" type="text" v-model="value" ref="input">
<button #click="onClick">A</button>
</div>
</template>
<script>
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
</script>
The whole pen can be found here.
v-on:change would only trigger on a direct change on the input element from a user action.
What you are looking for is a wathcer for your data property, whenever your value changes, watcher will execute your desired function or task.
watch: {
value: function() {
this.onChange();
}
}
The watch syntax is elaborated on the provided official vuejs docs link. use your data property as the key and provide a function as a value.
Check the snippet.
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
// this one:
watch: {
value: function() {
this.onChange();
}
},
// --- rest of your code;
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
When I build any new vue application, I like to use these events for a search input or for other inputs where I don't want to fire any functions on #change
<div class="class">
<input v-model="searchText" #keyup.esc="clearAll()" #keyup.enter="getData()" autofocus type="text" placeholder="Start Typing ..."/>
<button #click="getData()"><i class="fas fa-search fa-lg"></i></button>
</div>
These will provide a better user experience in my opinion.

Computed property doesn't update on readonly input

I have an input field which by default set to readonly and when you click a button it should be possible to edit the item.
Without the readonly field, it seems that the field updates correctly.
Without it, I edit the item and when the focus is lost it reverts to the text that was previously.
<template>
<input ref="input"
:readonly="!edit"
type="text"
v-model="inputValue"
#blur="edit = false">
<button #click="editItem"/>
</template>
data(){
return {
edit: false,
}
}
methods: {
...mapMutations(['setKey']),
editItem() {
this.edit = true;
this.$nextTick( () => this.$refs.input.focus() );
}
}
computed: {
...mapGetters(['getKey']),
inputValue: {
get() {
return this.getKey();
}
set(value) {
this.setKey({value});
}
}
}
Note: the store is updated correctly but the getter is not triggered.
Without the readonly he is triggered.
Could it be the incorrect way to use readonly?
Also tried this instead of true/false.
:readonly="edit ? null : 'readonly'"
In my opinion, there is a better way to handle your textfield value. Use mapGetters + mapActions. Use getter value with :value=inputValue. And commit this value on blur via your action. Btw, pls dont change your data inside html template (edit = false). :readonly works fine with true/false.
Or you could use watcher. Smth like this:
v-model="inputValue",
#blur="toggleEgit"
...
data () {
return {
inputValue: this.getKey
}
},
methods: {
toggleEgit () {
this.isEdit = !this.isEdit;
}
},
watch: {
inputValue: {
handler: function (value) {
this.$commit('setKey', value) // or whatever you want to update your store
}
}
}

Dynamic array name in v-for loop

Is it possible to make the array in v-for dynamic? Like this:
<div v-for="(company, index) in arrayName">
....
</div>
Data:
data() {
return {
companies: [],
arrayName: 'companies'
}
},
It can be achieved with a computed property
Template:
<div v-for="(company, index) in myArray">
....
</div>
Instance:
computed: {
myArray() {
return this[this.arrayName];
},
}
My initial reaction would be to question why you're doing this!
If you're sure that this is what you want, you could use a computed property:
computed: {
things() {
return this.$data[this.arrayName];
}
}
Then your template can just loop on things.
However, I would probably go for another approach. Based on the discussion in the comments, an approach using a filter could be:
data() {
companies,
filterActive: false
},
computed: {
things() {
if (this.filterActive) {
return this.companies.filter(company => company.isActive);
}
return this.companies
}
}
I assume that the array of companies contain some data that you can use to determine whether they are active or not.
Now all your button needs to do is toggle filterActive between true and false.

How can I make a reusable "CheckAll" checkbox solution in Vue2

I am trying to create a reusable "Check All" solution for displaying a list of objects retrieved from an API.
I really like the get/set methods of computed properties that I use in this example here, https://codepen.io/anon/pen/aLeLOZ but I find that rewriting the same function over and over again and maintaining a seperate checkbox state list is tedious.
index.html
<div id="app">
<input type="checkbox" v-model="selectAll1"> Check All
<div v-for="person in list1">
<input type="checkbox" v-model="checkbox" :value="person.id">
<span>{{ person.name }}</span>
</div>
<hr/>
<input type="checkbox" v-model="selectAll2"> Check All
<div v-for="person in list2">
<input type="checkbox" v-model="checkbox2" :value="person.id">
<span>{{ person.name }}</span>
</div>
</div>
main.js
new Vue({
el: '#app',
data () {
return {
list1: [
{ id: 1, name: 'Jenna1'},
{ id: 2, name: 'Jenna2'},
{ id: 3, name: 'Jenna3'},
{ id: 4, name: 'Jenna4'}
],
list2: [
{ id: 1, name: 'Mary1'},
{ id: 2, name: 'Mary2'},
{ id: 3, name: 'Mary3'},
{ id: 4, name: 'Mary4'}
],
checkbox: [],
checkbox2: []
}
},
computed: {
selectAll1: {
get: function () {
return this.list1 ? this.checkbox.length === this.list1.length : false
},
set: function (value) {
let selected = []
if (value) {
this.list1.forEach(function (bf) {
selected.push(bf.id)
})
}
this.checkbox = selected
}
},
selectAll2: {
get: function () {
return this.list2 ? this.checkbox2.length === this.list2.length : false
},
set: function (value) {
let selected = []
if (value) {
this.list2.forEach(function (bf) {
selected.push(bf.id)
})
}
this.checkbox2 = selected
}
},
}
});
How can I make a resuable selectAll() function that will work in this example that can be included as often as needed?
Is it possible to make a class that can maintain the check box state for each list and still function as a computed property to make use of the v-model directive?
It's not the same at all, but a method based solution would be
methods: {
selectUs: function(){
if (this.checkbox.length <= this.list1.length) {
this.checkbox = Array.from(Array(this.list1.length + 1).keys())
} else {
this.checkbox = []
}
}
}
with #click="selectUs" instead of v-model="selectAll1"
(you could keep the get part of your computed properties to keep track of whether all are selected, and then use if (selectAll1) { etc } in the method)