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>
Related
I'm building a small project where it has a component in it, I should render the data from the API.
Here is my code:
<template>
<div>
<p v-if="$fetchState.pending">Fetching products...</p>
<p v-else-if="$fetchState.error">An error occurred :(</p>
<div v-else>
<h1>Nuxt products</h1>
<ul>
<li
v-for="(product, key) of product"
:key="product.id"
:img="product.img"
>
{{ product.description }}
</li>
</ul>
<button #click="$fetch">Refresh</button>
</div>
</div>
</template>
<script>
export default {
async fetch() {
this.products = await this.$axios("https://dummyjson.com/products");
},
};
</script>
and here is the error code:
Property or method "product" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option or for class-based components, by initializing the property
This works
<template>
<div>
<p v-if="$fetchState.pending">Fetching products...</p>
<p v-else-if="$fetchState.error">An error occurred :(</p>
<div v-else>
<h1>Nuxt products</h1>
<ul>
<li v-for="product in products" :key="product.id" :img="product.img">
{{ product.description }}
</li>
</ul>
<button #click="$fetch">Refresh</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
products: [],
};
},
async fetch() {
const response = await this.$axios.$get('https://dummyjson.com/products')
this.products = response.products
},
}
</script>
You need v-for="product in products" as explained here: https://vuejs.org/guide/essentials/list.html
Also, regarding the the network request
We can see that as usual, the actual data is inside data, hence you can use the $get shortcut: https://axios.nuxtjs.org/usage#-shortcuts
Then you need to access the products field to have the data to iterate on. Using the Vue devtools + network tab greatly helps debugging that one!
so the answer is i missed putting the data as #kissu has mentioned above
<template>
<div>
<p v-if="$fetchState.pending">Fetching products...</p>
<p v-else-if="$fetchState.error">An error occurred :(</p>
<div v-else>
<h1>Nuxt products</h1>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.description }}
{{ product.images }}
</li>
</ul>
<button #click="$fetch">Refresh</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
products: [],
};
},
async fetch() {
const response = await this.$axios.$get("https://dummyjson.com/products");
this.products = response.products;
},
};
</script>
All code you can find here: https://stackblitz.com/edit/angular-display-show-json-in-propertlu-format?file=src%2Fapp%2Fapp.component.ts
I have a problem formatting the array I'm loop through.
Main logic function is this:
this.allFilters.push(
Array.isArray(val.value)
? {
name: val.name,
value: val.value.map((obj: any) => obj.value).join(" - "),
displayName: val.displayName,
displayValue: val.value.map((obj: any) => obj.value).join(" - ")
}
: {
name: val.name,
value: val.value,
displayName: val.displayName,
displayValue: val.displayValue
}
);
Let me explain what I did here. Loop the thought array and push values. If it is array, the first condition goes, if it is not through, then the second.
Example of allFilters and val you can see on stackblitz and code bellow.
I loop thought array very simple:
<span
*ngFor="let filter of allFilters">
{{ filter.displayName }}: {{ filter.displayValue }}
</span>
If are you using VUE.js this is
<span
v-for="filter in allFilters" :key="filter.name>
{{ filter.displayName }}: {{ filter.displayValue }}
</span>
I need create object like a:
First: 2
Second: dateFrom 2021-04-08
Second: dateTo: 2021-04-20
Third: 15
The issue is not connected out for vue or angular or even reactions. This is js problem.
No need for such remappings, displaying logic should be usually in template, something like:
<span
*ngFor="let filter of allFilters"
class="badge badge-light my-3 mx-2 px-3 py-1">
<ng-container *ngIf="isArray(filter.value); else noArray">
<div *ngFor="let obj of filter.displayValue">
{{filter.displayName}}: {{obj.name}} {{obj.value}}
</div>
</ng-container>
<ng-template #noArray>
{{filter.displayName}}: {{filter.displayValue}}
</ng-template>
</span>
js:
isArray(value) {
return Array.isArray(value);
}
recieveMessage(val) {
this.allFilters.push(val);
}
It is possible to do it in template, like this:
<hello name="{{ name }}"></hello>
<div class="span">
<span
*ngFor="let filter of allFilters"
class="badge badge-light my-3 mx-2 px-3 py-1"
>
<ng-container *ngIf="filter.name != 'Second';else withDate">
{{ filter.displayName }}: {{ filter.displayValue }}
</ng-container>
<ng-template #withDate>
Second: dateFrom {{ filter.displayValue|slice:0:10 }}<br>
Second: dateTo: {{ filter.displayValue|slice:13:24 }}
</ng-template>
</span>
</div>
I have a list of items rendered with v-for. I want each item to have a "?" that is clickable to show a modal containing a description for that specific item. My issue right now is that when the "?" is clicked, it shows the modal for every item in the v-for. How do i solve this?
<div
v-for="(item, index) in items"
:key="index"
>
<div>
{{ item.name }}
<div>
<span #click="itemModal = true">
?
</span>
<div v-show="itemModal">
{{ item.description }}
<button #click="itemModal = false">
Close modal
</button>
</div>
</div>
</div>
</div>
export default {
data() {
return {
itemModal: false
}
}
}
Your itemModal property is share with all items currently, so you need one modal status for each item.
eg. you can create a toggle method to update an array of modal status:
<div
v-for="(item, index) in items"
:key="index"
>
<div>
{{ item.name }}
<div>
<span #click="toggle(index)">
?
</span>
<div v-show="itemModal[index]">
{{ item.description }}
<button #click="toggle(index)">
Close modal
</button>
</div>
</div>
</div>
</div>
export default {
data() {
return {
itemModal: []
}
},
methods: {
toggle(index) {
this.$set(this.itemModal, index, !this.itemModal[index])
}
}
}
nb: an array (or an object) is not reactive in depth, so we have to use Vue.$set (cf. docs)
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>
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>