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

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

Использование расширения 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 );
  }
});

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

Перемещение потомков после завершения перемещения основной задачи

В качестве альтернативы, потомки можно обновлять только после того, как основная задача была перемещена. Такой подход проще визуально и обеспечивает лучшую производительность.

Идея заключается в том, чтобы дождаться завершения drag and drop, затем вычислить, насколько была перемещена основная задача, и сместить все связанные задачи на это значение.

Шаг 1

Сначала объявите итератор, как показано ранее в разделе Получение всех связанных задач.

Шаг 2

Когда пользователь завершает перетаскивание, зафиксируйте событие onBeforeTaskChanged. Это событие предоставляет как оригинальную, так и изменённую версии перемещённой задачи, что позволяет вычислить разницу в датах.

Обратите внимание, что на этом этапе drag-and-drop всё ещё может быть отменён (так как 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 );
    }
  });
})();
К началу