Dragging Tasks Together with Their Dependent Tasks

There are several ways of implementing tasks moving with their dependent tasks.

Using Auto Scheduling Extension

Firstly, you can make use of the Auto Scheduling extension. It allows scheduling tasks automatically depending on relations between them.

To use the auto scheduling functionality, you should enable it using the gantt.plugins method:

gantt.plugins({
    auto_scheduling: true
});

And set the auto_scheduling property to true:

gantt.config.auto_scheduling = true;

Moving Tasks Manually

Chapter Contents

The Main Idea

The common approach with dragging dependent tasks is the following:

  • you detect when the task is being moved
  • you traverse all dependent tasks and move them to the same (or different, depending on what you need) amount.

So, you can choose one of the two ways:

In both cases, you need to get all linked tasks first.

Getting all linked tasks

To retrieve the task related links, use the $source and $target properties of the task's object. The properties are autogenerated and store ids of the related links:

  • $source - the link that comes out from the task;
  • $target - the link that comes into task.
var taskObj = gantt.getTask("t1");
 
var sourceLinks = taskObj.$source;        //-> ["l1","l4"] - ids of coming-out links  
var targetLinks = taskObj.$target;       //-> ["l5","l8"] - ids of coming-into links

and from the links you can get the dependent tasks.

So, to get the linked tasks, we need to declare an iterator:

gantt.eachSuccessor = function(callback, root){
  if(!this.isTaskExists(root))
    return;
 
  // remember tasks we've already iterated in order to avoid infinite loops
  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));
 
        // iterate the whole branch, not only first-level dependencies
        this.eachSuccessor(callback, link.target, traversedTasks);
      }
    }
  }
};

Moving Descendants synchronously with the main task

Descendant tasks can be moved synchronously with the moving of the main tasks, i.e. when the user starts moving tasks, all dependent branches will be moved together. It will look good, but the downside is that there may be a performance drop, if you are moving many tasks at the same time.

Step 1

Firstly, we will declare the iterator, as it's shown above.

Step 2

Then, you need to attach a handler to the onTaskDrag event. It will be called on each frame of drag and drop, and from here we'll move all linked tasks.

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;
});

Step 3

Finally, when the user releases the mouse and drag-and-drop is finished, we need to round positions of the child items to scale. We can do it using onAfterTaskDrag event:

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 );
  }
});

This approach works fine if you don't have too many linked tasks.

Moving Descendants after movement of the main task is finished

Descendant tasks can be updated after the user finishes moving the main task. The result will look simpler, but have a better performance.

The approach is the following: when the drag and drop is finished, we check what amount the task has been moved for, and move all linked tasks to the same value.

Step 1

Firstly, we will declare the iterator, as it's shown above.

Step 2

When the user releases the mouse and drag and drop is finished, we can capture the onBeforeTaskChanged event, where both the modified and the original instances of the moved task are available and calculate date difference between them.

Note, that at this stage drag-and-drop can be canceled (since onBeforeTaskChanged allows canceling it, and your app may have handlers that can do it), so we don't modify dependent tasks here.

Instead, we'll put the calculated diff value in a variable in the same closure, so that it could be accessed later.

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;
});

Step 3

Finally, we capture the onAfterTaskDrag event, which tells that drag-and-drop has been performed. At this point we can update all dependent tasks using diff we've calculated at the previous step:

//rounds positions of the child items to scale
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 );
    }
});

The full code will be as follows:

(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;
  });
 
  //rounds positions of the child items to scale
  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 );
    }
  });
})();
Back to top