Lodec Lodec - 10 days ago 6
Javascript Question

Reducing code with the use of Handlebars.js

I'm beginning to use Handlebars and the architectural pattern Model View ViewModel and I wrote this code :

var data = {
currentPlayer: this._model.currentPlayer,
line: [
{
row:
[
{
caseNumber:1,
caseValue: this._model.getCaseState(0,0)
},
{
caseNumber:2,
caseValue: this._model.getCaseState(0,1)
},
{
caseNumber:3,
caseValue: this._model.getCaseState(0,2)
}
]
},
{
row:[
{
caseNumber:4,
caseValue:this._model.getCaseState(1,0)
},
{
caseNumber:5,
caseValue:this._model.getCaseState(1,1)
},
{
caseNumber:6,
caseValue:this._model.getCaseState(1,2)
}
]
},
{
row:[
{
caseNumber:7,
caseValue:this._model.getCaseState(2,0)
},
{
caseNumber:8,
caseValue:this._model.getCaseState(2,1)
},
{
caseNumber:9,
caseValue: this._model.getCaseState(2,2)
}
]
}
]
};
var htmlContent = this._template(data);
this._element.html(htmlContent);


With the following template :

<div>
<h3>It is to player {{currentPlayer}}</h3>
<table>
{{#each line}}
<tr>
{{#row}}
<td data="{{caseNumber}}" class="case{{caseValue}}">{{caseValue}}</td>
{{/row}}
</tr>
{{/each}}
</table>
</div>


This code works fine but I'm asking if I cannot reduce it. So I tried to use a for loop in the var data but I realized that I can't do this.
My other choice was to use an if in the template like this :

{{#each line}}
<tr>
{{#row}}
{{#if caseValue}}
<td data="{{caseNumber}}" class="case{{caseValue}}">O</td>
{{else}}
<td data="{{caseNumber}}" class="case{{caseValue}}">X</td>
{{/if}}
{{/row}}
</tr>
{{/each}}


by testing the value of the var
caseValue
. However, as
caseValue
takes the value of
1
or
0
or
undefined
, if the case isn't checked all the cells are filled with a "X".
So, I can't find a compact solution with the aim of :


  • At the beginning, all the TD tags are empty.

  • Depending on the value of
    getCaseState
    which returns
    0
    or
    1
    fill
    the cell with an "X" or an "O".



EDIT : I manage the different values of
getCaseState
with this code :

Handlebars.registerHelper('displayTd', function(data) {
var result;
if(data.caseValue === undefined) {
result = '<td data="' + data.caseNumber + '"></td>';
return new Handlebars.SafeString(result);
} else if(data.caseValue === 1) {
result = '<td data="' + data.caseNumber + '" class="case' + data.caseValue + '">X</td>';
return new Handlebars.SafeString(result);
} else {
result = '<td data="' + data.caseNumber + '" class="case' + data.caseValue + '">O</td>';
return new Handlebars.SafeString(result);
}
});

Answer

The first step I would take to reduce the code was the one you alluded to about using loops to construct your data. The data in your line object follows a simple pattern, so we can construct with the following code:

var numRows = 3;
var numColumns = 3;
var line = [];

for (var rowIndex = 0; rowIndex < numRows; rowIndex++) {
    line[rowIndex] = { row: [] };
    for (var columnIndex = 0; columnIndex < numColumns; columnIndex++) {
        line[rowIndex].row[columnIndex] = {
            caseNumber: ((rowIndex * numColumns) + columnIndex + 1),
            caseValue: getCaseState(rowIndex, columnIndex)
        };
    }
}

*Note that you will have to call getCaseState on your existing model object.

Our data object then becomes:

var data = {
    currentPlayer: this._model.currentPlayer,
    line: line
};

As for the conditional within your template, I would recommend creating your own Handlebars helper. Fortunately, Handlebars has an isEmpty utility method that returns true for:

  • Array with length 0
  • falsy values other than 0

This means that we can use this utility method to check if our caseValue is undefined:

Handlebars.registerHelper('getCharacter', function (caseValue) {
    return Handlebars.Utils.isEmpty(caseValue) ? '' : (caseValue === 0 ? 'X' : 'O');
});

We then use our new helper in our template in the following way:

{{#each row}}
    <td data="{{caseNumber}}" class="case{{caseValue}}">{{getCharacter caseValue}}</td>  
{{/each}}