recursive
structure of fields. A field type can be one of many primitive types like text, textarea, select, radio etc. or composite type that combines primitive types or other composite types or array of primitive or composite types. Form values are saved as a single row which is a stored composite object like SQL table row or a JSON object. Field values can be a reference to another stored object.We want a form designer that has full support for all types of fields. So, we need support for primitive, composite and array types and any combination of those types.
As first step I have designed a form viewer that can render the form from a form definition provided as JSON object. To keep the blog post simple I use one primitive type named
text
, composite
type, array
type and to define a form a separate form
type. Form , composite and array types has child fields array named fields
.Here is an example of possible form definition:
form = {'name':'form', 'type':'composite', 'fields': [ {'name': 'name','type': 'text'}, {'name':'address', 'type':'composite', 'fields':[ {'name': 'street','type': 'text'}, {'name': 'city','type': 'text'}, {'name': 'zip','type': 'text'}, {'name': 'state','type': 'text'}, {'name':'members', 'type':'array', 'fields':[ {'name': 'name','type': 'text'} ] } ] }, {'name':'assets', 'type':'array', 'fields':[ {'name': 'type','type': 'text'}, {'name': 'price','type': 'text'}, {'name': 'brand','type': 'composite', 'fields':[ {'name': 'name','type': 'text'}, {'name':'locations', 'type':'array', 'fields':[ {'name': 'city','type': 'text'} ] } ] } ] } ] };
This is a form that has primitive types, composite type, array of primitive type and array of composite type. Other than reference type which is just a primitive type with loaded data this covers what we need to create almost any type of form.
Here is the form viewer preview:
I have created an angular directive named field.
cosmosApp.directive('field', function($compile) { return { restrict: 'E', scope: { item:'=' }, link: function(scope, element, attributes) { scope.item_data = []; scope.add_item = function(position){ var newItem = {}; angular.forEach(scope.item.fields, function(value, index){ newItem[value.name] = "test"; }); scope.item_data.splice(position+1, 0, newItem); }; scope.removeItem = function(index){ scope.item_data.splice(index, 1); }; var template = '<span>{{item.name}}<input type="text" /></span> '; if(scope.item.type === "composite"){ template = '<div>{{item.name}}</div><ul><li ng-repeat="ph in item.fields">
<field item="ph"></field></li></ul>'; } else if(scope.item.type === "array"){ if(scope.item_data.length<1){ scope.item_data = []; scope.add_item(); } template = '<div>{{item.name}}<button ng-click="add_item(-1)">+</button></div>
<ul><li ng-repeat="d in item_data">
<field item="ph" ng-repeat="ph in item.fields"></field>
<button ng-click="removeItem($index)">-</button>
<button ng-click="add_item($index)">+</button></li>
</ul>'; } var newElement = angular.element(template); $compile(newElement)(scope); element.replaceWith(newElement); } }; });To use this directive we create a
form
in our controller and create a dom node like this: