# Development Cookbook
The following is a quick reference for the customization injected variables, return values and data formats commonly used in system development. Each section lists only the key points; click the links in each section for the full documentation.
# Target Audience
The target audience of this document: developers and implementers of this system
# Form Hook
Form-level customizations such as field linkage, default values, validation and option filtering are all implemented with the form hook (formHook) and data hook (dataHook) on the form. For injected variables and return values, see Form Customization (Form Hook).
TIP
The per-field field cascading, field search, field validation and field default value customizations (Field Hook) from before 1.0 have been removed since 1.0 (use the form hook instead).
# Object Creation Hook
For details, see Object Hooks (Object Hook).
# Injected Variables
| Variable name | Variable type | Description |
|---|---|---|
object | <? extends GormEntity> | The object instance to be created |
requestData | Map | The raw data submitted by the frontend |
userContext | tech.muyan.api.security.MuyanAuthentication | Information about the current user |
application | grails.core.GrailsApplication | The current Grails application context |
log | Closure | Prints execution logs; the content is saved to the execution record |
hookType | tech.muyan.enums.ObjectHookType | The type of the hook being executed |
# Return Value and Exception Handling
The return value of create, update and delete hooks is only recorded in the execution record; the platform does not use it to modify the object. To modify data, change the object (create) or newObject (update) instance directly in a "before save" hook, and the changes are saved together with the current operation:
// BEFORE_CREATE: default credit limit for new customers
if (object.creditLimit == null) {
object.creditLimit = 10000
}
2
3
4
Hook code can interrupt the operation or issue a warning by throwing an exception. The platform decides based on the exception's actual class name; subclasses are not recognized:
| Exception thrown | Result |
|---|---|
tech.muyan.exception.CustomLogicInterruptException | The operation is interrupted, the transaction is rolled back, and the exception message is shown to the user as an error. The status of the execution record is SUCCESS (an intentional interruption does not count as an execution failure) |
tech.muyan.exception.CustomLogicWarningException | The operation continues, and the exception message is shown to the user as a warning. The status of the execution record is SUCCESS_WITH_WARNING |
| Any other exception (including subclasses of the two classes above) | The operation is interrupted, the transaction is rolled back, and the status of the execution record is FAILED; the error message shown to the user contains the class name and message of the original exception |
// BEFORE_DELETE: approved documents cannot be deleted
if (object.status == 'APPROVED') {
throw new tech.muyan.exception.CustomLogicInterruptException("åˇ˛åŽĄæ ¸įåæŽä¸čŊå é¤")
}
2
3
4
# Object Update Hook
For details, see Object Hooks (Object Hook).
# Injected Variables
| Variable name | Variable type | Description |
|---|---|---|
oldObject | <? extends GormEntity> | The object before the update, re-read from the database in a separate Session |
newObject | <? extends GormEntity> | The object instance updated with the submitted data |
requestData | Map | The raw data submitted by the frontend |
userContext | tech.muyan.api.security.MuyanAuthentication | Information about the current user |
application | grails.core.GrailsApplication | The current Grails application context |
log | Closure | Prints execution logs; the content is saved to the execution record |
hookType | tech.muyan.enums.ObjectHookType | The type of the hook being executed |
# Return Value and Exception Handling
The return value of create, update and delete hooks is only recorded in the execution record; the platform does not use it to modify the object. To modify data, change the object (create) or newObject (update) instance directly in a "before save" hook, and the changes are saved together with the current operation:
// BEFORE_CREATE: default credit limit for new customers
if (object.creditLimit == null) {
object.creditLimit = 10000
}
2
3
4
Hook code can interrupt the operation or issue a warning by throwing an exception. The platform decides based on the exception's actual class name; subclasses are not recognized:
| Exception thrown | Result |
|---|---|
tech.muyan.exception.CustomLogicInterruptException | The operation is interrupted, the transaction is rolled back, and the exception message is shown to the user as an error. The status of the execution record is SUCCESS (an intentional interruption does not count as an execution failure) |
tech.muyan.exception.CustomLogicWarningException | The operation continues, and the exception message is shown to the user as a warning. The status of the execution record is SUCCESS_WITH_WARNING |
| Any other exception (including subclasses of the two classes above) | The operation is interrupted, the transaction is rolled back, and the status of the execution record is FAILED; the error message shown to the user contains the class name and message of the original exception |
// BEFORE_DELETE: approved documents cannot be deleted
if (object.status == 'APPROVED') {
throw new tech.muyan.exception.CustomLogicInterruptException("åˇ˛åŽĄæ ¸įåæŽä¸čŊå é¤")
}
2
3
4
# Object Deletion Hook
For details, see Object Hooks (Object Hook).
# Injected Variables
| Variable name | Variable type | Description |
|---|---|---|
object | <? extends GormEntity> | The object to be deleted |
userContext | tech.muyan.api.security.MuyanAuthentication | Information about the current user |
application | grails.core.GrailsApplication | The current Grails application context |
log | Closure | Prints execution logs; the content is saved to the execution record |
hookType | tech.muyan.enums.ObjectHookType | The type of the hook being executed |
# Return Value and Exception Handling
The return value of create, update and delete hooks is only recorded in the execution record; the platform does not use it to modify the object. To modify data, change the object (create) or newObject (update) instance directly in a "before save" hook, and the changes are saved together with the current operation:
// BEFORE_CREATE: default credit limit for new customers
if (object.creditLimit == null) {
object.creditLimit = 10000
}
2
3
4
Hook code can interrupt the operation or issue a warning by throwing an exception. The platform decides based on the exception's actual class name; subclasses are not recognized:
| Exception thrown | Result |
|---|---|
tech.muyan.exception.CustomLogicInterruptException | The operation is interrupted, the transaction is rolled back, and the exception message is shown to the user as an error. The status of the execution record is SUCCESS (an intentional interruption does not count as an execution failure) |
tech.muyan.exception.CustomLogicWarningException | The operation continues, and the exception message is shown to the user as a warning. The status of the execution record is SUCCESS_WITH_WARNING |
| Any other exception (including subclasses of the two classes above) | The operation is interrupted, the transaction is rolled back, and the status of the execution record is FAILED; the error message shown to the user contains the class name and message of the original exception |
// BEFORE_DELETE: approved documents cannot be deleted
if (object.status == 'APPROVED') {
throw new tech.muyan.exception.CustomLogicInterruptException("åˇ˛åŽĄæ ¸įåæŽä¸čŊå é¤")
}
2
3
4
# Dynamic Create Permission
An object hook of type CREATE decides at runtime whether a user can create a type of object. For details, see Object Permission Control.
# Injected Variables
| Variable | Type | Description |
|---|---|---|
objectType | Class<?> | The object type being operated on |
userContext | tech.muyan.api.security.MuyanAuthentication | The current user |
application | grails.core.GrailsApplication | The current grails application context |
log | Closure<?> | log closure for printing execution logs |
# Return Value
// Allow the user to create this object; create must be placed inside result
return [result: [create: true]] # Dynamic Update and Delete Permissions
The object hook type UPDATE_DELETE does not take effect in the current version: whether an object can be modified or deleted is decided only by the domain model's updateRoleRequirement and deleteRoleRequirement. When you need a runtime decision, configure custom logic (customLogic) for these two role requirements (RoleRequirement); see Object Permission Control for details.
WARNING
The built-in role requirements such as USER, DEVELOPER and ADMIN are shared by many models, forms and menus. Configuring custom logic directly on them affects every place that references them. Create a dedicated role requirement for the model that needs a runtime decision, and point the domain model's updateRoleRequirement or deleteRoleRequirement to it.
The custom logic of a role requirement receives the following injected variables, and returns [result: true] to indicate that the requirement is met. Once custom logic is configured, the system no longer checks the roles in hasPermissionRoles automatically; check them in the logic yourself if needed:
| Variable Name | Variable Type | Description |
|---|---|---|
object | Object | The object whose permission is being checked, may be null, see the note below |
hasPermissionRoles | List<HierarchyRole> | The roles configured in this role requirement |
user | tech.muyan.api.security.MuyanAuthentication | The current user |
object may be null
object is a concrete object only when the frontend queries permissions per row (POST /api/permissions/<domain model>/, which decides whether each row shows the Update and Delete buttons). When the backend handles update, batch update and delete requests (PUT, DELETE /api/data/...), it checks "whether this type of object can be operated on", and the object passed in is null.
The logic must therefore handle object == null first; otherwise it throws a null pointer exception or returns "not met", and all update and delete requests are rejected. For example:
boolean isSales = user.authorities*.authority.contains('ROLE_SALES')
if (object == null) {
// API-level check: decide by role only
return [result: isSales]
}
// Per-object check: closed records do not show the update and delete buttons
return [result: isSales && object.status != '厞å
ŗé']
2
3
4
5
6
7
The per-object check currently only controls the buttons in the UI. Calling the API directly can still modify or delete the record, so it cannot be relied on for data security. See Object Permission Control for details.
# Scheduled Task Core Logic
For more information, see Scheduled Tasks.
# Injected Variables
| Variable name | Variable type | Description |
|---|---|---|
triggerDatetime | java.time.OffsetDateTime | The scheduled time of this firing |
task | tech.muyan.task.DynamicTask | The current task; custom parameters can be read through task.parameters (a JSON string) |
log | Closure | Prints execution logs; the content is saved to the execution record |
application | grails.core.GrailsApplication | The current Grails application context |
# Return Value
return [
//Execution result, of type text
execResult: 'OK, Result'
] 2
3
TIP
Scheduled tasks have no enable logic; whether a task runs is decided by the task's Active, Start date and Expiry date.
# Dynamic Action Display Logic
The enable logic of an object action (dynamic logic of type DYNAMIC_ACTION_ENABLE_LOGIC) decides which records the action is available for. For more information, see Object Actions (Dynamic Action).
# Injected Variables
| Variable Name | Variable Type | Description |
|---|---|---|
userContext | tech.muyan.api.security.MuyanAuthentication | Information about the user performing the operation |
action | tech.muyan.dynamic.action.DynamicAction | The current action definition |
form | tech.muyan.dynamic.form.DynamicForm | The form the action belongs to |
objects | List | The list of records to evaluate |
domainClass | tech.muyan.DomainClass | Object type information of the records |
log | Closure | Prints execution logs |
# Return Value
Returns the list of ids of available records, instead of the [result: true/false] structure:
return [
enableIds: objects.findAll { it.accountLocked }.collect { it.id }
]
2
3
- When the return value has no
enableIds(or it isnull), all records are available. - When the enable logic throws an exception, no records are available, and the error is written to the backend log.
# Dynamic Action Core Logic
For more information, see Object Actions (Dynamic Action).
# Injected Variables
| Variable name | Variable type | Description |
|---|---|---|
userContext | tech.muyan.api.security.MuyanAuthentication | Information about the current user |
action | tech.muyan.dynamic.action.DynamicAction | The current action definition |
form | tech.muyan.dynamic.form.DynamicForm | The form that triggered the action |
objects | List | The list of selected records. In OBJECT_SINGLE mode it is also a list, with only one element; in CLASS_LEVEL mode it is an empty list |
objectIds | List<Long> | The list of ids of the selected records |
domainClass / objectType | tech.muyan.DomainClass | Object type information of the records; both variables hold the same value |
parameters | Map<String, Object> | The values entered by the user in the parameter form, converted according to field types |
rawParameters | Map<String, Object> | The raw values entered by the user in the parameter form |
searchConditions | Map | The current search conditions of the list page at execution time; see Batch Processing by Search Conditions |
log | Closure | Prints execution logs; the content is saved to the "Execute log" of the execution record |
application | grails.core.GrailsApplication | The current Grails application context |
# Return Value
[
// Execution result text, saved to the execution record and shown to the user
execResult: "Execution result",
// Type is tech.muyan.storage.StorageFieldValue; the frontend downloads this file automatically after execution completes
download: storageFieldValue,
// The following fields are only saved to the execution record and are not used by the current frontend
displayType: "markdown",
execResultInfo: "Extra info",
redirect: "/some/page"
] 2
3
4
5
6
7
8
9
WARNING
The dynamic logic engine OS_COMMAND (calls external commands) is not usable in 1.0.0-beta18: the engine cannot get the command content to run when it executes.
TIP
Action post-processing logic, form field group display logic, and wizard (Wizard) processing logic have been removed since 1.0.
# Widget Display Logic
For more information, see Dashboard Design.
# Injected Variables
# Return Value
// Indicates whether this dashboard widget is enabled (the enable logic of an object action returns [enableIds: [...]] and does not use this structure)
[result: true | false] # Widget Core Logic
# Injected Variables
Same as the widget display logic.
# Return Value
The structure of the widget core logic's return value differs by widget type; see the description of each widget type in Dashboard Design.
# Webhook
Webhooks that receive calls from external systems are configured in Integration > Webhook. For details, see System Integration. The incoming and outgoing system integrations (DynamicIntegration) from before 1.0 have been removed since 1.0 (use Webhook or dynamic services instead).
# Dynamic Service
For more information, see Dynamic Service.
# Service Definition
| UI label | Property | Type | Description |
|---|---|---|---|
| Name | name | String | Service name, unique within the tenant, cannot be changed after creation; used in the invocation address |
| Logic | logic | tech.muyan.dynamic.DynamicLogic | The logic the service runs; the logic type is DYNAMIC_SERVICE_CORE_LOGIC |
| Active | active | Boolean | Whether the service is active |
| Enable Anonymous | enableAnonymous | Boolean | Whether calls without login are allowed; off by default |
| Enable Log | enableLog | Boolean | Whether to record execution records; off by default. Execution records are produced only when it is on |
| Body Type | bodyType | Enum | How the request body is parsed: JSON, XML, INPUT_STREAM; treated as JSON when empty |
| Exec Records | execRecords | Execution records of the service |
# Injected Variables
| Variable | Type | Description |
|---|---|---|
user | tech.muyan.api.security.MuyanAuthentication | The caller. For calls without login it is an anonymous identity object, not null |
method | String | HTTP method of the request, for example GET, POST |
params | Map<String, Object> | URL parameters. A parameter with a single value is a string; a parameter with multiple values is a list |
body | Object | Request body, parsed according to Body Type: a JSON object for JSON, an XML object for XML, the raw input stream for INPUT_STREAM |
log | Closure<?> | log closure for printing execution logs; with Enable Log on, logs are written to the execution record |
# Dynamic Filter Condition Definition
The format of dynamic filter condition definitions is shown below. For more information, see Dynamic Filter.
// Description of the dynamic filter conditions below:
// 1. The status field equals SUCCESS
// 2. type is one of FINDER, UPDATE
{
// key is the column name: status
"status": {
// The target column to filter
"columnKey": "status",
// Match rule: equals
"matchMode": "=",
// Target value to match: SUCCESS
"value": "SUCCESS"
},
"type": { // key is the column name: type
// The target column to filter, same as the key in the previous line
"columnKey": "type",
// Match rule of the filter: isOneOf (is one of the values)
"matchMode": "isOneOf",
// Target values of the filter: [FINDER, UPDATE]
"value": ["FINDER", "UPDATE"]
}
} 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Dynamic Matching Condition List
The following lists the match conditions that can be used in dynamic filters and are rendered dynamically at runtime. For more information, see Dynamic Filter.
| Placeholder | Replaced with |
|---|---|
${currentHour} | The current hour, at zero minutes and zero seconds |
${currentDay} | Midnight of the current date |
${currentWeek} | Midnight on Monday of the current week |
${currentMonth} | Midnight on the first day of the current month |
${currentQuarter} | Midnight on the first day of the current quarter |
${currentYear} | Midnight on January 1 of the current year |
${currentUserGroups} | List of role names the current user has, in the format ['ROLE_USER','ROLE_ADMIN'] |
${currentUsername} | Username of the currently logged-in user, see the note below |
${currentUserId} | id of the currently logged-in user, see the note below |
# Get Dynamic Configuration
For more details, see System Configuration.
# Get Configuration in the Frontend
import { getConfig } from "@muyantech/frontend-lib";
// Calls GET /api/config/{key}, returns { key, value }, where value is a string
const config: any = await getConfig('websocket.heartbeatInterval');
const heartbeatInterval = Number(config?.value ?? 20);
2
3
4
5
# Get Configuration in the Backend
In a plugin's Service or other Spring bean, inject
tech.muyan.api.config.DynamicConfigServicedirectly.In dynamic logic, get the service through the static field
tech.muyan.ConfigHelper.dynamicConfigService:
import tech.muyan.ConfigHelper
Integer interval = ConfigHelper.dynamicConfigService
.getByKeyOrDefault("websocket.heartbeatInterval", 20)
2
3
4
- On fields of a plugin component (a class implementing
tech.muyan.api.MuyanPlatformComponent), you can use the annotation@tech.muyan.api.annotations.DynamicConfig("config key"). When the plugin is loaded, the platform writes the configuration value through the field's public setter, and calls the setter again automatically after the configuration changes:
import tech.muyan.api.annotations.DynamicConfig
class HeartbeatSettings implements tech.muyan.api.MuyanPlatformComponent {
@DynamicConfig("websocket.heartbeatInterval")
Integer heartbeatInterval
}
2
3
4
5
6
# Global Configuration or Data Sharing
For how to share global configuration, connections or cached data between dynamic logic executions, see the FAQ.
The system provides the tech.muyan.helper.RegistryHelper class for managing globally shared data. You can use
RegistryHelper.memoryGet(key)to get cached data,RegistryHelper.memoryPut(key, value)to set cached data; it returns the value previously stored under the key (or null if there was none),RegistryHelper.memoryRemove(key)to remove cached data.
# extInfo Quick Reference
TIP
In the detailed configurations, the ? in xxx?: means the configuration is optional and a default value is used if it is not set. The content of an extInfo field must not contain the ? character.
# In DomainClass Definitions
{
// Quick search by the user matches against the name and label fields
"inlineSearchColumns": ["name", "label"],
// When an Object control is shown in the UI, the value of its label field is displayed as the identifier
"labelField": "label",
// When importing CSV data, the current Domain is loaded after DynamicLogic and User
"loadAfter": ["DynamicLogic", "User"],
// When a CSV column that references the current Domain does not specify a query field, records are looked up by the name field
"queryField": "name"
} 2
3
4
5
6
7
8
9
For more information, see Dynamic Domain Model.
# In DomainClassField Definitions
{
// For Decimal fields: set the number of decimal places (scale) to 2
"scale": 2,
// For Decimal fields: set the precision to 10
"precision": 10,
// Specifies the Java enum type of an ENUM / ENUM_LIST field
// The enum class can be defined in a dynamic plugin
"enumClass": "tech.muyan.mes.enums.WorkTaskStatusEnum",
// For MAPPED_DOMAIN_OBJECT / MAPPED_DOMAIN_OBJECT_COLLECTION fields, the reverse reference field must be set
"mappedBy": "b"
} 2
3
4
5
6
7
8
9
10
For more information, see Dynamic Domain Model.
# In Dynamic Action Definitions
{
/** How the parameter form is shown: drawer (default) or modal */
"layout"?: "drawer" | "modal",
/** Text of the submit button of the parameter form, defaults to "Submit" */
"submitButtonText"?: string,
/** How the result is shown: toast for a message notification, inContainer to show it inside the modal/drawer.
Defaults to toast when there is no parameter form, and to inContainer when there is one */
"resultType"?: "toast" | "inContainer",
/** Modal style; only width is currently used (effective when layout is modal) */
"style"?: { "width"?: number | string }
} 2
3
4
5
6
7
8
9
10
For more information, see Object Actions (Dynamic Action).
# In Dynamic Form Definitions
For more details, see Basic Form Customization.
# Common Properties
{
/** Form title. Used only when the form's label (display name) is empty; if both are empty, the translated domain name is shown */
"domainTitle"?: string;
/** Whether the fields of the form are laid out horizontally (label and input on the same line) */
"horizontal"?: boolean;
} 2
3
4
5
# List Form Properties
{
/** Data refresh mode. Only realtime currently takes effect: the list subscribes to data changes and refreshes in real time; any other value is the same as not setting it */
"dataRefreshMode"?: "realtime";
/** Name of the create form used when clicking the "Create" button at the top right of the list; if not set, the CREATE form of the domain is used */
"createFormName"?: string;
/** Name of the update form used when clicking the row "Update" link; if not set, the UPDATE form of the domain is used */
"updateFormName"?: string;
/** Name of the finder form used by the search panel above the list; if not set, the FINDER form of the domain is used */
"finderFormName"?: string;
"listForm"?: {
/** Whether to hide the search panel above the list */
"disableSearchPanel"?: boolean;
/** Default filter conditions, in the same format as dynamic filter conditions; a condition on the same key entered by the user in the search panel overrides the one here */
"searchConditions"?: {
"fieldName": {
"columnKey": "fieldName",
"matchMode": "=",
"value": xxx
}
};
};
/** Whether inline editing refreshes other columns according to the form field's decides (decides does not take effect in the current version) */
"enableRefreshColumn"?: boolean;
/** Width of the operations column, default 120 */
"operationsColumnWidth"?: number;
/** Overrides the button permissions of the list; only affects whether buttons are shown, the backend still checks domain permissions; create set to false hides the "Create" button */
"permissions"?: {
"create"?: boolean;
"view"?: boolean;
"update"?: boolean;
"delete"?: boolean;
};
} 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
34
35
36
37
38
39
# MasterDetail Form Properties
{
/** MASTER_DETAIL_LIST forms currently have no configurable extInfo properties. */
/** The left side is always the object tree, and the right side is always the UPDATE / CREATE form of that object. */
/** The legacy detailFormType, detailField and detailUpdatable properties no longer take effect. */
} 2
3
4
# Dashboard Form Properties
{
/** DASHBOARD forms currently have no configurable extInfo properties; the legacy refreshInterval no longer takes effect. */
} 2
# In Dynamic Form Field Definitions
The extInfo available for a Dynamic Form Field depends on the field's type. For more details, see Basic Form Customization.
# Common Properties
The extended properties that can be used in the extInfo of fields of all types are as follows
{
// Hides the field label and shows only the control itself
"hideLabel"?: boolean;
// CSS styles of the field label and of the read-only value, e.g. {"color": "#cf1322"}
"labelStyle"?: object;
"valueStyle"?: object;
// meta overrides the metadata of the form field generated automatically by the system, or supplements some properties such as title, dataIndex, editable, updatable, elementType, etc.
// At runtime, the system overrides or supplements the automatically generated metadata of the form field with the properties in meta
"meta"?: {
// Field name
"key": string;
// Field display name
"title": string;
// Name of the field in the form; should be the same as key
"dataIndex": string;
// Whether the field is editable in create forms; false renders it read-only
"editable"?: boolean;
// Whether the field is editable in update forms; false renders it read-only
"updatable"?: boolean;
...
}
} 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# file Field
When type is file or tech_muyan_storage_StorageFieldValue, the following json can be used to set the properties of the control
{
/** The file, fileList, image and video controls currently do not read extInfo. */
/** The legacy accept, maxSizeMB, maxCount and totalMaxSizeMB properties no longer take effect. */
} 2
3
# code Field
When type is code, the following extInfo can be used to set the syntax highlighting
{
/** Syntax highlighting language used when the code editor control is shown */
/** If not set, the field's display type (code, json, markdown, etc.) is used */
"codeLanguage"?: "css" | "javascript" | "markdown" | "groovy" .....,
} 2
3
4
# object Field
For object selection controls, the following extInfo definition can be used to set the default options in the selection control
{
/** Filter conditions for the candidate options
* When the control loads, the first 20 records matching these conditions are queried as the default options; the conditions are also applied to keyword search
*/
"defaultOptionsCondition"?: {
"fieldName" : {
/** Value to match */
"value": xxxx,
/** Name of the field to match; supports dot (.) notation to reference a field of an associated object
* e.g. organization.name refers to the name field of the organization field
*/
"columnKey": "xxx",
/** Match rule */
"matchMode": matchMode
}
},
/** Maximum number of candidates returned by keyword search, default 20 */
"objectOptionsLimit"?: number,
/** Shows "Create" and "Edit" buttons at the bottom of the dropdown for creating the associated object directly. In the current version only "Create" of the single-select control works */
"enableObjectOperations"?: boolean,
/** Query conditions for the default field values of the create form opened by the "Create" button above; each key is the name of an object-type field of the associated domain, and the first matching record is used */
"createFormDefaultValueConditions"?: {
"fieldName": { "columnKey": "xxx", "matchMode": "=", "value": xxx }
},
/** Overrides the properties of fields in the create form above, e.g. {"owner": {"disabled": true}} */
"overwriteCreateFormFieldProps"?: object
} 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
# One-to-Many Object Field
When type is array (a one-to-many object field), the following extInfo definition can be used to set how its associated list is displayed
{
/** Name of the form used to display the list of associated objects; if not set, the LIST form of the associated domain is used */
"displayForm"?: "Form used to display the list of objects"
} 2
3
# Sub-table Field
// 1. extInfo of the sub-table field (form field)
{
/** Name of the form used by the sub-table (determines which columns are shown); if not set, the LIST form of the child domain is used */
"displayForm"?: "UserGroup Sub Table Form For User",
/** Only used by relativeSubTable: path of a one-to-many field starting from the current object, separated by dots */
"subTable"?: {
"relativeNamePath"?: "customer.contacts"
}
}
// 2. extInfo of the form referenced by displayForm (row operation permissions etc. go here, not on the field)
{
"subTable"?: {
/** Whether rows can be updated, created and deleted; overrides the defaults once set (updatable by default; for a one-to-many sub-table with mappedBy, rows can only be deleted after the owner object has been saved, and the "Create" button is currently not shown) */
"updatable"?: true | false,
"creatable"?: true | false,
"deletable"?: true | false,
/** Whether rows can be reordered by drag and drop */
"dragSort"?: true | false,
/** When true, the "Create" button is placed at the bottom of the table (top by default); in a drag-sortable sub-table, new rows are also appended to the end */
"asc"?: true | false,
/** Provides row-level object actions in the operations column; the operations column is shown even in read-only state */
"enableActions"?: true | false
}
} 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
â đ Platform Evolution â FAQ â