creating a loop to loop over the componennt created - vue.js

I have this data which is returning me the labels and every I need to create a component.
The component is already built when I pass the values. Now I want to create it as a for loop so that I can keep on adding entries, and it will create components as needed.
This is what I've tried:
data() {
return {
options: [{
heading: 'Welcome to the Card',
subheading: 'Manage your Profile here'
},
{
heading: 'Pay your bills',
subheading: 'Manage your bills and payments here'
}
]
}
}
I am trying to loop it over like this
<div v-for="(value, key) in options">
<componentName {{key}} = {{value}}/>
</div>
Previously, the above code was like this:
<componentName :heading='Welcome to the Card' :subheading='Manage your Profile here'/>
Which works well but to add more I have to recreate this <componentName which I want to avoid. I want to keep one entry and feed it with array of objects
I'm using Vue2. What am I doing wrong here?

You're very close. Given your data, the template would need to look like:
<div v-for="(option, index) in options" :key="index">
<h3>{{ option.heading }}</h3>
<p>{{ option.subheading}}</p>
</div>
Or, if you've got a custom component, which takes heading and subheading as props:
<componentName
v-for="(option, index) in options"
:key="index"
:heading="option.heading"
:subheading="option.subheading"
/>
If you can share a bit more of your code, I can create a runnable snippet, if that would be helpful.

Related

Vue Bootstrap, how to interact with plus/minus icon on dynamic generated collapse content separately

I have a VueJS view that creates collapsed contents using Bootstrap Vue Collapse Component.
The data is dynamic and can contains hundreds of items, which is why you see in the code below it was created via a v-for loop in Vue.
<div class="inventory-detail" v-for="(partNumberGroup,index) in inventory" :key="index" >
<b-button block v-b-toggle="partNumberGroup.partNumber" v-bind:id="partNumberGroup.partNumber" variant="primary"
#click="(evt) =>{isActive = !isActive && evt.target.id == partNumberGroup.partNumber}">
<i v-bind:id="partNumberGroup.partNumber" class="float-right fa" :class="{ 'fa-plus': !isActive, 'fa-minus': isActive }"></i>
{{ partNumberGroup.partNumber }}
</b-button>
<div class="inventory-detail__card" v-for="item in partNumberGroup.items">
<b-collapse v-bind:id="partNumberGroup.partNumber" >
<b-card>
<!--Accordion/Collapse content -->
</b-card>
</b-collapse>
</div>
</div>
This works fairly well in that I can individually expand and collapse each content separately. However, the one issue I'm facing is each time I click the icon fa-minus (-) orfa-plus (+), all of them changed as per the images below.
Any tips on how I should implementing this? in my code I tried the dynamic CSS class switching but I still lack the ability to switch on specific element.
I feel like the solution to this is to somehow conditionally apply dynamic CSS class or somehow able to use the attribute 'aria-expanded'.
You can try something like this. Whenever somebody clicks on the icon, set its index as activeIndex (using the setActiveIndex method). Then you can set the class accordingly by comparing the activeIndex with current index
<i
#click="setActiveIndex(index)"
v-bind:id="partNumberGroup.partNumber"
class="float-right fa"
:class="{ 'fa-plus': !isActive(index), 'fa-minus': isActive(index) }">.
</i>
then in the script part:
...
data() {
return {
activeIndex: -1
}
},
methods: {
/* set active index on click */
setActiveIndex(index) {
this.activeIndex = index;
},
/* check if index is active or not */
isActive(index) {
return index === this.activeIndex;
}
}

Add multiple components to child component via slot

I have a pretty simple implementation so I feel like this may be a silly question. Just starting with vue I am trying to make a component be able to list other components; kind of like a reusable List, Table, Grid, ect.
Parent component imports gridView as a normal component:
<template>
<div :id="id" class="grid-view">
<slot></slot>
</div>
</template>
And then in another component I attempt to build this:
<grid-view :dataSource="items">
<grid-view-item-test
v-for="(item, index) in items"
:key="index"
:title="item.title"
/>
</grid-view>
And then grid-view-item-test is simple. title being a prop. Works completely fine standing alone or without the use of a slot, but I wanted to make grid-view reusable to just take whatever other component I add and display as-is.
<template>
<div>
<div>{{ title }}</div>
</div>
</template>
If I were to do something like this:
<grid-view :dataSource="items">
<grid-view-item-test
:key="index"
:title="'Test 1'"
/>
<grid-view-item-test
:title="'Test 2'"
/>
<grid-view-item-test
:title="'Test 3'"
/>
</grid-view>
It works completely fine. Am I doing the loop wrong? I can confirm the loop works to get the number of items correctly. If I make a default fallback on the slot - I get the correct number, and I have printed it directly outside of the grid-view component.
Is this not a possibility? If not, would you just use HTML instead of a component for a reusable table as that seems to work fine.
Edit:
It works completely fine if I use an of strings, or numbers, but not objects.
I've tracked it down to items being an empty array as a computed variable and then throwing TypeError: null is not an object (evaluating 'oldChildren[i]'). Can confirm that items begins empty, and then is populated once a database call sets it, but I'm guess I'm not able to do something like that with slots?
After more testing it fails when you update the dataSet (items) at all. Is re-rending not possible with slots like this?
It works flawlessly if I have an empty div (or anything) when there are no items in the initial array.
You probably should provide more a more accurate code because I don't see any problem with what you provided
Working example:
const gridView = Vue.component('gridView', {
props: ['dataSource'],
template: `
<div class="grid-view">
<h4>My grid</h4>
<slot></slot>
</div>
`
})
const gridItem = Vue.component('grid-view-item-test', {
props: ['title'],
template: `
<div>
<div>{{ title }}</div>
</div>
`
})
const vm = new Vue({
el: '#app',
data: () => ({
items: [{
title: 'Test 1'
},
{
title: 'Test 2'
},
{
title: 'Test 3'
},
]
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<grid-view :data-source="items">
<grid-view-item-test v-for="(item, index) in items" :key="index" :title="item.title" />
</grid-view>
</div>

How to have a scoped toggle variable in VueJS

I'm coming from an AngularJS background where the ng-repeat has a scoped variables and I'm trying to figure out how to achieve a similar result without the need to create a new component which seems overkill for a lot of situations.
For example:
<div class="item" v-for="item in items">
<div class="title">{{item.title}}</div>
<a #click="showMore = !showMore">Show more</a>
<div class="more" v-if="showMore">
More stuff here
</div>
</div>
In AngularJS that code would work great, but in VueJS if you click on show more it causes the variable to update for every item in the items list, is there anyway to create a local scoped variable inside of the v-for without the need to make a new component?
I was able to get it to work by having the showMore variable be something like #click="showMore = item.id" and then v-if="showMore.id = item.id" but that also seems like too much extra complexity for something that should be much simpler? The other problem with that approach is you can only get one item to show more rather than allow for multiple items to be toggled shown at once.
I also tried changing the items model to include item.showMore but once again that adds more complexity and it causes a problem if you need to update an individual item since the model is changed.
Are there any simpler approaches to this?
What do you think about this: CODEPEN
<template>
<div>
<h1>Items</h1>
<div v-for="item in items"
:key="item.id"
class="item"
>
{{item.name}}
<button #click="show=item.id">
Show More
</button>
<div v-if="item.id == show">
{{item.desc}}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{id:1, name:"One", desc: "Details of One"},
{id:2, name:"Two", desc: "Details of Two"},
{id:3, name:"Three", desc: "Details of Three"}
],
show: null
};
}
};
</script>
<style>
.item{
padding: 5px;
}
</style>

How to setup multiple keys for components in template tag using v-for?

I wanted to render a list using v-for. It's simple enough, the documentation explains almost every use case. I want it to look like that:
<template v-for="(item, index) in items" :key="index">
<CustomComponent :item="item"/>
<Separator v-if="index !== items.length-1"/>
</template>
Unfortunately, the documentation does not say how to set a key for multiple custom components in one v-for.
Obviously, I don't want to include separator to my custom component, because it is used in other places too. Code I have pasted is generating those errors:
'template' cannot be keyed. Place the key on real elements instead.
I can set a key on component and separator using an index but I got errors: Duplicate keys detected: 'x'. This may cause an update error.
For now, I'm doing it like that but it's an ugly hack and would not work with more components in one template.
<template v-for="(item, index) in items">
<CustomComponent :item="item" :key="(index+1)*-1"/>
<Separator v-if="index !== items.length-1" :key="(index+1)"/>
</template>
Example from documentation explains templates on the list with basic components which does not require keys.
Does anyone know how should I do it correctly?
Ps. It is not recommended to use v-if on v-for. Could someone suggest how to change my code not to use v-if but don't render separator under the last element?
Here is how I was able to generate a key -- you could customize the generateKey method to return whatever you like.
<template>
<div>
<div
v-for="(item, index) in items"
:key="generateKey(item, index)"
>Item {{ index }} : {{ item }}</div>
</div>
</template>
<script>
export default {
data() {
return {
items: ["Sun", "Moon", "Stars", "Sky"]
};
},
methods: {
generateKey(item, index) {
const uniqueKey = `${item}-${index}`;
return uniqueKey;
}
}
};
</script>
Working Example: https://codesandbox.io/s/30ojo1683p
I was talking with a friend and he suggested the simplest and in my opinion the best solution. Just add a component prefix to every key e.g:
<template v-for="(item, index) in items">
<CustomComponent :item="item" :key="'custom_component-'+index"/>
<Separator v-if="index !== items.length-1" :key="'separator-'+index"/>
</template>

Not getting different class names while iterating

I am having a v-for loop in my vuejs application. i want each span to have a different class name. For example .spa0, .spa1, .spa3..... How do I do it using index in v-for loop?
I tried something like this:
:class="`spa${index}`"
Some of my code is:
<div>
<span v-for="(t, index) in table_data" :class="`spa${index}`">{{t}}</span>
</div>
I expect each span to have a different class.
If table_data is like this as you stated in the comments:
{
type: 'LDAP',
des: 'LDAP for Demo - Do not edit or delete this App',
api: 'show token',
scim: 'cloud.kapstonellc.com:8082/scim/v2/10VN44ZN',
endis: true,
act: true
}
You should probably iterate over table_data keys instead:
<div>
<span v-for="(key, index) in Object.keys(table_data)" :class="`spa${index}`">
{{ table_data[key] }}
</span>
</div>