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>
Related
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()
}
}
}
Model: Article.
id.
name.
type: ['code', 'design']
API gets all articles
How can I display two lists:
all articles with type ='Code',
all articles with type = 'Design'
In other words, is it possible to filter the API query
Or is it better to do it on the API side?
Extra: same as above but in a nested environment (ie Articles belong to Category. How to do it on the category detail page.
You can use computed properties. I built a sample component:
EDIT: Took some time to DRY it up.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<article-list title="Code Articles" :articles="codeArticles" />
</div>
<div class="col-md-6">
<article-list title="Design Articles" :articles="designArticles" />
</div>
</div>
</div>
</template>
<script>
import ArticleList from './ArticleList.vue'
export default {
components: {
ArticleList
},
data() {
return {
articles: [
{
id: 1,
name: 'Article1',
type: 'Code'
},
{
id: 2,
name: 'Article2',
type: 'Design'
},
{
id: 3,
name: 'Article3',
type: 'Code'
},
{
id: 4,
name: 'Article4',
type: 'Design'
},
]
}
},
computed: {
codeArticles() {
return this.articles.filter(article => article.type === 'Code');
},
designArticles() {
return this.articles.filter(article => article.type === 'Design');
}
}
}
</script>
ArticleList.vue
<template>
<div class="two-filtered-lists">
<h5>{{ title }}</h5>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>TYPE</th>
</tr>
</thead>
<tbody>
<tr v-for="article in articles" :key="article.id">
<td>{{ article.id }}</td>
<td>{{ article.name }}</td>
<td>{{ article.type }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true
},
articles: {
type: Array,
required: true
}
}
}
</script>
In loop like this, I mostly just iterate over item values as strings, but sometimes need to return rendered component, for example build link element, or dropdown menu, for that table cell - need to find a way to return other component output instead of raw html string
<tr class="listing-item listing-item-category">
<td v-for="td in headeritems">{{val(td.k)}}</td>
</tr>
Is that even possible? I've found no mention of this, how should the method code go to return other component output? I know I would have to use v-html, but how to get it?
Assume we have a list like this:
headerItems: [
{
type: 'text',
value: 'Some text'
},
{
type: 'img',
props: {
src: 'http://some-where....'
}
},
{
type: 'my-component',
value: 'v-model value',
props: {
prop1: 10,
prop2: 'Blah bla',
},
events: {
myEvt: () => console.log('myEvt has fired')
}
},
],
So, We can render it:
<tr>
<td
v-for="(item, i) in headerItems" :key="i"
>
<div v-if="item.type === 'text'"> {{ item.value }}</div>
<component
v-else
:is="item.type"
v-model="item.value"
v-bind="item.props"
v-on="item.events"
/>
</td>
</tr>
I'm building a small application using vuejs where I'm calling a url to get some data. I need to manipulate the data before showing it. In the response I'm receiving an array of elements which has fields
client_name: "ABCD Company"
event_type: 3
type: "meeting"
venue: "Mumbai"
with_client: 1
Additionally I have a data set of event_type that looks like this:
events: [
{value: 1, label: "One-on-One meeting"},
{value: 2, label: "Group meeting"},
{value: 3, label: "Broker Roadshow"},
{value: 4, label: "Broker Conference"},
{value: 5, label: "Site Visit"},
{value: 6, label: "Only Management Meet"},
{value: 7, label: "Only IR Meeting"}
],
and with_client is true or false.
So basically my final data will look like something like this:
client_name: "ABCD Company",
event_type: "Broker Roadshow",
type: "meeting",
venue: "Mumbai",
with_client: "yes"
Currently I'm have a v-for loop that looks like this:
<tr v-for="(item, index) in meeting_data">
<td class="text-center">{{ index+1 }}</td>
<td class="text-center">{{ item.client_names }}</td>
<td class="text-center">{{ item.type }}</td>
<td class="text-center">{{ item.event_type }}</td>
<td class="text-center">{{ item.with_client }}</td>
<td class="text-center">{{ item.schedule }}</td>
<td class="text-center"><router-link :to="{name: 'interaction-update', params: {id: item.id}}"><i class="fa fa-pencil-square-o text-navy"></i></router-link></td>
<td class="text-center"><a #click.prevent="deleteInteraction(item.id)"><i class="fa fa-trash-o text-navy"></i></a></td>
</tr>
Use a computed.
This assumes your meeting_data is an array of objects. If it's an object as you suggest in your comment, then show us an example and I'll update the answer.
computed:{
formattedData(){
if (!this.meeting_data) return []
return this.meeting_data.map(d => {
return {
client_name: d.client_name,
type: d.type,
// this find could blow up if the event_type doesn't exist
event_type: this.events.find(e => e.value == d.event_type).label,
with_client: d.with_client ? "yes" : "no",
venue: d.venue
}
})
}
},
Iterate over the formatted data.
<tr v-for="(item, index) in formattedData">
Example.
Based on your pen, it would look something like this:
computed: {
tableFilter: function () {
// Do the filter
let interactions = this.model.interactions
if(this.model.interactions)
{
interactions = this.model.interactions.filter((item) =>
item.client_names.includes(this.search_by_name)
&& item.event_type.includes(this.search_by_event_type));
}
if (!interactions.length > 0) return []
// Return formatted data
return this.interactions.map(d => {
return {
client_name: d.client_name,
type: d.type,
// this find could blow up if the event_type doesn't exist
event_type: this.events.find(e => e.value == d.event_type).label,
with_client: d.with_client ? "yes" : "no",
venue: d.venue
}
})
}
}
That's obviously not a working example but gives you the structure.
How can I filter by searching in multiple search keys?
I'm trying something like this, but (of course) it won't work:
<tr v-repeat="questions | filterBy search in 'reference','user.name','shop.shopName'">
The filterBy custom filter is not documented AFAIK, but you can use a method to make your own filter:
var demo = new Vue({
el: '#demo',
data: {
search: 're',
people: [
{name: 'Koos', age: 30, eyes:'red'},
{name: 'Gert', age: 20, eyes:'blue'},
{name: 'Pieter', age: 12, eyes:'green'},
{name: 'Dawid', age: 67, eyes:'dark green'},
{name: 'Johan', age: 15, eyes:'purple'},
{name: 'Hans', age: 12, eyes:'pink'}
]
},
methods: {
customFilter: function(person) {
return person.name.indexOf(this.search) != -1
|| person.eyes.indexOf(this.search) != -1
;
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="demo">
<input type="text" class="form-control" v-model="search"/>
<br/>
<table class="table">
<thead>
<tr>
<th>name</th>
<th>eyes</th>
<th>age</th>
</tr>
</thead>
<tr v-for="person in people | filterBy customFilter">
<td>{{ person.name }}</td>
<td>{{ person.eyes }}</td>
<td>{{ person.age }}</td>
</tr>
</table>
</div>
This problem resolve with "Computed Properties":
var app = new Vue({
el: '#demo',
data: {
search: '',
people: [
{name: 'Koos', age: 30, eyes:'red'},
{name: 'Gert', age: 20, eyes:'blue'},
{name: 'Pieter', age: 12, eyes:'green'},
{name: 'Dawid', age: 67, eyes:'dark green'},
{name: 'Johan', age: 15, eyes:'purple'},
{name: 'Hans', age: 12, eyes:'pink'}
]
},
computed: {
filteredPerson: function () {
var self = this;
return this.people.filter(function (person) {
return person.name.toLowerCase().indexOf(self.search.toLowerCase()) >= 0
|| person.eyes.toLowerCase().indexOf(self.search.toLowerCase()) >= 0;
});
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="demo">
<input type="text" class="form-control" v-model="search"/>
<br/>
<table class="table">
<thead>
<tr>
<th>name</th>
<th>eyes</th>
<th>age</th>
</tr>
</thead>
<tr v-for="person in filteredPerson">
<td>{{ person.name }}</td>
<td>{{ person.eyes }}</td>
<td>{{ person.age }}</td>
</tr>
</table>
</div>
Since Vue.js version 0.12.11 this is possible with:
<li v-repeat="user in users | filterBy searchText in 'name' 'phone'"></li>
Make sure to checkout the official guide on this: http://vuejs.org/api/#orderBy
computed: {
filteredItems() {
var result = this.accountList;
if (this.flt.userName != undefined) {
result = result.filter(
item =>
item.userName.toLowerCase().includes(
this.flt.userName.toLowerCase()
)
);
}
if (this.flt.tenantId != undefined) {
result = result.filter(
item =>
item.tenantId.includes(
this.flt.tenantId
)
);
}
if (this.flt.providerId != undefined) {
result = result.filter(
item =>
item.providerId.includes(
this.flt.providerId
)
);
}
return result;
}
},