跳到主要内容

拖动任务及其依赖任务

有几种方式可以处理将任务与其依赖任务一起移动的情况。

使用自动调度扩展

一种选择是使用 자동 스케줄링 扩展。它会根据任务之间的关系自动调度任务。

要启用自动调度,请使用 gantt.plugins 方法:

gantt.plugins({
auto_scheduling: true
});

同时,将 auto_scheduling 属性设置为 true:

gantt.config.auto_scheduling = true;

手动移动任务

###章节目录

###核心思路 一种常见的拖动依赖任务的方式是:

  • 检测任务正在被移动
  • 找到所有依赖任务,并以相同(或调整后)的幅度移动它们

你可以选择以下两种方法之一:

无论哪种方式,第一步都是获取所有关联任务。

获取所有关联任务

要查找与任务相关的链接,可以使用任务对象上的 $source$target 属性。 这些属性是自动生成的,包含相关链接的 ID:

  • $source - 从该任务出发的链接
  • $target - 指向该任务的链接
var taskObj = gantt.getTask("t1");

var sourceLinks = taskObj.$source; //-> ["l1","l4"] - 外发链接的ID
var targetLinks = taskObj.$target; //-> ["l5","l8"] - 入链的ID

通过这些链接,可以找到依赖的任务。

要收集所有关联任务,可以定义如下迭代器:

gantt.eachSuccessor = function(callback, root){
if(!this.isTaskExists(root))
return;

// 跟踪已访问的任务,避免死循环
var traversedTasks = arguments[2] || {};
if(traversedTasks[root])
return;
traversedTasks[root] = true;

var rootTask = this.getTask(root);
var links = rootTask.$source;
if(links){
for(var i="0;" i < links.length; i++){
var link = this.getLink(links[i]);
if(this.isTaskExists(link.target) && !traversedTasks[link.target]){
callback.call(this, this.getTask(link.target));

// 遍历整个依赖分支,不仅仅是第一层
this.eachSuccessor(callback, link.target, traversedTasks);
}
}
}
};

与主任务同步移动后代任务

在拖动主任务时,可以将其后代任务一起移动。也就是说,用户移动主任务时,所有依赖任务会同步移动。这种方式视觉上流畅,但如果涉及任务较多,可能会影响性能。

####第1步

首先,按照获取所有关联任务中的方法声明迭代器。

####第2步

接下来,绑定 onTaskDrag 事件的处理器。该事件会在每一帧拖动时触发,在此可以移动所有关联任务。

gantt.attachEvent("onTaskDrag", function(id, mode, task, original){
var modes = gantt.config.drag_mode;
if(mode == modes.move){
var diff = task.start_date - original.start_date;
gantt.eachSuccessor(function(child){
child.start_date = new Date(+child.start_date + diff);
child.end_date = new Date(+child.end_date + diff);
gantt.refreshTask(child.id, true);
},id );
}
return true;
});

####第3步

最后,当拖动结束,用户释放鼠标时,将子任务的位置对齐到时间刻度。这可以通过 onAfterTaskDrag 事件完成:

gantt.attachEvent("onAfterTaskDrag", function(id, mode, e){
var modes = gantt.config.drag_mode;
if(mode == modes.move ){
gantt.eachSuccessor(function(child){
child.start_date = gantt.roundDate(child.start_date);
child.end_date = gantt.calculateEndDate(child.start_date, child.duration);
gantt.updateTask(child.id);
},id );
}
});

除非关联任务数量非常多,否则这种方法效果良好。

在主任务移动完成后再移动后代任务

另一种方式是,仅在主任务移动完成后才更新其后代任务。这种方式视觉上更简单,性能也更好。

思路是等待拖拽操作完成后,再计算主任务移动了多少天,然后将所有关联任务按此幅度移动。

####第1步

首先,按照前文获取所有关联任务的方法声明迭代器。

####第2步

当用户完成拖动时,捕获 onBeforeTaskChanged 事件。该事件会同时提供被移动任务的原始版本和修改后的版本,可以用来计算日期差。

注释

注意,此时拖拽操作仍有可能被取消(因为 onBeforeTaskChanged 支持取消,并且你的应用可能有相关处理器),因此此处不会更新依赖任务。

相反,可以将计算出的差值保存在稍后可访问的变量中。

var diff = 0;

gantt.attachEvent("onBeforeTaskChanged", function(id, mode, originalTask){
var modes = gantt.config.drag_mode;
if(mode == modes.move ){
var modifiedTask = gantt.getTask(id);
diff = modifiedTask.start_date - originalTask.start_date;
}
return true;
});

####第3步

最后,使用 onAfterTaskDrag 事件,利用之前计算的 diff 更新所有依赖任务:

//将子任务位置对齐到时间刻度
gantt.attachEvent("onAfterTaskDrag", function(id, mode, e){
var modes = gantt.config.drag_mode;
if(mode == modes.move ){
gantt.eachSuccessor(function(child){
child.start_date = gantt.roundDate(new Date(child.start_date.valueOf() + diff));
child.end_date = gantt.calculateEndDate(child.start_date, child.duration);
gantt.updateTask(child.id);
},id );
}
});

完整代码如下:

(function(){

var diff = 0;

gantt.attachEvent("onBeforeTaskChanged", function(id, mode, originalTask){
var modes = gantt.config.drag_mode;
if(mode == modes.move ){
var modifiedTask = gantt.getTask(id);
diff = modifiedTask.start_date - originalTask.start_date;
}
return true;
});

//将子任务位置对齐到时间刻度
gantt.attachEvent("onAfterTaskDrag", function(id, mode, e){
var modes = gantt.config.drag_mode;
if(mode == modes.move ){
gantt.eachSuccessor(function(child){
child.start_date = gantt.roundDate(new Date(child.start_date.valueOf() + diff));
child.end_date = gantt.calculateEndDate(child.start_date, child.duration);
gantt.updateTask(child.id);
},id );
}
});
})();

hidden