I\'m trying to build a form dynamically from a JSON object, which contains nested groups of form elements:
$scope.formData = [
{label:\'First Name\', type:\'text\', required:\'true\'},
{label:\'Last Name\', type:\'text\', required:\'true\'},
{label:\'Coffee Preference\', type:\'dropdown\', options: [\"HiTest\", \"Dunkin\", \"Decaf\"]},
{label: \'Address\', type:\'group\', \"Fields\":[
{label:\'Street1\', type:\'text\', required:\'true\'},
{label:\'Street2\', type:\'text\', required:\'true\'},
{label:\'State\', type:\'dropdown\', options: [\"California\", \"New York\", \"Florida\"]}
]},
];
I\'ve been using ng-switch blocks, but it becomes untenable with nested items, like in the Address object above.
Here\'s the fiddle:
http://jsfiddle.net/hairgamiMaster/dZ4Rg/
Any ideas on how to best approach this nested problem? Many thanks!
I think that this could help you. It is from an answer I found on a Google Group about recursive elements in a tree.
The suggestion is from Brendan Owen: http://jsfiddle.net/brendanowen/uXbn6/8/
<script type=\"text/ng-template\" id=\"field_renderer.html\">
{{data.label}}
<ul>
<li ng-repeat=\"field in data.fields\" ng-include=\"\'field_renderer.html\'\"></li>
</ul>
</script>
<ul ng-controller=\"NestedFormCtrl\">
<li ng-repeat=\"field in formData\" ng-include=\"\'field_renderer.html\'\"></li>
</ul>
The proposed solution is about using a template that uses the ng-include directive to call itself if the current element has children.
In your case, I would try to create a template with the ng-switch directive (one case per type of label like you did) and add the ng-include at the end if there are any child labels.
Combining what @jpmorin and @Ketan suggested (slight change on @jpmorin\'s answer since it doesn\'t actually work as is)...there\'s an ng-if
to prevent \"leaf children\" from generating unnecessary ng-repeat
directives:
<script type=\"text/ng-template\" id=\"field_renderer.html\">
{{field.label}}
<ul ng-if=\"field.Fields\">
<li ng-repeat=\"field in field.Fields\"
ng-include=\"\'field_renderer.html\'\">
</li>
</ul>
</script>
<ul>
<li ng-repeat=\"field in formData\" ng-include=\"\'field_renderer.html\'\"></li>
</ul>
here\'s the working version in Plunker
Might consider using ng-switch to check availability of Fields property. If so, then use a different template for that condition. This template would have an ng-repeat on the Fields array.
I know this is an old question, but for others who might come by here though a search, I though I would leave a solution that to me is somewhat more neat.
It builds on the same idea, but rather than having to store a template inside the template cache etc. I wished for a more \"clean\" solution, so I ended up creating https://github.com/dotJEM/angular-tree
It\'s fairly simple to use:
<ul dx-start-with=\"rootNode\">
<li ng-repeat=\"node in $dxPrior.nodes\">
{{ node.name }}
<ul dx-connect=\"node\"/>
</li>
</ul>
Since the directive uses transclusion instead of compile (as of the latest version), this should perform better than the ng-include example.
Example based on the Data here:
angular
.module(\'demo\', [\'dotjem.angular.tree\'])
.controller(\'AppController\', function($window) {
this.formData = [
{ label: \'First Name\', type: \'text\', required: \'true\' },
{ label: \'Last Name\', type: \'text\', required: \'true\' },
{ label: \'Coffee Preference\', type: \'dropdown\', options: [\"HiTest\", \"Dunkin\", \"Decaf\"] },
{ label: \'Address\', type: \'group\',
\"Fields\": [{
label: \'Street1\', type: \'text\', required: \'true\' }, {
label: \'Street2\', type: \'text\', required: \'true\' }, {
label: \'State\', type: \'dropdown\', options: [\"California\", \"New York\", \"Florida\"]
}]
}, ];
this.addNode = function(parent) {
var name = $window.prompt(\"Node name: \", \"node name here\");
parent.children = parent.children || [];
parent.children.push({
name: name
});
}
this.removeNode = function(parent, child) {
var index = parent.children.indexOf(child);
if (index > -1) {
parent.children.splice(index, 1);
}
}
});
<div ng-app=\"demo\" ng-controller=\"AppController as app\">
<form>
<ul class=\"unstyled\" dx-start-with=\"app.formData\" >
<li ng-repeat=\"field in $dxPrior\" data-ng-switch on=\"field.type\">
<div data-ng-switch-when=\"text\">
<label>{{field.label}}</label>
<input type=\"text\"/>
</div>
<div data-ng-switch-when=\"dropdown\">
<label>{{field.label}}</label>
<select>
<option ng-repeat=\"option in field.options\" value=\"{{option}}\">{{option}}</option>
</select>
</div>
<div data-ng-switch-when=\"group\" class=\"well\">
<h2>{{field.label}}</h2>
<ul class=\"unstyled\" dx-connect=\"field.Fields\" />
</div>
</li>
</ul>
<input class=\"btn-primary\" type=\"submit\" value=\"Submit\"/>
</form>
<script src=\"https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js\"></script>
<script src=\"https://rawgit.com/dotJEM/angular-tree-bower/master/dotjem-angular-tree.min.js\"></script>
</div>
Just want to extend jpmorin post in case of property based structure:
JSON:
{
\"id\": 203,
\"question_text_id\": 1,
\"yes\": {
\"question_text_id\": 25,
\"yes\": {
\"question_text_id\": 26
}
},
\"no\": {
\"question_text_id\": 4
}
}
As you can see json object here doesn\'t contain array structure.
HTML
<div>
<script type=\"text/ng-template\" id=\"tree_item_renderer.html\">
<span>{{key}} {{value}}</span>
<ul>
<li ng-repeat=\"(key,value) in value.yes\" ng-include=\"\'tree_item_renderer.html\'\"></li>
<li ng-repeat=\"(key,value) in value.no\" ng-include=\"\'tree_item_renderer.html\'\"></li>
</ul>
</script>
<ul>
<li ng-repeat=\"(key,value) in symptomeItems\" ng-include=\"\'tree_item_renderer.html\'\"></li>
</ul>
</div>
However you can iterate over it.
Angular documentation for ng-repeat over properties here
And some row implementation here