VueJS: How to output a comma separated array? - vue.js

I know that in VueJS I can loop through an array:
<span v-for="(element, index) in list">{{ element }}</span>
But what if I wanted a list that is comma separated? For example, if list = ["alice", "bob", "chuck"], then the above would output:
<span>alice</span><span>bob</span><span>chuck</span>
What I want, though, is:
<span>alice</span>, <span>bob</span>, <span>chuck</span>
Is this possible?

If all you care about is comma separation, use Javascript's built-in join method:
{{ list.join(', ') }}
For arrays of objects, you could do something like this:
{{ list.map(entry => entry.title).join(', ') }}

You can use conditional rendering to hide last , like following:
var demo = new Vue({
el: '#demo',
data: function() {
return {
lists: ['Vue', 'Angular', 'React']
};
}
})
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="demo">
<span v-for="(list, index) in lists">
<span>{{list}}</span><span v-if="index+1 < lists.length">, </span>
</span>
</div>

You could do it using a v-if attribute with a condition over the first argument, avoiding the usage of .length:
var app = new Vue({
el: '#app',
data: {
list: ['john', 'fred', 'harry']
}
})
<script src="https://vuejs.org/js/vue.min.js"></script><div id="app">
<span v-for="(element, index) in list">
<span v-if="index != 0">, </span><span>{{ element }}</span>
</span>
</div>

What I ended up doing instead was:
<span v-for="element in list" class="item">
<span>{{ element }}</span>
</span>
And in CSS:
.item + .item:before {
content: ", ";
}

Solution using "template"
<template v-for="(element, index) in list">
<span>{{element}}</span><template v-if="index + 1 < list.length">, </template>
</template>

If you wanted to do it the JS way, you can just do a computed property; you could even continue the span method.
computed {
listCommaSeparated () { return this.list.join(', '); },
listCommaSpans () { return '<span>' + this.list.join('</span><span>') + '</span>'; },
},
This would definitely be the preferred way from a rendering performance standpoint.

Just adding another alternative which I prefer to use:
<span v-for="(item, index) in list">
{{ item }}{{ (index+1 < list.length) ? ', ' : '' }}
</span>

My component:
<template>
<ul v-if="model.length">
<li v-for="item in model">{{item}}</li>
</ul>
</template>
<style scoped>
ul {
list-style: none;
}
li {
display: inline-block;
}
li:not(:last-child)::after {
content: ", ";
}
</style>
<script>
export default {
props: ['model']
};
</script>

Its possible sample
<span v-for="(item,i) in items">
{{(item !='' && i !=0) ? ',' : ''}} {{item.title}}
</span>

If you do want control over how the dom looks (e.g. actually want to achieve the dom structure that you asked about), you can create a functional component like so:
<script>
// RenderList.vue
export default {
functional: true,
render(createElement, context) {
// Read the list of entries by accessing the prop "list"
const listEntries = context.props.list;
// Return a custom list of elements for each list entry.
// Only return a `,` if it's not the last entry.
return listEntries.map((listElement, idx) => {
return [
createElement("span", listElement),
idx < listEntries.length - 1 ? ", " : "",
];
});
},
};
</script>
You would use this component like so:
<template>
<RenderList :list="['alice', 'bob', 'chuck']" />
</template>

May I suggest using i > 0 as check?
I've wrapped the separator so that {{ ' ' }} can be used for just having a space and avoiding span treated as being empty.
<span
v-for="(item, i) in list"
:key="i">
<span v-if="i>0">{{ ', ' }}</span>
<span class="text-nowrap">{{ item }}</span>
</span>
or
<span
v-for="(item, i) in list"
:key="i">
<!-- if not wrapped in a span will leave a space before the comma -->
<span>{{ (i > 0 ? ', ' : '') }}</span>
<span class="text-nowrap">{{ item }}</span>
</span>

There are different views where using :after or :before to have a comma is not a good idea with respect to browser not handling it as a string when the window is resized.
If we try to use conditional operator in each iteration to check whether it is the first or the last element, it would be an overhead if we have large number of items. Also the conditions will be re-evaluated whenever we have change detection.
To overcome these 2 things, we can enclose the comma in a separate element and hide it using CSS :last-child selector as
<template v-for="item in list">
<span class="item">
{{item}}
<span class="comma">
,
</span>
</span>
</template>
and in CSS
.item:last-child .comma {
display: none;
}

Related

How can I mix & match my properties in <p>?

I have a <p v-for="index in attrLength" :key="index" class="text-3xl"></p>. The idea is to take an object, in this case const project = projects[index] put it inside the <p> tag, add a . to it, and add the index number after that from the loop, but with the letter "p" injected in front of it, so you'd end up with project.p1, project.p2 and so on, inside the double curly braces like so: <p v-for="index in attrLength" :key="index" class="text-3xl">{{project.p1}}</p>.
How do I do that? I didn't find anything in the docs. Is this even possible?
You can use bracket notation with computed object keys:
<p v-for="index in attrLength" :key="index" class="text-3xl">
{{ project[`p${index}`] }}
</p>
demo
I am assuming attrLength is a number. Hence, as iteration will always start from 1 inside v-for loop. You can access project objects by using projects[index - 1] instead of projects[index].
Live Demo :
new Vue({
el: '#app',
data: {
projects: [{
p1: 'Paragraph 1'
}, {
p2: 'Paragraph 2'
}]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p v-for="index in 2" :key="index" class="text-3xl">
{{ projects[index - 1][`p${index}`] }}
</p>
</div>

Dynamic v-for loop in VueJS

I have to yml files (one in Fr and another in Nl), each with a line called "language", with the respective language.
Let's say that I'm receiving from the back a list in both languages of words, I save them as listFr & listNl.
And what I want to do is a dynamic v-for loop, using the variable language mentioned above, maybe something like this
<ul>
<li
v-for="(list, index) in `list${$t('language')}`"
:key="index"
>
<p class="element-title">{{ list.title }}</p>
</li>
</ul>
But obviously this is not correct as it's not showing the list of words.
Thanks for your help!
I think this is the best way
<template>
<ul>
<li v-for="(list, index) in dynamicList" :key="index">
<p class="element-title">{{ list.title }}</p>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
listFr: [],
listNi: [],
}
},
computed: {
dynamicList() {
return this["list" + this.$t('language')]
}
}
};
</script>

Can I create a conditional v-for loop to loop on the same div?

I'm curious is there is a way to have a v-for loop with a conditional statement inside of it so I can reduce redundancies in my program. I have a div tag that needs to loop on the tagfitlers object if the tag_filters object does not exist, otherwise, I need it to loop on the tag_fitlers object.
This is a snippet of my current loop:
<div v-else class="text-left mt-2 border" v-for="(filter, index) in tagfilters" :key="index">
<span v-for="(f, i) in filter" :key="i">
<div class="d-flex justify-content-between align-items-center pr-3 pl-3 pt-3">
<!-- Multiselect Search with Tagging -->
<div>
<multiselect #change="onEdit(filter, 'code', f.code)" class="mb-2" v-model="f.code" placeholder="Search & Select Code Options" :custom-label="customCodesLabel" track-by="code" :options="codesArr"></multiselect>
</div>
</div>
</div>
I am hoping to do something like this:
v-for="tag_filters ? (filter, index) in tag_filters : (filter, index) in tagfilters"
Is this possible?
According to Vue.js,
Using v-if and v-for together is not recommended. See the style guide for further information.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-with-v-for
You could use a computed property to get the correct list.
new Vue({
el: "#app",
data: function() {
return {
tag_filters: {
filterC: "tag_filter C.",
filterD: "tag_filter D.",
},
tagfilters: {
filterA: "tagfilter A.",
filterB: "tagfilter B."
}
}
},
computed: {
getTagFilters() {
if (Object.keys(this.tag_filters).length === 0) {
return this.tagfilters;
} else {
return this.tag_filters;
}
}
}
})
<div id="app">
<div v-for="(filter, key) of getTagFilters" :key="key">
<span>{{ filter }}</span>
</div>
<button #click="tag_filters = {}">Remove tag_filter data</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
One option is to use a computed prop, but not sure if it is enough for what you need:
get realTagFilters() {
return this.tagFilters ?? this.tag_filters;
}
And you can call it like
<div v-for="(filter, index) in realTagFilters">
Or you can try with the logical or operator, which also seems to work:
<div v-for="filter in (tag_filters || tagFilters)" :key="filter.id">

How to bind data in a link inserted to a v-html in vuejs?

Here's my code:
<pre
class="pre-text-formatted feed mt-2"
v-html="post.description.length >= 200 ? post.description.substring(0,200) + `... <a #click='readMore' class='text-muted pull-right' href>...read more</a>` : post.description">
</pre>
And here's the output:
The readMore method does not trigger its function.
Hello I personally don't like using v-html except in some rare cases. For this question I will suggest you render your templates conditionally. here's a sample that might help.
var app = new Vue({
el: '#app',
data: {
post: {
description: 'Hello you\'re awesome today.',
},
},
methods: {
readMore() {
alert('You click read more');
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<pre
class="pre-text-formatted feed mt-2">
<template v-if="post.description.length >= 200">
{{ post.description.substring(0,200)}} <a #click='readMore' class='text-muted pull-right' href>...read more</a>
</template><template v-else>{{post.description}}</template>
</pre>
</div>
you should try with conditional rendering, as below:
<pre
class="pre-text-formatted feed mt-2">
<template v-if="post.description.length >= 200">
{{ post.description.substring(0,200) }}...
<a #click='readMore' class='text-muted pull-right' href>...read more</a>
</template>
<template v-else>
{{ post.description }}
</template>
</pre>

Skip object items if the value is null

I have a nested for ... in loop in vue js. What I'm trying to to is to skip elements if the value of the element is null. Here is the html code:
<ul>
<li v-for="item in items" track-by="id">
<ol>
<li v-for="child in item.children" track-by="id"></li>
</ol>
</li>
</ul>
null elements may be present in both item and item.children objects.
For example:
var data = {
1: {
id: 1,
title: "This should be rendered",
children: {
100: {
id: 100,
subtitle: "I am a child"
},
101: null
}
},
2: null,
3: {
id: 3,
title: "Should should be rendered as well",
children: {}
}
};
With this data data[1].children[101] should not be rendered and if data[1].children[100] becomes null later it should be omitted from the list.
P.S. I know this is probably not the best way to represent data but I'm not responsible for that :)
Edit: Actually, a simple v-if might work:
<li v-for="item in items" v-if="item !== null" track-by="id">
Give it a try. If not, do this:
You can add a filter for that (in main.js before App instance):
Vue.filter('removeNullProps',function(object) {
// sorry for using lodash and ES2015 arrow functions :-P
return _.reject(object, (value) => value === null)
})
then in the template:
<li v-for="item in items | removeNullProps" track-by="id">
<ol>
<li v-for="child in item.children | removeNullProps" track-by="id"></li>
</ol>
</li>
In Vue 2, filters have been deprecated in v-fors.
Now you should use computed properties. Demo below.
new Vue({
el: '#app',
data: {
items: [
'item 1',
'item 2',
null,
'item 4',
null,
'item 6'
]
},
computed: {
nonNullItems: function() {
return this.items.filter(function(item) {
return item !== null;
});
}
}
})
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
Using a computed property:
<ul>
<li v-for="item in nonNullItems">
{{ item }}
</li>
</ul>
<hr>
Using v-if:
<ul>
<li v-for="item in items" v-if="item !== null">
{{ item }}
</li>
</ul>
</div>
I would advise you against using v-if and v-for in the same element. What I found worked and didn't affect performance is this :
<li v-for="(value, key) in row.item.filter(x => x !== null)" :key="key"{{value}}</li>
You just need to run the filter function on the array you are going through. This is a common use in c# and found it was no different in JavaScript. This will basically skip the nulls when iterating.
Hope this helps (3 years later).
Just use v-if to do with it. But the first, do not use track-by="id" because of the null item and null child. You can check the demo here https://jsfiddle.net/13mtm5zo/1/.
Maybe the better way is to deal with the data first before the render.
VueJs style guide tell us to :
"Never use v-if on the same element as v-for."
How to handle v-if with v-for properly according to the vue style guide :
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
See more on how to handle v-if with v-for here : https://v2.vuejs.org/v2/style-guide/#Avoid-v-if-with-v-for-essential