DEV Community

Explorer
Explorer

Posted on

πŸ› οΈ Dynamic Joget Form Generation Using BeanShell Scripting

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 rows variable (a FormRowSet).
  • βš™οΈ 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 the className (field type) and properties (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 main Column element.
  • βš™οΈ Form Saving: Finally, the fully assembled JSON is wrapped in a FormDefinition object. The script uses the AppService bean to call createFormDefinition, 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");
}
Enter fullscreen mode Exit fullscreen mode


`


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 source rows into 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 options or optionsBinder properties to the formElement JSON to ensure the new field is fully functional upon creation.
  • πŸ’‘ Metadata Refresh: If you use this script to update a form, use appService.updateFormDefinition instead of createFormDefinition.

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)