Reputation: 3347
A form is used to submit text and two options which tell vue which column to display the text in. When the col2 radio button is checked the submitted text should display in column 2. This is not happening, on column 1 text is displaying.
I have two radio buttons which should pass the value 'one' or 'two' to a newInfo.option On submnit a method pushed the form data to the array 'info'.
<input type="radio" id="col1" value="one" v-model="newInfo.col">
<input type="radio" id="col2" value="two" v-model="newInfo.col">
This data is being pushed to the array 'info' correctly and I can iterate through it. I know this is working because I can iterate through the array, an console.log all the data in it. All the submitted form data is there.
Next I iterate through this array twice in the template. Once for info.col==="one" and the other iteration should only display when info.col==="two". I am using a v-for and v-if together, which the vue.js documentation says is ok to do,
https://v2.vuejs.org/v2/guide/conditional.html#v-if-with-v-for
<div class="row">
<div class="col-md-6">
<ol>
<li v-for="item in info" v-if="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-for="item in info" v-if="!item.col==='two'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
</div>
The full vue.js code is on github here
And it is running on gh-pages here
Upvotes: 58
Views: 151901
Reputation: 1
You can work around layout issues caused by the extra wrapper element by using CSS's display: contents
. This makes the container “invisible” to layout and flexbox, allowing you to nest the v-if without altering your layout.
For example:
<div style="display: contents;" v-for="item in items">
<div v-if="item.type === 'aaa'">AAA</div>
<div v-else>BBB</div>
</div>
This approach maintains the desired structure without adding extra DOM elements that could disrupt your styling.
Upvotes: 0
Reputation: 1806
From Vue docs:
When they exist on the same node, v-if has a higher priority than v-for. That means the v-if condition will not have access to variables from the scope of the v-for:
<!--
This will throw an error because property "todo"
is not defined on instance.
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
This can be fixed by moving v-for to a wrapping tag (which is also more explicit):
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
If you don't mind your element remaining present in the html as "display:none" you can combine v-show with v-for.
Upvotes: 54
Reputation: 1
For me, the best option whas to use filter.
<div v-for="target in targets.filter((target) => target.zone_id == zone.id)">
{{ target.id}}
</div>
Upvotes: 0
Reputation: 357
You could also use JavaScript in your template to filter the array elements of the v-for. Instead of v-for="item in infos"
you could narrow down the info-array to v-for="item in infos.filter(info => info.col === 'one')"
.
I renamed your info-array to infos to improve readability of my suggestion because of the usage of info in the callbacks.
<div class="row">
<div class="col-md-6">
<ol>
<li v-for="item in infos.filter(info => info.col === 'one')">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-for="item in infos.filter(({ col }) => col === 'two')">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
</div>
Upvotes: 14
Reputation: 941
<div class="row">
<div class="col-md-6">
<ol>
<li v-for="item in info">
<template v-if="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
<template>
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-for="item in info">
<template v-if="!item.col==='two'">
text: {{ item.text }}, col: {{ item.col }}
<template>
</li>
</ol>
</div>
</div>
Upvotes: 7
Reputation: 5543
Computed
In most cases a computed
property is indeed the best way to do it like DobleL said, but in my own case I'm having a v-for
within another v-for
so then the computed doesn't make sense for the second v-for.
V-show
So instead of using v-if
which has a higher priority than v-for
(as mentioned by Mithsew), an alternative would be to use v-show
which doesn't have that higher priority.
It basically does the same, and works in your case.
No wrapper
Using v-show
avoids having to add a useless wrapper element as I've seen in some answers, which in my case was a requirement to avoid messing up CSS selectors and html structure.
Downside of v-show
The only downside of using v-show
is that the element will still be added in your HTML, which in my own case still messes up CSS :first selectors for example. So I personally actually went for the .filter()
solution mentioned by inTheFlow. But in most basic cases, you can definitely use v-show
to solve this problem.
<div class="row">
<div class="col-md-6">
<ol>
<li v-for="item in info" v-show="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-for="item in info" v-show="item.col!=='two'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
</div>
If you are curious why the hell I'm using a v-for
within another v-for
, here's a stripped-down version of my use case:
It's a list of conversations (which is a computed property), then displaying the avatars of all the participants within each conversation, except for the avatar of the user who is currently viewing it.
<a v-for="convo in filteredConversations" class="card">
<div class="card-body">
<div class="row">
<div class="col-auto">
<div class="avatar-group">
<div class="avatar" v-for="participant in convo.participants.filter(info => !thatsMe(info))">
<img :src="userAvatarUrl(participant)" :alt="participant.name" class="avatar-img">
</div>
</div>
</div> ...
Upvotes: 3
Reputation: 3928
Why don't use the power of Computed Properties ?
computed: {
infoOne: function () {
return this.info.filter(i => i.col === 'one')
},
infoTwo: function () {
return this.info.filter(i => i.col === 'two')
}
}
Then on each list just iterate over its respective property without the need to check. Example
<ol>
<li v-for="item in infoOne">{{item}}</li>
</ol>
Here the working fiddle
Upvotes: 93
Reputation: 59
If for some reason, filtering the list is not an option, you can convert the element with both v-for
and v-if
in to a component and move the v-if
in to the component.
Original Example
Original Loop
<li v-for="item in info" v-if="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</li>
Suggested Refactor
Refactored Loop
<custom-li v-for="item in info" :visible="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</custom-li>
New Component
Vue.component('custom-li', {
props: ['visible'],
template: '<li v-if="visible"><slot/></li>'
})
Upvotes: 5
Reputation: 1437
Remove !
from second if v-if="item.col==='two'"
better you can do this way (to iterate only once):
<div class="row" v-for="item in info">
<div class="col-md-6">
<ol>
<li v-if="item.col==='one'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
<div class="col-md-6">
<ol>
<li v-if="item.col==='two'">
text: {{ item.text }}, col: {{ item.col }}
</li>
</ol>
</div>
</div>
Upvotes: 5
Reputation: 528
Your second check is !item.col==='two'
and would only display if it does not equal 'two'.
EDIT: The ! not operator is likely binding more closely than === so that will always return false. Add brackets to control the order of application. I say likely because it may be a bit of Vue magic that I'm not familar with, rather than a pure JavaScript expression.
I think you want to remove that exclamation mark. Or to make it !(item.col==='one')
to display for any value other than 'one'.
Upvotes: 2