将 dhtmlxGantt 与后端连接的最佳方式是,在服务器端搭建一个 RESTful API,并在客户端使用 dataprocessor 模块。
DataProcessor 是一个内置功能,用于跟踪 Gantt 数据的更改,并以所需格式将更新发送到 REST API。这使得与服务器端平台集成变得非常简单。当使用对象数据源时,DataProcessor 可以设置回调函数来处理数据更改,这对于数据绑定非常有用。
此外,还有一个视频教程,演示了如何创建 Gantt 图表并加载数据,以 Node.js 为例。
通常,要通过 REST API 从服务器加载数据,你需要:
1) 使用 load 方法,通过提供返回 JSON 格式数据的 URL 来加载 Gantt 数据。
2) 有两种方式创建 DataProcessor 实例:
gantt.init("gantt_here");
gantt.load("apiUrl");
// 保持以下代码行的顺序
const dp = new gantt.dataProcessor("apiUrl");
dp.init(gantt);
dp.setTransactionMode("REST");
dp.deleteAfterConfirmation = true;
推荐使用第二种方法。
const dp = gantt.createDataProcessor({
url: "apiUrl",
mode: "REST",
deleteAfterConfirmation: true
});
更多详情请参阅下文相关章节。
通过 API 方法 createDataProcessor 创建 DataProcessor 时,有几种传递参数的方式。
1. 使用预定义的请求模式之一,例如:
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST",
deleteAfterConfirmation: true
});
其中:
2. 提供自定义的 router 对象:
const dp = gantt.createDataProcessor(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 的存储方式,或在创建和删除分别由不同服务器处理的场景下使用。
Related sample: Custom data api - using local storage
URL 遵循如下模式:
其中 "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 的请求中。如需包含,需要显式启用。详情请参考 此处。
新增、更新和删除请求会包含客户端任务或链接对象的所有公开属性:
任务:
链接:
注意:
公开属性指名称不以下划线(_)或美元符号($)开头的属性,因此像 task._owner 或 link.$state 这样的属性不会被发送到后端。
除了 "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 后端集成的服务器端实现示例:
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"
});
保存顺序有多种方式,这里举一个例子。
MAX(sortorder) + 1
。HTTP 方法 | URL | 参数 | 响应 |
---|---|---|---|
PUT | /apiUrl/task/taskId | target=adjacentTaskId | {"action":"updated"} |
target 参数包含当前任务紧邻的前一个或后一个任务的 id。
其值有两种格式:
更改顺序通常需要更新多个任务。以下是伪代码示例:
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);
}
});
Related sample: Custom data api - using local storage
Gantt AJAX 模块 在设置自定义路由时非常有用。Gantt 期望自定义路由返回一个 Promise 对象,这样可以检测操作何时完成。
AJAX 模块支持 promise,非常适合在自定义路由中使用。Gantt 会处理该 Promise,并在其被 resolve 后处理内容。
在下面的示例中,创建了一个新任务。如果服务器响应包含新任务的 id,Gantt 会相应地应用该 id。
gantt.createDataProcessor((entity, action, data, id) => {
...
switch (action) {
case "create":
return gantt.ajax.post({
headers: {
"Content-Type": "application/json"
},
url: `${server}/task`,
data: JSON.stringify(data)
});
}
});
从 v8.0 开始,资源分配的更改可以作为带有持久 ID 的单独条目发送到 DataProcessor,这简化了后端 API 集成。资源对象本身的更改也可以发送到 DataProcessor。
请注意,此功能默认关闭。默认情况下,DataProcessor 只接收任务和链接的更改。要启用资源处理,请进行如下设置:
gantt.config.resources = {
dataprocessor_assignments: true,
dataprocessor_resources: true,
};
当资源模式启用且 DataProcessor 处于 REST 模式时,资源及资源分配会分别以独立请求发送到后端。
如果 DataProcessor 使用自定义路由模式,您可以在处理函数中捕获资源分配和资源的更改:
gantt.createDataProcessor({
task: {
create: (data) => {
return createRecord({type: "task", ...data}).then((res) => {
return { tid: res.id, ...res };
});
},
update: (data, id) => {
return updateRecord({type: "task", ...data}).then(() => ({}));
},
delete: (id) => {
return deleteRecord({type: "task:", id: id}).then(() => ({}));
}
},
link: {
create: (data) => {
...
},
update: (data, id) => {
...
},
delete: (id) => {
...
}
},
assignment: {
create: (data) => {
...
},
update: (data, id) => {
...
},
delete: (id) => {
...
}
},
resource: {
create: (data) => {
...
},
update: (data, id) => {
...
},
delete: (id) => {
...
}
}
});
或者,使用函数声明方式:
gantt.createDataProcessor((entity, action, data, id) => {
switch (entity) {
case "task":
break;
case "link":
break;
case "resource":
break;
case "assignment":
break;
}
});
如果服务器报告某个操作失败,可以返回如下内容:
{"action":"error"}
可以在客户端通过 gantt.dataProcessor 捕获这样的响应:
const dp = gantt.createDataProcessor({
url: "/api",
mode: "REST"
});
dp.attachEvent("onAfterUpdate", (id, action, tid, response) => {
if (action === "error") {
// 在这里处理错误
}
});
响应对象可能包含其他属性,可通过 onAfterUpdate 事件中的 response
参数访问。
该事件仅对如上所示返回 JSON 响应的已管理错误触发。 如需处理 HTTP 错误,请参考 onAjaxError API 事件。
如果服务器响应为错误但客户端更改已保存,最佳同步方式是清除客户端状态并从服务器重新加载正确数据:
dp.attachEvent("onAfterUpdate", (id, action, tid, response) => {
if (action === "error") {
gantt.clearAll();
gantt.load("url1");
}
});
如果希望在不与服务器通信的情况下同步客户端和服务器状态,可使用 silent() 方法,在操作期间阻止内部事件或服务器请求:
gantt.silent(() => {
gantt.deleteTask(item.id);
});
gantt.render();
默认情况下,删除任务会触发其嵌套任务及相关链接的级联删除。Gantt 会为每个被移除的任务和链接发送 delete 请求。
这意味着无需手动维护后端数据完整性,Gantt 会自动处理。
但这种方式可能会导致大量 AJAX 请求发送到后端,因为 dhtmlxGantt 不支持批量请求,并且任务和链接数量可能很多。
如有需要,可以通过 cascade_delete 配置禁用级联删除。
禁用后,删除项目分支只会为顶层项发送删除请求,后端需自行处理相关链接和子任务的删除。
需要注意的是,Gantt 并未内置防护 SQL 注入、XSS 或 CSRF 等安全威胁的机制。
确保应用安全是后端开发者的责任。
请参阅 应用程序安全 文章,了解组件最易受攻击的点及推荐的安全增强措施。
Back to top