Vue JS: Looping through object inline attribute - javascript

I am new to vue.js so this might be an easy question, but I unfortunatelly didnt come to a result on my own.
So this is the basis of my template:
<template v-for="page in $props.data.pageInfo" >
<div class="overviewItem" :key="page.uid" :data-cat=" **looping through the object to get both cat_id's ** ">
<div v-for="(cat, index) in page.categories" :key="index" >
<p v-if="index === 0" class="subtitle">
{{ cat.title }}
</p>
</div>
</div>
</template>
{{ page.categories }} has the following values at the first .overviewItem:
[
{
"cat_id": 1,
"title": "Kategorie 1"
},
{
"cat_id": 2,
"title": "Kategorie 2"
}
]
The loop to get the subtitle works perfectly fine, but I cannot find any option on how to loop through this object so that i get the two values of data-cat attribute.

Use .map to generate a new array of just the cat_id and then join them together with a space in between:
<div
class="overviewItem"
:key="page.uid"
:data-cat="page.categories.map(c => c.cat_id).join(' ')"
>
...
</div>

Related

How to get array index from html element?

Hey In my Vue application I have the opportunity to create several persons:
<div v-for="newContent in temp" :key="newContent.id" #click="showId(newContent.id)" v-show="true">
<!-- Head of part personal data -->
<h4 class="person" style="margin-left: 0.95vw">Person 1</h4>
<!-- Enter Person Data -->
<testmodul1></testmodul1>
<div class="trash">
<button class="btn btn-outline-danger" #click="deletePerson()">Person löschen</button>
</div>
<hr />
</div>
If I want to add more persons, there is a button, which appends more of these person inputs:
<button class="btn btn-outline-secondary" #click="useIt()">Add more persons</button>
useIt(){
this.temp.push({
id:this.id+=1
})
console.log(this.temp);
},
data() {
return {
id:0,
temp:[{
id:0,
}]
};
},
Output of the console.log method in the console (clicked the add button 2 times):
Proxy {0: {…}, 1: {…}, 2: {…}}
[[Handler]]: Object
[[Target]]: Array(2)
0: {id: 0}
1: {id: 0}
length: 2
[[Prototype]]: Array(0)
Now the following problem: Let's say for example we created 3 new persons. Now I recognize, that the second person is false and I want to delete it. When I click on the of person 2 I want to get the array index of the html element. I have already tried this, but it does not really work well:
<div v-for="newContent in temp" :key="newContent.id" #click="showId(newContent.id)" v-show="true">
showId(index){
alert(index);
}
Is there an other way how I could find out the index in the array of the html div I clicked?
Please, check Vue.js list rendering.
You can iterate through array with both element and index like this:
<li v-for="(item, index) in items">
{{ index }} - {{ item.message }}
</li>
For your case:
<div v-for="(newContent, index) in temp" :key="newContent.id" #click="showId(index)" v-show="true">
You can define index variable in v-for loop itself. Like
<div v-for="(person,index) in persons" :key="index">
{{index}} //which is index of current item
</div>

How to increment a loop in Vue

I've got a multi-object array and I'd like to have it loop through a particular group of objects.
This is my code so far
<template>
<div>
<h1>My Test App</h1>
<button v-on:click="getHockeyData">Get Team Data</button>
<div v-for="hockeyData in hockeyDataList" :key="hockeyData.id" >
<p>{{ hockeyDataList.teams[0].roster.roster[1].person.fullName }}</p>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: "Weather",
data() {
return {
hockeyDataList: []
};
},
methods: {
getHockeyData() {
axios.get("https://statsapi.web.nhl.com/api/v1/teams/21?expand=team.roster").then(response => (this.hockeyDataList = response.data));
}
}
};
</script>
I know the loop won't work in it's current state. The portion of my output within the loop that I need to increment is roster[1] - I'm new to Vue so not sure how to use the v- commands to increment that 1 until there are no more instances of it.
Any help or feedback would be greatly appreciated!
Just loop over the others, really you want to start from teams as that's the way the JSON is structured
Example using various parts of the JSON, view the file for others.
<div v-for="team in hockeyDataList.teams" :key="team.id">
<h1>{{ team.name }}</h1>
<div>{{ team.venue.name }}, {{ team.venue.city }}</div>
<div>Created in: {{ team.firstYearOfPlay }}</div>
<div>Division: {{ team.division.name }} - Conference: {{ team.conference.name }}</div>
<h2>Roster</h2>
<div v-for="player in team.roster.roster" :key="team.id + '-' + player.person.id">
<h3>{{ player.person.fullName }}</h3>
<div>
Number: {{ player.jerseyNumber }} - Position: {{ player.position.name }} {{ player.position.type }}
</div>
</div>
</div>
<div v-for="team in hockeyDataList.teams" :key="team.id" >
<p v-for="roster in team.roster.roster :key="roster.id">
{{ roster.person.fullName }}
</p>
</div>
It's not necessary, but if you want to, you can get index in a v-for as well:
<div v-for="(team, index) in hockeyDataList.teams" :key="team.id" >
<p>
{{ hockeyDataList.teams[index].roster.roster[1].person.fullName }}
</p>
</div>
v-for increments the index automatically for you. The problem is that the api returns a json and not an array we can iterate on. By looking at the api response, we can see that teams is the array we can iterate on.
<v-for="(team, i) in hockeyDataList.teams" :key="i">
where index automatically increments until the end of list. We can then iterate through the roster.
<v-for="(roster, j) in team.roster.roster" :key="j">
Putting it all together
<div v-for="(team, i) in hockeyDataList.teams" :key="i">
<div v-for="(roster, j) in team.roster.roster" :key="j">
<p>{{ hockeyDataList.teams[i].roster.roster[j].person.fullName }}</p>
</div>
</div>

How to use the "ngFor" without repeating the element you've applied it on?

Sorry, I can't post all the code here. But, if you could give me some hint as to what needs to be done, I'd appreciate it.
Let me try to explain my predicament.
So, this is the code snippet I've been trying to use ngFor and ngIf on:
<li class="list-group-item" *ngFor="let option of question.options">
<div class="form-check lead">
<input
class="form-check-input"
type="checkbox"
value=""
id="{{ option.id }}"
[(ngModel)]="option.selected"
(change)="onSelect(question, option)"
disabled="disabled"
/>
<label
class="form-check-label d-block fw-normal"
[attr.for]="option.id"
>
{{ option.name }}
<ng-container *ngFor="let obj of response"
><fa-icon
*ngIf="
obj.qId == option.questionId && obj.correct.includes(option.id)
"
[icon]="faCheck"
></fa-icon
></ng-container>
</label>
<div>
<img
src="{{ option.image }}"
alt=""
onerror="this.onerror=null; this.remove();"
width="55"
height="55"
/>
</div>
</div>
</li>
Before this code there is this element,
<div class="d-flex flex-column bg-white px-5" *ngFor="let question of filteredQuestions" style="white-space: pre-line">
filteredQuestions() loads the array of objects in which there are questions and inside of a question, there is array of options.
Now, what I want is to display the check icon right next to the correct option.
This is the response array:
response = [
{ qId: '60e57c069107a038085ae3a1', correct: [1001, 1002] },
{ qId: '60e57cc09107a038085ae3a2', correct: [1002] },
{ qId: '60e57d289107a038085ae3a3', correct: [1003] },
{ qId: '60e57d9e9107a038085ae3a4', correct: [1001, 1002, 1003] },
{ qId: '60e57e7c9107a038085ae3a5', correct: [1004] }];
This is the overall structure for the options:
[{id: number;
questionId: number;
name: string;
image: string;
selected: boolean;}]
I've tried to use the ngFor on different tags but with no luck. Right now, there are no check icons based on the if condition on the fa-icon tag.
And when I remove the ngIf from the fa-icon tag in this case, the checks just print multiple times. Where can I apply the tag so that the element won't repeat and I can get my desired output?
The problem is that your option.id is not number, so this obj.correct.includes(option.id) was not satisfied and does not meet your *ngIf condition in below line of code:
<fa-icon *ngIf="obj.qId == option.questionId && obj.correct.includes(+option.id)" [icon]="faCheck"></fa-icon>
So easy way to solve the problem is that put + next to the option.id:
obj.correct.includes(+option.id)
<ng-container *ngFor="let obj of response">
<fa-icon *ngIf="obj.qId == option.questionId && obj.correct.includes(+option.id)" [icon]="faCheck"></fa-icon>
</ng-container>
Here is working sample
And the result:

Conditionally hide the nth element of a v-for loop without modifying the array. vue 3 composition api search function

I have a ref variable (foxArticles ), which holds a list that contains 100 items. In a v-for loop i loop over each value. As a result, i have 100 values rendered on the page.
<template>
<div class="news_container">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput)"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const foxArticles = ref([]);
</script>
I also have a search function, which returns the value, if it includes the passed in keyword. The function is used in the child of the v-for loop.
<div class="search_input_container">
<input
type="text"
class="search_input"
v-model="keywordInput"
/>
</div>
<script>
const keywordInput = ref("");
function containsKeyword(article, keywordInput) {
if (article.title.toLowerCase().includes(keywordInput.toLowerCase())) {
return article;
}
}
</script>
The problem is, i can't use .slice() on the foxArticles array in the v-for loop, because that screws up the search functionality, as it returns only the values from the sliced range.
How can i have the access the all of the values of the array, while not rendering all 100 of returned articles on the initial load?
Any suggestions?
I think your approach will make it incredibly complex to achieve. It would be simpler to always iterate over some set, this set is either filtered based on a search-term, or it will be the first 100 items.
I'm not very familiar yet with the Vue 3 composition api so I'll demonstrate with a regular (vue 2) component.
<template>
<div class="news_container">
<div
v-for="article in matchingArticles"
v-bind:key="article"
class="article_single_cell"
>
... news_box ...
</div>
</div>
</template>
<script>
export default {
...
computed: {
matchingArticles() {
var articles = this.foxArticles;
if (this.keywordInput) {
articles = articles.filter(article => {
return this.containsKeyword(article, this.keywordInput)
})
} else {
// we will limit the result to 100
articles = articles.slice(0, 100);
}
// you may want to always limit results to 100
// but i'll leave that up to you.
return articles;
}
},
....
}
</script>
Another benefit is that the template does not need to worry about filtering results.
ok, so i kind of came up with another solution, for which you don't have to change the script part...
instead of having one v-for loop , you can make two of them, where each one is wrapped in a v-if statement div
The first v-if statement says, If the client has not used the search (keywordInput == ''), display articles in the range of (index, index)
The second one says = If the user has written something (keywordInput != ''), return those articles.
<template>
<div class="news_container">
<!-- if no search has been done -->
<div v-if="keywordInput == ''">
<div
v-for="article in foxArticles.slice(0, 4)"
v-bind:key="article"
class="article_single_cell"
>
<div class="news_box shadow hover:bg-red-100 ">
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
<!-- if searched something -->
<div v-else-if="keywordInput != ''">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput) && keywordInput != ''"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
im not sure how this impacts performance tho, but that's a problem for another day

Adding some logic in v-for with v-if Vue

I have some json with below results
{
"module": [
{
"id": 1,
"title": "Module 1",
"type": "URL",
"size": 1,
"image": "https://localhost/image1.png"
},
{
"id": 2,
"title": "Module 2",
"type": "YOUTUBE",
"size": 2,
"image": "https://localhost/image2.png"
}
]
}
Now i want to render it on a page with some loop and conditional, like below
<template>
<section class="page-section homescreen mt-4">
<div class="container">
<div class="row">
<div class="col-lg-3" v-bind:key="data.index" v-for="data in modules" v-if="data.size == 1">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
<div class="col-lg-6" v-bind:key="data.index" v-for="data in modules" v-if="data.size == 2">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
</div>
</div>
</section>
</template>
<script>
export default {
data() {
return {
modules: [
{
"id": 1,
"title": "Module 1",
"type": "URL",
"size": 1,
"image": "https://localhost/image1.png"
},
{
"id": 2,
"title": "Module 2",
"type": "YOUTUBE",
"size": 2,
"image": "https://localhost/image2.png"
}
]
};
}
};
</script>
But instead of success, i got some error saying
5:99 error The 'modules' variable inside 'v-for' directive should be
replaced with a computed property that returns filtered array instead.
You should not mix 'v-for' with 'v-if' vue/no-use-v-if-with-v-for
8:99 error The 'modules' variable inside 'v-for' directive should be
replaced with a computed property that returns filtered array instead.
You should not mix 'v-for' with 'v-if' vue/no-use-v-if-with-v-for
Actually i want to create the <div class="col-lg-3"> part to be dynamic based on the json, if size:1 mean to have col-lg-3 and if size:2 mean to have col-lg-6
Any explanation and suggestion will be appreciated.
Thank you
eslint-plugin-vue was telling you to do this:
<div class="col-lg-3" v-bind:key="data.index" v-for="data in modules.filter(o=>o.size == 1)">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
<div class="col-lg-6" v-bind:key="data.index" v-for="data in modules.filter(o=>o.size == 2)">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
In your case, it can be simplified as
<div :class="'col-lg-'+data.size*3" v-bind:key="data.index" v-for="data in modules">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
If it's essentially something with css classes, why don't you use v-bind:class or :class with your condition ?
https://v2.vuejs.org/v2/guide/class-and-style.html
But like said in the error message you'll certainly have to create a sub component and then use props on it
https://v2.vuejs.org/v2/guide/components-props.html
In case you have just two sizes:
You can use Computed Properties to achieve this requirement.
First, create two new computed properties like:
computed: {
modulesSize1: function() {
return this.modules.filter(x => x.size == 1)
},
modulesSize2: function() {
return this.modules.filter(x => x.size == 2)
}
}
Now, you can easily loop through computed properties modulesSize1 && modulesSize2 like:
<div class="row">
<div class="col-lg-3" v-bind:key="data.index" v-for="data in modulesSize1" >
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
<div class="col-lg-6" v-bind:key="data.index" v-for="data in modulesSize2" >
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
</div>
In case you have multiple sizes:
First, create a simple method like:
methods: {
getClass: function(size) {
return `col-lg-${size * 3}`
},
}
and then we can update template and use Class Bindings like:
<div :class="getClass(data.size)" :key="data.index" v-for="data in modules">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
The v-if is essentially baked in to the v-for (if modules is empty nothing will render) so it's recommended not to use them in combination. If you need it for a separate condition, like you do here, then you'll have to move your v-for on to the <img> itself.
You also won't be able to use data.size this way so you'd have to use something like v-if="modules[0].size == 1" etc.
Edit
#Palash answer is probably the more efficient way to solve this.
Edit 2
#r0ulito and #xianshenglu also makes a really good point, if it's just a class issue, use v-bind.

Categories

Resources