服务器端集成
将 dhtmlxGantt 与后端连接的最佳方式是,在服务器端搭建一个 RESTful API,并在客户端使用 dataprocessor 模块。
DataProcessor 是一个内置功能,用于跟踪 Gantt 数据的更改,并以所需格式将更新发送到 REST API。这使得与服务器端平台集成变得非常简单。当使用对象数据源时,DataProcessor 可以设置回调函数来处理数据更改,这对于数据绑定非常有用。
此外,还有一个视频教程,演示了如何创建 Gantt 图表并加载数据,以 Node.js 为例。
技术说明
通常,要通过 REST API 从服务器加载数据,你需要:
客户端
- 初始化 DataProcessor 并将其关联到 dhtmlxGantt 对象:
gantt.init("gantt_here");
gantt.load("apiUrl");
// 保持以下代码行的顺序
const dp = new gantt.dataProcessor("apiUrl");
dp.init(gantt);
dp.setTransactionMode("REST");
dp.deleteAfterConfirmation = true;
推荐使用第二种方法。
- 通过传递包含配置选项的对象,使用 createDataProcessor 方法:
const dp = gantt.createDataProcessor({
url: "apiUrl",
mode: "REST",
deleteAfterConfirmation: true
});
更多详情请参阅下文相关章节。
创建 DataProcessor
通过 API 方法 createDataProcessor 创建 DataProcessor 时,有几种传递参数的方式。
- 使用预定义的请求模式之一,例如:
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST",
deleteAfterConfirmation: true
});
其中:
- url - 服务器端的接口地址
- mode - 发送数据到服务器的方法:"GET" | "POST" | "REST" | "JSON" | "REST-JSON"
- deleteAfterConfirmation - 仅在服务器确认删除后才从 gantt 中移除任务。依赖关系和子任务将在父任务删除确认后一并删除。
- 提供自定义的 router 对象:
const dp = gantt.createDataProcessor(router);
- 其中 router 可以是一个函数:
// entity - "task"|"link"|"resource"|"assignment"
// action - "create"|"update"|"delete"
// data - 包含任务或链接数据的对象
// id – 被处理对象(任务或链接)的 id
const dp = gantt.createDataProcessor((entity, action, data, id) => {
switch(action) {
case "create":
return gantt.ajax.post(
server + "/" + entity,
data
);
break;
case "update":
return gantt.ajax.put(
server + "/" + entity + "/" + id,
data
);
break;
case "delete":
return gantt.ajax.del(
server + "/" + entity + "/" + id
);
break;
}
});
- 或者是如下结构的对象:
const dp = gantt.createDataProcessor({
task: {
create: (data) => {},
update: (data, id) => {},
delete: (id) => {}
},
link: {
create: (data) => {},
update: (data, id) => {},
delete: (id) => {}
}
});
router 对象中的所有函数应返回一个 Promise 或数据响应对象。这样 dataProcessor 可以应用数据库 id 并触发 onAfterUpdate 事件。
const router = (entity, action, data, id) => {
return new gantt.Promise((resolve, reject) => {
// … 相关逻辑
return resolve({ tid: databaseId });
});
};
这种方式使你可以将 DataProcessor 用于 localStorage 等本地存储,或其他不依赖于特定 URL 的存储方式,或在创建和删除分别由不同服务器处理的场景下使用。
Custom data api - using local storage
请求与响应详情
URL 遵循如下模式:
- api/link/id
- api/task/id
- api/resource/id
- api/assignment/id
其中 "api" 是 dataProcessor 配置中设置的 URL。
以下是可能的请求和响应列表:
| 操作 | HTTP 方法 | URL | 响应 |
|---|---|---|---|
| 加载数据 | GET | /apiUrl | JSON 格式 |
| 任务 | |||
| 新增任务 | POST | /apiUrl/task | ("action":"inserted","tid":"id") |
| 更新任务 | PUT | /apiUrl/task/id | ("action":"updated") |
| 删除任务 | DELETE | /apiUrl/task/id | ("action":"deleted") |
| 链接 | |||
| 新增链接 | POST | /apiUrl/link | ("action":"inserted","tid":"id") |
| 更新链接 | PUT | /apiUrl/link/id | ("action":"updated") |
| 删除链接 | DELETE | /apiUrl/link/id | ("action":"deleted") |
| 资源 | |||
| 新增资源 | POST | /apiUrl/resource | ("action":"inserted","tid":"id") |
| 更新资源 | PUT | /apiUrl/resource/id | ("action":"updated") |
| 删除资源 | DELETE | /apiUrl/resource/id | ("action":"deleted") |
| 资源分配 | |||
| 新增分配 | POST | /apiUrl/assignment | ("action":"inserted","tid":"id") |
| 更新分配 | PUT | /apiUrl/assignment/id | ("action":"updated") |
| 删除分配 | DELETE | /apiUrl/assignment/id | ("action":"deleted") |
默认情况下,资源和资源分配不会包含在 DataProcessor 的请求中。如需包含,需要显式启用。详情请参考 此处。
请求参数
新增、更新和删除请求会包含客户端任务或链接对象的所有公开属性:
任务:
- start_date: 2025-04-08 00:00:00
- duration: 4
- text: Task #2.2
- parent: 3
- end_date: 2025-04-12 00:00:00
链接:
- source: 1
- target: 2
- type: 0
注意:
- start_date 和 end_date 的格式由 date_format 配置项设置。
- 客户端会发送任务或链接的所有公开属性,因此请求中可能包含额外参数。
- 如果你在数据模型中添加了新的列或属性,gantt 会自动将它们发送到后端。
公开属性指名称不以下划线(_)或美元符号($)开头的属性,因此像 task._owner 或 link.$state 这样的属性不会被发送到后端。
REST-JSON 模式
除了 "POST"、"GET"、"REST" 和 "JSON" 模式外,Gantt DataProcessor 还支持 "REST-JSON" 模式。
gantt.load("apiUrl");
const dp = gantt.createDataProcessor({
url: "/apiUrl",
mode: "REST-JSON"
});
它使用相同的 请求 URL,但参数的发送方式不同。
在 REST 模式下,数据以表单数据形式发送:
Content-Type: application/x-www-form-urlencoded
而在 REST-JSON 模式下,数据以 JSON 形式发送:
Headers
Content-type: application/json
参数以 JSON 对象的形式发送:
请求载荷
- 任务
{
"start_date": "20-09-2025 00:00",
"text": "New task",
"duration": 1,
"end_date": "21-09-2025 00:00",
"parent": 0,
"usage": [
{ "id": "1", "value": "30" },
{ "id": "2", "value": "20" }
]
}
- 链接
{
"source": 1,
"target": 2,
"type": "0"
}
这种格式简化了服务器端对复杂记录的处理。
服务器端
每当 Gantt 中发生变化(添加、更新或删除任务或链接)时,dataProcessor 会向服务器发送 AJAX 请求。
每个请求都包含更新数据库所需的全部数据。 由于 dataProcessor 设置为 REST 模式,因此会根据操作类型使用不同的 HTTP 动词。
使用 REST API 可以用多种框架和语言实现服务器端。 以下是可用于 Gantt 后端集成的服务器端实现示例:
- dhtmlxGantt 与 ASP.NET Core 2 集成
- dhtmlxGantt 与 PHP: Slim 集成
- dhtmlxGantt 与 PHP: Laravel 集成
- dhtmlxGantt 与 Node.js 集成
- dhtmlxGantt 与 ASP.NET MVC 集成
- dhtmlxGantt 与 Ruby on Rails 集成
任务顺序的存储
Gantt 会按照数据源中的顺序显示任务。如果用户可以手动调整任务顺序, 你需要将这种顺序保存到数据库,并确保数据源返回的任务已按此顺序排序。
客户端设置:
// 在 整个 gantt 内拖动任务排序
gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
gantt.init("gantt_here");
gantt.load("/api");
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST"
});
保存顺序有多种方式,这里举一个例子。
- 在任务表中添加一个数值类型的列,例如 'sortorder'。
- 处理 GET 请求时,按此列升序排序任务。
- 新增任务时,赋值为
MAX(sortorder) + 1。 - 当客户端顺序发生变化时,gantt 会发送一个 PUT(或未启用 REST 模式时为 POST),请求中包含所有任务属性以及描述任务在项目树中位置的参数。
| HTTP 方法 | URL | 参数 | 响应 |
|---|---|---|---|
| PUT | /apiUrl/task/taskId | target=adjacentTaskId | ("action":"updated") |
target 参数包含当前任务紧邻的前一个或后一个任务的 id。
其值有两种格式:
- target="targetId" - 将当前任务放在 targetId 任务之前
- target="next:targetId" - 将当前任务放在 targetId 任务之后
更改顺序通常需要更新多个任务。以下是伪代码示例:
const target = request["target"];
const currentTaskId = request["id"];
let nextTask;
let targetTaskId;
// 判断更新的任务是在相邻任务之前还是之后
if (target.startsWith("next:")) {
targetTaskId = target.substr("next:".length);
nextTask = true;
} else {
targetTaskId = target;
nextTask = false;
}
const currentTask = tasks.getById(currentTaskId);
const targetTask = tasks.getById(targetTaskId);
if (!targetTaskId) return;
// 将相邻任务的 sortorder 赋给当前任务
let targetOrder = targetTask.sortorder;
// 如果放在相邻任务之后,则递增 sortorder
if (nextTask) targetOrder++;
// 递增所有在当前任务之后的任务的 sortorder
tasks.where(task => task.sortorder >= targetOrder)
.update(task => task.sortorder++);
// 更新当前任务的 sortorder
currentTask.sortorder = targetOrder;
tasks.save(currentTask);
你可以在以下平台查找关于任务顺序存储的详细示例: plain PHP、Laravel、 Node.js、ASP.NET Web API 以及 Rails。
自定义请求头和参数
添加自定义请求头
可以在发送到后端的请求中包含额外的请求头。例如,您可能希望在请求中添加授权令牌:
gantt.init("gantt_here");
gantt.load("/api");
const dp = gantt.createDataProcessor({
url: "/api",
mode:"REST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
});
目前,load 不支持在 GET 请求中添加 header 或 payload 参数,因此如果您需要包含这些内容,必须手动发送 xhr,然后通过 parse 将数据加载到 gantt,如下所示:
gantt.ajax.get({
url: "/api",
headers: {
"Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
}).then(xhr => {
gantt.parse(xhr.responseText);
});
向请求中添加自定义参数
有几种方式可以在请求中包含额外的参数。
由于 gantt 会将数据对象的所有属性发送到后端,您可以直接在数据对象中添加额外属性,这些属性会自动包含在请求中:
gantt.attachEvent("onTaskCreated", (task) => {
task.userId = currentUser;
return true;
});
另一种方式是通过 payload 属性,为 data processor 发送的每个请求添加自定义参数:
gantt.init("gantt_here");
gantt.load("/api");
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST",
payload: {
token: "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}
});
您还可以通过 DataProcessor 的 onBeforeUpdate 事件为请求添加自定义参数:
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST",
});
dp.attachEvent("onBeforeUpdate", (id, state, data) => {
data.projectId = "1";
return true;
});
从脚本触发数据保存
当 dataProcessor 初始化后,用户通过界面或脚本进行的任何更改都会自动保存到数据源。
如果要通过脚本更新某个特定任务或依赖关系,通常可使用 updateTask 和 updateLink 方法:
gantt.parse([
{ id: 1, text: "Task 1", start_date: "2025-05-13 06:00", duration: 2 },
{ id: 2, text: "Task 2", start_date: "2025-05-19 08:00", duration: 3 }
]);
const task = gantt.getTask(1);
task.text = "Task 37"; // 更新任务数据
gantt.updateTask(1); // 重新渲染已更新的任务
其他会触发向后端发送更新的方法包括:
自定义路由
如果 RESTful AJAX API 不满足您的后端需求,或者您希望完全控制发送到服务器的数据内容,可以使用自定义路由。
例如,在 Angular 或 React 等框架中,一个组件可能不会直接将更改发送到服务器,而是传递给另一个负责保存数据的组件。
要为 DataProcessor 设置自定义路由,请使用 createDataProcessor() 方法:
gantt.createDataProcessor(function(entity, action, data, id) {
const services = {
"task": this.taskService,
"link": this.linkService
};
const service = services[entity];
switch (action) {
case "update":
return service.update(data);
case "create":
return service.insert(data);
case "delete":
return service.remove(id);
}
});
Custom data api - using local storage