Search code examples
handlebars.js

Helper is not working in HandlebarsJS template


I am new in working with HandlebarsJS. I have an array like below.

enter image description here

I wrote a helper like below.

 Handlebars.registerHelper("show_data", (data, catalog_number, attr_name, index) => {
     if (
       data.CATALOG_NUMBER == catalog_number &&
       data.ATTRIBUTE_NAME == attr_name[index]
    ) {
       return data.VALUE;
    }
 });

My template is like below.

   <tbody>
        {{#each this.catalog_number}}
            <tr>
                <td>
                    {{ this }}
                </td>               
                {{#each ../this.data }}
                    <td>
                        {{show_data this ../this ../../this.attr_name @index}}
                    </td>
                {{/each}}
            </tr>
        {{/each}}
    </tbody> 

But I am not getting any output.


Solution

  • Thank you for sharing your desired result, that makes it possible for me to help.

    What you are trying to do with your helper is to take a Catalog Number and an Attribute Name and search through the entire data array for the item which has the matching Catalog Number and Attribute Name and return its VALUE property. You are doing this for each catalog_number times the length of data for a total count of loops approximately equal to catalog_number.length * data.length * data.length. This is a lot of unnecessary looping.

    I would recommend creating an index data object and passing it to the template. The index would be of this shape:

    {
      [CATALOG_NUMBER]: {
        [ATTRIBUTE_NAME]: VALUE
      }
    }
    

    This way, in your template you can loop through each attr_name for each catalog_number and do a single lookup on your index object to get the corresponding value.

    First, we will create our index object using Array.prototype.reduce.

    // assuming our data array is referenced by a variable called `items`
    
    const indexedItems = items.reduce((result, item) => {
      const catalogNumber = item.CATALOG_NUMBER;
      const attrName = item.ATTRIBUTE_NAME;
      const value = item.VALUE;
    
      if (!result[catalogNumber]) {
        result[catalogNumber] = {};
      }
    
      result[catalogNumber][attrName] = value;
    
      return result;
    }, {});
    

    We will pass indexedItems to our template as the value of data. Now we no longer need to write a custom helper. We can use the built-in lookup helper, but we will need to use it twice because we need to lookup by attributeName and catalogNumber. Our template becomes:

    {{#each this.catalog_number as |catalogNumber|}}
      <tr>
        <td>{{catalogNumber}}</td>
        {{#each @root.attr_name as |attrName| }}
          <td>
            {{lookup (lookup @root.data catalogNumber) attrName}}
          </td>
        {{/each}}
      </tr>
    {{/each}}
    

    I have created a fiddle for reference.

    Notes:

    @root is a convenience variable provided by Handlebars, just as @index is. It gives us a reference to the root context object rather than having to use a path like ../. In my example code, @root.attr_name would be equivalent to ../attr_name and @root.data to ../../data.

    indexedItems is intended to replace the array of objects at your data property of your root context object. I am not sure where you are constructing the object that you pass to your template function, but this is where you would create the indexedItems object and pass it to the template. Like:

    {
      attr_name: [/* ... */],
      catalog_number: [/* ... */],
      data: indexedItems
    }