We are looking to (re)build a web app. We would like to make it isomorphic, so the server can render the same templates but a javascript state model would exist and handle updates. Am looking at ractivejs and mustache/handlebars as one fairly promising option.
However, I have run into a problem. Most of our site is mainly forms with some extended functionality. We have a number of fields but then maybe many rows of data for which to render each field. Some of the fields have dependant fields: for example, a field which is a list of countries could have a dependant field state: for some countries the state field does not exist and for others it does, but has different possible values depending on the country.
For example, we might have the following data
{
"data": [{
"firstName": "Joe",
"lastName": "Bloggs",
"travelDocument": "PASS BOOK",
"issueCountry": "US",
"issueState": "CA",
"iterator_row_num": "0"
},
{
"firstName": "Anne",
"lastName": "Other",
"travelDocument": "ID",
"expiryDate": "2021-01-02",
"issueDate": "1921-03-04",
"iterator_row_num": "1"
}],
"fields": {
"firstName": {
"type": "text",
"isList": false,
"isDate": false
},
"lastName": {
"type": "text",
"isList": false,
"isDate": false
},
"travelDocument": {
"type": "list",
"possibleValues": [{
"name": "Passport",
"dependentFields": [{
"name": "Issue Country",
"type": "list",
"possibleValues": [{
"name": "\u00c5land",
"value": "AX"
},
{
"name": "Finland",
"value": "FI"
},
{
"name": "Sweden",
"value": "SV"
},
{
"name": "United States",
"dependentFields": [{
"name": "Issue State",
"type": "list",
"possibleValues": [{
"name": "Alaska",
"value": "AL"
},
{
"name": "California",
"value": "CA"
},
{
"name": "New York",
"value": "NY"
}],
"isList": true,
"isDate": false,
"fieldName": "issueState"
}],
"value": "US"
}],
"isList": true,
"isDate": false,
"fieldName": "issueCountry"
},
{
"name": "Expiry Date",
"type": "date",
"isList": false,
"isDate": true,
"fieldName": "expiryDate"
}],
"value": "PASS BOOK"
},
{
"name": "ID Card",
"dependentFields": [{
"name": "Expiry Date",
"type": "date",
"isList": false,
"isDate": true,
"fieldName": "expiryDate"
},
{
"name": "Issue Date",
"type": "date",
"isList": false,
"isDate": true,
"fieldName": "issueDate"
}],
"value": "ID"
}],
"isList": true,
"isDate": false
}
}
}
I had some issues with the <select>
elements for the list type fields. What I wanted to do in the template was something like:
{{#data}}
<input type="text" value="{firstName}" name="firstname[{{iterator_row_num}}]" />
<input type="text" value="{lastName}" name="lastname[{{iterator_row_num}}]" />
<select name="travelDocument[{{iterator_row_num}}]">
{{#fields.travelDocument.possibleValues}}
<option value="{{value}}"
{{#if value == travelDocument }}
selected
{{/if}}
>{{name}}</option>
{{/fields.travelDocument.possibleValues}}
</select>
{{#fields.travelDocument.possibleValues}}
{{#if value == travelDocument}}
dependent fields here
{{/if}}
{{/fields.travelDocument.possibleValues}}
{{/data}}
But moustache doesn't allow if with comparisons. I tried adding an internal counter to my data model, and an increment function at the start of each loop. This function basically went through the fields and added/updated a boolean isCurrent
property for each possible value based on the current data. This works in PHP, but then I had to repeat my increment functions in the js layer. And I can't get it to work. It seems to set all rows based on the first for the dependent fields
My template then looked something like:
{{initLoop}}
{{#data}}
{{advanceLoop}}
<input type="text" value="{firstName}" name="firstname[{{iterator_row_num}}]" />
<input type="text" value="{lastName}" name="lastname[{{iterator_row_num}}]" />
<select name="travelDocument[{{iterator_row_num}}]">
{{#fields.travelDocument.possibleValues}}
<option value="{{value}}"
{{#isCurrent }}
selected
{{/isCurrent}}
>{{name}}</option>
{{/fields.travelDocument.possibleValues}}
</select>
{{#fields.travelDocument.possibleValues}}
{{#isCurrent}}
{{#dependentFields}}
dependent fields here
{{/dependentFields}}
{{/isCurrent}}
{{/fields.travelDocument.possibleValues}}
{{/data}}
I have indexN
(integer) and a fixedIndex
(boolean) properties on my data model. The initLoop
function simply sets indexN
to -1.
I then have functions a bit like:
data._set_current_name = function() {
this._currentName = false;
if( this.data[this.indexN] !== undefined ) {
this._currentName = this.data[this.indexN];
}
for( var field_id in this.fields ) {
this._update_field_values(field_id, this.fields[field_id]);
}
};
data._get_current_val = function(field_id) {
var currentVal = '';
if( false !== this._currentName && this._currentName[field_id] !== undefined ) {
currentVal = this._currentName[field_id];
}
return currentVal;
};
data._update_field_values = function(field_id, field) {
var currentVal = this._get_current_val(field_id);
switch( field.type ) {
case 'list':
if( field.possibleValues ) {
for( var posVal_i in field.possibleValues ) {
var posVal = field.possibleValues[posVal_i];
posVal.isCurrent = ( posVal.value === currentVal );
if( posVal.dependentFields ) {
for( var depField_i in posVal.dependentFields ) {
var depField = field.possibleValues[posVal_i].dependentFields[depField_i];
this._update_field_values(depField.fieldName, field.possibleValues[posVal_i].dependentFields[depField_i]);
}
}
}
}
break;
default:
field.currentValue = currentVal;
break;
}
};
data.advanceNameLoop = function() {
if( !this._fixedCounters ) {
++this.indexN;
}
this._set_current_name();
return '';
};
We also want it to automatically update which dependent fields are shown when you change the value of the "parent" field. Tried adding an observer on the ractive side which sets the indexes for the current row but it doesn't hold all the way. Also, changing the select doesn't even seem to trigger it, whereas changing a text field does? (Tried with observer both on the select specifically and on .*
to get all the fields).
My observer looked like:
names.observe('data.*.*', function(newValue, oldValue, keyPath){
var pathParts = keyPath.split('.');
this.viewmodel.data.setNameIndexes(pathParts[2]);
});
The setNameIndexes
is:
data.setNameIndexes = function(nameIndex) {
this.indexN= nameIndex;
this._fixedCounters = true;
};
Am I even along the right lines here? How should one handle an array of data for multiple fields. Given that some of the fields (country for example) can have a lot of possible values I really want to avoid having a separate copy of the whole field for each data row. It seems odd there if isn't a better way of handling selects in moustache/ractive
(Note: this is a simplified version of what I have: we have a categorisation of the data so I have a double index (category and then name) and many more fields. There might be small errors due to this in the above)