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)