In a solution I'm building, I adopted the following as pattern.
In the controller, I have a couple of publicly visible elements:
A public Map<String,Object>
returned from a method called getViewBag().
a public method named getControlTree() that returns the dynamically generated control tree.
The page looks like this:
<apex:page controller="MyPageController">
<apex:sectionHeader title="My Section Title" />
<apex:pageMessages id="pageMessages" />
<apex:form id="theForm">
<apex:dynamicComponent id="dynComp" componentValue="{!ControlTree}" />
</apex:form>
</apex:page>
In the getControlTree Method, I first check if my Map is null. If it is, I initialize it by adding field names and objects. These field names are read from another custom object. The values are chosen based on a "type" of the field, so I add a boolean for example if the field is a checkbox.
With the Map built, I then build the control tree. My getControlTree method returns a PageBlock component which is the container for the hierarchy of form controls.
The knack is configuring the right binding expression on the dynamically created forms. For example, I do this:
Component.Apex.InputText ctl = new Component.Apex.InputText();
ctl.label = field.label;
ctl.expressions.value = buildBindingExpression(field.name);
with the helper function:
private string buildBindingExpression(string fieldName) {
String expr = '{!ViewBag[\'' + fieldName + '\']' + '}';
return expr;
}
So the binding expression {!ViewBag['Surname']}
refers to the object obtained by m_viewBag.get('Surname')
I encountered four main problems:
Firstly, SelectLists required special handling. The options part has to be bound to a List<SelectOption>
and cannot be bound to List<String>
or string[]
as I expected. The value part of the SelectList must be bound to a String. If you choose to make the SelectList multiselect, the value might have to be a String[]
or a List<String>
. Haven't tried. Don't know.
Secondly, the list of possible controls seems limited to Checkbox, Radio, SelectList and Textbox and there doesn't seem to be a way of marking a control as mandatory (prove me wrong).
Thirdly, validation. If a textbox is numeric only, or an email address - or if a field is a datepicker, there doesn't seem to be a way of rendering the right control. Component.Apex.InputField will render the right control(s) but can only be bound to sObjects.
Fourthly, there doesn't seem to be a way of programmatically setting html5 pass-through attributes, so I can add an InputText and set the type to "email", for example.
For the above reasons, I'll probably soon re-write my dynamic page to do the control-tree generation on the client with heavy JavaScript support.
It just occurred to me I could create a custom object with a property for every type of possible property, and bind InputField instances to the appropriate property on instances of my custom object - but that would be desperate and possibly a bit dumb.