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

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>

Related

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.

Vue JS toggle class on individual items rendered with v-for

I am using the v-for directive to render a list .
<li v-for="group in groupList" :key="group.id" #dragenter="toggleClass ...."#dragleave="toggleClass ...." >
Content
</li>
What I want is to add a class to the li on which the dragenter event is fired ?
How can I accomplish this ?
How do I even get a reference to the item (the item,not the data property of the parent component)in the first place inside the event handle?and even If I get the reference how to toggle the class from there?
Thanks.
I know vue is data-driven , change the data to reflect on the DOM but I would like a concise solution to this rather than index/Id on the data-model based solutions.Thanks
You can access the li being dragged in the dragenter-callback by accessing event.currentTarget (or even event.target would work in this case), where event is the callback's parameter.
new Vue({
el: '#app',
data() {
return {
grouplist: [
{ id: 1, text: 'a' },
{ id: 2, text: 'b' },
{ id: 3, text: 'c' },
]
}
},
methods: {
onDragEnter(e) {
e.currentTarget.classList.add('drag-enter');
},
onDragLeave(e) {
e.currentTarget.classList.remove('drag-enter');
}
}
})
.drag-enter {
background: #eee;
}
<script src="https://unpkg.com/vue#2.5.16"></script>
<div id="app">
<p draggable>Drag this text over the list items below</p>
<ul>
<li v-for="group in grouplist"
:key="group.id"
#dragenter="onDragEnter"
#dragleave="onDragLeave">{{group.text}}</li>
</ul>
</div>

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.

Mix Dynamic class with data binders in Vue.js

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>

Vue2 error when trying to splice last element of object

I have a Vue2 app wit a list of items which I can choose and show, or delete.
When deleting the last element in the list (and only the last one) - I get Vue warn - "[Vue warn]: Error when rendering root instance: "
my HTML:
<body >
<div id="app">
<ul>
<li v-for="(item, index) in list" v-on:click = "selectItem(index)" >
<a>{{ item.name }}</a>
<div v-on:click="deleteItem(index)">X</div>
</li>
</ul>
<div>
<span>{{selectedItem.name}}</span>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</body>
The JS:
var app = new Vue({
el: '#app',
data: {
index: 0,
selectedItem: {},
list : [
{ id: 1, name: 'org1', desc: "description1"},
{ id: 2, name: 'org2', desc: "description2"},
{ id: 3, name: 'org3', desc: "description3"},
{ id: 4, name: 'org4', desc: "description4"}
]
},
methods: {
deleteItem: function(index) {
this.list.splice(index,1);
},
selectItem: function(index) {
this.selectedItem = this.list[index];
},
}
})
Can you please advise why does this happen and how to solve this issue?
The problem is happening as you have having selectItem bind at li level, so event when you click cross button, selectItem gets executed and that same item gets deleted as well, causing this error.
One way to solve this problem can be moving the selectItem binding inside li as follows
<li v-for="(item, index) in list">
<a v-on:click = "selectItem(index)" >{{ item.name }}</a>
<div v-on:click="deleteItem(index)">X</div>
</li>
See working fiddle.
Another approach can be when printing selectedItem.name in your HTML, you put a null check, whether selectedItem exist or not like following:
<span>{{selectedItem && selectedItem.name}}</span>
See Working fiddle.

Categories

Resources