mongodbexpresshandlebars.jsexpress-handlebars

How to use handlebars to output nested mongodb docs


I have a mongodb called categories and structured as:

{ (id), name, tree }

and data:

{ (639...bc78), "ABC", null},
{ (63d...c891), "DEF", null},
{ (63f...718d), "yyy", ",ABC,"}

'yyy' is a child of 'ABC'.

I can output ALL the data using handlebars

{{#each categories}}
  {{#if tree}}
    <tr><td> </td><td> </td><td>TREE:{{tree}}</td></tr>
  {{else}}
    <tr><td>{{_id}}</td><td>{{name}}</td></tr>
  {{/if}}
{{/each}}

This outputs:

639...bc78    ABC
63d...c891    DEF
63f...718d    yyy    TREE:ABC

What I would like to achieve is interleaved output of the child docs (i.e. 'yyy' outputs after its parent 'ABC'):

639...bc78    ABC
              TREE: yyy
63d...c891    DEF

I am fairly new to Express and very new to express-handlebars and cannot find any documentation or suggestions that would provide this functionality. Any ideas/code would be greatly appreciated.


Solution

  • It will be challenging to get the output you want by performing the logic in the Handlebars template. You would need to write a custom helper and that helper would need to do a lot of looping because in each iteration of the outer category loop it would need to do another category loop to find the children.

    The better approach would be to map your data to a different data structure before you pass it to your template. In this structure, the top-level category objects would "own" their own arrays of children. For example:

    const categoriesByName = categories.reduce((acc, category) => {
      if (!category.tree) {
        acc[category.name] = { ...category, children: [] };
      } else {
        const parent = category.tree.replace(/,/g, '');
        
        if (acc[parent]) {
          acc[parent].children.push(category);
        } else {
          // Note: We are assuming that children _always_ exist higher
          // in the array than their parents.
          // If no parent name has been indexed, we lose the child.
          console.error(`No parent with name: ${name}`);
        }
      }
      
      return acc;
    }, {});
    

    This will produce a structure like:

    {
      "ABC": {
        "_id": "639...bc78",
        "name": "ABC",
        "tree": null,
        "children": [
          {
            "_id": "63f...718d",
            "name": "yyy",
            "tree": ",ABC,"
          }
        ]
      },
      "DEF": {
        "_id": "63d...c891",
        "name": "DEF",
        "tree": null,
        "children": []
      }
    }
    

    Note: This structure does not get deeper than two-levels - ie., it doesn't allow children to have children.

    With this structure, our template needs to iterate through the top-level categories and, for each, include an inner-loop for any children they might have:

    {{#each categories}}
      <tr>
        <td>{{_id}}</td>
        <td>{{name}}</td>
     </tr>
     {{#each children}}
       <tr>
         <td></td>
         <td>TREE: {{name}}</td>
       </tr>
     {{/each}}
    {{/each}}
    

    Here is an example fiddle.