Vue.JS for loop producing confusing results - vue.js

Trying some vue.js for the first time and I've run into a weird issue. Right now my goal is to style text using a unique color for lines in a play spoken by different characters, here's my code:
<div id="app">
<ul>
<li v-for="part in scene">
<span v-if="part.character = 'MACBETH'">
<p>{{part.character}}</p>
<p v-bind:style="'color:red;'" >{{part.dialogue}}</p>
</span>
<span v-else-if="part.character = 'LADY MACBETH'">
<p>{{part.character}}</p>
<p v-bind:style="'color:blue;'" >{{part.dialogue}}</p>
</span>
</li>
</ul>
</div>
<script>
var app = new Vue({
el: '#app',
data:{
//Scene from http://shakespeare.mit.edu/macbeth/macbeth.1.7.html
scene:[
{character:'MACBETH', dialogue:"Hath he ask'd for me?"},
{character:'LADY MACBETH', dialogue:"Know you not he has?"},
{character:'MACBETH', dialogue:"We will proceed no further in this business:\nHe hath honour'd me of late; and I have bought\nGolden opinions from all sorts of people,\nWhich would be worn now in their newest gloss,\nNot cast aside so soon."},
{character:'LADY MACBETH', dialogue:"Was the hope drunk\nWherein you dress'd yourself? hath it slept since?\nAnd wakes it now, to look so green and pale\nAt what it did so freely? From this time\nSuch I account thy love. Art thou afeard\nTo be the same in thine own act and valour\nAs thou art in desire? Wouldst thou have that\nWhich thou esteem'st the ornament of life,\nAnd live a coward in thine own esteem,\nLetting 'I dare not' wait upon 'I would,'\nLike the poor cat i' the adage?"}
]
//Next line
//MACBETH
//Prithee, peace:
//I dare do all that may become a man;
//Who dares do more is none.
}
});
</script>
The intended result is that Macbeth's lines appear in red and Lady Macbeth's lines appear in blue, however as it currently functions my output looks like this:
The character name is being changed and all the text is red rather than blue for Lady Macbeth's lines.
Any pointers? I'm brand new to this so still trying to figure out the basics.

You should look at the vue documentation - how you would style elements based on conditions. You could use the :style binding or the :class binding like this:
Vue.createApp({
data() {
return {
scene:[
{character:'MACBETH', dialogue:"Hath he ask'd for me?"},
{character:'LADY MACBETH', dialogue:"Know you not he has?"},
{character:'MACBETH', dialogue:"We will proceed no further in this business:\nHe hath honour'd me of late; and I have bought\nGolden opinions from all sorts of people,\nWhich would be worn now in their newest gloss,\nNot cast aside so soon."},
{character:'LADY MACBETH', dialogue:"Was the hope drunk\nWherein you dress'd yourself? hath it slept since?\nAnd wakes it now, to look so green and pale\nAt what it did so freely? From this time\nSuch I account thy love. Art thou afeard\nTo be the same in thine own act and valour\nAs thou art in desire? Wouldst thou have that\nWhich thou esteem'st the ornament of life,\nAnd live a coward in thine own esteem,\nLetting 'I dare not' wait upon 'I would,'\nLike the poor cat i' the adage?"}
]
}
},
methods: {
}
}).mount('#demo')
.red {
color: red;
}
.blue {
color: blue;
}
<script src="https://unpkg.com/vue#next"></script>
<div id="demo" class="demo">
<ul>
<li v-for="part in scene" :key="part.id">
<p>{{ part.character }}</p>
<p :class="[
{ 'red': part.character === 'MACBETH' },
{ 'blue': part.character === 'LADY MACBETH' }
]" >{{part.dialogue}}</p>
</li>
</ul>
</div>
With this approach you can remove your if/else conditions to keep your template clean.

You need to use == instead of = when comparing two values in the v-if/v-else-if directives.
<div id="app">
<ul>
<li v-for="part in scene">
<span v-if="part.character == 'MACBETH'">
<p>{{part.character}}</p>
<p v-bind:style="'color:red;'" >{{part.dialogue}}</p>
</span>
<span v-else-if="part.character == 'LADY MACBETH'">
<p>{{part.character}}</p>
<p v-bind:style="'color:blue;'" >{{part.dialogue}}</p>
</span>
</li>
</ul>
</div>
<script>
var app = new Vue({
el: '#app',
data:{
//Scene from http://shakespeare.mit.edu/macbeth/macbeth.1.7.html
scene:[
{character:'MACBETH', dialogue:"Hath he ask'd for me?"},
{character:'LADY MACBETH', dialogue:"Know you not he has?"},
{character:'MACBETH', dialogue:"We will proceed no further in this business:\nHe hath honour'd me of late; and I have bought\nGolden opinions from all sorts of people,\nWhich would be worn now in their newest gloss,\nNot cast aside so soon."},
{character:'LADY MACBETH', dialogue:"Was the hope drunk\nWherein you dress'd yourself? hath it slept since?\nAnd wakes it now, to look so green and pale\nAt what it did so freely? From this time\nSuch I account thy love. Art thou afeard\nTo be the same in thine own act and valour\nAs thou art in desire? Wouldst thou have that\nWhich thou esteem'st the ornament of life,\nAnd live a coward in thine own esteem,\nLetting 'I dare not' wait upon 'I would,'\nLike the poor cat i' the adage?"}
]
//Next line
//MACBETH
//Prithee, peace:
//I dare do all that may become a man;
//Who dares do more is none.
}
});
</script>

The surprising thing with the answers is that none has added the very important v-for attribute -> key.
Even if it's not that the issue of this missing attribute, however it is recommended always the key to be added as a best practise.
If each iterated item has an id you can do this:
v-for="part in scene" :key="part.id"
or alternatively use the index
v-for="(part,index) in scene" :key="part.index"
More info here

Related

Any problems with defining variables inside v-for?

AFAIK, first shown here by Vladimir Milosevic
<div
v-for="( id, index, user=usersData[id], doubleId=doubleThat(id) ) in users"
:key="id">
{{ user.name }}, {{ user.age }} years old And DoubleId = {{ doubleId }}
</div>
I expanded his codepen. Looks like you can define several variables local to the loop this way.
Are there any side-effects or risks you see with this technique?
I find this way to be the most convenient and clean out there but since that is apparently not the intended use it may not be future-proof.
EDIT:
Here is another pen which demonstrates the use case better. While here we reference just one computed property in one place, if the number of properties and references grows, it will lead to a verbose code.
In general, I think the clean way to do what you want to do is to bring the data into a format, where you can loop over it without extra steps. This way, you will never need those additional variables in v-for.
The example from your codepen could look like this:
const app = new Vue({
el: '#app',
data: {
years: [2024, 2025, 2120],
usersData: {...}
},
computed: {
// user objects for each year
ageData() {
return Object.fromEntries(this.years.map(year => [year,
Object.values(this.usersData).map(user => this.userInYear(user, year))
] ))
}
},
methods: {
userInYear(user, year){
return {...user, age: user.age + (year - 2023)}
}
},
});
Now you can iterate it without interruption and shenanigans:
<div v-for="(users, year) in ageData" :key="year">
<b>In {{ year }}:</b>
<div v-for="user in users" :key="user.name">
{{ user.name }} will be {{ user.age }} years old.
</div>
</div>
It is easier to read and change, and it has the added benefit that you can inspect ageData in the console.
That said, I think it is very interesting that you can declare additional variables in v-for, I assume it is possible because of the way vue parses and builds expressions from the provided string, I am pretty sure it won't be removed, and if you and the people you work with are comfortable with it (and are prepared to hear the above over and over like just now), by all means, go for it.

Vue Warning on Console : You may have an infinite update loop in a component render function

I'm a new student of Computer Science and I have a school project to do on Vue.js.
It is working, but it shows the following Warning:
[Vue warn]: You may have an infinite update loop in a component render function.
English is not my first language, so I'm sorry if I write anything wrong in advance, but I'll try to explain what I have to do on the project.
I have to create a Photo Grid/Album that shows no more than 3 photos on each line. Something like this:
Example of how it has to look
The code is like this:
<template>
<v-container>
<!-- 'v-container' est´substituindo ' div class="clubes-lista" '-->
<div class="grid-photo">
<v-container class="grey lighten-3">
<v-row
v-for="index in numberLines()"
:key="index"
class="grey lighten-3"
>
<v-col v-for="n in 3" :key="n" cols="4">
<v-card v-if="numberPhotos > checkInsertedAllPhotos()" outlined>
<v-img
src="https://i.pinimg.com/736x/96/28/b7/9628b7892fd0543782f53eeec4bae502.jpg"
alt="Bird Photo"
>
</v-img>
<div class="photo-subtitle">
<v-icon size="15">mdi-heart-outline</v-icon>
<p>The Cockatiel</p>
</div>
</v-card>
<v-card v-else></v-card>
</v-col>
</v-row>
</v-container>
</div>
</v-container>
<script>
export default {
name: "PhotoGrid",
data() {
return {
listPhotosOnGrid: [],
numberPhotos: 0,
numberTotalLines: 0,
countPhotos: 0,
};
},
computed: {},
methods: {
createPhotosList() {
var listPhotos = [];
for (var i = 0; i < 10; i++) {
var aux = {
id: i + 1,
src:
"https://i.pinimg.com/736x/96/28/b7/9628b7892fd0543782f53eeec4bae502.jpg",
subtitle: "The cockatiel",
};
listPhotos.push(aux);
}
this.listPhotosOnGrid = listPhotos;
this.numberPhotos = listPhotos.length;
},
numberLines() {
this.createPhotosList();
var photosMultipleThree = this.numberPhotos;
while (photosMultipleThree % 3 != 0) {
photosMultipleThree = photosMultipleThree + 1;
}
this.numberTotalLines = photosMultipleThree / 3;
return photosMultipleThree / 3;
},
checkInsertedAllPhotos() {
var cont = this.countPhotos++;
return cont;
},
},
};
<style scoped>
.photo-subtitle {
font-size: 10px;
display: flex;
flex-direction: row;
justify-content: left;
align-items: flex-start;
padding: 0;
margin-top: 10px;
}
.photo-subtitle p {
margin-left: 5px;
}
So... Trying to figure out what is happening, I think the Warning comes from the following line:
<v-card v-if="numberPhotos > checkInsertedAllPhotos()" outlined>
The if is because it can't show more images that what is given. For example, each line shows 3 photos, so if I don't have a total of photos that is multiple os 3, I don't want to fill all columns on the last line, only the necessary to show all the photos.
But I just think, based on some tests, I'm not sure if the Warning comes from this line.
The code works, but I feel something is not right. (It works partialy actually, because I still don't know how to get the info from the array to show, so it only loops the same thing)
I want to learn how to make it right.
So does anyone can tell me what I can do to make the Warning disappear or if I have to change everything, and make make a new logic for it to work properly.
I just want to learn.
Thank you so, so much and sorry again if this is a stupid question, or if I'm making everything wrong, hehe.
And sorry if I wrote anything wrong.
:)
I've had a look to your code. First I would recommend you to stop using the var keyword but use let or const instead (you may check the question here and have a look at the scope based on those keyword, the var scope is very unusual nowadays and it will lead you to some issues).
Regarding your particular issue of infinite update loop, the problem comes from your checkInsertedAllPhotos function. I would explain it like this :
In the v-if Vue internal mechanisms checks if the element should be rendered by triggering the function checkInsertedAllPhotos
When executing the function checkInsertedAllPhotos the data countPhotos is increased (by the way it would be better to just do
this.countPhotos ++; return this.countPhotos;
Since the Data has been modified Vue is triggering a re-render of the page
Since there is a re-render Vue checks in the v-if if the element should be rendered by triggering the function checkInsertedAllPhotos.
And it could be infinite if Vue doesn't prevent it !
I would suggest to re-work a little bit your logic indeed. However a quick solution for your problem would be that when you're checking an index in your list : for example if you want to display listPhotosOnGrid[i], you check if i<numberPhotos with a v-if and in the v-else you can display something else if you want.
I also think you could have a much simpler code, by leveraging css (for example css-grid). You can have something as simple as this :
<div v-for="n in 100">
<img v-bind:src="`https://i.pinimg.com/photos/${n}.png`">
</div>
and some css to style it and only have 3 items per rows. If you want to learn about css-grid I would recommend Mozzila documentation or the following tutoriel.
Right now I have a little trouble understanding the exact question, since there are multiples topics inside to solve but I hope my explanation on the infinite loop will help you !
Have fun while learning Vue !
Thank you everyone who helped me, and you sure helped me!!!!
I'm sorry I could only anwser today.
I think I was able to do much better on the code, I changed it a lot, specially the logic on the code. And I think it can get even better, but for sure I already made a big progress, hehe.
I was a bit reluctant of posting this question because even though that as the time that I posted it I wasn't sure of what to do, I kinda of had the feeling that was something stupid (like a stuid question), and that I would only be taking the time of anyone who stopped by my question. But I only got amazing answers, with everyone being so patient, so THANK YOU SO, SO MUCH!!
(I'm also new to StackOverflow, so I hope I'm making everything right, hehe)

how to use vue-glide-js events on nuxt and load more slides on last one

i'm new on all of these so i need help. first of all how vue-glide-js events work to begin with. its documentation just listed them without examples.
And second and more important, i wanna send an axios request at the end of my carousel and load more items. i couldn't work with events so used active slide and watched it and on last slide sent and updated my data but slide still shows the previous one. what should i do?
it is the simplified version of my code
<template>
<div>
<vue-glide v-bind="carouselOptions" v-model="active">
<vue-glide-slide v-for="i in array" :key="i">
Slide {{ i }}
</vue-glide-slide>
</vue-glide>
<p>{{active}}</p>
</div>
</template>
<script>
export default {
data(){
return{
carouselOptions:{
direction: process.env.SITE_DIRECTION,
autoplay: false,
perView: this.$device.isMobileOrTablet ? 4 : 8,
peek: { before: 0, after: 50 }
},
array: [1,2,3,4,5,6,7],
active: 0
}
},
watch:{
active(){
console.log('active = ' + this.active)
if(this.active > this.array.length - 3){
this.array= [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
}
}
},
mounted(){
//
}
}
</script>
as you can see i manually added more items to my array but glide still show 7 items. i know it has something to do with mounting but dont know what to do.
and one more thing. if there is any better carousel that support rtl and breakpoint (items per view on different width) i would be happy to know. tanx
To use call a method at the end of slides just using #glide:run-end:
<template>
<div>
<vue-glide v-bind="carouselOptions" v-model="active" #glide:run-end="myMethod">
<vue-glide-slide v-for="i in array" :key="i">
Slide {{ i }}
</vue-glide-slide>
</vue-glide>
</div>
</template>
to show the new slides, it's a bit tricky! as far as i know the only way is to refresh (reload) the component! so put a key on vue-glide and change it after each new added slides. the only problem is, if there is images on slides, old ones also will be load again. (it's a suggestion and didn't tried my self yet, but maybe with a caching system, it may be solved)

VueJS: computed property using another computed property isn't working correctly

this is not these: post1, post2
i'm building a small SPA and so i started with this basic principle that i tested and built up into the code farther below that i'm having problems with. it works ONLY if i call the secondary property in the DOM (why??) and i've included it to illustrate where i started from, hoping that the principles would translate as i built up.
JS:
var app = new Vue({
el: '#app',
computed: {
today: function() {
return new Date().getTime();
},
tomorrow: function() {
return this.today + (1*24*60*60*1000);
},
});
DOM:
<div id='app'>
<p>{{ today }}</p>
<p>{{ tomorrow }}</p>
</div>
so. that works. again, ONLY if app.tomorrow is called in the DOM. (wish i knew how NOT to need that...) BUT my problem is this next part, which i based on this starter code. it DOESN'T work:
JS:
var app = new Vue({
el: '#app',
data: {
dueNow: 0,
reports: { 90: [], 'DN': [] },
},
computed: {
today() {
return new Date().getTime();
},
tMinus30() {
return new Date().getTime() - (30 * 24 * 60 * 60 * 1000);
},
dueNow() {
var count = 0;
$.each(this.rows, function(index, obj) {
var workingDate = new Date(obj.dateSubmitted).getTime();
console.log('workingDate: '+workingDate);
console.log('today: '+this.today); <-- console reports 'undefined'!?
console.log('today: '+this.tMinus30); <-- console reports 'undefined'!?
if(workingDate < this.today && workingDate > this.tMinus30){
console.log('knock, knock: due NOW'); <-- therefore this never fires
count++;
console.log('count: '+count);
// build reports array - doesn't work, but that's for another day...
this.reports['DN']['count'] = push(count);
this.reports['DN'][index]['reportID'] = push(obj.id);
this.reports['DN'][index]['assetID'] = push(obj.assetid);
}
});
return count;
},
}
});
DOM:
<div id='app'>
<div class="col-lg-2">
<h1 class="text-center pt-2">NOW</h1>
<p>{{ today.getDay() }}</p>
<p class="img-text mb-1" #click="expandInfo('DN')">{{ dueNow }}</p>
</div>
</div>
so not only am i not getting the count of rows that are dueNow, i'm not getting today.getDay() calculated either, which, based on this from VueJS, should work. i suspect this is a flying nightmare, but i don't see it... it DOES iterate over the rows properly. it DOES calculate the working date for each row. but nothing else works. i thought i understood VueJS well enough to attempt this. and i even built it up from the first example... but i'm at a loss as to what's broken.
what principle am i overlooking to make this gel? it feels like some concept has eluded me.
EDIT: i also perused this article but don't know that it applies as there's nothing that isn't declared in the data already...
EDIT: OK! :) i need to clarify. when i say "it doesn't work", what i mean is the computed property tomorrow does not render and reports the other computed property in the console as "undefined", despite it (today) rendering just fine (without the additional method). my expected/desired result is that i will see a number indicating how many rows passed muster and are dueNow and i want to see the today rendered as "Monday" and tomorrow as "Tuesday" (for example). this last should be interchangeable with other date functions. if possible, i'd also like to have the array reports update properly... but that's out of scope right now.

Vue.js auto-convert HTML and Unicode entities

I'm building a simple book browsing app using the open Google Books API. The problem is that in the API response, there are quotes and unicode entities. Now, when rendering, Vue.js is converting the quotes to HTML entities, and while it's converting unicode back to text, it doesn't apply HTML tags where they should. Let me give you an example:
Pronunciation Guide for \u003cb\u003eElixir\u003c/b\u003e Aether: EE-ther Ananke: ah-NAN-key Agapi: ah-\u003cbr\u003e\nGAH-pee Apollyon: a-POL-ee-on Atropos: ah-TRO-poes Clothe: KL O-tho \u003cbr\u003e\nDaimon: DEE-mun Hematoi: HEM-a-toy Khalkotauroi: kal-koh-TOR-oy CHAPTER \u003cbr\u003e\n1 ALEX ...
The text above (from the raw API response) gets converted into this:
You can see that the <b> tags were left untouched. I can understand this because one decoding was definitely done (from Unicode to HTML), but how can I make it to actually interpret and apply the HTML?
Here's another example, where I'd like to convert a quote code to the actual quotation mark, but it doesn't happen:
Raw API response:
ABOUT THE AUTHOR Benedict Anderson is one of the world's leading authorities on South East Asian nationalism and particularly on Indonesia.
Rendering:
Here's the code I'm using in my component (rather simple):
<template>
<div class="book-listing">
<div class="book-image">
<img :src="book.volumeInfo.imageLinks.smallThumbnail">
</div>
<div class="book-title">
{{ book.volumeInfo.title }}
</div>
<div class="book-description">
{{ book.searchInfo.textSnippet }}
</div>
</div>
</template>
<script>
export default {
props: [ 'book' ]
}
</script>
Maybe you can use v-html
<div class="book-title" v-html="book.volumeInfo.title">
Btw - v-html sets the whole inner html content of element, if wonder if exists some simple way to use result as attribute:
// Amortisseur arri\ère >> Amortisseur arrière
function decodeHtml(html) {
var txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}