# 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 Form Effect

# 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;
  };
}
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
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 = []

}
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
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
]
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

# 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]

}
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
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'

}
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
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'
}

1
2
3
4
5
6

# Form Configuration

  1. First, create a form with the type Gantt, then bind it to a domain. Note that this domain must be a row domain model.
  2. Create a menu and bind the form to the menu.
  3. 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""
    }
}"
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
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"
1
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,,
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
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
1
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返回逻辑
1
2

TIP

Last Updated: 9/17/2024, 3:08:20 AM