Search code examples
javascriptmustache

Mustache template for separate lists by category Id


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>

Solution

  • 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 class="category-{{category}}">
        {{#products}}
        <li>{{product}}</li>
        {{/products}}
      </ul>
      {{/.}}
    </template>