1. Overview
While Joget's Form Builder is excellent for visual design, there are advanced scenarios where you need to generate form definitions programmatically. This is typically done when a form's structure is dynamic or based on an external metadata source (e.g., a database table, a configuration list, or another Joget list).
This blog post analyzes a powerful BeanShell/Groovy script designed to run within Joget's server-side tools. It reads form field definitions from a FormRowSet (or similar data source) and constructs a complete Joget Form Definition JSON, finally saving it to the App Definition.
2. How It Works
The script operates by constructing the complete JSON object that defines a Joget Form, adhering strictly to the internal schema used by the platform.
-
βοΈ Data Source: The script assumes field metadata (ID, Label, Class Name) is available in the
rowsvariable (aFormRowSet). -
βοΈ Field Construction: It iterates over the
rows. For each entry, it creates a string representation of a JSON object for a single form element, ensuring theclassName(field type) andproperties(ID and Label) are correctly inserted. -
βοΈ JSON Aggregation: All individual field JSONs are placed into a
JSONArray(fields). This array is then inserted as the content of the Form's mainColumnelement. -
βοΈ Form Saving: Finally, the fully assembled JSON is wrapped in a
FormDefinitionobject. The script uses theAppServicebean to callcreateFormDefinition, making the new form instantly available in the App Designer.
3. Full Code
This code is typically executed within a Process Tool using the BeanShell/Groovy language or in a Form/Datalist Binder script where the rows variable is populated.
import java.util.Collection;
import org.joget.apps.app.model.AppDefinition;
import org.joget.apps.app.model.FormDefinition;
import org.joget.apps.app.service.AppService;
import org.joget.apps.app.service.AppUtil;
import org.joget.apps.form.model.FormRow;
import org.joget.apps.form.model.FormRowSet;
import org.joget.commons.util.LogUtil;
import org.json.JSONArray;
import org.json.JSONObject;
// Get form metadata from request or script tool properties
String formDefId = formData.getRequestParameter("form_def_id");
String formName = formData.getRequestParameter("form_name");
String formTableName = formData.getRequestParameter("form_table_name");
// Example: Assume 'rows' is a FormRowSet containing field configurations
// FormRowSet rows = ...
try {
JSONArray fields = new JSONArray();
// Loop through the source data (e.g., field definitions)
for (FormRow row : rows) {
String fieldId = row.getProperty("field_id");
String fieldLabel = row.getProperty("field_label");
// Must be a valid Joget Form Element class name (e.g., org.joget.apps.form.lib.TextField)
String className = row.getProperty("field_type");
JSONObject formElement;
// Build the JSON structure for a single form element
formElement = new JSONObject("{\n" +
" \"className\" : \"" + className + "\",\n" +
" \"properties\": {\n" +
" \"controlField\": \"\",\n" +
" \"id\": \"" + fieldId + "\",\n" +
" \"label\": \"" + fieldLabel + "\",\n" +
" \"permissionHidden\": \"\",\n" +
" \"readonly\": \"\",\n" +
" \"readonlyLabel\": \"\",\n " +
" \"size\": \"\",\n" +
" \"validator\": {\n" +
" \"className\": \"\",\n" +
" \"properties\": {}\n" +
" },\n" +
" \"value\": \"\",\n" +
" \"workflowVariable\": \"\"\n" +
" }\n" +
"}");
fields.put(formElement);
}
// Embed all generated fields into the main Form structure
JSONObject form = new JSONObject("{\n" +
" \"className\": \"org.joget.apps.form.model.Form\",\n" +
" \"elements\": [{\n" +
" \"className\": \"org.joget.apps.form.model.Section\",\n" +
" \"elements\": [{\n" +
" \"className\": \"org.joget.apps.form.model.Column\",\n" +
" \"elements\":" + fields.toString() + ",\n" +
" \"properties\": {\"width\": \"100%\"}\n" +
" }],\n" +
" \"properties\": {\n" +
" \"id\": \"section1\",\n" +
" \"label\": \"Dynamic Fields Section\"\n" +
" }\n" +
" }],\n" +
" \"properties\": {\n" +
" \"description\": \"Dynamically generated form.\",\n" +
" \"id\": \"" + formDefId + "\",\n" +
" \"loadBinder\": {\n" +
" \"className\": \"org.joget.apps.form.lib.WorkflowFormBinder\",\n" +
" \"properties\": {}\n" +
" },\n" +
" \"name\": \"" + formName + "\",\n" +
" \"noPermissionMessage\": \"\",\n" +
" \"permission\": {\n" +
" \"className\": \"\",\n" +
" \"properties\": {}\n" +
" },\n" +
" \"postProcessor\": null,\n" +
" \"postProcessorRunOn\": \"create\",\n" +
" \"storeBinder\": {\n" +
" \"className\": \"org.joget.apps.form.lib.WorkflowFormBinder\",\n" +
" \"properties\": {}\n" +
" },\n" +
" \"tableName\": \"" + formTableName + "\"\n" +
" }\n" +
"}");
// Save the new Form Definition to the current App
FormDefinition formDefinition = new FormDefinition();
formDefinition.setJson(form.toString());
formDefinition.setId(formDefId);
formDefinition.setName(formName);
formDefinition.setTableName(formTableName);
AppService appService = (AppService) AppUtil.getApplicationContext().getBean("appService");
AppDefinition appDef = AppUtil.getCurrentAppDefinition();
Collection errors = appService.createFormDefinition(appDef, formDefinition);
// Optional: Log errors if form creation failed
if (errors != null && !errors.isEmpty()) {
LogUtil.warn("FormCreator", "Errors encountered while creating form: " + errors.toString());
}
} catch (Exception ex) {
LogUtil.error("FormCreator", ex, "Cannot Create Form");
}
`
4. Example Use Cases
- β Dynamic Survey Generation: Create different forms instantly based on survey questions stored in a master configuration list.
- β Automated Integrations: Automatically generate forms that mirror the structure of an external API or third-party database table, minimizing manual development work.
- β User-Defined Forms: Allow specific power-users to define the fields they need in a secondary Joget list, then use this script to generate the final functional form.
5. Customization Tips
- π‘ Field Type Mapping: Use a map structure in your BeanShell script to translate simple data types (e.g.,
"TEXT") from your sourcerowsinto the necessary full Java Class Names (e.g.,"org.joget.apps.form.lib.TextField"). - π‘ Adding Complex Properties: For field types like Select Boxes, dynamically add the
optionsoroptionsBinderproperties to theformElementJSON to ensure the new field is fully functional upon creation. - π‘ Metadata Refresh: If you use this script to update a form, use
appService.updateFormDefinitioninstead ofcreateFormDefinition.
6. Key Benefits
- π Metadata-Driven Design: Decouples form structure from application code, making forms easier to manage and update.
- π Consistency: Ensures all generated forms follow a consistent structure (e.g., using the same default binders and field properties).
- π Extensibility: Enables the creation of complex applications where form fields are determined by business rules or configuration, not just static design.
7. Security Note
π Access Control: Since this script can alter the core application definition, it must be carefully permissioned. It is best used in a workflow process tool that runs under the secure system user account or is triggered only by application administrators. Avoid exposing this logic directly in front-end form validation or client-side scripts.
8. Final Thoughts
Leveraging BeanShell to dynamically manipulate Joget's JSON configurations is the pinnacle of advanced Joget development. It unlocks the ability to build flexible, configuration-driven solutions that are resilient to changes in business requirements.
Top comments (0)