在网格中进行内联编辑
dhtmlxGantt 提供了两种内容编辑方式:
- 使用 Lightbox 编辑表单
- 直接在网格区域内使用内联编辑器
内联编辑允许用户直接在网格中进行更改:创建和更新任务、设置任务之间的依赖关系、定义开始和结束日期或调整持续时间--所有操作都可以通过内置编辑器完成。

要启用内联编辑,你需要:
- 定义编辑器配置列表,并在编辑器对象中使用 map_to 属性将编辑器绑定到相应的网格列
var textEditor = {type: "text", map_to: "text"};
var dateEditor = {type: "date", map_to: "start_date", min: new Date(2018, 0, 1),
max: new Date(2019, 0, 1)};
var durationEditor = {type: "number", map_to: "duration", min:0, max: 100};
- 在列配置中指定 editor 属性,为该列分配编辑器
gantt.config.columns = [
{name: "text", tree: true, width: '*', resize: true, editor: textEditor},
{name: "start_date", align: "center", resize: true, editor: dateEditor},
{name: "duration", align: "center", editor: durationEditor},
{name: "add", width: 44}
];
有关 inlineEditors 对象 API 的详细信息,请参阅 Inline Editors Extension 文章。
此外,还有一段视频教程演示了如何在网格中实现内联编辑。
编辑器类型
内联编辑器在 editor_types 配置对象中定义。
系统预定义了几种内联编辑器:
- text 编辑器 - 用于如任务名称等文本列
- number 编辑器 - 用于如任务持续时间或排序等数值列
- duration 编辑器 - 用于持续时间列,特别是在 map_to: "duration" 且编辑器类型为 "duration" 时:
{ type: "duration", map_to: "duration", formatter: formatter }
当需要指定同时包含数字和 持续时间单位(例如:5 days)的持续时间时,此编辑器类型非常有用。它默认使用 Duration Formatter。你也可以自定义其配置或提供 自定义格式化器。
- date 编辑器 - 用于编辑如开始和结束日期等日期列
- select 编辑器 - 用于从列表中选择选项
- predecessor 编辑器 - 用于为当前任务设置前置任务。该编辑器使用 任务的 WBS 编码 来与前置任务建立关联。
var editors = {
text: {type: "text", map_to: "text"},
start_date: {type: "date", map_to: "start_date", min: new Date(2018, 0, 1),
max: new Date(2019, 0, 1)},
end_date: {type: "date", map_to: "end_date", min: new Date(2018, 0, 1),
max: new Date(2019, 0, 1)},
duration: {type: "number", map_to: "duration", min:0, max: 100},
priority: {type:"select", map_to:"priority", options:gantt.serverList("priority")},
predecessors: {type: "predecessor", map_to: "auto"}
};
日期编辑器中的日期限制
自 v6.3 起,date 内联编辑器没有预设的最小或最大值。
如果你希望时间轴上可见的日期动态决定 date 内联编辑器的 min 和 max 值(除非自定义了 min/max),可以指定动态的 min/max 函数:
const dateEditor = {type: "date", map_to: "start_date",
min: function(taskId){
return gantt.getState().min_date
},
max: function( taskId ){
return gantt.getState().max_date
}
};
支持包含性结束日期的编辑器
当你为任务使用 包含性结束日期格式,并希望在网格中正确支持内联编辑时,需要如下特殊的结束日期编辑器:
// 包含性结束日期编辑器
// 使用默认编辑器,但重写 set_value/get_value 方法
var dateEditor = gantt.config.editor_types.date;
gantt.config.editor_types.end_date = gantt.mixin({
set_value: function(value, id, column, node){
var correctedValue = gantt.date.add(value, -1, "day");
return dateEditor.set_value.apply(this, [correctedValue, id, column, node]);
},
get_value: function(id, column, node) {
var selectedValue = dateEditor.get_value.apply(this, [id, column, node]);
return gantt.date.add(selectedValue, 1, "day");
},
}, dateEditor);
var textEditor = {type: "text", map_to: "text"};
var startDateEditor = {type: "date", map_to: "start_date"};
var endDateEditor = {type: "end_date", map_to: "end_date"};
var durationEditor = {type: "number", map_to: "duration", min:0, max: 100};
gantt.config.columns = [
{name: "text", label: "Name", tree: true, width: 200, editor: textEditor,
resize: true},
{name: "duration", label: "Duration", width:80, align: "center",
editor: durationEditor, resize: true},
{name: "start_date", label: "Start", width:140, align: "center",
editor: startDateEditor, resize: true},
{name: "end_date", label: "Finish", width:140, align: "center",
editor: endDateEditor, resize: true}
];
// 更新 lightbox 和 grid 模板以显示包含性结束日期
gantt.templates.task_end_date = function(date){
return gantt.templates.task_date(new Date(date.valueOf() - 1));
};
var gridDateToStr = gantt.date.date_to_str("%Y-%m-%d");
gantt.templates.grid_date_format = function(date, column){
if(column === "end_date"){
return gridDateToStr(new Date(date.valueOf() - 1));
}else{
return gridDateToStr(date);
}
}
Related example: 包含性结束日期编辑器
有关结束日期格式化的更多信息,请参阅 任务结束日期显示 & 包含性结束日期 文章。
前置任务编辑器的值格式化
此功能仅在 PRO 版本中可用。
自 v6.3 起,Gantt 支持在内联编辑器中直接指定链接类型以及滞后/提前量。
要启用此功能,请使用 Link Formatter 模块,并将 LinksFormatter 实例传递给 predecessor 编辑器:
var formatter = gantt.ext.formatters.durationFormatter({
enter: "day",
store: "day",
format: "auto"
});
var linksFormatter = gantt.ext.formatters.linkFormatter({durationFormatter: formatter});
var editors = {
text: {type: "text", map_to: "text"},
start_date: {type: "date", map_to: "start_date",
min: new Date(2018, 0, 1), max: new Date(2019, 0, 1)},
end_date: {type: "date", map_to: "end_date",
min: new Date(2018, 0, 1), max: new Date(2019, 0, 1)},
duration: {type: "duration", map_to: "duration",
min:0, max: 100, formatter: formatter},
priority: {type: "select", map_to: "priority",
options:gantt.serverList("priority")},
predecessors: {type: "predecessor", map_to: "auto", formatter: linksFormatter} /*!*/
};
gantt.config.columns = [
{name: "wbs", label: "#", width: 60, align: "center", template: gantt.getWBSCode},
{name: "text", label: "Name", tree: true, width: 200, editor: editors.text,
resize: true},
{name: "start_date", label: "Start", width:80, align: "center",
editor: editors.start_date, resize: true},
{name: "predecessors", label: "Predecessors",width:80, align: "left",
editor: editors.predecessors, resize: true, template: function(task){
var links = task.$target;
var labels = [];
for(var i = 0; i < links.length; i++){
var link = gantt.getLink(links[i]);
labels.push(linksFormatter.format(link)); /*!*/
}
return labels.join(", ")
}},
{name:"add"}
];
Inline editing - keyboard navigation mode
下面是自定义编辑器的代码示例:
自定义内联编辑器
你可以通过如下方式定义新的编辑器对象,实现自定义内联编辑器:
gantt.config.editor_types.custom_editor = {
show: function (id, column, config, placeholder) {
// 当输入框显示时调用,向 placeholder 插入 HTML 标记并初始化编辑器
var html = "<div><input type='text' name='" + column.name + "'/></div>";
placeholder.innerHTML = html;
},
hide: function () {
// 当输入框隐藏时调用
// 在此清理复杂编辑器或解绑事件监听
},
set_value: function (value, id, column, node) {
// 设置输入值
},
get_value: function (id, column, node) {
// 返回输入值
},
is_changed: function (value, id, column, node) {
// 在保存/关闭前调用。如果新值与原始值不同则返回 true
// true 触发保存更改,false 跳过保存
},
is_valid: function (value, id, column, node) {
// 验证输入,返回 false 则放弃更改
return true/false;
},
save: function (id, column, node) {
// 针对 map_to:auto 的输入。复杂的保存逻辑可写在这里
},
focus: function (node) {
}
}
方法说明如下:
- show (id, column, config, placeholder): void - 当内联编辑器打开时触发。在这里添加 DOM 元素并初始化库。参数:
- id - (string | number) - 任务 ID
- column - (GridColumn) - 列配置对象
- config - (any) - 自定义编辑器配置对象
- placeholder - (HTMLElement) - 内联编辑器 DOM 元素
- hide? (): void - 可选,编辑器关闭时调用
- set_value (value, id, column, node): void - 在 show 后调用,从任务对象设置值。参数:
- value - (any) - 任务属性值
- id - (string | number) - 任务 ID
- column - (GridColumn) - 列配置对象
- node - (HTMLElement) - 内联编辑器 DOM 元素
- get_value (id, column, node): any - 关闭前调用,从编辑器获取值。参数:
- id - (string | number) - 任务 ID
- column - (GridColumn) - 列配置对象
- node - (HTMLElement) - 内联编辑器 DOM 元素
- is_changed? (value, id, column, node): boolean - 可选,关闭前触发。返回 true 保存更改,false 取消。参数:
- value - (any) - 任务属性值
- id - (string | number) - 任务 ID
- column - (GridColumn) - 列配置对象
- node - (HTMLElement) - 内联编辑器 DOM 元素
- is_valid? (value, id, column, node): boolean - 可选验证方法。返回 false 拒绝更改。参数:
- value - (any) - 任务属性值
- id - (string | number) - 任务 ID
- column - (GridColumn) - 列配置对象
- node - (HTMLElement) - 内联编辑器 DOM 元素
- save? (id, column, node): void - 可选,适用于
map_to:auto的复杂保存。参数:- id - (string | number) - 任务 ID
- column - (GridColumn) - 列配置对象
- node - (HTMLElement) - 任务对象
- focus? (node): void - 可选,编辑器获得焦点时调用。
- node - (HTMLElement) - 内联编辑器 DOM 元素
可复用编辑器的几个要点:
- 通常,
get_value只应返回当前编辑器的值而不修改任务对象。如果有效,Gantt 会自动更新任务。 - 使用
map_to选项指定编辑器要更新的任务属性,避免在编辑器内部硬编码以便复用。 - 除非需要清理事件或销毁复杂控件,否则
hide方法可以为空。 - 建议实现
is_changed和is_valid方法:is_changed只有在值实际发生变化时才返回 true,以避免不必要的更新。is_valid用于阻止无效输入。
- 对于不仅仅是更新属性的编辑器(如内置的 predecessor editor),应在
save方法中实现逻辑,并将map_to设为 "auto"。此时 gantt 不会直接修改任务,而是调用save应用更改。
下面是一个简单数值输入编辑器的示例。hide 方法为空,save 被省略。
var getInput = function(node){
return node.querySelector("input");
};
gantt.config.editor_types.simpleNumber = {
show: function (id, column, config, placeholder) {
var min = config.min || 0,
max = config.max || 100;
var html = "<div><input type='number' min='" + min +
"' max='" + max + "' name='" + column.name + "'/></div>";
placeholder.innerHTML = html;
},
hide: function () {
// 无需清理,编辑器移除后无需操作
},
set_value: function (value, id, column, node) {
getInput(node).value = value;
},
get_value: function (id, column, node) {
return getInput(node).value || 0;
},
is_changed: function (value, id, column, node) {
var currentValue = this.get_value(id, column, node);
return Number(value) !== Number(currentValue);
},
is_valid: function (value, id, column, node) {
return !isNaN(parseInt(value, 10));
},
focus: function (node) {
var input = getInput(node);
if (!input) {
return;
}
if (input.focus) {
input.focus();
}
if (input.select) {
input.select();
}
}
};
然后,像内置编辑器一样使用它:
var numberEditor = {type: "simpleNumber", map_to: "quantity", min:0, max: 50};
gantt.config.columns = [
...
{name: "quantity", label: "Quantity", width: 80, editor: numberEditor,
resize: true},
...
];
由于 Gantt 会自动移除编辑器 DOM 元素,这里的 hide 不需要额外清理。
editor.hide
当在内联编辑器中使用更复杂的控件时,可能需要添加 hide 方法进行清理。
例如,下面是一个用 jQuery UI 实现的 DatePicker 输入。需要在编辑器从 DOM 移除时销毁 datepicker 控件。
前置条件:
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
编辑器实现:
gantt.config.editor_types.custom_datepicker_editor = {
show: function (id, column, config, placeholder) {
placeholder.innerHTML = "<div><input type='text' id='datepicker' name='" +
column.name + "'/></div>";
$("#datepicker").datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateStr){
gantt.ext.inlineEditors.save()
}
});
},
hide: function (node) {
$("#datepicker").datepicker( "destroy" );
},
set_value: function (value, id, column, node) {
$("#datepicker").datepicker("setDate", value);
},
get_value: function (id, column, node) {
return $("#datepicker").datepicker( "getDate" );
},
is_changed: function (value, id, column, node) {
return (+$("#datepicker").datepicker( "getDate" ) !== +value);
},
is_valid: function (value, id, column, node) {
return !(isNaN(+$("#datepicker").datepicker( "getDate" )))
},
save: function (id, column, node) {
},
focus: function (node) {
}
};
let dateEditor = {
type: "custom_datepicker_editor",
map_to: "start_date"
};
gantt.config.columns = [
{name: "text", tree: true, width: '*', resize: true},
{name: "start_date", align: "center", resize: true, editor: dateEditor},
{name: "duration", align: "center"},
{name: "add", width: 44}
];
Related example: 在编辑器中使用 jQuery Datepicker
editor.save
save 函数在编辑器需要同时更新任务的多个属性,或需要修改任务以外的对象时非常有用。
在这种情况下,你仍然可以实现 get_value 用于内置校验,但 gantt 不会尝试直接将编辑器的值应用到任务上,而是会调用 save 函数。
当调用 save 后,你需要解析输入值,并通过自定义代码将必要的更改应用到 gantt。save 方法执行完毕后,Gantt 会触发 onSave 事件。但它不会为已更新的行调用 gantt.updateTask。
注意! 只有在编辑器配置中设置 map_to:"auto" 时,才会调用 save 方法:
var editors = {
...
predecessors: {type: "predecessor", map_to: "auto"}
};
一个很好的例子是内置的前置任务编辑器。你可以在相关示例中看到其简化实现:
Related example: Built-in predecessor editor