Практические руководства

Информацию об основных принципах кастомизации читайте в соответствующей статье.

Как добавить столбцы в Дерево задач

Чтобы добавить столбец, создайте класс, наследуя его от gantt.views.tree. Внутри метода config разбейте исходный интерфейс и сконфигурируйте новый столбец:

class CustomTree extends gantt.views.tree {
  config() {
    const compact = this.getParam("compact", true);
    const ui = super.config();
 
    ui.columns.splice(compact ? 3 : 2, 0, {
      id: "progress",
      header: "~",
      template: "#progress#%", // поле "progress" приходит из данных
      width: 40,
    });
 
    if (!compact) ui.width += 40;
 
    return ui;
  }
}

Не забудьте переопределить класс по умолчанию с помощью свойства override:

webix.ui({
  view: "gantt",
  override: new Map([[gantt.views.tree, CustomTree]]),
});

Related sample:  Gantt: Как добавить столбцы в Дерево задач

Как добавить произвольный тултип

Чтобы добавить расширенный тултип для задач, создайте класс, наследуя его от класса по умолчанию (gantt.views["chart/bars"]). Внутри метода config удалите исходный тултип и опишите темплейт для нового:

class CustomBarsView extends gantt.views["chart/bars"] {
  config() {
    const ui = super.config();
 
    // удаляем стандартный тултип 'overflow'
    const tpl = ui.cells[1].type.templateStart;
    ui.cells[1].type.templateStart = function(obj) {
      return tpl(obj).replace("webix_tooltip=", "");
    };
 
    // описываем свой тултип
    ui.cells[1].tooltip = function(obj) {
      const parser = webix.i18n.longDateFormatStr;
      return `${obj.text}<br>
        <br>${parser(obj.start_date)} - ${parser(obj.end_date)} 
        (${obj.duration} days)
        <br>${obj.progress}% complete
      `;
    };
    return ui;
}

Не забудьте переопределить класс по умолчанию с помощью свойства override:

webix.ui({
  view: "gantt",
  override: new Map([[gantt.views["chart/bars"], CustomBarsView]]),
});

Related sample:  Gantt: Как добавить произвольный тултип

Как масштабировать шкалы

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

Прежде всего опишите функцию, которая будет отвечать за смену шкал:

function resetScales(v, o) {
  const current = gantt1.getService("local").getScales();
  if (!(originalEndDate || originalStartDate)) {
    originalStartDate = webix.Date.add(current.start, 1, o, true);
    originalEndDate = webix.Date.add(current.end, -1, o, true);
  }
 
  const cellWidth = cellWidths[v];
  const scales = getScales(v);
 
  const start = webix.Date.add(originalStartDate, -1, v, true);
  const end = webix.Date.add(originalEndDate, 1, v, true);
 
  gantt1
    .getService("local")
    .setScales(
      start,
      end,
      !(v == "day"),
      cellWidth,
      current.cellHeight,
      scales
    );
}
 
// возвращает массив шкал, которые будут отрисованы
function getScales(minScale) {
  const scales = [];
  switch (minScale) {
    case "year":
      scales.push(yearScale);
      break;
    case "quarter":
      scales.push(yearScale, quarterScale);
      break;
    // другие условия
  }
  return scales;
}

Для непосредственного зуммирования, определите список Richselect с необходимыми опциями. При смене значения в контроле будет вызываться функция resetScales:

const zoom = {
  view: "richselect",
  label: "Zoom:",
  value: "month",
  width: 300,
  options: ["hour", "day", "week", "month", "quarter", "year"],
  on: {
    onChange: resetScales,
  },
};

Добавьте тулбар и поместите ричселект в его поле elements:

webix.ui({
  rows: [
    {
      view: "toolbar",
      id: "toolbar",
      css: "webix_dark",
      paddingX: 10,
      elements: [zoom, {}],
    },
    {
      view: "gantt",
      id: "gantt1",
      url: "https://docs.webix.com/gantt-backend/",
      // другие свойства
    },
  ],
});

Related sample:  Gantt: Как масштабировать шкалы

Всплывающее окно с информацией о событии и широкая форма для редактирования

Этот пример показывает, как убрать правую панель и:

  • отображать небольшой попап с информацией вместо правой панели
  • открывать форму для редактирования на месте сетки задач.

Изменения касаются только полноэкранного режима, компактный режим остаётся без изменений.

Подготавливаем попап

Компонент Task Info необходимо поместить в попап, поэтому внесём некоторые правки заранее:

  • укажите внутренние отступы и размеры UI элементов
  • спрячьте некоторые детали (напр., successors, predecessors...)
  • опишите действи при клике по кнопке "Edit", чтобы оно открывало форму на месте сетки задач.
class CustomInfo extends gantt.views["task/info"] {
   // стилизация
  config() {
    this.Compact = this.getParam("compact", true);
    const ui = super.config();
    if (!this.Compact) {
      ui.body.rows[0].padding = 4; //toolbar
      ui.body.rows[1].padding = 4; //main area
      ui.body.rows[1].rows[0].autoheight = true; //template
      ui.body.rows[1].rows[1].inputWidth = 0; //button
    }
    return ui;
  }
  // открываем форму для редактирования
  EditTask() {
    if (!this.Compact) {
      this.getParentView().Hide();
      this.app.show("top/task.form");
    } else super.EditTask();
  }
 // убираем некоторые детали
  InfoTemplate(obj) {
    obj.targets = obj.sources = [];
    delete obj.details;
    return super.InfoTemplate(obj);
  }
}

После этого создайте класс InfoPopup с компонентом Popup, который содержит вышеупомянутый компонент Info внутри. Также опишите методы, чтобы показывать/прятать попап:

class InfoPopup extends gantt.views.JetView {
  config() {
    return {
      view: "popup",  width: 350,
      body: CustomInfo,
    };
  }
  Show(node) {
    this.getRoot().show(node);
  }
  Hide() {
    this.getRoot().hide();
  }
}

Показываем и прячем Info попап

Следующим шагом будет показ/скрытия попапа при клике по событию. Для этого вам необходимо переопределить класс по умолчанию gantt.views["chart/bars"]:

class CustomBarsView extends gantt.views["chart/bars"] {
  config() {
    const ui = super.config();
    // показываем InfoPopup при клике по задаче
    ui.cells[1].on.onItemClick = (id, e, node) => {
      this.State.$batch({
        parent: null, selected: id,
      });
      this.Info.Show(node);
    };
    return ui;
  }
  init(view) {
    super.init(view);
    // создаём попап
    this.Info = this.ui(InfoPopup);
    // обрабатываем событие, чтобы спрятать попап
    this.on(this.app, "edit:stop", () => {
      this.State.selected = this.State.parent = null;
      this.Info.Hide();
    });
  }
}

Подготавливаем форму для редактирования

Теперь переопределим Form View. При необходимости вы можете разбить форму на столбцы, а поле "Notes" показывать справа. Так как форма перекрывает сетку задач, можно убрать редактирование при изменении данных.

class CustomForm extends scheduler.views["event/form"] {
    config() {
      let ui = super.config();
      if (!this.Compact) {
        // textarea
        const notes = ui.body.rows[1].elements.splice(5, 1)[0];
        notes.labelPosition = "top";
        notes.height = 334;
 
        // форма с 3 столбцами
        const form = ui.body.rows[1];
        form.cols = [
          { margin: form.margin, rows: ui.body.rows[1].elements },
          { rows: [notes, {}] },
          { width: 300 },
        ];
        form.margin = 20;
        delete ui.body.rows[1].elements;
        ui.body.rows[1] = form;
 
        // bar
        ui.body.rows[0].padding.right = 335;
      }
 
      return ui;
    }
}

Теперь необходимо поправить логику CustomForm:

  • при клике по кнопке "Done" данные должны сохраняться, а форма - закрываться
  • при клике по кнопке "Close", вы можете спрашивать у пользователей, сохранять ли измнения, если таковые есть.
// действие при клике по кнопке "Done" 
 Done(exit) {
  // сохраняем данные при создании нового события или редактировании уже существующего
    if (this.State.selected === "$temp" || this.Form.isDirty())
      this.UpdateTask().then(() => this.Back(exit));
    else this.Back(exit);
 }
// действие при клике по иконке "Close" 
Close() {
    // спрашивает пользователя в случае изменённых данных
  if (this.Form.isDirty()) {
    webix.confirm("Save changes?")
   .then(() => this.Done(true))
               .catch(() => this.Back(true));
  } else this.Back(true);
}
// очищаем форму и показывает сетку задач
Back(exit) {
    if (!this.Compact) {
      this.Form.clear();
      this.State.selected = this.State.parent = null;
      this.app.show("top/chart");
    } else super.Back(close);
}

Настраиваем навигацию

Чтобы показывать форму вместо сетки задач, необходимо заменить статичный subview на динамичным и затем показывать задачи после инициализации.

class CustomTopView extends gantt.views.top {
  config() {
      const ui = super.config();
       // вставляем динамический subview для показа формы или сетки задач
       ui.cells[0].cols[2] = { $subview: true };
       return ui;
   }
   init(view) {
        super.init(view);
        // показывает сетку задач
        this.show("chart/bars");
    }
  }

После этого вы сможете показывать форму из CustomInfo:

// метод EditTask класса CustomInfo (описан выше)
this.app.show("top/task.form");

и открывать форму при создании новой задачи:

// открываем форму при создании новой задачи
ShowTask(path) {
     if (this.Compact) super.ShowTask(path);
    else if (path === "form")
        this.show("task.form");
}
// сохраняем исходную логику только для компактного режима
HideTask() {
       if (this.Compact);
         super.HideTask();
}

Не забудьте переопределить класс по умолчанию с помощью свойства override:

webix.ui({
  view: "gantt",
  override: new Map([
    [gantt.views["chart/bars"], CustomBarsView],
    [gantt.views.top, CustomTopView],
    [gantt.views["task/info"], CustomInfo],
    [gantt.views["task/form"], CustomForm]
  ])
});

Related sample:  Gantt: Всплывающее окно с информацией о событии и широкая форма для редактирования

Изменение времени задачи

В этом примере вы можете увидеть как:

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

Настройка шкалы

Прежде всего, включите шкалу с часами в массив scales в конфигурации Gantt.

view:"gantt",
scales: [
  { unit: "day", format: "%M %d" },
  { unit: "hour", format: "%H:00" },
]

Далее вам необходимо модифицировать несколько внутренних классов виджета, чтобы отобразить время задач в интерфейсе.

Изменение формы и Информационной панели

Для того, чтобы предоставить возможность устанавливать время задачи, создайте класс, наследуя его от gantt.views["task/form"]. Измените JSON конфигурацию формы, чтобы выключить таймпикер для внутренних календарей:

class CustomForm extends gantt.views["task/form"] {
  config() {
    const ui = super.config();
 
    const form = ui.body.rows[1];
    // дата начала
    form.elements[2].timepicker = true;
    // дата завершения
    form.elements[3].timepicker = true;
 
    return ui;
  }
}

Для того, чтобы отобразить установленное время на Информационной панели, создайте класс, наследуя его от gantt.views["task/info"]. Верните строку, которая содержит дату и время, используя метод DateFormat:

class CustomInfo extends gantt.views["task/info"] {
  DateFormat(date) {
    return webix.i18n.fullDateFormatStr(date);
  }
}

Отображение времени в Дереве задач

Для того, чтобы показать дату и время в “Дереве задач”, создайте класс, наследуя его от gantt.views.tree. Для отображения строки с датой и временем, измените JSON конфигурацию, возвращенную методом config родительского класса:

class CustomTree extends gantt.views.tree {
  config() {
    const tree = super.config();
 
    tree.columns[1].format = webix.i18n.fullDateFormatStr;
    tree.columns[1].minWidth = 170;
 
    return tree;
  }
  //еще один метод
}

Вы также можете установить продолжительность задачи по умолчанию 1 час. Для этого вам необходимо изменить метод GetTempTask как показано ниже:

class CustomTree extends gantt.views.tree {
  // ...
  GetTempTask(start) {
      return {
        id: "$temp",
        start_date: new Date(start),
        end_date: webix.Date.add(start, 1, "hour", true) /* ! */,
        progress: 0,
        text: "",
      };
    }
}

Расширение тултипа задачи

Вы можете изменить тултип задачи таким образом, что дата задачи и его продолжительность будут учитывать значение времени. Создайте класс, наследуя его от gantt.views["chart/bars"]. Верните шаблон с информацией о времени задачи из метода GetTooltip:

class CustomBars extends gantt.views["chart/bars"] {
  GetTooltip(obj, _) {
    const parser = webix.i18n.fullDateFormatStr;
    let tip = `${obj.text || _("(no title)")}<br>
<br>${_("Start date")}: ${parser(obj.start_date)}`;
 
    if (obj.type != "milestone") {
      const duration =
            obj.duration < 1
      ? this.GetPreciseDuration(obj, _)
      : this.GetDayDuration(obj, _);
 
      tip += `<br>${_("End date")}: ${parser(obj.end_date)}
<br>${_("Lasts")} ${duration}`;
    }
    return tip;
  }
 
  /*получает продолжительность задач, 
  которые длятся менее 1 дня, и возвращает ее в часах 
  (для почасовой шкалы и шкал у которых задано свойство *precise:true*).  
  В ином случае будет возвращен 1 день*/
 
  GetPreciseDuration(obj, _) {
    const { minUnit, precise } = this.Local.getScales();
    const showHours = minUnit === "hour" || precise;
    return showHours 
      ? `${Math.round(obj.duration * 24)} ${_("hours")}`
      : `1 ${_("day")}`;
  }
 
  GetDayDuration(obj, _) {
    return `${obj.duration} ${obj.duration > 1 ? _("days") : _("day")}`;
  }
}

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

webix.ui({
  view: "gantt",
  url: "https://docs.webix.com/gantt-backend/",
  override: new Map([
    [gantt.views.tree, CustomTree],
    [gantt.views["task/info"], CustomInfo],
    [gantt.views["task/form"], CustomForm],
    [gantt.views["chart/bars"], CustomBars],
  ]),
});

Related sample:  Gantt: Изменение времени задачи

Наверх