# Advanced Forms
Advanced forms, such as Gantt charts, flowcharts, and swimlane diagrams, are typically used for complex business scenarios beyond the CRUD operations of domain model objects.
# Target Audience
The target audience for this document is: Development and implementation personnel of this system.
# Overview
The advanced form documentation of this system differs from the CRUD basic forms mentioned in the Form Customization document. This document mainly introduces how to use the advanced form functions provided by the system.
# Gantt Chart
A Gantt chart is a chart tool that displays task scheduling, progress, and dependencies in the form of bar graphs, used for visual management and planning of projects, production scheduling, course scheduling, and other tasks.
The Gantt chart form requires users to provide two types of domain model definitions: Row and Task, representing the rows and tasks of the Gantt chart. A row can have 0 or multiple tasks. The relationships between rows can be peer-level or hierarchical based on a tree structure.
A typical display effect is shown below:
# Gantt Chart View Configuration
The Gantt chart supports configuring view information through the extInfo field in the gantt form. The configuration format for view information is as follows:
{
/** 甘特图相关配置 */
/** Gantt chart related configuration */
"gantt"?: {
/** 默认显示的甘特图中的开始时间, ISO8601 和 GB/T 7408-2005格式,例如 2023-05-22T00:00:00+08:00 */
/** Default start time displayed in the Gantt chart, ISO8601 and GB/T 7408-2005 format, e.g., 2023-05-22T00:00:00+08:00 */
"viewDateStart"?: string;
/** 默认显示的甘特图的时间长度, ISO8601 和 GB/T 7408-2005 格式,例如 P5D */
/** Default duration displayed in the Gantt chart, ISO8601 and GB/T 7408-2005 format, e.g., P5D */
"viewDuration"?: string;
/** 左侧行列表展示字段,默认显示 gantt 表单关联领域模型的所有字段 */
/** Fields displayed in the left row list, by default shows all fields of the domain model associated with the Gantt form */
"rowListDisplayColumns"?: [{
// 字段名
// Field name
"key": string,
// 显示名
// Display name
"title": string,
}];
/** 任务展示字段,默认显示被 hover 的 task 领域模型的所有字段 */
/** Task display fields, by default shows all fields of the hovered task domain model */
"tooltipDisplayColumns"?: [{
// 字段名
// Field name
"key": string,
// 显示名
// Display name
"title": string,
}];
/** 任务组字段 */
/** Task group field */
"taskGroupColumnKey"?: string;
/** 任务是否可以更新, Since version 0.29 */
/** Whether tasks can be updated, Since version 0.29 */
"taskUpdatable"?: 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
# Gantt Chart Example
Here's a simple explanation using the platform's built-in example:
TIP
If your commercial license includes direct access to the platform's code repository, you can obtain the complete code for this example through grails-plugin:samples (opens new window).
If your commercial needs do not include access to the platform's code repository, you can contact customer service or technical personnel to obtain the complete code for this example.
We'll use a tree-structured project management as an example. A project (SampleProject) can have multiple milestones (SampleMilestone), a milestone can have multiple sub-milestones, a sub-milestone can have multiple tasks (SampleTask), and a task can have multiple task dependencies.
# SampleProject
By declaring the ganttEnableCombinedTasks
and ganttRowColumn
fields, we
inform the Gantt chart form how to find the "whether to calculate child task
envelope" field and the "sub-row" field.
It's important to note that the sub-row field subMilestones
declared here is
not an actual field of this object; it is dynamically generated through API
rendering.
class SampleProject {
String name
String label
String description
static constraints = {
name nullable: false, blank: false
label nullable: true
description nullable: true, blank: true
}
static mapping = {
tablePerHierarchy true
}
static hasMany = [
milestones: SampleMilestone
]
static mappedBy = [
milestones: 'project'
]
static inlineSearchColumns = ['label']
static ganttCombinedTaskLabel = 'label'
// this field will be generated by render api
static ganttRowColumn = 'subMilestones'
// this field will be generated by render api
static ganttEnableCombinedTasks = 'enableCombineSubRowTasks'
static loadAfter = []
}
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
- API Render
import grails.core.GrailsApplication
import org.grails.datastore.gorm.GormEntity
import tech.muyan.domain.DomainDataService
import tech.muyan.enums.FetchType
import tech.muyan.sample.gantt.SampleMilestone
import tech.muyan.sample.gantt.SampleProject
GrailsApplication grailsApplication = application as GrailsApplication
DomainDataService domainDataService = grailsApplication.mainContext.getBean(DomainDataService)
// The object might be a map containing all dynamic and static fields of a domain instance, or the domain instance itself, depending on whether the domain contains dynamic fields.
// If the object is a domain entity, here's a method to convert the domain instance to a map.
def res = object instanceof GormEntity ? domainDataService.convertDomainObject2Map(object, FetchType.EXCLUDE_ARRAY_COLUMNS) : object
def sampleProject = object as SampleProject
// Set the 'enableCombineSubRowTasks' field to false to make the Gantt chart aware that the project has disabled the child task envelope calculation function
res['enableCombineSubRowTasks'] = false
// Since all SampleMilestones belong to the same project, and there are hierarchical relationships between milestones.
// If we want to display all milestones in a tree structure, we need to filter out all non-root milestones under the project,
// otherwise, when rendering, both the project's child nodes and milestone child nodes will appear redundantly.
Collection<SampleMilestone> subMilestones = sampleProject.milestones.findAll {
it.parentMilestone == null
}
// We defined a subMilestones field in the ganttRowColumn of SampleProject. The value of this field will be used by the Gantt chart to render the project's child nodes.
// Therefore, we only need to assign the filtered milestones to the subMilestones field.
res['subMilestones'] = subMilestones
return [
result: res
]
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
# SampleMilestone
By declaring the ganttRowColumn
and ganttTasksColumn
fields, we inform the
Gantt chart form how to find the fields for "sub-rows" and "tasks".
class SampleMilestone {
String name
String label
String description
SampleProject project
SampleMilestone parentMilestone
static constraints = {
name nullable: false, blank: false
label nullable: true
description nullable: true, blank: true
subMilestones nullable: true
tasks nullable: true
parentMilestone nullable: true
project nullable: false
}
static mapping = {
tablePerHierarchy true
}
static hasMany = [
subMilestones: SampleMilestone,
tasks: SampleTask,
]
static mappedBy = [
subMilestones: 'parentMilestone',
tasks: 'milestone',
]
static inlineSearchColumns = ['label']
static ganttRowColumn = 'subMilestones'
static ganttTasksColumn = 'tasks'
static loadAfter = [SampleProject]
}
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
40
41
# SampleTask
By declaring the dependsOnTasks
field, we inform the Gantt chart form how to
find the field for "dependent tasks".
class SampleTask {
String name
String label
ZonedDateTime taskStartDate
ZonedDateTime taskDueDate
Integer progress
GanttTaskType taskType
String groupName
Integer displaySequence
List<SampleTask> dependsOnTasks
SampleMilestone milestone
static constraints = {
name nullable: false, blank: false
label nullable: false
taskStartDate nullable: false
taskDueDate nullable: false
progress nullable: true
taskType nullable: false
dependsOnTasks nullable: true
groupName nullable: true
displaySequence nullable: true
milestone nullable: true
}
static mapping = {
progress defaultValue: '0'
taskType enumType: 'string'
displaySequence defaultValue: '0'
tablePerHierarchy true
}
static inlineSearchColumns = ['label']
static ganttTaskStartColumn = 'taskStartDate'
static ganttTaskGroupColumn = 'groupName'
static ganttTaskEndColumn = 'taskDueDate'
}
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
40
41
42
43
44
45
46
47
48
49
50
51
# Gantt Chart Domain Field Mapping Relationships
Below are the static field names for configuring mapping relationships, their types, and default values:
Field | Description | Default Field Name | Field Type |
---|---|---|---|
ganttRowColumn | Field name for the list of sub-rows in a Gantt chart row | rows | Object |
ganttTasksColumn | Field name for the list of tasks contained in a Gantt chart row | entities | List<Object> |
ganttEnableCombinedTasks | Field name for whether the Gantt chart row automatically calculates envelope tasks based on all tasks of sub-rows, default is on | enableCombinedTasks | Boolean |
ganttCombinedTaskLabel | Field name for the name of the envelope task in a Gantt chart row | label | String |
ganttTaskLabelColumn | Field name for the display name of a Gantt chart task | label | String |
ganttTaskTypeColumn | Field name for the type of a Gantt chart task | taskType | tech.muyan.enums.GanttTaskType |
ganttTaskProgressColumn | Field name for the progress of a Gantt chart task | progress | Integer |
ganttTaskGroupNameColumn | Field name for the group name of a Gantt chart task, tasks with the same group name will be rendered in a uniform color in the frontend | groupName | String |
ganttTaskDependentColumn | Field name for the tasks that a Gantt chart task depends on, dependent tasks will be rendered with an arrow pointing to the task that depends on them | dependsOnTasks | List<Object> |
ganttTaskStartColumn | Field name for the start time of a Gantt chart task | scheduleStart | ZonedDateTime |
ganttTaskEndColumn | Field name for the end time of a Gantt chart task | scheduleEnd | ZonedDateTime |
TIP
If fields are made aware to the form by specifying fields, the rendering of these fields can be loose. This means these fields can be dynamically generated in the object's customized render logic.
Note that if some fields that may be updated, such as task start and end times, are not used in this way, it will cause the drag-and-drop task update time function in the Gantt chart to fail.
WARNING
If you need to dynamically generate fields specified by ganttRowColumn
or
ganttTasksColumn
in the customized render logic of an object, please ensure
that the values of these fields returned in the render logic must be either a
GormEntity or a Map<String, Object>
collection containing an @CLASS@
field.
This is because Gantt chart rendering needs to obtain the object type of child
nodes. If the object is a GormEntity, it will directly get the class of the
object; otherwise, it will try to read the @CLASS@
field as its type.
The domainDataService.convertDomainObject2Map() => Map<String, Object>
method
will put an @CLASS@
field in the returned Map
.
Therefore, if needed, it's recommended to use the
domainDataService.convertDomainObject2Map()
method to convert domain instances
to maps before inserting them into custom fields.
Here's an example:
res['subMilestones'] = subMilestones.collect {
def map = domainDataService.convertDomainObject2Map(it, FetchType.INCLUDE_ARRAY_COLUMNS_WITH_LABEL)
map['customField'] = 'customValue'
}
2
3
4
5
6
# Form Configuration
- First, create a form with the type
Gantt
, then bind it to a domain. Note that this domain must be a row domain model. - Create a menu and bind the form to the menu.
- After entering the newly created menu, create Gantt chart rows through the create button in the title bar. When creating a row, you can choose whether to create a task. If you choose to create a task, a task will be created at the same time as creating the row.
The relevant form and menu configurations for the above example are as follows:
organization.name(*),name(*),label,description,objectType.shortName(*),type(*),menu.label,enableRoles,extInfo
$ROOT_ORG$,List of SampleProjects,,,SampleProject,LIST,SampleProject,"ROLE_ADMIN,ROLE_DEVELOPER",
$ROOT_ORG$,Create SampleProject,,,SampleProject,CREATE,NULL,"ROLE_ADMIN,ROLE_DEVELOPER",
$ROOT_ORG$,Update SampleProject,,,SampleProject,UPDATE,NULL,"ROLE_ADMIN,ROLE_DEVELOPER",
$ROOT_ORG$,List of SampleTasks,,,SampleTask,LIST,SampleTask,"ROLE_ADMIN,ROLE_DEVELOPER",
$ROOT_ORG$,Create SampleTask,,,SampleTask,CREATE,NULL,"ROLE_ADMIN,ROLE_DEVELOPER",
$ROOT_ORG$,Update SampleTask,,,SampleTask,UPDATE,NULL,"ROLE_ADMIN,ROLE_DEVELOPER",
$ROOT_ORG$,List of SampleMilestones,,,SampleMilestone,LIST,SampleMilestone,"ROLE_ADMIN,ROLE_DEVELOPER",
$ROOT_ORG$,Create SampleMilestone,,,SampleMilestone,CREATE,NULL,"ROLE_ADMIN,ROLE_DEVELOPER",
$ROOT_ORG$,Update SampleMilestone,,,SampleMilestone,UPDATE,NULL,"ROLE_ADMIN,ROLE_DEVELOPER",
$ROOT_ORG$,Project Gantt,,,SampleProject,GANTT,SampleProjectGantt,"ROLE_ADMIN,ROLE_DEVELOPER","{
""gantt"": {
""viewDateStart"": ""2023-05-22T00:00:00+08:00"",
""viewDuration"": ""P1D"",
""rowListDisplayColumns"": [{
""key"": ""label"",
""title"": ""label""
}, {
""key"": ""description"",
""title"": ""description""
}],
""tooltipDisplayColumns"": [{
""key"": ""label"",
""title"": ""label""
}, {
""key"": ""taskStartDate"",
""title"": ""taskStartDate""
}, {
""key"": ""taskDueDate"",
""title"": ""taskDueDate""
}, {
""key"": ""groupName"",
""title"": ""groupName""
}],
""taskGroupColumnKey"": ""groupName""
}
}"
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
# Dynamic Menu Definition
organization.name(*),parent.label,label(*),icon,link,type,displaySequence,enableRoles
$ROOT_ORG$,NULL,Sample,FileTextOutlined,,MENU_GROUP,1001,"ROLE_ADMIN,ROLE_DEVELOPER"
$ROOT_ORG$,Sample,SampleProject,ProjectOutlined,,FORM,10,"ROLE_ADMIN,ROLE_DEVELOPER"
$ROOT_ORG$,Sample,SampleTask,TaskOutlined,,FORM,20,"ROLE_ADMIN,ROLE_DEVELOPER"
$ROOT_ORG$,Sample,SampleMilestone,MilestoneOutlined,,FORM,30,"ROLE_ADMIN,ROLE_DEVELOPER"
$ROOT_ORG$,Sample,SampleProjectGantt,CalendarOutlined,,FORM,40,"ROLE_ADMIN,ROLE_DEVELOPER"
2
3
4
5
6
# Dynamic Form Field Definition
form.name(*),fieldName(*),displaySequence,label,helpText,fieldType,nullable,group.name,extInfo
;; Create form
Create SampleProject,label,10,Label,Project Label,STATIC_FIELD,true,,
Create SampleProject,description,20,Description,Project Description,STATIC_FIELD,true,,
Create SampleProject,milestones,30,Milestones,Project Milestones,STATIC_FIELD,true,,
;; Update form
Update SampleProject,id,0,ID,Project ID,STATIC_FIELD,false,,
Update SampleProject,label,10,Label,Project Label,STATIC_FIELD,true,,
Update SampleProject,description,20,Description,Project Description,STATIC_FIELD,true,,
Update SampleProject,milestones,30,Milestones,Project Milestones,STATIC_FIELD,true,,
;; List form
List of SampleProjects,id,0,ID,Project ID,STATIC_FIELD,false,,
List of SampleProjects,label,10,Label,Project Label,STATIC_FIELD,true,,
List of SampleProjects,description,20,Description,Project Description,STATIC_FIELD,true,,
List of SampleProjects,milestones,30,Milestones,Project Milestones,STATIC_FIELD,true,,
;; Create form
Create SampleMilestone,label,10,Label,Milestone Label,STATIC_FIELD,true,,
Create SampleMilestone,project,15,Project,Project,STATIC_FIELD,true,,
Create SampleMilestone,description,20,Description,Milestone Description,STATIC_FIELD,true,,
Create SampleMilestone,subMilestones,30,SubMilestones,Milestone SubMilestones,STATIC_FIELD,true,,
Create SampleMilestone,tasks,40,Tasks,Milestone Tasks,STATIC_FIELD,true,,
;; Update form
Update SampleMilestone,id,0,ID,Milestone ID,STATIC_FIELD,false,,
Update SampleMilestone,label,10,Label,Milestone Label,STATIC_FIELD,true,,
Update SampleMilestone,project,15,Project,Project,STATIC_FIELD,true,,
Update SampleMilestone,description,20,Description,Milestone Description,STATIC_FIELD,true,,
Update SampleMilestone,subMilestones,30,SubMilestones,Milestone SubMilestones,STATIC_FIELD,true,,
Update SampleMilestone,tasks,40,Tasks,Milestone Tasks,STATIC_FIELD,true,,
;; List form
List of SampleMilestones,id,0,ID,Milestone ID,STATIC_FIELD,false,,
List of SampleMilestones,label,10,Label,Milestone Label,STATIC_FIELD,true,,
List of SampleMilestones,project,15,Project,Project,STATIC_FIELD,true,,
List of SampleMilestones,description,20,Description,Milestone Description,STATIC_FIELD,true,,
List of SampleMilestones,subMilestones,30,SubMilestones,Milestone SubMilestones,STATIC_FIELD,true,,
List of SampleMilestones,tasks,40,Tasks,Milestone Tasks,STATIC_FIELD,true,,
;; Create form
Create SampleTask,label,10,Label,Task Label,STATIC_FIELD,false,,
Create SampleTask,milestone,15,Milestone,Milestone,STATIC_FIELD,true,,
Create SampleTask,taskStartDate,20,Task Start Date,Task Start Date,STATIC_FIELD,false,,
Create SampleTask,taskDueDate,30,Task Due Date,Task Due Date,STATIC_FIELD,false,,
Create SampleTask,taskType,40,Task Type,Task Type,STATIC_FIELD,false,,
Create SampleTask,dependsOnTasks,50,Depends On Tasks,Task Dependencies,STATIC_FIELD,true,,
;; Update form
Update SampleTask,id,0,ID,Task ID,STATIC_FIELD,false,,
Update SampleTask,label,10,Label,Task Label,STATIC_FIELD,false,,
Update SampleTask,milestone,15,Milestone,Milestone,STATIC_FIELD,true,,
Update SampleTask,taskStartDate,20,Task Start Date,Task Start Date,STATIC_FIELD,false,,
Update SampleTask,taskDueDate,30,Task Due Date,Task Due Date,STATIC_FIELD,false,,
Update SampleTask,taskType,40,Task Type,Task Type,STATIC_FIELD,false,,
Update SampleTask,dependsOnTasks,50,Depends On Tasks,Task Dependencies,STATIC_FIELD,true,,
;; List form
List of SampleTasks,id,0,ID,Task ID,STATIC_FIELD,false,,
List of SampleTasks,label,10,Label,Task Label,STATIC_FIELD,false,,
List of SampleTasks,milestone,15,Milestone,Milestone,STATIC_FIELD,true,,
List of SampleTasks,taskStartDate,20,Task Start Date,Task Start Date,STATIC_FIELD,false,,
List of SampleTasks,taskDueDate,30,Task Due Date,Task Due Date,STATIC_FIELD,false,,
List of SampleTasks,taskType,40,Task Type,Task Type,STATIC_FIELD,false,,
List of SampleTasks,dependsOnTasks,50,Depends On Tasks,Task Dependencies,DYNAMIC_FIELD,true,,
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# DynamicLogic Definition
name(*),logicType,code(F),description,isSystem,DELETE_FLAG
Project render core logic,OBJECT_DYNAMIC_HOOK,"groovy/gantt/projectApiRender.groovy",示例项目的 API 返回值客制化,Y,N
2
# DynamicObjectHook Definition
organization.name,objectType.shortName,name(*),hookType,coreLogic.name,active,isSystem,description
$ROOT_ORG$,SampleProject,Project API render,RENDER,Project render core logic,Y,N,SampleProject 的客制化API返回逻辑
2
TIP
For ISO 8601 standard, refer to https://www.loc.gov/standards/datetime/iso-tc154-wg5_n0038_iso_wd_8601-1_2016-02-16.pdf (opens new window)
For the time duration representation in the GB/T 7408-2005 format standard, refer to http://spec.nstl.gov.cn/metadata/download/upload/attpdf/60ab3074-78fb-47b7-96ba-91b66f9c849a.pdf (opens new window) Section A.4 Time Interval