Dynamic array name in v-for loop - vue.js

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.

Related

Computed properties with v-model by index

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});
}

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
}
}
}

Dynamically apply search filter on js data using vuejs

I have a array list which contains column names. I want to write a search filter which will dynamically pick the value from the array and display all the rows and column. Search Filter should be with array contains column names.
Column name can be set dynamically
I have written code like below
Inside script tag
Its not working.. udata value is comming as array
export default {
el: '#apicall',
mixins: [Vue2Filters.mixin],
data()
{
return {
toggle: false,
userData: [],
search: "",
apidata: ['Id', 'Name', 'Version'],
currentvalue: '',
}
computed:
{
filteruserData: function(){
var self = this;
var list =[];
return this.userData.filter((udata) => {
for(var i in self.apidata)
{
list.push(udata[self.apidata[i]])
}
return list.toLowerCase().indexOf(self.search.toLowerCase()) != -1
});
},
I didn't really get exactly what you need. From what I understood you need to search for column name in apidata array using a filter. If your search filter find an element of your apidata array, you want to diplay the row values corresponding to column name your search filter found.
From elements you gave in your post, I don't understand what value you need to diplay and where are they supposed to come from.
Anyway, here is my try to help you.
Lets suppose that you have a data object that contains the row value to display.
The code below search inside the apidata array and diplay the value of the corrsponding attribute of the data object if anything has been found.
App.vue
<template>
<div id="app"><searchCompoenent :data="data"></searchCompoenent></div>
</template>
<script>
import searchCompoenent from "./components/searchComponent.vue";
export default {
name: "App",
data() {
return {
data: {
Id: [1, 9, 2, 3, 4, 5],
Name: ["name1", "name2"],
Version: ["2.0", "1.0.0", "1.1-beta"]
}
};
},
components: {
searchCompoenent
}
};
</script>
searchComponent.vue
<template>
<div>
<input v-model="search" placeholder="Search..." type="text" />
<button type="button" #click="searchColumn">Search!</button>
</div>
</template>
<script>
export default {
data() {
return {
search: "",
apidata: ["Id", "Name", "Version"]
};
},
props: ["data"],
methods: {
searchColumn() {
let result = this.apidata.find(element => {
return element === this.search;
});
if (result !== undefined) {
console.log(result);
console.log(this.data);
}
}
}
};
</script>

Filtering a list of objects in Vue without altering the original data

I am diving into Vue for the first time and trying to make a simple filter component that takes a data object from an API and filters it.
The code below works but i cant find a way to "reset" the filter without doing another API call, making me think im approaching this wrong.
Is a Show/hide in the DOM better than altering the data object?
HTML
<button v-on:click="filterCats('Print')">Print</button>
<div class="list-item" v-for="asset in filteredData">
<a>{{ asset.title.rendered }}</a>
</div>
Javascript
export default {
data() {
return {
assets: {}
}
},
methods: {
filterCats: function (cat) {
var items = this.assets
var result = {}
Object.keys(items).forEach(key => {
const item = items[key]
if (item.cat_names.some(cat_names => cat_names === cat)) {
result[key] = item
}
})
this.assets = result
}
},
computed: {
filteredData: function () {
return this.assets
}
},
}
Is a Show/hide in the DOM better than altering the data object?
Not at all. Altering the data is the "Vue way".
You don't need to modify assets to filter it.
The recommended way of doing that is using a computed property: you would create a filteredData computed property that depends on the cat data property. Whenever you change the value of cat, the filteredData will be recalculated automatically (filtering this.assets using the current content of cat).
Something like below:
new Vue({
el: '#app',
data() {
return {
cat: null,
assets: {
one: {cat_names: ['Print'], title: {rendered: 'one'}},
two: {cat_names: ['Two'], title: {rendered: 'two'}},
three: {cat_names: ['Three'], title: {rendered: 'three'}}
}
}
},
computed: {
filteredData: function () {
if (this.cat == null) { return this.assets; } // no filtering
var items = this.assets;
var result = {}
Object.keys(items).forEach(key => {
const item = items[key]
if (item.cat_names.some(cat_names => cat_names === this.cat)) {
result[key] = item
}
})
return result;
}
},
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<button v-on:click="cat = 'Print'">Print</button>
<div class="list-item" v-for="asset in filteredData">
<a>{{ asset.title.rendered }}</a>
</div>
</div>

vue.js: Tracking currently selected row

I have a simple table where I would like to handle click elements:
<div class="row"
v-bind:class="{selected: isSelected}"
v-for="scanner in scanners"
v-on:click="scannerFilter">
{{scanner.id}} ...
</div>
JS:
new Vue({
el: "#checkInScannersHolder",
data: {
scanners: [],
loading: true
},
methods: {
scannerFilter: function(event) {
// isSelected for current row
this.isSelected = true;
// unselecting all other rows?
}
}
});
My problem is unselecting all other rows when some row is clicked and selected.
Also, I would be interested to know, it it is possible accessing the scanner via some variable of the callback function instead of using this as I might need to access the current context.
The problem is you have only one variable isSelected using which you want to control all the rows. a better approach will be to have variable: selectedScanner, and set it to selected scanner and use this in v-bind:class like this:
<div class="row"
v-bind:class="{selected: selectedScanner === scanner}"
v-for="scanner in scanners"
v-on:click="scannerFilter(scanner)">
{{scanner.id}} ...
</div>
JS
new Vue({
el: "#checkInScannersHolder",
data: {
scanners: [],
selectedScanner: null,
loading: true
},
methods: {
scannerFilter: function(scanner) {
this.selectedScanner = scanner;
}
}
});
I was under the impression you wanted to be able to selected multiple rows. So here's an answer for that.
this.isSelected isn't tied to just a single scanner here. It is tied to your entire Vue instance.
If you were to make each scanner it's own component your code could pretty much work.
Vue.component('scanner', {
template: '<div class="{ selected: isSelected }" #click="toggle">...</div>',
data: function () {
return {
isSelected: false,
}
},
methods: {
toggle () {
this.isSelected = !this.isSelected
},
},
})
// Your Code without the scannerFilter method...
Then, you can do:
<scanner v-for="scanner in scanners"></scanner>
If you wanted to keep it to a single VM you can keep the selected scanners in an array and toggle the class based on if that element is in the array or not you can add something like this to your Vue instance.
<div
:class="['row', { selected: selectedScanners.indexOf(scanner) !== 1 }]"
v-for="scanner in scanners"
#click="toggle(scanner)">
...
</div>
...
data: {
return {
selectedScanners: [],
...
}
},
methods: {
toggle (scanner) {
var scannerIndex = selectedScanners.indexOf(scanner);
if (scannerIndex !== -1) {
selectedScanners.splice(scannerIndex, 1)
} else {
selectedScanners.push(scanner)
}
},
},
...