Mix Dynamic class with data binders in Vue.js - javascript

So I have the following v-for in a HTML:
<ul v-for="(item, index) in openweathermap.list">
<li>{{item.dt_txt}}</li>
<li>{{item.weather[0].description}}</li>
<li>{{item.weather[0].id}}</li>
<li>{{item.main.temp}}°C</li>
</ul>
What I want to do is to add an icon to these information, like font awesome.
So I found these: <i class="owf owf-200"></i> This will serve me just right but the number must change dynamically. So the number is the {{item.weather[0].id}} in the v-for.
My question is this; How can I mix these two together?
I tried something like this <i class="owf owf-{{item.weather[0].id}}"></i>
but it obviously has wrong syntax.
Any help will be greatly appreciated!

You can use the v-bind:class - which allows you to append two strings, just like in Javascript. So the value should be 'owf owf-' + item.weather[0].id.
In the snippet, I've done that with dummy data and color changes for two different classes, but you should get the idea.
var app = new Vue({
el: "#app",
data:{
items: [
{
weather: [{ id: 200 }],
txt: "Some text"
},
{
weather: [{ id: 300 }],
txt: "Some other text"
}
]
}
});
.owf.owf-200 {
color: red;
}
.owf.owf-300 {
color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<template v-for="item in items">
<span v-bind:class="'owf owf-' + item.weather[0].id">
{{ item.txt }}
</span>
<br />
</template>
</div>

Related

How to iterate the HTML elements with some specific condition?

Problem statement : v-for loop is iterating and binding the data properly in HTML template but not able to iterate it partially based on some condition. Please find below the JSFiddle link for demo.
Requirement : In above demo link, for "Second Section" I want to display the input textbox only once which will be vertically aligned center (in front of beta) instead of repeating it multiple times. other values will be repeat (i.e. alpha, beta, gama).
Fiddle
var arr = [{
sectionName: 'First Section',
data: ['alpha', 'beta']
}, {
sectionName: 'Second Section',
data: ['alpha', 'beta', 'gama']
}];
var myitem = new Vue({
el: '#my-items',
data: {
items: arr
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="my-items">
<div v-for="item in items">
{{ item.sectionName }} <hr>
<div v-for="sectionData in item.data" style="margin: 5px">
<span style="width:50px;text-align:left;display:inline-block;">{{ sectionData }}</span> <input type="textbox"/>
</div>
</div>
</div>
You can pass index into your v-for, then use it in conditionals inside the loop. I've modified your example to show the principle. The text box appears for every section on the first loop, or if sectionData === beta. You can see this condition in the v-if.
This works, but in general, every time you use v-for, you should create a component. The structure quickly gets difficult to understand otherwise.
var arr = [{
sectionName: 'First Section',
data: ['alpha', 'beta']
}, {
sectionName: 'Second Section',
data: ['alpha', 'beta', 'gama']
}];
var myitem = new Vue({
el: '#my-items',
data: {
items: arr
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="my-items">
<div v-for="(item, index) in items">
{{ item.sectionName }} <hr>
<div v-for="sectionData in item.data" style="margin: 5px">
<span style="width:50px;text-align:left;display:inline-block;">{{ sectionData }}</span>
<input
v-if="index === 0 || sectionData === 'beta'"
type="textbox"
/>
</div>
</div>
</div>

Random number changes every time an event happens

So I have this array of colors in my data and I'm getting a random element from it by using Math.random() the problem is every time I click on <v-autocomplete :items="types" label="User type" multiple v-model="filters"></v-autocomplete>
the random color changes.
Here is a simplified version of my code:
<template>
<div>
<div
:key="index"
style="width:200px;height:200px;"
:style="'background-color:' + colors[Math.floor(Math.random() * 10)]"
v-for="(item, index) in items"
>
</div>
<v-autocomplete :items="types" label="User type" multiple v-model="filters"></v-autocomplete>
</div>
</template>
my data:
data: () => ({
items: [
{
name: "a"
},
{
name: "b"
},
{
name: "c"
}
],
colors: [
"#C004D9",
"#AB05F2",
"#A69C0F",
"#2745F2",
"#1B78F2",
"#F2BE22",
"#F2E635",
"#F29849",
"#2405F2",
"#6503A6",
"#010440",
"#F2E74B"
],
types: ["user", "admin", "manager"]
})
My question is can I stop Vue from updating the random number when I click on something or change some data?
If the data properties or computed properties the colorful DIVs references will not change when click 'AutoComplete', you may consider below two solutions:
Solution 1:
Uses the directive v-once:
Rendering plain HTML elements is very fast in Vue, but sometimes you
might have a component that contains a lot of static content. In these
cases, you can ensure that it’s only evaluated once and then cached by
adding the v-once directive to the root element
Vue.use(VAutocomplete.default)
new Vue({
el: "#app",
data () {return {
items: [
{
name: "a"
},
{
name: "b"
},
{
name: "c"
}
],
colors: [
"#C004D9",
"#AB05F2",
"#A69C0F",
"#2745F2",
"#1B78F2",
"#F2BE22",
"#F2E635",
"#F29849",
"#2405F2",
"#6503A6",
"#010440",
"#F2E74B"
],
types: ["user", "admin", "manager"],
filters: []
}}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/v-autocomplete#1.8.2/dist/v-autocomplete.min.js"></script>
<div id="app">
<v-autocomplete :items="types" label="User type" multiple v-model="filters"></v-autocomplete>
<div v-once>
<div
:key="index"
style="width:200px;height:200px;"
:style="'background-color:' + colors[Math.floor(Math.random() * 10)]"
v-for="(item, index) in items"
>
</div>
</div>
</div>
Solution 2:
Wraps the colorful DIVs into one component, if the dependencies of the component don't trigger the reactivity, the component will not be updated.
Vue.use(VAutocomplete.default)
Vue.component('v-color', {
'template': `
<div>
<div
:key="index"
style="width:200px;height:200px;"
:style="'background-color:' + colors[Math.floor(Math.random() * 10)]"
v-for="(item, index) in items"
>
</div>
</div>
`,
data () {
return {
colors: [
"#C004D9",
"#AB05F2",
"#A69C0F",
"#2745F2",
"#1B78F2",
"#F2BE22",
"#F2E635",
"#F29849",
"#2405F2",
"#6503A6",
"#010440",
"#F2E74B"
],
items: [
{
name: "a"
},
{
name: "b"
},
{
name: "c"
}
],
}
}
})
new Vue({
el: "#app",
data () {return {
types: ["user", "admin", "manager"],
filters: []
}},
methods: {
clickSomething() {
this.types.push('a')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/v-autocomplete#1.8.2/dist/v-autocomplete.min.js"></script>
<div id="app">
<!-- <v-autocomplete :items="types" label="User type" multiple v-model="filters"></v-autocomplete> -->
<v-autocomplete :items="types" label="User type" multiple v-model="filters"></v-autocomplete>
<v-color></v-color>
</div>
or as the answer from #Rick, you can pre-calculate the color value for each DIVs first in data properties or computed properties, then binds it to the :style="color:_"
you don't want to have a random function in your style. That style is going to fire an indefinite number of times.
instead create a variable when the page loads that gets populated by your random function. Then use that variable to define your style.

v-model on input change is heavy on performance

So I have a page rendering a v-list based on an array like so :
<v-list-tile v-for="item in array">
{{item}}
</v-list-tile>
and a dialog with a v-text-field :
<v-dialog>
<v-text-field v-model="myInput">
</v-text-field>
</v-dialog>
For now it's pretty normal.
But with a performance test, I saw that for every event triggered by a change on myInput model (like a key press) the v-for is also triggered re-rendering the list when they are actually not related.
On my huge array, it's a serious problem and make the UI really laggy. I think it's a normal behavior for a vuejs application, but I was wondering if I could precisely tell wish element to check for re-rendering.
I tried some v-if statements but it didn't do the trick.
I hope that there is an answer to that, i guess i'm missing something.
If you want to test what i'm talking about here is a ready to go html file, please debug it with your debug console, you will see a [vue warn] message of the duplicated key attesting of the fact that the v-for is indeed called for every key press.
Imagine now if the array (here items) is way bigger than that, and wrapped into complex components, making that call is just too heavy on performance when we are just aiming to change the "myInput" value.
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{data}}
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
<input v-model="data"></input>
</div>
</body>
<script>
new Vue({
el: '#app',
data: () => ({
data: '',
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})
</script>
</html>
Here's a codepen showing the inner loop in its own component
Codepen.io
I've added Date.now() after items[x].message list items to show when the list is being rerendered.
In case codepen ever goes down:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
Main vue: {{data}}
<loop-component :data="loopdata"></loop-component>
<input v-model="data"></input>
<input v-model="loopdata"></input>
</div>
<script>
Vue.component('loop-component', {
props: ['data'],
data() {
return {
items: [
{message: 'Foo'},
{message: 'Bar'}
]
}
},
template: `
<div>
Loop component: {{ data }}
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.message + ' Date.now(): ' + Date.now() }}
</li>
</ul>
</div>
`
});
let app = new Vue({
el: '#app',
data: () => ({
data: '',
'loopdata': '',
items: [
{message: 'Foo'},
{message: 'Bar'},
]
}),
});
</script>
</body>
</html>
Try using .lazy modifier to sync after change events.
<input v-model.lazy="data"></input>
https://v2.vuejs.org/v2/guide/forms.html#lazy
EDIT
#IVO GELOV is right, when a component changes, this re-render. The solution is split your component into several child components.
https://v2.vuejs.org/v2/guide/reactivity.html
This is a code using slots to make it look like your example.
HTML
<div id="app">
<new-component>
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
</new-component>
</div>
Javascript
Vue.component('new-component', {
data: () => {
return {
data: ''
}
},
template: `
<div>
<div>{{ data }}</div>
<slot></slot>
<input v-model="data"></input>
</div>`
})
new Vue({
el: '#app',
data: () => ({
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})
Since Vue 2.0+ whenever a change is detected - the whole component is re-rendered. If you want to avoid that - split your component into several child components.
Your example does not prove your point - the fact that there is a warning about duplicate keys inside the v-for does not mean that v-for is re-evaluated on each keypress. To confirm my statement - just change your code like this:
<li v-for="(item,idx) in items" :key="idx">
Now there is no warning.

Vuejs warning: infinite update loop in a component render function

working through my first project in vue js.
Here, looping through different tabs and show correct content for each tab when clicked on.
https://codepen.io/anon/pen/vWPMGq?editors=1010
problem here is with line
this.cardData = $(".card-content").html(this.coinInfo[this.activeTabName]);
but I'm not sure how to fix this.
You shouldn't be mixing jquery and Vue if it's not 100% necessary.
Here a simple way to do it:
https://jsfiddle.net/gmmujLs4/2/
HTML
<div id="root">
<div class="navbar-start" v-for="tab in tabs">
<a class="navbar-item" href="#" #click="activeTabName = tab.name">{{tab.name}}</a>
</div>
<div class="card-content">
{{ coinInfo[activeTabName] }}
</div>
</div>
Vue instance
new Vue({
el: '#root',
data: {
activeTabName: 'Description',
tabs: [
{
name: 'Description',
},
{
name: 'Features',
},
{
name: 'Technology',
}
],
coinInfo: {
Description:'DescriptionContent',
Features:'FeaturesContent',
Technology:'TechnologyContent'
}
}
})
coinInfo could be passed by properties instead of beeing declared as data.

binding the property of a data object to DOM element's attribute

I'm a newbie in Vue.js. I have the following lines of code in my HTML and JS file:
HTML
<div id="app">
<ul>
<li v-for="item in items" v-bind:class="{{item.className}}">{{item.text}}</li>
</ul>
</div>
JS
var app = new Vue({
el: '#app',
data: {
items: [
{
className: 'item-1',
text: 'Item 1'
},
{
className: 'item-2',
text: 'Item 2'
},
{
className: 'item-3',
text: 'Item 3'
}
]
}
})
What I want to happen is bind the value of each className to the class attribute of each DOM element. I hope someone could correct me on this.
When using v-bind you don't need to use the {{...}} syntax, because Vue already assumes you will want to use some kind of a property or object.
So you can for example output the value of each className simply like this:
<li v-for="item in items" v-bind:class="item.className">{{item.text}}</li>
Or shorthand version:
<li v-for="item in items" :class="item.className">{{item.text}}</li>
Or if the classes are always going to follow the pattern of item-i:
<li v-for="item, i in items" :class="`item-` + i">{{item.text}}</li>

Categories

Resources