Given some JSON data as array of items:
[
{
"category": 1,
"product": "Sugar"
},
{
"category": 1,
"product": "Oil"
},
{
"category": 2,
"product": "Spice"
},
{
"category": 3,
"product": "Salt"
},
{
"category": 3,
"product": "Eggs"
},
{
"category": 5,
"product": "Bread"
},
{
"category": 5,
"product": "Butter"
},
{
"category": 7,
"product": "Milk"
}
]
How can I render it using Mustache.js, so that the items are grouped by category Id into separate lists, also adding an empty placeholder for missing Id's (4 and 6 in this case), like:
<ul id="category-1">
<li>Sugar</li>
<li>Oil</li>
</ul>
<ul id="category-2">
<li>Spice</li>
</ul>
<ul id="category-3">
<li>Salt</li>
<li>Eggs</li>
</ul>
<ul id="category-4">
</ul>
<ul id="category-5">
<li>Bread</li>
<li>Butter</li>
</ul>
<ul id="category-6">
</ul>
<ul id="category-7">
<li>Milk</li>
</ul>
CodePudding user response:
First you need to transform your data into a format you can easily map over with Mustache. Once you've done that, the templating becomes fairly straightforward.
Here's the basic approach below:
- Find the largest category number in your data, and save it as
largestCategoryNumber
- Create an array that is
largestCategoryNumber
in length - Populate that array with objects where the value of
category
is the array index and the value ofproduct
is the products from our original data that had that category number - Render the template
const data = [
{
"category": 1,
"product": "Sugar"
},
{
"category": 1,
"product": "Oil"
},
{
"category": 2,
"product": "Spice"
},
{
"category": 3,
"product": "Salt"
},
{
"category": 3,
"product": "Eggs"
},
{
"category": 5,
"product": "Bread"
},
{
"category": 5,
"product": "Butter"
},
{
"category": 7,
"product": "Milk"
}
];
const largestCategoryNumber = Math.max(
...data.map(({category}) => category)
);
const formattedData = [];
Array.from(Array(largestCategoryNumber)).forEach((_, index) => {
formattedData.push({
category: index,
products: data
.filter(obj => obj.category === index)
.map(({product}) => product)
});
});
const templateData = { data: formattedData };
window.onload = () => {
const template = document.getElementById('template').innerHTML;
const rendered = Mustache.render(template, templateData);
document.getElementById('target').innerHTML = rendered;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.2/mustache.min.js"></script>
<div id="target"></div>
<script id="template" type="x-tmpl-mustache">
{{#data}}
<ul >
{{#products}}
<li>{{.}}</li>
{{/products}}
</ul>
{{/data}}
</script>
CodePudding user response:
I think we need to change the response according to our needs :
const data = response;
const dataMap = data.reduce((r,v) =>{
return{
...r,
[v.category]:[
...r?.[v.category]||[],
{product: v.product}
]
};
},{});
in UI: result dataMap is
{
"1": [
{
"product": "Sugar"
},
{
"product": "Oil"
}
],
"2": [
{
"product": "Spice"
}
],
"3": [
{
"product": "Salt"
},
{
"product": "Eggs"
}
],
"5": [
{
"product": "Bread"
},
{
"product": "Butter"
}
],
"7": [
{
"product": "Milk"
}
]
}
and we can use in our ui
for (i of Object.keys(dataMap)) {
// i for category
// dataMap[i] for array product
}
hope it helps.
CodePudding user response:
Figured out a solution that does involve data transformation to suit Mustache template format, but performs fewer iterations than cjl750's answer below (which does filter
and map
for each item):
// fill data array with empty/no products for all categories (max 7 in this case)
for (let i = 1; i < 8; i )
data.push({ category: i });
// group by categories and add products
const grouped = Object.values(data.reduce((r, i) => {
r[i.category] = r[i.category] || { category: i.category, products: [] };
if (i.product)
r[i.category].products.push({ product: i.product });
return r;
}, {}));
It will transform the data array to a grouped object array, like:
[
{
"category": 1,
"products": [
{ "product": "Sugar" },
{ "product": "Oil" }
]
},
:
:
]
Now the template can simply be:
<template id="template">
{{#.}}
<ul >
{{#products}}
<li>{{product}}</li>
{{/products}}
</ul>
{{/.}}
</template>