Перетаскивание задач вместе с их зависимыми задачами

Вот обзор различных способов перемещения задач вместе с их зависимыми задачами.

Использование расширения Auto Scheduling

Один из вариантов — использовать расширение Автоматическое планирование. Эта функция автоматически планирует задачи на основе их взаимосвязей.

Чтобы включить автоматическое планирование, активируйте его с помощью метода gantt.plugins:

gantt.plugins({
    auto_scheduling: true
});

Затем установите свойство auto_scheduling в true:

gantt.config.auto_scheduling = true;

Ручное перемещение задач

Содержание главы

Основная идея

Когда речь идет о ручном перетаскивании зависимых задач, общий подход следующий:

  • Обнаружить, когда задача перемещается.
  • Пройти по всем зависимым задачам и соответственно скорректировать их позиции.

Вы можете выбрать один из двух методов:

В любом случае первым шагом будет получение всех связанных задач.

Получение всех связанных задач

Чтобы определить связанные задачи, используйте свойства $source и $target в объекте задачи. Эти свойства автоматически генерируются и содержат идентификаторы связанных ссылок:

  • $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, чтобы применить вычисленную разницу ко всем зависимым задачам после подтверждения перетаскивания:

// Округлите позиции зависимых элементов до масштаба
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 );
    }
  });
})();
К началу