# Field Customization

The system supports customization to implement default values, validation, linkage, and other behaviors for fields.

# Target Audience

The target audience for this document is: developers and implementers of this system

# Customizing Field Default Values

In addition to supporting the use of grails model definition method (opens new window) to specify default values for fields when saved to the database, the system also supports specifying default values for fields in the creation form through customization.

# Injected Variables

The injected variables for this customization at runtime are listed in the following table:

Variable Name Variable Type Description
objectType tech.muyan.DomainClass Current operating object type
userContext grails.plugin.springsecurity.userdetails.GrailsUser Current operating user information
destColumn java.lang.String Target column for setting default value
destColumnType java.lang.String Full type name of the target column
application grails.core.GrailsApplication Current grails application context
log Closure<?> Log closure for printing execution logs

# Return Result

The result of this customization needs to return a Map containing a key "result", as shown below

// result key 对应的 value 即为系统会传递到前台的默认值
// The value corresponding to the result key is the default value passed to the frontend by the system
return [result: xxx]
1
2

# Customizing Field Validation

Form fields can define dynamic validation logic. When the field is modified in the front-end form, it will call the backend validation logic through the interface for validation. If the validation fails, an error message will be displayed on the front-end.

# Injected Variables

The related variables injected by the system when executing dynamic field validation logic are shown in the following table

Variable Name Variable Type Description
userContext grails.plugin.springsecurity.userdetails.GrailsUser Current operating user information
application grails.core.GrailsApplication Current grails application context
domainName java.lang.String Name of the current operating object type
destColumn java.lang.String Name of the target column for validation
destColumnValue java.lang.Object Value of the target column
create boolean Whether it's a create operation, if false it indicates an update operation
requestData java.lang.Object JSON serialization of all field values in the front-end form
objectId java.lang.Long ID of the object being validated, for create operations, the value is -1
log Closure<?> Log closure for printing execution logs

TIP

Example structure of requestData:

{
  "formValues":{},  // Values of form fields displayed on the interface, including values not yet saved to the database
  "record":{}, // For update operations, this is the value of the current record saved in the database; for create operations, this is the value of all fields with default values
  "unique":[] // List of fields combined with the current trigger field for uniqueness validation
}
1
2
3
4
5

You can use requestData.getAt("formValues").getAt("fieldName") to get the value of a field named fieldName on the interface

# Return Result

The result of custom validation is shown below, where valid indicates whether the validation passed, and message is the prompt message when validation fails. If validation succeeds, this value will be ignored.

// result key 对应的 value 即为系统会传递到前台的默认值
// The value corresponding to the result key is the default value passed to the frontend by the system
return [
  valid: true | false,
  message: "校验失败的界面提示信息 Field validation failure information"
]
1
2
3
4
5

# Customizing Field Linkage

The system supports implementing linkage between fields through customization. The achievable logic includes:

  • Determining whether the target field is hidden
  • Determining whether the target field is read-only
  • Determining whether the target field is required
  • Directly setting the value of the target field
  • If the target field is a selection type, the options for other selection fields can be set

The following is an overview of the Dynamic Field Hook object that needs to be created to inject field linkage processing logic into the system:

Object Type: Select the object type to which this custom logic applies
Logic source: Logic source, can be static field or dynamic field
Trigger field: The trigger field for linkage, changes to this field will trigger linkage
Target field: The target field for linkage, i.e., the target field for this custom logic
Trigger dynamic field: The trigger dynamic field for linkage, changes to this dynamic field will trigger linkage
Target dynamic field: The target dynamic field for linkage, i.e., the target dynamic field for this custom logic
Hook Type: Select Field Dependencies hook
Core Logic: Custom code, refer to the following documentation for injectable variables and return value conventions that can be used in the specific code
1
2
3
4
5
6
7
8

TIP

Only one of Trigger field and Trigger dynamic field can have a value set at the same time Only one of Target field and Target dynamic field can have a value set at the same time

Which field to set the value for is determined by the Logic source field

# Injected Variables

The injectable variables that can be used in this custom code are shown in the following table:

Variable Name Variable Type Description
application grails.core.GrailsApplication Current grails application context
userContext grails.plugin.springsecurity.userdetails.GrailsUser Current operating user information
domainName java.lang.String Current operating object type without the package part
sourceColumn java.lang.String Driving column, changes to this field triggered this customization
destColumn java.lang.String Target column, the return result of customization will act on this column
sourceColumnValue java.lang.String Value of the driving column
object org.grails.web.json.JSONObject Current values of each field on the interface of the object being edited by the user
log Closure<?> Log closure for printing execution logs

TIP

At the point when the custom code is called, the injected object is only temporary data for each column in the input boxes on the interface, not yet saved to the backend database.

# Return Result

The result that needs to be returned by this custom code is a multi-level Map data. The specific return data and its description are as follows:

return [
  //指定该字段的显示状态:hide 为隐藏、show 为显示且可编辑、readonly 为只读
  //Specify the display status of the field: hide for hidden, show for editable, and readonly for read-only
  display: hide | show | readonly

  // 指定该字段是否必填, true 表示必填,false 表示非必填
  // Specify whether the field is required: true for required, false for non-required
  required: true | false

  // 指定该字段的值,如果是个多选字段,可以使用 [] 的形式来指定多个值
  // Specify the value of the field, if it is a multi-select field, you can use the [] form to specify multiple values
  value: [] 或者 xxx,

  // 如果该字段是个选择类型的字段,如下的返回值指定其备选项,
  // 每个备选项均包括显示给用户看的 Label 属性和实际保存的 value 属性
  // If the field is a select type field, the following return value specifies its options,
  // each option includes the Label attribute displayed to the user and the value attribute actually saved
  options: [
      {
          "value": "ABSTRACT_DATE",
          "label": "Abstract date"
      },
      {
          "value": "ABSTRACT_DATE_TIME",
          "label": "Abstract date with time"
      }
  ]
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

TIP

If you want to clear the options of a selection field, you need to set options to an empty array and pass it to the front-end, rather than passing undefined or null to the front-end

{
  options: []
}
1
2
3

# Field Linkage Code Examples

The following related code snippets demonstrate some return forms that might be used in business

  • Set the target field of customization to hidden or read-only
// 声明返回结果的 Map 对象
// Declare a Map object for the return result
Map result = [:] 

// 以下的两句代码是互斥的,否则后一句会覆盖前一句
// The following two lines of code are mutually exclusive, otherwise the latter will override the former
// 设置目标字段在界面上隐藏
// Set the target field to be hidden on the interface
result.put("display", "hide") 

// 设置目标字段在界面上为只读字段
// Set the target field to be read-only on the interface
result.put("display", "readonly") 

// 返回结果
// Return the result
result 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • Set the customization field to display and required
// 声明返回结果的 Map 对象
// Declare a Map object for the return result
Map result = [:] 

// 设置目标字段在界面上的正常显示
// Set the target field to be displayed normally on the interface
result.put("display", "show") 

// 设置目标字段为必填字段
// Set the target field as required
result.put("required", true) 

// 返回结果
// Return the result
result 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • Modify the options of the target field based on the value of the source field (this is a very common example of province and city field linkage)
// 声名返回结果的 Map 对象
// Declare a Map object for the return result
Map result = [:] 

// 设置目标字段在界面上的正常显示
// Set the target field to be displayed normally on the interface
result.put("display", "show") 

// 设置目标字段为必填字段
// Set the target field as required
result.put("required", true) 

// 如果来源字段的值是 "JS"
// If the value of the source field is "JS"
if (sourceColumnValue == "JS") { 
    // 将目标字段的选项设置为 [NJ, HA],显示分别为 "南京", "淮安"
    // Set the options of the target field to [NJ, HA], displayed as "南京", "淮安" respectively
    result.put("options", [
        [value: "NJ", label: "南京"],
        [value: "HA", label: "淮安"],
    ]) 
    // 将目标字段的值设置为 "NJ"
    // Set the value of the target field to "NJ"
    result.put("value", "NJ") 
}
// 返回结果
// Return the result
result 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Real System Usage Examples

This section lists two examples of using custom code to control form behavior in a formal system.

# Controlling Field Display

Control the display of the optionsJson field based on fieldType when creating DynamicFieldDefinition

# Dynamic Field Hook Object

Target Field: opitonsJson
Logic source: Static field
Object Type: Dynamic Field Definition
Field Name: fieldType
Hook Type: Field dependencies hook
Core logic: Associated Dynamic Logic definition, refer to the following code snippet
1
2
3
4
5
6

# Custom Code

// 声明返回的 Map 对象
// Declare a Map object for the return result
def result = [:] 

// 如果 fieldType 字段的值等于 OBJECT、FILE、BOOLEAN 或者 IMAGE
// If the value of the fieldType field equals OBJECT, FILE, BOOLEAN or IMAGE
if (sourceColumnValue == tech.muyan.enums.FieldType.OBJECT.name()
    || sourceColumnValue == tech.muyan.enums.FieldType.FILE.name()
    || sourceColumnValue == tech.muyan.enums.FieldType.BOOLEAN.name()
    || sourceColumnValue == tech.muyan.enums.FieldType.IMAGE.name()) {
  // 将目标字段 optionsJson 设置为只读
  // Set the target field optionsJson to read-only
  result.put("display", "readonly") 
} else {
  // 否则将目标字段 opitonsJson 设置为显示且可编辑
  // Otherwise, set the target field optionsJson to be displayed and editable
  result.put("display", "show") 
}
// 返回 result 对象作为客制化逻辑执行的结果
// Return the result object as the result of custom logic execution
result 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Controlling Options for Selection Fields

The following custom configuration and code implement controlling the options in the displayComponentType field based on the user's selection of the dynamicField field when creating an Object Dynamic Field object.

# Dynamic Logic Object

Target Field: opitonsJson
Object Type: Dynamic Field Definition
Field Name: fieldType
Logic Type: Field dependencies
Code: Refer to the following code snippet
1
2
3
4
5

# Custom Code

// 声明返回的 result Map,默认显示该字段
// Declare the result Map to be returned, default to show this field
def result = ["display": "show"]

// 声明返回的 options 数组
// Declare the options array to be returned
def options = []

// 从传递到后台的 sourceColumnValue 字段,在这里是 dynamicFieldDefinition 的 id 字段获取对象
// Get the object from the sourceColumnValue field passed to the backend, which is the id field of dynamicFieldDefinition here
def dynamicField = tech.muyan.DynamicFieldDefinition.get(Long.valueOf(sourceColumnValue))

if (null != dynamicField) {
  // 获取对象中保存的 fieldType 信息
  // Get the fieldType information saved in the object
  def fieldType = dynamicField.fieldType

  // 从配置的映射表中获取每一种 fieldType 可以使用的 displayComponentType 列表
  // Get the list of displayComponentTypes that can be used for each fieldType from the configured mapping table
  def optionConfig = tech.muyan.config.DynamicFieldTypeToComponentMapping.FieldTypeToComponentMapping.get(fieldType)
  if (optionConfig != null && optionConfig.size() > 0) {
    // 遍历该列表,将枚举值的 name 和 label 分别放入 options 列表
    // Iterate through the list, putting the name and label of the enum values into the options list respectively
    for (option in optionConfig) {
      options.add(Map.of("value", option.name(), "label", option.label))
    }
    // 将 options 数组放入 result map
    // Put the options array into the result map
    result.put("options", options)
  }
}
// 返回结果
// Return the result
result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# Field Quick Search Logic

The system supports customizing the filtering logic for quick search when referencing other objects on the page of an object. Specifically,

For custom search logic, you need to create a Dynamic Field Hook object as follows

Target Field: Set to the object field to be searched
Object Type: Select the object type to which this custom logic applies. Here, set the type of the object to which the field belongs, not the type of the object to be searched
Hook Type: Select Field search addon hook
Core Logic: Custom code, refer to the following documentation for injectable variables and return value conventions that can be used in the specific code
1
2
3
4

# Injected Variables

The injectable variables that can be used in this custom code are shown in the following table:

Variable Name Variable Type Description
ownerClass java.lang.Class<?> The object type to which this field belongs
fieldName java.lang.String The field name of the field to be searched in the object
fieldClass java.lang.Class<?> The field type of the field to be searched
keyword java.lang.String Search keyword
userContext grails.plugin.springsecurity.userdetails.GrailsUser Current operating user information
application grails.core.GrailsApplication Current grails application context
log Closure<?> Log closure for printing execution logs

# Return Result

The result that needs to be returned by this custom code is a Map data type. This Map contains an element with a key of result and a value of a list of domain objects. The specific return data example is as follows:

return [
  // 包含且仅包含一个 key 为 result 的数组
  // Contains and only contains an array with a key of result
  'result': [// result 中是一个数组
    // 数组中每个元素都是一个 domain 对象
    // Each element in the array is a domain object
  ]
]
1
2
3
4
5
6
7

# Action and Wizard Field Customization

Dynamic fields used in Actions and Wizards also support DynamicFieldHook customization. When creating DynamicFieldHook objects for fields used in Actions and Wizards, note the following points:

  1. triggerField should be set to df_<DynamicFieldDefinition field name attribute>
  2. logicSource should be set to DYNAMIC_FIELD
  3. objectType should be set to empty, because this custom logic is not for a specific object type, but for a specific dynamic field. If creating a DynamicFieldHook object through CSV import, you can set the objectType.shortName column to empty.

Here's an example from an actual project, where a field named "User" is defined:

// DynamicFieldDefinition.csv
// Define a dynamic field with name "User"
name(*),fieldType,optionsJson,label,referenceClazz.fullName
User,OBJECT,,User,tech.muyan.security.User

// DynamicFieldInstance.csv
// Define a dynamic field instance, associated with the above dynamic field definition named "User",
// its type is "Action parameter field", associated with the Action named "BatchAddUserToGroupInUserGroupList"
// type,label,displayComponentType,dynamicField.name(*),objectType.shortName(*),action.name,wizardStep.name,editable,display,required,displaySequence
Action parameter field,User,OBJECT_MULTIPLE_SELECTION,User,,BatchAddUserToGroupInUserGroupList,,true,true,true,10

// DynamicFieldHook.csv
// Define a dynamic field customization, objectType.shortName is empty, its triggerField is "df_User", logicSource is "DYNAMIC_FIELD"
organization.name,objectType.shortName,coreLogic.name,hookType,triggerField,targetField,triggerDynamicField.label,targetDynamicField.label,logicSource,name(*),active,isSystem,description
$ROOT_ORG$,,User search,SEARCH,,df_User,,,DYNAMIC_FIELD,User search in bulk add user to group action,Y,Y,"User search in bulk add user to group Action"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Note

Note that if multiple Actions or Wizards use the same dynamic field definition (the same DynamicFieldDefinition) and define DynamicFieldHook at the same time, because the system uses df_<DynamicFieldDefinition Name> to identify field customization, the system cannot distinguish between field customizations corresponding to different Actions or Wizards.

In this case, multiple DynamicFieldDefinition objects need to be created, and field instances for each Action or Wizard should be created based on different DynamicFieldDefinition definitions.

Last Updated: 9/4/2024, 12:06:19 PM