网格中的内联编辑
dhtmlxGantt 提供两种编辑内容的选项:
- 通过使用 Lightbox 编辑表单
- 通过在 Grid 区域中使用内联编辑器
内联编辑让你直接在网格中进行任何修改:创建与更新任务,设置它们之间的连接,定义开始和结束日期,或修改持续时间——全部通过内置编辑器完成。

要启用内联编辑,你需要:
- 指定编辑器配置列表,并使用编辑器对象的 map_to 属性将所需的编辑器映射到网格列
const textEditor = { type: "text", map_to: "text" };
const dateEditor = { type: "date", map_to: "start_date", min: new Date(2025, 0, 1),
max: new Date(2026, 0, 1) };
const durationEditor = { type: "number", map_to: "duration", min: 0, max: 100 };
- 在列配置中使用 editor 属性来定义该列应使用的编辑器
gantt.config.columns = [
{ name: "text", tree: true, width: "*", editor: textEditor, resize: true },
{ name: "start_date", align: "center", editor: dateEditor, resize: true },
{ name: "duration", align: "center", editor: durationEditor },
{ name: "add", width: 44 }
];
请在 Inline Editors Extension 文章中查看 inlineEditors 对象 API 的详细信息。
你也可以查看视频指南,了解如何在网格中实现内联编辑。
编辑器的类型
内联编辑器存储在 editor_types 配置对象中。
有几种预定义的内联编辑器:
- text 编辑器 - 用于编辑文本列,例如任务名称
- number 编辑器 - 用于编辑数字列,例如任务持续时间、顺序等
- duration 编辑器 - 用于编辑持续时间列,即任务持续时间。仅当使用 map_to: "duration" 配置且编辑器类型设为 "duration" 时才起作用:
{ type: "duration", map_to: "duration", formatter: formatter }
如果你需要指定包含数字和 duration unit 的持续时间,这种内联编辑器很有用。
例如:5 days。
默认使用 Duration Formatter。
除了使用默认的持续时间格式化器,你也可以修改其配置或设置一个 自定义格式化器。
- date 编辑器 - 用于编辑日期列,例如任务的开始和结束日期
- select 编辑器 - 用于从列表中选择一个选项
- predecessor 编辑器 - 用于为当前正在编辑的任务设置前置任务连接。该编辑器会获取任务的 WBS 编码 来与前置任务建立连接。
const editors = {
text: { type: "text", map_to: "text" },
start_date: { type: "date", map_to: "start_date", min: new Date(2025, 0, 1),
max: new Date(2026, 0, 1) },
end_date: { type: "date", map_to: "end_date", min: new Date(2025, 0, 1),
max: new Date(2026, 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" }
};
Dates limits in the Date editor
从 v6.3 开始,date 内联编辑器的最小值和最大值没有默认限制。
如果你想让在时间刻度上可见的日期限制date 内联编辑器的最小值和最大值(除非提供了自定义的 min/max 值),你可以指定动态的 min/max 值:
const dateEditor = {
type: "date",
map_to: "start_date",
min: taskId => gantt.getState().min_date,
max: taskId => gantt.getState().max_date
};
Editor for inclusive end dates
如果你使用任务的 包含结束日期的格式 并希望使其在网格中的内联编辑工作正常,你需要为编辑包含结束日期的特殊编辑器,如下所示:
// 包含性结束日期编辑器
// 使用默认编辑器,但重写 set_value/get_value 方法
const dateEditor = gantt.config.editor_types.date;
gantt.config.editor_types.end_date = gantt.mixin({
set_value: function(value, id, column, node) {
const correctedValue = gantt.date.add(value, -1, "day");
return dateEditor.set_value.apply(this, [correctedValue, id, column, node]);
},
get_value: function(id, column, node) {
const selectedValue = dateEditor.get_value.apply(this, [id, column, node]);
return gantt.date.add(selectedValue, 1, "day");
}
}, dateEditor);
const textEditor = { type: "text", map_to: "text" };
const startDateEditor = { type: "date", map_to: "start_date" };
const endDateEditor = { type: "end_date", map_to: "end_date" };
const 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 = date =>
gantt.templates.task_date(new Date(date.valueOf() - 1));
const gridDateToStr = gantt.date.date_to_str("%Y-%m-%d");
gantt.templates.grid_date_format = (date, column) =>
column === "end_date"
? gridDateToStr(new Date(date.valueOf() - 1))
: gridDateToStr(date);
相关示例 Inclusive end date editor
有关结束日期格式化的更多细节,请参阅 Task end date display & Inclusive end dates 文章。
Formatting values of the Predecessor editor
此功能仅在 PRO 版中可用。
从 v6.3 开始,Gantt 允许直接在内联编辑器中指定链接类型以及延迟/提前值。
要实现这一点,你需要使用 Link Formatter 模块,并将一个 LinksFormatter 实例提供给 predecessor 编辑器:
const formatter = gantt.ext.formatters.durationFormatter({
enter: "day",
store: "day",
format: "auto"
});
const linksFormatter = gantt.ext.formatters.linkFormatter({
durationFormatter: formatter
});
const editors = {
text: { type: "text", map_to: "text" },
start_date: { type: "date", map_to: "start_date", min: new Date(2025, 0, 1),
max: new Date(2026, 0, 1) },
end_date: { type: "date", map_to: "end_date", min: new Date(2025, 0, 1),
max: new Date(2026, 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: task => {
const links = task.$target || [];
const labels = links.map(id => {
const link = gantt.getLink(id);
return linksFormatter.format(link);
});
return labels.join(", ");
}
},
{ name: "add" }
];
Inline editing - keyboard navigation mode
下面的章节为以下自定义编辑器提供代码示例:
自定义内联编辑器
你也可以指定一个自定义内联编辑器。为此,你需要按以下方式创建一个新的编辑器对象:
gantt.config.editor_types.custom_editor = {
show: (id, column, config, placeholder) => {
// 在输入显示时被调用,将编辑器的 HTML 标记放入
// placeholder,并在需要时初始化你的编辑器:
const html = "<div><input type='text' name='" + column.name + "'/></div>";
placeholder.innerHTML = html;
},
hide: () => {
// 输入隐藏时被调用
// 在此处销毁任何复杂编辑器或分离事件监听器
},
set_value: (value, id, column, node) => {
// 设置输入值
},
get_value: (id, column, node) => {
// 返回输入值
},
is_changed: (value, id, column, node) => {
// 在保存/关闭前被调用
// 如果新值与初始状态不同,则返回 true
// 返回 true 将触发保存更改,返回 false 将跳过保存
},
is_valid: (value, id, column, node) => {
// 验证,若返回 false,修改将被放弃
return true/false;
},
save: (id, column, node) => {
// 仅对 map_to:auto 的输入适用。复杂的保存行为在这里处理
},
focus: (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,改动将被保存;否则将被取消。参数包括:
- 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配置选项来指定编辑器应更新任务的哪个属性,但不要将其硬编码在编辑器中。这样可以使你在不同列之间重用编辑器。 - 除非你使用的是复杂的 JavaScript 小部件,否则无需在
hide函数中编写任何逻辑,可以将其留空。否则,可以在此方法中调用析构函数或清理在显示编辑器时附加的事件处理程序。 - 确保实现
is_changed和is_valid函数:- 如果
is_changed始终返回 true,编辑器将在每次编辑结束时触发更新(可能会发送给后端)。该方法应仅在输入值确实与初始状态不同的情况下返回 true; is_valid用于阻止无效值的输入。
- 如果
- 如果你实现的编辑器比简单地将值写入任务属性更复杂——例如内置的 predecessor editor——你需要在
save函数中实现所需逻辑,并将输入的map_to选项设为"auto"。在这种情况下,Gantt 不会直接修改任务对象,而是在适用编辑器更改时调用save函数。
下面是一个简单数字输入实现的示例。
注意,hide 方法可以是空 函数,save 方法也可以完全省略。
const getInput = node => node.querySelector("input");
gantt.config.editor_types.simpleNumber = {
show: (id, column, config, placeholder) => {
const min = config.min ?? 0,
max = config.max ?? 100;
const html = "<div><input type='number' min='" + min +
"' max='" + max +
"' name='" + column.name + "'/></div>";
placeholder.innerHTML = html;
},
hide: () => {
// 可以为空,因为在编辑器分离后没有需要清理的东西
},
set_value: (value, id, column, node) => {
getInput(node).value = value;
},
get_value: (id, column, node) => {
return getInput(node).value || 0;
},
is_changed: function(value, id, column, node) {
const currentValue = this.get_value(id, column, node);
return Number(value) !== Number(currentValue);
},
is_valid: (value, id, column, node) => {
return !isNaN(parseInt(value, 10));
},
focus: node => {
const input = getInput(node);
if (!input) return;
if (input.focus) input.focus();
if (input.select) input.select();
}
};
之后,你就可以像内置编辑器那样使用该编辑器:
const numberEditor = { type: "simpleNumber", map_to: "quantity", min: 0, max: 50 };
gantt.config.columns = [
...
{ name: "quantity", label: "Quantity", width: 80, editor: numberEditor,
resize: true },
...
];
注意,在这种情况下,你无需实现 hide 方法,因为 Gantt 会自动分离编辑器的 DOM 元素,并且在编辑器关闭后不再需要清理任何其他内容。
editor.hide
如果在内联编辑器中使用了复杂的小部件,你可能需要添加一个 hide 逻辑。
例如,下面给出使用 jQuery 实现的 DatePicker 输入的示例。在从 DOM 分离后,我们需要销毁日期选择器小部件。
前提条件:
<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: (id, column, config, placeholder) => {
placeholder.innerHTML =
`<div><input type="text" id="datepicker" name="${column.name}"/></div>`;
$("#datepicker").datepicker({
dateFormat: "yy-mm-dd",
onSelect: () => gantt.ext.inlineEditors.save()
});
},
hide: (node) => {
$("#datepicker").datepicker("destroy");
},
set_value: (value, id, column, node) => {
$("#datepicker").datepicker("setDate", value);
},
get_value: (id, column, node) => {
return $("#datepicker").datepicker("getDate");
},
is_changed: (value, id, column, node) => {
return +$("#datepicker").datepicker("getDate") !== +value;
},
is_valid: (value, id, column, node) => {
return !isNaN(+$("#datepicker").datepicker("getDate"));
},
save: (id, column, node) => {
},
focus: (node) => {
}
};
const 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 }
];
相关示例 在编辑器中使用 jQuery Datepicker
editor.save
仅在你的编辑器需要同时修改任务的多个属性,或想要修改的对象不是任务对象时,才需要使用 save 函数。
在这种情况下,你可以保留对内置校验的正确实现的 get_value,但 Gantt 不会尝试将编辑器的值应用到任务上,而是改为调用 save 函数。
在调用了 save 之后,你需要对输入值进行解释并通过自定义代码应用更改给 Gantt。
Gantt 将在完成 save 方法后触发 onSave 事件,但不会为修改的行调用 gantt.updateTask。
注意! 只有在配置编辑器时指定了 map_to:"auto" 时,才会调用 save 方法:
const editors = {
...
predecessors: { type: "predecessor", map_to: "auto" }
};
这样的控件一个很好的例子就是内置的前置编辑器。你可以在相关示例中找到它的简化实现:
Related sample Built-in predecessor editor