Search code examples
templatesnestedhandlebars.jshelper

How to use path from within each clause as helper argument in handlebars.js


I have the following scenario

    {{#each (concatArray setra.features lion.features)}}
    <tr>
        <td><h4 class="ui header">{{this}}</h4></td>
        <td>{{#if contains ../setra.features this}}YES{{/if}}</td>
        <td>{{#if contains ../lion.features this}}YES{{/if}}</td>
    </tr>
    {{/each}}

where concatArray is a helper function that returns an array of strings which is a concatenation of all features.

How do I properly write the statement above?

From within the each clause I am able to access the setra.features and lion.features by escaping the each context:

{{#each (concatArray setra.features lion.features)}}
{{../setra.features}} <!-- is accessible -->
{{/each}}

But once I want to use either as an argument in my helper function contains, it gives me errors depending on how I try to implement it. The example above currently gives me a "Cannot read property 'includeZero' of undefined" error, which seems most likely because the path is not properly evaluated.

The helper functions are as follows:

var contains = function(array, string){

    if(array && array.indexOf(string) > -1){ return true; }
    return false;
}

exports.contains = contains;

var concatArray = function(array1, array2){
    var newArray = array1;

    array2.forEach(function(element){
        if(newArray.indexOf(element) < 0){
            newArray.push(element);
        }
    });

    return newArray;
}

exports.concatArray = concatArray;

Solution

  • The first issue is that all of the items after your #if are being treated as separate arguments to the Handlebars #if block helper. #if expects a single argument, a boolean expression.

    Handlebars supports subexpressions. From the docs:

    Handlebars offers support for subexpressions, which allows you to invoke multiple helpers within a single mustache, and pass in the results of inner helper invocations as arguments to outer helpers. Subexpressions are delimited by parentheses.

    Following these instructions, the #if blocks in your template must be updated to the following:

    <td>{{#if (contains ../setra.features this)}}YES{{/if}}</td>
    <td>{{#if (contains ../lion.features this)}}YES{{/if}}</td>
    

    Running the code now should not throw the "includeZero" error. However, there is still a problem. It seems the contains helper always returns true when passed ../setra.features.

    This is due to the fact that the concatArray helper is changing the value of setra.features. In fact, 'concatArray` modifies its first array argument such that its value becomes the result of all "concatenated" arrays. (Please note that your helper is not strictly "concatentation" because it also removes duplicate elements.)

    To fix this, we need concatArray to modify a copy of its first array argument, rather than modifying the first array argument itself. There are a few ways to do this, but I will use JavaScript's Array.prototype.concat:

    //var newArray = array1;
    var newArray = [].concat(array1);
    

    Finally, I would refactor your contains helper a little bit, just for cleanliness. I think the following is much prettier:

    var contains = function (array, string) {
        return (Array.isArray(array) && array.indexOf(string) > -1);
    }