Vuejs v-for on inline elements trims whitespace - javascript

When looping over an array (or object) with v-for on an inline element, vuejs does not render whitespace around said element.
For example, I have this html:
<div id="app">
Vue Rendering<br>
<a v-for="fruit in fruits" v-bind:href="fruit.url" v-html="fruit.label"></a>
</div>
<div>
Navite rendering<br>
Apple
Banana
Peach
</div>
and this javascript:
var fruits = [
{
label: 'Apple',
url: 'apple.html'
},
{
label: 'Banana',
url: 'banana.html'
},
{
label: 'Peach',
url: 'peach.html'
}
];
var app = new Vue({
el: '#app',
data: {
fruits: fruits
}
});
When Vue renders this, it deletes the spaces between the links. See this jsfiddle.
How can I counter this behaviour ?

From the Docs on List Rendering > v-for on a <template>
Similar to template v-if, you can also use a <template> tag with v-for to render a block of multiple elements. For example:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
So in order to get sibling elements (and yeah, a breaking space character would count as one), you'll have to add the loop to a parent <template> container and then include any elements / spacing you want in the looped contents like this:
<template v-for="fruit in fruits" >
<span>{{fruit}}</span>
</template>
As of Vue 2.x+, templates trim any breaking space characters, even if they are escaped.
Instead, you can add a slot or text interpolation like this:
<template v-for="fruit in fruits" >
<span>{{fruit}}</span><slot> </slot>
</template>
<template v-for="fruit in fruits" >
<span>{{fruit}}</span>{{ ' ' }}
</template>
If you only want spaces in-between elements, you can output the space conditionally:
<template v-for="(fruit, i) in fruits" >
<span>{{fruit}}</span>{{ i < fruits.length -1 ? ', ': '' }}
</template>
Demo in Stack Snippets
var app = new Vue({
el: '#app',
data: {
fruits: ["apple", "banana", "carrot"]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.0/vue.js"></script>
<div id="app">
<template v-for="(fruit, i) in fruits" >
<span>{{fruit}}</span>{{ i < fruits.length -1 ? ', ': '' }}
</template>
</div>
Further Reading:
Issue #1841 - Suggestion: v-glue / v-for element joins

I had a case where I needed to wrap each character (including spaces) of a string in a <span> and solved it this way.
<template v-for="(letter, i) in 'My text with spaces'">
<span v-if="letter !== ' '">{{ letter }}</span>
<span v-else> </span>
</template>

You can use CSS:
<a v-for="fruit in fruits" v-bind:href="fruit.url" v-html="fruit.label"
style="margin-right: 5px;"></a>

Related

Rendering VUE slot only if content matches specific value

I have two routes that render the same component but with different data coming from an API.
This component has a child component called <base-section> that has a v-if directive that checks if a specific slot has content or not (because if it has no content, I don't want the slot to show).
However, there might be more than one instance of this child component on the same parent component, and therefore, if one of the instances has content in the slot, but the other one doesn't, VUE will automatically assume that all slots have content.
Therefore, I would like to know if there is any way of checking the specific slot content of each instance and then compare it with the data coming from the API. Please find my code below:
Parent component (Content.vue)
<template>
<base-section
v-for="entry in this.entries"
:key="entry.title"
lang="lang-markup"
:title="entry.title"
>
<template v-slot:content>
<span v-html="entry.content"></span>
</template>
<template v-slot:examples>
<div v-html="entry.examples"></div>
</template>
<template v-slot:code>
{{ entry.code }}
</template>
</base-section>
</template>
Child component (BaseSection.vue)
<template>
<hr class="my-6" />
<h4 class="text-salmon">{{ title }}</h4>
<section>
<div class="section-sm txt-justify" v-if="this.$slots.content">
<slot name="content"></slot>
</div>
<span class="medal bg-light text-dark code-medal">Examples</span>
<div class="section-sm border-light-1 mb-3">
<slot name="examples"></slot>
</div>
<span class="medal text-dark code-medal">Code</span>
<pre :class="lang + ' border-light-1 bg-light'">
<code><slot name="code"></slot></code>
</pre>
</section>
</template>
The data coming from the API follows this structure:
getData() {
const url = this.apiUrl + this.$route.name + this.apiToken
fetch(url)
.then((collection) => collection.json())
.then((collection) => {
const entries = [];
this.entries = [];
for (const id in collection.entries) {
if (collection.entries[id].Version === this.byteVersion) {
entries.push({
title: collection.entries[id].Title,
content: collection.entries[id].Content,
examples: collection.entries[id].Examples,
code: collection.entries[id].Code,
});
}
}
this.entries = entries;
});
}
Thank you very much for your help!
Regards,
T.
Maybe you can pass the "entry.content" into your BaseSection component. and v-if the entryContent.
Parent component (Content.vue)
<template>
<base-section
v-for="entry in this.entries"
:key="entry.title"
lang="lang-markup"
:title="entry.title"
:entryContent="entry.content"
>
<template v-slot:content>
<span v-html="entry.content"></span>
</template>
<template v-slot:examples>
<div v-html="entry.examples"></div>
</template>
<template v-slot:code>
{{ entry.code }}
</template>
</base-section>
</template>
Child component (BaseSection.vue)
<div class="section-sm txt-justify" v-if="entryContent">
<slot name="content"></slot>
</div>
Or you can v-if your content template
<template v-slot:content v-if="entry.content">
<span v-html="entry.content"></span>
</template>

Display an object in Vue template

I have an object like:
Obj = {
'min_mix': 1,
'max_mix': 2,
'climbing': {
'easy':[
{
'hour': 1.0,
'id':0,
}
],
'height': [
{
'hour': 1.0,
'price': 100
}
]
}
}
I have to display this in my HTML:
min_mix : 1
max_mix : 2
climbing:
easy:
hour : 1.0
id : 0
height:
hour: 1.0
price: 100
For now I use some v-for, but I don't have a good result,
I don't understand how display correctly the array in easy and height?
https://jsfiddle.net/CanadianDevGuy/fjaoztgn/15/
Please check this https://jsfiddle.net/Lewvsp0k/
<v-card-text v-for='(value,name) in obj' :key='name' class="pt-0 pb-0">
<template v-if="typeof value !== 'object'">
<b>{{name}} : {{ value }} </b>
</template>
<template v-else>
<div v-for="(rowValue, rowIndex) in value" :key='rowIndex' class="mb-2 pa-0">
<b>{{name}} : </b>
<div v-for="(rowValue1, rowIndex1) in rowValue" :key='rowIndex1' class="mb-2 pa-0">
<b>{{rowIndex1}} : </b>
<div>
<div v-for="rowValue2 in rowValue1" :key='rowValue2' class="mb-2 pa-0">
<div v-for="(rowValue3, rowIndex3) in rowValue2" :key='rowValue3' class="mb-2 pa-0">
<b> {{rowIndex3}} : {{rowValue3}} </b>
</div>
</div>
</div>
</div>
</div>
</template>
</v-card-text>
Should work
<template v-for="(item, index) in Obj ">
<template v-if="typeof item !== Object">
<div :key="index">
{{ index }} : {{ item }}
</div>
</template>
<template v-else>
<div :key="'subitem' + indexSubItem" v-for="(subItem, indexSubItem) in item">
{{ indexSubItem }} : {{ subItem }}
</div>
</template>
</template>
The easiest way is to actually create a new component, say <object-entry>, that can perform iterative nesting logic: if the value it encounters is a string or number, we print it out directly; otherwise, we nest another level of <object-entry> in it.
The <object-entry> component (you can also rename it to anything you like, that makes more sense) will do the following:
always print the key from the key-value pair.
decide if it should print the value from the key-value pair:
if value is a typeof string/number, we print it
if value is not (then it is an array or object), we then call the component itself again (effectively nesting itself recursively)
In pseudo-code it looks like this:
<div>
<strong>{{ key }}</strong>:
<template v-if="isStringOrNumber(value)">
{{ value }}
</template>
<object-entry
v-else />
</div>
This ensures that we will keep indenting until we encounter the "bottommost" value that is either a number or string.
Note: You cannot truly get rid of the object index in the markup, due to the way your data is structured: if climbing is supposed to only contain 'easy' and 'height', they should not be double nested inside a meaningless key of 0. Likewise, for easy and height, it makes little sense they are stored as array of objects when it should be just an object itself.
See proof-of-concept here, with suggested changes to your data (you can also use your original data structure, but it will come with an extraneous level of nesting):
Vue.use(Vuetify);
Vue.component('object-entry', {
template: '#object-entry-template',
props: ['entryKey', 'entryValue'],
methods: {
isStringOrNumber: function(v) {
return typeof v === 'string' || typeof v === 'number';
}
}
})
var vm = new Vue({
el: "#app",
data: () => ({
obj: {
'min_mix': 1,
'max_mix': 1,
'climbing': {
'easy': {
'hour': 1.0,
'id': 0
},
'height': {
'hour': 1.0,
'price': 100.0
}
}
},
})
});
<link href="https://unpkg.com/vuetify#0.14.8/dist/vuetify.min.css" rel="stylesheet"/>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<script src="https://unpkg.com/vuetify#0.14.8/dist/vuetify.min.js"></script>
<div id="app">
<v-app>
<object-entry
v-for="(value, key) in obj"
v-bind:key="key"
v-bind:entry-key="key"
v-bind:entry-value="value" />
</v-app>
</div>
<script type="text/x-template" id="object-entry-template">
<v-card-text class="pt-0 pb-0">
<strong>{{ entryKey }}</strong> :
<template v-if="isStringOrNumber(entryValue)">
{{ entryValue }}
</template>
<!-- Here, the component nests itself if value is not a string/number -->
<object-entry
v-else
v-for="(value, key) in entryValue"
v-bind:key="key"
v-bind:entry-key="key"
v-bind:entry-value="value" />
</v-card-text>
</script>

Render a checkbox and check it based on the existence of a value from a dataset to another dataset

My idea is to checked whether a value exists from a dataset to another dataset. In this case I would like to use the id property.
I'm try handlebars.js , But the following is invalid.
spot_categories array
{spot_categories:[{category:1},{category:2},{category:3},{category:4},{category:5},{category:6}]}
data array
{data:[{spot_category_id:1},{spot_category_id:3}]}
vue.js code
<!-- if have var -->
{{# var ok = false}}
<!-- one for -->
<li v-for="(category,j) in spot_categories">
<label>
<!-- two for -->
<template v-for="(val, l) in data">
<!-- if -->
<template v-if="val.spot_category_id == category.id">
{{ ok = true }}
</template>
</template>
<!-- if ok = true , show value -->
<input :value="category.id" :checked="ok">
</label>
</li>
How can I do? Please help me. thanks!
You are looking at looping once only in your template using your spot_categories dataset and then calling a method to evaluate if its id exists on another dataset. I use v-if and v-else to decide which checkbox element to render. Then I implement array.some() on my evaluation method which is the most fitting in this scenario (you can use a manual for loop but it's too lengthy and generally not recommended if you want a clean-looking code).
new Vue({
el: categoryContainers,
data: {
spot_categories: [
{id: 1},
{id: 2}
],
data: [
{spot_category_id: 1},
{spot_category_id: 3}
]
},
methods: {
checkIfExists: function(item1){
return this.data.some(function(val){ return val.spot_category_id == item1.id });
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="categoryContainers">
<ul>
<li v-for="(category,j) in spot_categories">
<label>
<input type="checkbox" v-if="checkIfExists(category)" :value="category.id" checked>
<input type="checkbox" v-else :value="category.id">
{{ 'Category ID ' + category.id }}
</label>
</li>
</ul>
</div>

using 'v-for' to output object to list

I have an object that get dynamically updated with various key/value, for the most part it will look like below:
object: {
parentKey1:{
childKey1:'value',
childKey2:'value'
},
parentKey2:{
childKey3:'value',
childKey4:'value'
}
}
I am then using 'v-for' in a list to export the object, like so:
<ul>
<li v-for="(value, key) in object">{{ key }} - {{ value }}</li>
</ul>
Now, it's displaying on the DOM generally how I want it, except that its printing out {} curly brackets around the values.
PARENT - { "CHILD": "VALUE" }
I'd like it to be:
PARENT - CHILD VALUE
Try this
new Vue({
el: '#app',
data: {
object: {
parentKey1: {
childKey1: 'value',
childKey2: 'value'
},
parentKey2: {
childKey3: 'value',
childKey4: 'value'
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<div v-for="(value, key) in object">
{{key}} -
<span v-for="(cvalue, ckey) in value">
{{ ckey }} {{ cvalue }} {{' '}}
<span>
</div>
</div>
You need to provide a new nested iteration to do that.
<ul>
<li v-for="(parentValue, parentKey) in object">
<template v-for((childValue, childKey) in parentValue)>
{{ parentKey }} - {{ childKey }} {{ childValue }}
</template>
</li>
</ul>
Well the value is still an Array thats why it is displayed like that.
Maybe try
<ul>
<li v-for="(value, key) in object">{{ key }} - <span v-for="val in value">{{ val }}</span></li>
</ul>

Vue v-model not updating array values while using v-for

I'm new to Vue, and am playing around with v-model to see what I can create. I want to make an editable array of names. Here is my code so far:
var app = new Vue({
el: '#app',
data: {
names: ['Josh', 'Tom']
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
Editable list:
<ul>
<li v-for="name in names"><input type="text" v-model="name"></li>
</ul>
{{ names }}
</div>
When this is run, the inputs show up, and each one contains the correct value. However, typing in the inputs doesn't update the names array as I would have expected.
What am I doing wrong?
You're trying to use v-model to operate on a value, whereas you need to operate on a vue data field. For example, try the following:
var app = new Vue({
el: '#app',
data: {
people: [{name: 'Josh'}, {name: 'Tom'}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
Editable list:
<ul>
<li v-for="person in people"><input type="text" v-model="person.name"></li>
</ul>
{{ people }}
</div>
Alternatively, you could do:
var app = new Vue({
el: '#app',
data: {
names: ['Josh', 'Tom']
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
Editable list:
<ul>
<li v-for="(name, index) in names"><input type="text" v-model="names[index]"></li>
</ul>
{{ names }}
</div>

Categories