Search code examples
ember.jshandlebars.jsexpress-handlebars

handlebars dynamic table row


I am using Ember with Handlebars. I have a situation where I need to dynamically create new table rows after 3 iterations of an array.

<table>
{{#each items as |item itemIndex|}}
    {{#if (compare (mod itemIndex 3) '===' 0)}}<tr>{{/if}}
        <td>{{item.id}}</td>  
    {{#if (compare (mod itemIndex 3) '===' 2)}}</tr>{{/if}}
{{/each}}
</table>

For the sake of example, please assume that there will always be a factor of 3 in the items array. When I try to save this code, I'm getting a syntax error that there is no close tag matching the open tag. How can I dynamically create the and without the syntax error?

I'm using ember CLI version 2.9.1.


Solution

  • When you run the code snippet you have provided you will most likely encounter an error indicating Error while processing route: index Unclosed element tr (on line 8). Error: Unclosed element tr (on line 8). This is because ember-template-compiler does not know whether the rendering will result in a correct HTML page as a result of rendering. What if the items array is not divisible by 3; we will get an error in the runtime. In order to prevent that; ember-template-compiler throws an error indicating an unclosed tr tag.

    So; what can be done to prevent this? You can create a computed property within your js file that simply returns the number of rows desired as follows:

    rowCount: Ember.computed('items.length', function() {
        return this.get('items.length')/3;
    })
    

    afterwards you can simply make use of range helper provided by ember-composable-helpers and do the following within your template:

    <table>
        {{#each (range 0 rowCount) as |rowIndex|}}
        <tr>
        {{#each (range 0 3) as |columnIndex|}}
            {{! `number` will go from 0 to 2}}
          <td>
            {{get (get-item-at-array-index items columnIndex rowIndex) 'id'}}
          </td>
            {{/each}}
        </tr>
        {{/each}}
    </table>
    

    The template code snippet provided above contains an helper named get-item-at-array-index which simply finds the relevant item at the desired index position and it should have a simple code as follows:

    export function getItemAtArrayIndex(params/*, hash*/) {
      let array = params[0];
      let index = params[1];
      let rowIndex = params[2];
    
      return array[index+rowIndex*3];
    }
    

    I have prepared the following twiddle for you that wraps up what I already explained above. By doing so; you will avoid the Unclosed element tr error you get; since the tags will be closing explicitly and template compiler will not complain.