In my Vue application, I have a very small line of code based on a v-for loop and it works until I throw a V-IF into it.
THe following works:
<div v-for="date in dates" :key="date">
<th v-for="store in stores">#{{store.stock}}</th>
</div>
However, when I try to get it to only show that value if the date in the object matches the dates object, I get that store is undefined
<div v-for="date in dates" :key="date">
<div v-for="store in stores">
<th v-if="store.date === date">#{{store.stock}}</th>
</div>
</div>
Here are my objects:
stores: [
{
store: "123",
date: "2021-09-01",
stock: "145"
}
]
dates: [
{
date: "2021-09-01"
}
]
Why am I having such an issue when trying to match the dates in the v-if?
instead of use v-if with v-for you can use computed to get store you need display and then use just one v-for
<template>
<div>
<div
v-for="store in activeStore"
:key="store.stock"
>
{{ store.date }}
</div>
</div>
</template>
<script>
export default ({
data() {
return {
count: 1110,
stores: [{
store: 'test',
date: '2021-09-01',
stock: '145'
}, {
store: 'test',
date: '2021-09-02',
stock: '146'
}, {
store: 'test',
date: '2021-09-03',
stock: '147'
}, {
store: 'test',
date: '2021-09-04',
stock: '148'
}],
dates: ['2021-09-01', '2021-09-02', '2021-09-03', '2021-09-00']
}
},
computed: {
activeStore: function() {
return this.stores.filter((store) => {
if (this.dates.includes(store.date)) {
return store
}
})
}
}
})
</script>
You made a little mistake. date should be date.date!
<div v-for="date in dates" :key="date">
<div v-for="store in stores">
<th v-if="store.date === date.date">#{{store.stock}}</th>
</div>
</div>
Here is a simple solution using computed property
<div v-for="date in dates" :key="date">
<div v-for="(store, indexer) in getFilteredStores(date)" :key="indexer">
<th>#{{store.stock}}</th>
</div>
</div>
and in your computed, define the below handler to get the store for particular dates
computed: {
getFilteredStores() {
return targetDate => { // targetDate is the argument passed to the handler
return this.stores.filter(store => store.date === targetDate);
}
}
}
vue discourages the use of v-if inside a v-for
it is much better to use a computed property that filters the list and in the
v-for you pass the already filtered list
Related
I have a component that accepts props
<BarChart
header="header name"
dataPoint="data_to_look_at"
size=35
/>
With the dataPoint prop I want to use this in my component so that I can use it (I think, idk if this is the right solution) in an interpolated string like this to access items in an object
// inside of a v-for loop that iterates over an object etc
{{ data[index].attributes.${dataPoint} }}
I'm not sure how to do this and of course the above doesn't work
string interpolation Vue js
Not relevant to my question
How can I solve "Interpolation inside attributes has been removed. Use v-bind or the colon shorthand"? Vue.js 2
Not quite it either
How do interpolate a prop in a interpolation?
Observation : As you are iterating your item list by using v-for loop, No need to access the item by index. You can simply do like this :
<p v-for="data in items" :key="data.id">
{{ data.attributes[datapoint] }}
</p>
Live Demo :
Vue.component('child', {
data: function() {
return {
items: [{
id: 1,
attributes: {
name: 'Alpha'
}
}, {
id: 2,
attributes: {
name: 'Beta'
}
}, {
id: 3,
attributes: {
name: 'Gamma'
}
}]
}
},
props: ['header', 'datapoint'],
template: `<div>
<p v-for="data in items" :key="data.id">{{ data.attributes[datapoint] }}</p>
</div>`
});
var app = new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child header="Header Name" dataPoint="name"></child>
</div>
So now i'm doing this to organise my results by category but if feel like this could be better:
<div><h2>Gloves</h2></div>
<div v-for="stash in stashes" :key="stash.id">
<div v-for="item in stash.items" :key="item.id">
<div v-if="item.extended.subcategories[0] === 'gloves'">
{{ item.extended.baseType }}
</div>
</div>
</div>
<div><h2>Boots</h2></div>
<div v-for="stash in stashes" :key="stash.id2">
<div v-for="item in stash.items" :key="item.id2">
<div v-if="item.extended.subcategories[0] === 'belt'">
{{ item.extended.baseType }}
</div>
</div>
</div>
<div><h2>Helmets</h2></div>
..
<div><h2>Weapons</h2></div>
..
If found this article doing this with a computed property and i feel like this should be the way but can't get it to work (also because i need a argument for it to work this way i think?):
computed: {
filter(category) {
return this.stashes.items.filter(a => a.extended.subcategories[0] === category);
}
}
and then something like this:
<div v-for="item in filter('gloves')" :key="item.id">
..
</div>
But yeah, it says i can't pass a argument in that for loop like this so that is where i ended for now.
Anyone got an idea how to do this?
Stashes looks like this:
stashes: [
{
id: 1
items: [{
name: 'lorem',
extended: {
subcategories: ["gloves"]
}
}]
},
{
id: 2
items: [{
name: 'ipsum',
extended: {
subcategories: ["boots"]
}
}]
},
]
While using a method in the template might solve this, it's not a good pattern because it causes the method to run every time the template is rerendered for any reason. Add another level of v-for:
<div v-for="category in categories" :key="category">
<div><h2>{{ category }}</h2></div>
<div v-for="stash in stashes" :key="stash.id">
<div v-for="item in stash.items" :key="item.id">
<div v-if="item.extended.subcategories[0] === category">
{{ item.extended.baseType }}
</div>
</div>
</div>
</div>
And create an array of categories like:
data() {
return {
categories: ['gloves','belt']
}
}
You can achieve this by returning a method from your computed but I do not recommend this solution. Instead of computed I recommend you to use a method.
[RECOMENDED]
method: {
filter(category) {
return this.stashes.items.filter(a => a.extended.subcategories[0] === category);
}
}
[USING COMPUTED]
computed: {
filter() {
return category => this.stashes.items.filter(a => a.extended.subcategories[0] === category);
}
}
Here you can read a little more about this: Why I can not pass parameter to the computed
Suppose, My Name is "Aerofoil Todo Kite" I want AK
I got a code from Stackoverflow. I hope it will work. But my question is, I am printing data from Array of Objects with v-for loop.
How do I pass the Name to compute that?
I think, Computed Property don't accept Parameter.
Then what will be the process??
Method can do. But It is calling for many times!!!
data:
tableData: [
{ customer: 'EE Fashion'},
{ customer: 'Tom Hangs Ron'}
}]
methods: {
nameOfCompany(fullName) {
console.log(fullName);
return "HL";
}
}
Code of Mine:
<template slot-scope="scope">
<p style="margin-top: 5px;"><b>{{ nameOfCompany(scope.row.customer) }}</b></p>
</template>
Here is the problem:
{{ nameOfCompany(scope.row.customer) }}
This function is calling for many times!!!!
What will be the approach to do that?
You may write a Customer component so you only compute the company's name once:
It takes a name, and in data, computes the associated companyName.
const mytable = {
props: ['rows'],
template: `
<table>
<tr v-for="row in rows">
<slot :row="row"></slot>
</tr>
</table>
`
}
const mycustomer = {
props: ['name'],
data () {
return {
companyName: this.name.split(' ').map(x => x[0].toUpperCase()).join('')
}
},
template: `
<td>{{ name }} - <abbr>{{ companyName }}</abbr></td>
`
}
let vm = new Vue({
el:'#el',
components: { mytable, mycustomer },
template: `
<mytable :rows="['grod zi', 'tu rok']">
<template v-slot:default="{ row: user }">
<mycustomer :name="user"/>
</template>
</mytable>
`
});
abbr {
color: blue;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="el"></div>
You can use either filters or computed.
Filters: Vue.js allows you to define filters that can be used to apply common text formatting. Filters are usable in two places: mustache
interpolations and v-bind expressions (the latter supported in
2.1.0+). Filters should be appended to the end of the JavaScript expression, denoted by the “pipe” symbol: doc
new Vue({
el: "#app",
data: {
tableData: [
{ customer: 'EE Fashion', company_name: "FOO BAR" },
{ customer: 'Tom Hangs Ron', company_name: "BAZ FOO BAR"},
{ customer: 'Jerry', company_name: "Lorem Ipsum Dorsum Zaren" }
],
},
filters: {
short_hand (company_name) {
// You can put your logic here...
let words = company_name.split(" ")
let short_hand = words[0][0] + words[words.length-1][0]
return short_hand // <-- The return value as Per logic
}
},
computed: {
getTableData () {
return this.tableData.map(data => {
let words = data.company_name.split(" ")
let short_hand = words[0][0] + words[words.length-1][0]
return { short_hand, ...data }
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
USING FILTER: <br>
<div
v-for="(data, i) in tableData"
:key="'using-filter-'+i"
>
{{ data.company_name | short_hand }}
</div>
<hr> USING COMPUTED: <br>
<div
v-for="(data, i) in getTableData"
:key="'using-computed-'+i"
>
{{ data.short_hand }}
</div>
</div>
I'm making timeline with users activities in vue template. I would like to list fetched data by grouping it by created_at.
I have fatched data with axios from laravel controller. Resulting array is bellow.
activities:Array[2]
0:Object
causer_id:1
created_at:"2019-09-20 08:55:29"
description:"updated"
id:1
subject_type:"App\User"
1:Object
causer_id:1
created_at:"2019-10-20 08:58:10"
description:"updated"
id:2
subject_type:"App\User"
How to group array by created_at, populate "timeline-day" with value, and then fill "timeline-box" with activities from that day?
<div class="timeline-day">
***created_at***
</div>
<div class="timeline-box" >
<div class="box-content" v-for="(activity, i) in activities" :key="i">
<div class="box-item">{{ activity.subject_type }}</div>
<div class="box-item">{{ activity.description }}</div>
<div class="box-item">{{ activity.created_at }}</div>
</div>
Something like this should do it.
new Vue({
el: '#app',
data () {
return {
activities: [
{
causer_id: 1,
created_at: "2019-09-20 08:55:29",
description: "updated",
id: 1,
subject_type: "App\User"
}, {
causer_id: 1,
created_at: "2019-09-20 09:54:25",
description: "updated",
id: 3,
subject_type: "App\User"
}, {
causer_id: 1,
created_at: "2019-10-20 08:58:10",
description: "updated",
id: 2,
subject_type: "App\User"
}
]
}
},
computed: {
days () {
const map = {}
for (const activity of this.activities) {
const day = activity.created_at.slice(0, 10)
map[day] = map[day] || { created_at: day, activities: [] }
map[day].activities.push(activity)
}
return Object.keys(map).sort().map(day => map[day])
}
}
})
.timeline-day {
border-top: 1px solid #000;
font-weight: bold;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<template v-for="day in days">
<div class="timeline-day">
{{ day.created_at }}
</div>
<div class="timeline-box">
<div class="box-content" v-for="(activity, i) in day.activities" :key="i">
<div class="box-item">{{ activity.subject_type }}</div>
<div class="box-item">{{ activity.description }}</div>
<div class="box-item">{{ activity.created_at }}</div>
</div>
</div>
</template>
</div>
It strips out the date using activity.created_at.slice(0, 10).
For each day it builds up an object containing the date and the array of activities. These are all stored in map. Once it's finished it puts those objects in an array for use by the template.
While I have included a sort() for the dates it doesn't sort the activities, so in practice they would need to already be sorted. You could add in extra sorting if that assumption doesn't hold.
Sorting the activities first lends itself to a slightly different algorithm. The temporary map wouldn't be needed and the objects can just be held in the final array from the get go. As the activities are sorted the only object we need access to is the one at the end of the array. Either the next activity will be added to that last object or a new object needs creating.
I am using the <b-table> component from BootstrapVue and trying to customize field output by using a formatter callback function. The table data displays fine , but for some reason the callback function method branchName() is not being called and the value is not being formatted to the branch name instead of the branch id.
I set up a codesandbox to demonstrate the problem: code demo
The goal of the method is to return the name of the branch item. However, only the branch_id is being returned. In other words, the record row under the Branch table column should say ACME10 and not 10.
My file named App.vue:
<template>
<div id="app">
<b-table striped hover :items="userProfiles"></b-table>
</div>
</template>
<script>
export default {
data() {
return {
userProfiles: [
{
uid: "3",
branch: 10
},
{
uid: "1",
branch: 20
},
{
uid: "2",
branch: 13
}
],
branches: [
{
branch_id: 13,
branch: "ACME13"
},
{
branch_id: 10,
branch: "ACME10"
},
{
branch_id: 20,
branch: "ACME20"
}
],
fields: [
{
key: "branch",
formatter: "branchName"
}
]
};
},
methods: {
branchName(value) {
const name = this.branches[0].find(branch => value === branch.branch_id);
return name;
}
}
};
</script>
Thanks for your help!
To answer the OPs original question (why the formatter function was not being called):
In your App.vue file you are missing binding your fields definition array to the fields prop of <b-table>. Try this:
<template>
<div id="app">
<b-table striped hover :items="userProfiles" :fields="fields"></b-table>
</div>
</template>
If <b-table> isn't passed a field definition array, it will peek at the first row's item data and grab the field names that it finds, and then generates its own internal fields definition (just the field keys and humanized labels)
You should use slots to implement custom behavior like. For more information https://bootstrap-vue.js.org/docs/components/table#scoped-field-slots
Here is how you could implement your code
<template>
<div id="app">
<b-table striped hover :items="userProfiles">
<template slot="[uid]" slot-scope="data">{{ data.item.uid }}</template>
<template slot="[branch]" slot-scope="data">{{ branchName(data.item.branch) }}</template>
</b-table>
</div>
</template>
Method:
branchName(value) {
const branch = this.branches.find(branch => value === branch.branch_id);
if (branch) {
return branch.branch
}
return ""
}
try this in your template
<template v-slot:cell(name)="data">
<router-link :to="/route/${data.index}">
{{ data.value }}
</router-link>