Информацию об основных принципах кастомизации читайте в соответствующей статье.
Чтобы добавить столбец, создайте класс, наследуя его от 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"]. ВВнутри него вызовите метод GetTooltip() и опишите темплейт для нового тултипа:
class CustomBarsView extends gantt.views["chart/bars"] {
GetTooltip(obj) {
if (obj.type == "milestone") return `You've reached ${obj.text}`;
const type = obj.type.charAt(0).toUpperCase() + obj.type.slice(1);
const days = obj.duration > 1 ? "days" : "day";
const title = obj.text ? obj.text : "(no title)";
return `${type}: ${title}<br>
Scheduled for ${obj.duration} ${days}<br>
${obj.details ?? ""}`;
}
}
Не забудьте переопределить класс по умолчанию с помощью свойства 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 необходимо поместить в попап, поэтому внесём некоторые правки заранее:
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();
}
}
Следующим шагом будет показ/скрытия попапа при клике по событию. Для этого вам необходимо переопределить класс по умолчанию 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"
Done(exit) {
// сохраняем данные при создании нового события или редактировании уже существующего
if (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];
const startCal = form.elements[2];
startCal.timepicker = startCal.suggest.body.timepicker = true;
const endCal = form.elements[3];
endCal.timepicker = endCal.suggest.body.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;
}
//еще один метод
}
Вы можете изменить тултип задачи таким образом, что дата задачи и его продолжительность будут учитывать значение времени. Создайте класс, наследуя его от 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: Изменение времени задачи
В данном примере показано, как установить планируемые даты в виде снэпшота текущего положения задач.
Чтобы включить планируемые даты, задайте baseline значение true:
webix.ui({
view: "gantt",
baseline: true,
...
})
Нам нужен какой-то элемент управления для создания снэпшота. Сделаем кнопку с названием "Create a snapshot". При нажатии будет вызываться пользовательская функция createSnapshot ():
{
view: "button",
value: "Create a snapshot",
...
click: () => createSnapshot(),
},
Используя метод serialize(), мы получаем текущее состояние Gantt:
function createSnapshot() {
const localTasks = $$("gantt")
.$app.getService("local")
.tasks();
const data = localTasks.serialize();
...
Для сохранения дат начала и окончания как planned_start и planned_end, а продолжительности как planned_duration, мы объявляем отдельную функцию setBaseline():
function setBaseline(branch) {
branch.forEach(t => {
if (t.type != "milestone") {
t.planned_start = t.start_date;
t.planned_end = t.end_date;
t.planned_duration = t.duration;
if (t.data) setBaseline(t.data);
}
});
}
Затем мы вызовем ее внутри метода createSnapshot() для установки базового плана для всех задач:
...
setBaseline(data);
...
В конце нам необходимо очистить все коллекции методом clearAll() и с помощью метода parse() загрузить в компонент наши измененные данные:
...
localTasks.clearAll();
localTasks.parse(data);
}
Related sample: Gantt: Creating Snapshots
Наверх