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>
Related
I'm retrieving all groups from an axios request at the page load and I store that data inside an empty array inside a reactive object like this,
const groupHandler = reactive({
groups: [],
});
And when the user clicked one of his product and click on edit a form will appear like this,
Here you can see Group 1 has been repeated. And there is another reactive object to store that user's product's group id,
const productForm = reactive({
group: 2,
});
So when the user clicks on a product productForm.group will be filled with that product's group id. I want to prevent this been duplicated in my edit product form. I'm using the v-for directive to loop the groups array,
<li
v-for="group in groupHandler.groups"
:key="group.id"
:group-id="group.id" >
{{ group.name }}
</li>
So how to prevent this duplicate? In the v-for directive, I could use a condition like if group.id is not equal to productForm.group print group.name But I have no clue to do this. Really appreciate if somebody could help thanks.
You can use v-for in the template element, and then in the li element, you can use v-if condition to only render the group which doesn't have that id
<template v-for="group in groupHandler.groups">
<li
v-if="group.id !== productForm.group"
:key="group.id"
:group-id="group.id" >
{{ group.name }}
</li>
</template>
You can simply achieve this by using Array.filter() method in the v-for directive itself.
v-for="group in groupHandler.groups.filter(({ name }) => !uniqPropValue[name] && (uniqPropValue[name] = true))"
Live Demo :
new Vue({
el: '#app',
data: {
uniqPropValue: {},
groupHandler: {
groups: [{
id: 1,
name: 'Group 1'
}, {
id: 2,
name: 'Group 1'
}, {
id: 3,
name: 'Group 2'
}, {
id: 4,
name: 'Group 3'
}]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<li
v-for="group in groupHandler.groups.filter(({ name }) => !uniqPropValue[name] && (uniqPropValue[name] = true))"
:key="group.id"
:group-id="group.id">
{{ group.name }}
</li>
</div>
I'm making a simple shopping list app in Vue.js and I was curious if there's a standard way of doing what I need to do. I have a list of items with add and delete buttons:
const app = new Vue({
el: '#app',
data: {
items: [
'Chocolate',
'Pizza',
'Coca-Cola',
],
newItem: ''
},
template: `
<div>
<div>{{ items.length }} item{{ items.length !== 1 ? 's' : '' }}</div>
<ul>
<li v-for="(item, index) of items">
{{ item }}
<button #click="deleteItem(index)">X</button>
</li>
<li>
<input type="text" v-model="newItem" placeholder="Item name">
<button #click="addItem">+</button>
</li>
</ul>
</div>
`,
methods: {
addItem() {
const item = this.newItem.trim();
if (item === '') return;
this.items.push(item);
this.newItem = '';
},
deleteItem(index) {
this.items.splice(index, 1);
}
}
});
It works just as it should, but I'm not sure about using data entry that is never displayed anywhere. There's also another approach with $refs:
const app = new Vue({
el: '#app',
data: {
items: [
'Chocolate',
'Pizza',
'Coca-Cola',
],
},
template: `
<div>
<div>{{ items.length }} item{{ items.length !== 1 ? 's' : '' }}</div>
<ul>
<li v-for="(item, index) of items">
{{ item }}
<button #click="deleteItem(index)">X</button>
</li>
<li>
<input type="text" placeholder="Item name" ref="newItem">
<button #click="addItem($refs.newItem.value)">+</button>
</li>
</ul>
</div>
`,
methods: {
addItem(item) {
item = item.trim();
if (item === '') return;
this.items.push(item);
this.$refs.newItem.value = '';
},
deleteItem(index) {
this.items.splice(index, 1);
}
}
});
Instead of using separate data entry and v-model, I'm using $refs directly. Is any of these approaches more widely accepted in Vue.js community or guidelines? Or perhaps there's even more popular way?
I just wanted to share my views here. Personally I like to use v-model as it provides few added benefits like:
We can use .trim modifier with v-model which automatically trims whitespace from user input like:
<input v-model.trim="msg">
This way you don't need to write additional code to trim text like item = item.trim();. Few lines of code saved here.
Using this.newItem = '' we can easily clear out the previously entered text after button click using v-model reactivity feature. So, again less line of code instead of doing this.$refs.newItem.value = '';
Another advantage of using v-model is that, instead of doing
<button #click="addItem($refs.newItem.value)">
You can simply call the function like:
<button #click="addItem">
So, you can see these are the few benefits of using a simple v-model, which is mostly related to the developer experience (DX) point of view.
Working Demo:
const app = new Vue({
el: '#app',
data: {
items: [
'Chocolate',
'Pizza',
'Coca-Cola',
],
newItem: ''
},
template: `
<div>
<div>{{ items.length }} item{{ items.length !== 1 ? 's' : '' }}</div>
<ul>
<li v-for="(item, index) of items">
{{ item }}
<button #click="deleteItem(index)">X</button>
</li>
<li>
<input type="text" placeholder="Item name" v-model.trim="newItem">
<button #click="addItem">+</button>
</li>
</ul>
</div>
`,
methods: {
addItem() {
if (this.newItem === '') return;
this.items.push(this.newItem);
this.newItem = '';
},
deleteItem(index) {
this.items.splice(index, 1);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<div id="app">
</div>
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>
I have this Vue code:
var itembox = new Vue({
el: '#itembox',
data: {
items: {
cookiesncreme: {
name: "Cookies N Cream",
description: "description"
},
chocolateswirl: {
name: "Chocolate Swirl",
description: "description"
},
peanutbutter: {
name: "Peanut Butter",
description: "description"
}
}
}
});
And this HTML code:
<div id="itembox">
<div v-for="(item, index) in items">{{ index }} - "{{ item.name }}"</div>
</div>
I am trying to output the code in a numbered list, for example:
<div>1 - Cookies N Creme</div>
<div>2 - Chocolate Swirl</div>
<div>3 - Peanut Butter</div>
But since my items have keys already, it comes out like this:
<div>cookiesncreme - Cookies N Creme</div>
<div>chocolateswirl - Chocolate Swirl</div>
<div>peanutbutter - Peanut Butter</div>
Is there any other way to get a number count for each item? Thanks!!
In Vue.js, there's actually a third parameter that you can pass in this case. For example, you could do this:
<div id="itembox">
<div v-for="(item, index, i) in items">{{ i+1 }} - {{ index }} - "{{ item.name }}"</div>
</div>
Notice how I used the i parameter, which is actually the index number. I've created a working Fiddle of this here. If you would like to learn more about Vue.js, I hope you'll checkout my Vue.js training course. If this answer helped, I hope you'll mark it as such and vote this item up.
Accourding to the documentation Vue API: v-for
Alternatively, you can also specify an alias for the index (or the key if used on an Object):
<div v-for="(item, index) in items"></div>
<div v-for="(val, key) in object"></div>
<div v-for="(val, key, index) in object"></div>
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.