I have an object that i am displaying dynamically on the screen with nested v-if statements. This object is displayed as a table.
I want each "cell" to have a click handler that returns the indices of that exact "cell" so that i can assign a value on click.
This is my object:
const table = {
header: ['Name', 'Runter', 'Frei', 'Hoch', 'Neiba'],
one: { name: '1', runter: '', frei: '', hoch: '', neiba: '' },
two: { name: '2', runter: '', frei: '', hoch: '', neiba: '' },
three: { name: '3', runter: '', frei: '', hoch: '', neiba: '' },
};
This is my html code:
<table>
<tr v-for="(key, object) in table" :key="object">
<td v-for="value in key" :key="value" #click="logIt(key)">
{{ value }}
</td>
</tr>
</table>
logIt is just a simple function to console.log the current cell:
function logIt(cell) {
console.log(cell);
}
With the current configuration this is the output in chrome dev tools when i click on a cell in the row starting with 2:
{name: '2', runter: '', frei: '', hoch: '', neiba: ''}
I am trying to achieve a output like this so i can assign a value to that variable:
two.runter
If it helps here is a screenshot of the displayed table:
v-for on objects returns the object key and value 'backwards'. This means that you probably want to reverse the variable names used in your code:
<table>
<tr v-for="(data, key) in table" :key="key">
<!-- key = one; data = { name: '1', runter: '', frei: '', hoch: '', neiba: '' } -->
<td v-for="(value, subkey) in data" :key="subkey" #click="logIt(key)">
<!-- subkey = name; value = '1' -->
{{ value }}
</td>
</tr>
</table>
Depending on which parameters you want, you can pass multiple parameters to logIt - e.g. #click=logIt(key, value)
Bear in mind that since the value of the first item in data - headers - is a list, you will need to take extra steps to handle this differently, probably as a thead row.
if I understood you correctly try like following snippet:
new Vue({
el: '#demo',
data() {
return {
table: {
header: ['Name', 'Runter', 'Frei', 'Hoch', 'Neiba'],
one: { name: '1', runter: '', frei: '', hoch: '', neiba: '' },
two: { name: '2', runter: '', frei: '', hoch: '', neiba: '' },
three: { name: '3', runter: '', frei: '', hoch: '', neiba: '' },
},
}
},
methods: {
logIt(row, cell) {
if (cell !== 'name') this.table[row][cell] = 'selected ' + row + ' ' + cell
}
}
})
Vue.config.productionTip = false
Vue.config.devtools = false
td {
border: 1px solid gray;
padding: .5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<p>Click on some cell</p>
<table>
<tr v-for="(key, object) in table" :key="object">
<td v-for="(value, i) in key" :key="i" #click="logIt(object, i)">
{{ value }}
</td>
</tr>
</table>
</div>
Related
I'm receiving a response from an api with data and would like to show the results with vuejs in a table. This works fine, until some parameters a missing.
So how can I skip the whole row in v-for, when e.g this {{ sp . article_data . desc_de }} is undefined?
Or how can I show the results except the values which are empty / null?
Backgrund information: When there's a conflict with incomplete data, then the sp.article object is completely missing. Parameters like {{ sp . name }} or {{ sp . type }} are always available.
There are so many possibilities:
Use v-if to display data conditionally
filter the unwanted rows in a computed property
use optional chaining to cope with missing data
As you said, In some cases whole article object will be missing. In this case you can use Optional chaining (?.) which enables you to read the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid.
In below Demo, article object is missing from the 4th object and for undefined/null values, It is showing an empty cell.
Demo :
new Vue({
el: '#app',
data: {
spData: [{
id: 1,
name: 'Alpha',
type: 'Type 1',
article: {
desc_de: 'abc'
}
}, {
id: 2,
name: 'Beta',
type: 'Type 2',
article: {
desc_de: undefined
}
}, {
id: 3,
name: 'Gamma',
type: 'Type 3',
article: {
desc_de: 'def'
}
}, {
id: 4,
name: 'Delta',
type: 'Type 4'
}]
}
})
table, td {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Article</th>
</tr>
<tr v-for="sp in spData" :key="sp.id">
<td>{{ sp.name }}</td>
<td>{{ sp.type }}</td>
<td>{{ sp.article?.desc_de }}</td>
</tr>
</table>
</div>
Now, If you want to remove whole row if any of the column value is empty. Then, you can achieve that by simply applying the Array.filter() method while iterating.
v-for="sp in spData.filter(x => x.article && x.article.desc_de)"
Demo :
new Vue({
el: '#app',
data: {
spData: [{
id: 1,
name: 'Alpha',
type: 'Type 1',
article: {
desc_de: 'abc'
}
}, {
id: 2,
name: 'Beta',
type: 'Type 2',
article: {
desc_de: undefined
}
}, {
id: 3,
name: 'Gamma',
type: 'Type 3',
article: {
desc_de: 'def'
}
}, {
id: 4,
name: 'Delta',
type: 'Type 4'
}]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Article</th>
</tr>
<tr v-for="sp in spData.filter(x => x.article && x.article.desc_de)" :key="sp.id">
<td>{{ sp.name }}</td>
<td>{{ sp.type }}</td>
<td>{{ sp.article?.desc_de }}</td>
</tr>
</table>
</div>
I have this code in a vue3 template:
<a v-if="card.type == 'link'" :href="card.linkData.url">
<img v-if="card.linkData.image" :src="card.linkData.image">
<img v-else :src="card.linkData.favicon">
</a>
The code gets an array of cards for firebase. They might be of type 'image', 'note' or 'link'. By iterating through the array with a v-for, It renders every card with it's info.
If they are of type 'link', they contain an object called linkData.
I want the bit of code shown above to show up only if the card is of type 'link'.
But, I get an error saying that card.linkData is undefined when it's rendering cards of type 'image' or 'note'.
Is that normal? Does it need to read card.linkdata even though it won't render it?
I tried doing this:
<div v-if="card.type == 'link'">
<a :href="card.linkData.url">
<img v-if="card.linkData.image" :src="card.linkData.image">
<img v-else :src="card.linkData.favicon">
</a>
</div>
but the error is still there.
What would be the proper way to do this?
these are examples of card objects obtained fro firebase:
{
id: 1599762353,
imageName: '',
imageUrl: '',
thumbnail: 'link to thumbnail',
title: 'hello',
content: 'a string of text',
createdAt: 'date',
type: 'note'
}
{
id: 1599765625,
imageName: '',
imageUrl: '',
thumbnail: 'link to thumbnail',
title: 'hello',
content: 'a string of text',
createdAt: 'date',
type: 'link',
linkData: {
link: 'https//:www.userlink.com',
image: 'https//:www.linkimage.com',
favicon: ''https//:www.linkicon.com'
}
}
I also tried giving a 'linkData: null' property to cards of type 'image' and 'note' but now the error is card.linkData is null.
Try like following snippet (v-if="card.linkData && card.linkData.link">):
new Vue({
el: '#demo',
data() {
return {
items: [
{id: 1599762353, imageName: '', imageUrl: '', thumbnail: 'link to thumbnail', title: 'hello note', content: 'a string of text', createdAt: 'date', type: 'note'},
{id: 1599765625, imageName: '', imageUrl: '', thumbnail: 'link to thumbnail', title: 'hello', content: 'a string of text', createdAt: 'date', type: 'link', linkData: {link: 'https//:www.userlink.com', image: 'https://picsum.photos/70', favicon: 'https//:www.linkicon.com'}},
{id: 1599765626, imageName: 'https://picsum.photos/50', imageUrl: '', thumbnail: 'link to thumbnail', title: 'hello', content: 'a string of text', createdAt: 'date', type: 'link',}
]
}
}
})
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<li v-for="card in items" :key="card.id">
<div v-if="card.type == 'link'">
<a :href="card.linkData" v-if="card.linkData && card.linkData.link">
<img v-if="card.linkData.image" :src="card.linkData.image">
<img v-else :src="card.linkData.favicon">
</a>
<a :href="card.linkData" v-else>
<img :src="card.imageName">
</a>
</div>
<div v-else>
<p>{{ card.title }}</p>
</div>
</li>
</div>
I'm trying to build an application in Vue2 I have a parent component where I'm passing a data as props to child component using v-for.
Here is my parent component:
<template>
<div class="row" v-for="(item, index) in variables">
<design-arch :details="item" :selectedFields="selectedFields"></design-arch>
</div>
</template>
props:['selectedFields'],
data() {
return {
variables:[
{id: 1, role: 2, specialisation: 13, name: 'ABC - spec 1', role_name: 'ABC', spec_name: 'spec 1'},
{id: 2, role: 2, specialisation: 24, name: 'ABC - spec 2', role_name: 'ABC', spec_name: 'spec 2'},
{id: 3, role: 2, specialisation: 27, name: 'ABC - spec 3', role_name: 'ABC', spec_name: 'spec 3'},
]
}
}
and below is my child component:
<template>
<table v-if="tableData && tableData.data.length">
<thead class="">
<tr>
<th>Sr No</th>
<th>Projects Count</th>
<th>Value</th>
<th>Area</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in tableData.data">
<td>{{tableData.meta.from + index}}</td>
<td>{{item.projects_count}}</td>
<td>{{item.value}}</td>
<td>{{item.area}}</td>
</tr>
</tbody>
</table>
</template>
props: ['details', 'selectedFields'],
data(){
return{
loading: false,
tableData:{},
filters: '',
}
},
methods:{
fetchData(){
this.filters = this.selectedFields;
this.filters['role'] = typeof this.selectedFields['role'] !== 'undefined' ? this.selectedFields['role'] : [{id: this.details.role, name: this.details.role_name}];
this.filters['specialisation'] = typeof this.selectedFields['specialisation'] !== 'undefined' ? this.selectedFields['specialisation'] : [{id: this.details.specialisation, name: this.details.spec_name}];
this.filters['sort_by_column'] = typeof this.selectedFields['sort_by_column'] !== 'undefined' ? this.selectedFields['sort_by_column'] : { column: 'projects_count', order: 'desc'};
console.log(this.filters)
//axios call... with payload as this.filters
}
},
In above code we need to concatenate or modify the prop - selectedFields and call the API to get the data. Since each component has specific modifications, we need to re-calculate in the child component.
Currently my filters are similar in each child component and the modifications are not reflected during the Axios call.
How we can modify the props element inside the local data. So that we can have different executions.
Any better approach is appreciated. Thanks.
I would suggest adding a Vue watch on the props details. Anytime details changes it will rerun fetchData(). See Vue watch.
watch: {
details: {
immediate: true,
deep: true,
handler (oldValue, newValue) {
this.fetchData()
}
}
}
I'm trying to build a custom checkbox component with options that are generated with a v-for loop from an array with options and values. How can I bind the v-model correctly to the checkbox component so that it's correctly updated?
The problem now is that the model only updates to the latest checkbox that is checked and does not give an array with all checked options.
Vue.component('ui-checkbox', {
props: {
label: {
type: String,
required: true,
},
index: {
type: Number
},
inputValue: {
type: String
}
},
methods: {
onChange(e) {
this.$emit('input', e.target.value);
},
},
template: `<div>
<input
:id="index"
type="checkbox"
:value="inputValue"
#change="onChange" />
<label :for="index">
{{ label }}
</label>
</div>`,
})
new Vue({
el: "#app",
data: {
checkOptions: [
{
label: 'Option 1',
value: 'value of option 1',
},
{
label: 'Option 2',
value: 'value of option 2',
},
{
label: 'Option 3',
value: 'value of option 3',
},
{
label: 'Option 4',
value: 'value of option 4',
},
],
myCheckBoxModel: []
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<span>checked Checkboxes: {{ myCheckBoxModel }} </span>
<ui-checkbox
v-for="(option, index) in checkOptions"
v-model="myCheckBoxModel"
:key="index"
:index="index"
:input-value="option.value"
:label="option.label" />
</div>
When you do
this.$emit('input', e.target.value);
it works like
myCheckBoxModel = e.target.value
So it just assigns the value of the last checkbox you clicked to myCheckBoxModel.
If you want to keep all checked items in myCheckBoxModel, you need to do the following:
add value property to ui-checkbox component to have access to the current value of myCheckBoxModel. Value is default property name for this goal (see vue guide).
in your onChange method copy the current value to the variable, because it's not good to mutate value property directly
if your checkbox is checked, push the correspondent value to the array. If the checkbox is not checked, delete to correspondent value from the array
emit input event with the resulting array as value
Vue.component('ui-checkbox', {
props: {
label: {
type: String,
required: true,
},
index: {
type: Number
},
inputValue: {
type: String
},
value: {
type: Array
}
},
methods: {
onChange(e) {
let currentValue = [...this.value]
if (e.target.checked) {
currentValue.push(e.target.value)
} else {
currentValue = currentValue.filter(item => item !== e.target.value)
}
this.$emit('input', currentValue);
},
},
template: `<div>
<input
:id="index"
type="checkbox"
:value="inputValue"
#change="onChange" />
<label :for="index">
{{ label }}
</label>
</div>`,
})
new Vue({
el: "#app",
data: {
checkOptions: [
{
label: 'Option 1',
value: 'value of option 1',
},
{
label: 'Option 2',
value: 'value of option 2',
},
{
label: 'Option 3',
value: 'value of option 3',
},
{
label: 'Option 4',
value: 'value of option 4',
},
],
myCheckBoxModel: []
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
checked Checkboxes:
<span v-for="item in myCheckBoxModel"> {{ item }}; </span>
<ui-checkbox
v-for="(option, index) in checkOptions"
v-model="myCheckBoxModel"
:key="index"
:index="index"
:input-value="option.value"
:label="option.label" />
</div>
I don't know if you need to set checkbox state programmatically, i.e. when you change myCheckBoxModel the state of checkboxes changes correspondently. If you do, you need to watch value property in your ui-checkbox component: set the state of the check box in dependance of if its value is in value array. Do the same also in created hook if you want to initialize the state of checkboxes by myChexkboxModel
The solution presented by #Lana is just too complicated. The onChange method is not needed at all - what you want is to use build-in power of v-model. See below...
Vue.component('ui-checkbox', {
props: {
label: {
type: String,
required: true,
},
index: {
type: Number
},
inputValue: {
type: String
},
value: {
type: Array
}
},
computed: {
model: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
}
},
},
template: `<div>
<input
:id="index"
type="checkbox"
:value="inputValue"
v-model="model" />
<label :for="index">
{{ label }}
</label>
</div>`,
})
new Vue({
el: "#app",
data: {
checkOptions: [{
label: 'Option 1',
value: 'value of option 1',
},
{
label: 'Option 2',
value: 'value of option 2',
},
{
label: 'Option 3',
value: 'value of option 3',
},
{
label: 'Option 4',
value: 'value of option 4',
},
],
myCheckBoxModel: []
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
model: {{ myCheckBoxModel }}
<ui-checkbox v-for="(option, index) in checkOptions" v-model="myCheckBoxModel" :key="index" :index="index" :input-value="option.value" :label="option.label" />
</div>
NOTE that using v-for index as a key works in this case BUT is not recommended in general, especially if set of checkboxes is dynamic and can change over the lifetime of the component that is rendering them.
As of date of posting, I cannot find any documentation to use the "custom filter" prop in data tables.
I just want to create a custom filter to filter my data table by headers.
I have a dropdown, and when user click on one of the options for the dropdown, it will filter the list for one specific header.
Example:
Dropdown options:
Food type: fruit, meat, vegetable
Bakchoi (vegetable)
Pork (meat)
Chicken Thigh (meat)
watermelon (fruit)
If I select dropdown as meat, it should only show me pork and chicken thigh.
Looking at the code on Github1, it looks like the customFilter prop is used to overwrite the default method used to determine how the filter prop is applied to the items in the table.
The default customFilter method applies the filter function to each property name of each item object and filters out any items that don't include one property name that passes the filter:
customFilter: {
type: Function,
default: (items, search, filter) => {
search = search.toString().toLowerCase()
return items.filter(i => (
Object.keys(i).some(j => filter(i[j], search))
))
}
},
You might want to overwrite this function if you wanted to prevent any columns from being included in the filter or if there were specific rows that you always wanted to prevent from being filtered out.
You'll notice that the method also depends on the search prop, which must be a string.
All that said, you really don't need to use that prop for what you want to do. You should just make a computed property to filter the items based on your dropdown value and pass that computed property as the items prop.
Here's an example:
new Vue({
el: '#app',
data() {
return {
food: [
{ name: 'Bakchoi', type: 'vegetable', calories: 100 },
{ name: 'Pork', type: 'meat', calories: 200 },
{ name: 'Chicken Thigh', type: 'meat', calories: 300 },
{ name: 'Watermelon', type: 'fruit', calories: 10 },
],
headers: [
{ text: 'Name', align: 'left', value: 'name' },
{ text: 'Food Type', align: 'left', value: 'type' },
{ text: 'Calories', align: 'left', value: 'calories' },
],
foodType: null,
};
},
computed: {
filteredItems() {
return this.food.filter((i) => {
return !this.foodType || (i.type === this.foodType);
})
}
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#0.15.2/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#0.15.2/dist/vuetify.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons">
<div id="app">
<v-app>
<v-select
label="Food Type"
:items="['vegetable', 'meat', 'fruit']"
v-model="foodType"
></v-select>
<v-data-table
:headers="headers"
:items="filteredItems"
hide-actions
>
<template slot="items" scope="{ item }">
<td>{{ item.name }}</td>
<td>{{ item.type }}</td>
<td>{{ item.calories }}</td>
</template>
</v-data-table>
</v-app>
</div>
This answer was written when Vuetify was at v0.15.2. The source code for the VDataTable component at that version can be found here.
You can also go the customFilter approach like this, I've restricted the search to the type field.
new Vue({
el: '#app',
data() {
return {
food: [
{ name: 'Bakchoi', type: 'vegetable', calories: 100 },
{ name: 'Pork', type: 'meat', calories: 200 },
{ name: 'Chicken Thigh', type: 'meat', calories: 300 },
{ name: 'Watermelon', type: 'fruit', calories: 10 },
],
headers: [
{ text: 'Name', align: 'left', value: 'name' },
{ text: 'Food Type', align: 'left', value: 'type' },
{ text: 'Calories', align: 'left', value: 'calories' },
],
search: '',
};
},
methods: {
customFilter(items, search, filter) {
search = search.toString().toLowerCase()
return items.filter(row => filter(row["type"], search));
}
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#0.15.2/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#0.15.2/dist/vuetify.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons">
<div id="app">
<v-app>
<v-select
label="Food Type"
:items="['vegetable', 'meat', 'fruit']"
v-model="search"
></v-select>
<v-data-table
:headers="headers"
:items="food"
:search="search"
:custom-filter="customFilter"
hide-actions
>
<template slot="items" scope="{ item }">
<td>{{ item.name }}</td>
<td>{{ item.type }}</td>
<td>{{ item.calories }}</td>
</template>
</v-data-table>
</v-app>
</div>
In my case I have 2 different way of filtering which are search bar and drop down. I tried to use custom-filter for both of them, but it doesn't work, so I came up with another approach
<v-text-field v-model="search" label="Label"></v-text-field>
<v-select
v-model="select"
:items="food"
item-text="type"
item-value="type"
:label="Types"
#change="filterFoodUsingDropDown"
>
</v-select>
<v-data-table
:search="search"
:headers="headers"
:items="food"
:custom-filter="filterFoodUsingSearchbar"
>
data() {
return {
food: [
{ name: 'Bakchoi', type: 'vegetable', calories: 100 },
{ name: 'Pork', type: 'meat', calories: 200 },
{ name: 'Chicken Thigh', type: 'meat', calories: 300 },
{ name: 'Watermelon', type: 'fruit', calories: 10 },
],
headers: [
{ text: 'Name', align: 'left', value: 'name' },
{ text: 'Food Type', align: 'left', value: 'type' },
{ text: 'Calories', align: 'left', value: 'calories' },
],
search: '',
select: '',
};
},
methods: {
filterFoodUsingSearchbar(items, search, filter) {
// Condition
}
filterFoodUsingDropDown() {
if (this.select !== '') {
// In this case I use vuex to store the original data of the
food so that all the data is still exist even we filtered it out
this.food = this.$store.state.food.filter((item) => item.type === this.select)
}
}