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

Об общих правилах кастомизации читайте в соответствующей статье.

Как убрать режимы работы с панели

Чтобы убрать какие-либо вкладки (контролы для переключения режима работы) с верхнего тулбара, вам необходимо переопределить опции для NavBarView. Обратите внимание, что ID вкладок должны совпадать с названиями режимов работы.

class CustomBarsView extends scheduler.views["bars/nav"] {
    config() {
        const ui = super.config();
 
        ui.options = [
            { id: "week", value: "Week" },
            { id: "month", value: "Month" },
        ];
        return ui;
    }
}

Чтобы убрать опции в компактном режиме, вам необходимо внести соответствующие изменения в NavPopupView:

class CustomNavPopupView extends scheduler.views["bars/navpopup"] {
    config() {
        const ui = super.config();
 
        const options = [
            { id: "week", value: "Week", icon: "shi-week" },
            { id: "month", value: "Month", icon: "shi-month" },
        ];
 
        ui.body.body.rows[0].data = options;
        return ui;
    }
}

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

webix.ui({
  view: "scheduler",
  override: new Map([
    [scheduler.views["bars/nav"], CustomBarsView],
    [scheduler.views["bars/navpopup"], CustomNavPopupView]
  ])
});

Related sample:  Scheduler: Limited Tabs

Как показывать только рабочие дни в режиме Week

Сперва необходимо настроить WeekView, чтобы отображать только 5 столбцов (для дней) и переписать метод GetWeek таким образом, чтобы он возвращал данные только для первых пяти дней.

class CustomWeekView extends scheduler.views["modes/week"] {
    config() {
        const ui = super.config();
        if (!this.Compact) ui.rows[2].body.cols.length = 6; // шкала + 5 дней
        return ui;
    }
    GetWeek(start, end) {
        if (!this.Compact) end = webix.Date.add(end, -2, "day");
        return super.GetWeek(start, end);
    }
}

После чего вам нужно настроить MultiDayView и WeekHeaderView, чтобы показывать только 5 ячеек:

class CustomWeekHeader extends scheduler.views["modes/week/header"] {
    config() {
        const ui = super.config();
        ui.cols[1].xCount = 5;
        return ui;
    }
}
/* и то же самое для scheduler.views["modes/week/multiday"] */

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

webix.ui({
  view: "scheduler",
  mode: "week",
  override: new Map([
    [scheduler.views["modes/week"], CustomWeekView],
    [scheduler.views["modes/week/header"], CustomWeekHeader],
    [scheduler.views["modes/week/multiday"], CustomWeekMultiDay],
  ]),
});

Related sample:  Scheduler: Working Days

Обратите внимание, что данная кастомизация будет работать только в том случае, если в вашем регионе неделя начинается с понедельника. Если же нет - укажите эту настройку заранее.

webix.Date.startOnMonday = true;

Как показывать только рабочие часы на шкалах Day/Week

Прежде всего вам нужно определить и запарсить необходимые ячейки часов в HourScale и Day Events. При необходимости вы можете увеличить высоту ячеек шкалы.

Компонент DayEvents также используется в шкале недели, поэтому для неё не потребуется дополнительных действий.

 
class CustomHours extends scheduler.views["modes/common/hourscale"] {
    config() {
        const ui = super.config();
        ui.type.height = 60;
        return ui;
    }
    ParseHours() {
        const data = [];
        for (let h = 8; h < 22; h++) {
            data.push({ id: h + "" });
        }
        this.List.parse(data);
    }
}
/* и то же самое для scheduler.views["modes/day/events"]*/

Кроме этого, если ваши данные содержат события, которые не входят в рамки рабочих часов, вы можете настроить их до отрисовки:

class CustomDayEvents extends scheduler.views["modes/day/events"] {
    /* как выше */
    config() { ... },
    ParseHours() { ... }
    // подгоняем события 
    RenderEvents() {
        const evs = this.Events;
        if (evs && evs.length) {
            for (let i = 0; i < evs.length; i++) {
                if (evs[i].start_date.getHours() < 8) {
                    evs[i].start_date.setHours(8);
                    evs[i].start_date.setMinutes(0);
                }
                // same logic for the end_date
            }
        }
 
        super.RenderEvents();
    }
}

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

webix.ui({
  view: "scheduler",
  mode: "week",
  override: new Map([
    [scheduler.views["modes/common/hourscale"], CustomHours],
    [scheduler.views["modes/day/events"], CustomDayEvents],
  ]),
});

Related sample:  Scheduler: Working Hours

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

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

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

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

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

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

  • укажите внутренние отступы и размеры UI элементов
  • опишите действи при клике по кнопке "Edit", чтобы оно открывало форму на месте сетки событий
class CustomInfo extends scheduler.views["event/info"] {
  // стилизация
  config() {
    this.Compact = this.getParam("compact", true);
    const ui = super.config();
    if (!this.Compact) {
      ui.body.rows[0].padding = 4;
      ui.body.rows[1].padding = 4;
      ui.body.rows[1].rows[1].inputWidth = 0;
    }
 
    return ui;
  }
  // открываем форму для редактирования
  EditEvent() {
    if (!this.Compact) {
      this.getParentView().Hide();
      this.app.show("main/event.form");
    } else super.EditEvent();
  }
}

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

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

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

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

class CustomMainView extends scheduler.views.main {
  init(view) {
    super.init(view);
    this.Info = this.ui(InfoPopup);
  }
  ShowEvent(ev) {
    //"0" для новых событий, формы должна быть открыта
    if (!this.Compact) {
      if (ev.id === "0") {
        const mode = this.app.getState().mode;
        this.show(`event.form`);
      } else this.Info.Show(ev.node);
    } else super.ShowEvent(ev);
  }
  HideEvent() {
    this.Info.Hide();
    super.HideEvent();
  }
}

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

Теперь переопределим 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;
  }
}

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

  • при клике по кнопке "Done" данные должны сохраняться, а форма - закрываться;
  • при клике по кнопке "Close", вы можете спрашивать у пользователей, сохранять ли измнения, если таковые есть;
// очищаем форму и показывает сетку событий
Back(close) {
  this.Form.clear();
  this.State.selected = null;
  if (!this.Compact)
    this.app.show(`main/modes.${this.State.mode}/event.form`);
  else super.Back(close);
}
// действие при клике по кнопке "Done" 
Done(close) {
  // сохраняем данные при создании нового события или редактировании уже существующего
  const change = this.Form.getDirtyValues();
  if (this.SubForm && this.SubForm.IsDirty()) {
    change.$recurring = this.SubForm.GetValues();
    delete change.rec_option;
  }
 
  this.UpdateEvent(change).then(() => this.Back(close));
}
// действие при клике по иконке "Close" 
Close() {
  const dirty =
        this.Form.isDirty() || (this.SubFrom && this.SubForm.IsDirty());
  // спрашивает пользователя в случае изменённых данных
  if (dirty) {
    webix
      .confirm({
      text: "Save changes?",
    })
      .then(() => this.Done(true))
      .catch(() => this.Back(true));
  } else this.Back(true);
}

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

webix.ui({
  view: "scheduler",
  override: new Map([
    [scheduler.views.main, CustomMainView],
    [scheduler.views["event/info"], CustomInfo],
    [scheduler.views["event/form"], CustomForm]
  ])
});

Related sample:  Scheduler: Info Window and Wide Form

Как добавить новую шкалу времени для таймлайна

По умолчанию у виджета есть три шкалы времени: день, неделя и месяц. Однако внутренняя логика поддерживает и другие типы, которые вы можете добавить с помощью кастомизации. Давайте добавим шкалу 'quarter' (квартал).

1.Добавляем настройки для шкалы:

class CustomTimelineView extends scheduler.views["modes/timeline"] {
  // массив со шкалами
  GetScalesArray(type) {
    if (type === "quarter") return [{
      unit: "month",
      format: "%F %Y"
    }];
    return super.GetScalesArray(type);
  }
 
  // ширина юнита (в этом случае - месяц)
  GetScalesCellWidth(type) {
    if (type === "quarter") return 500;
    return super.GetScalesCellWidth(type);
  }
}

2.Добавляем опцию 'quarter' к селектору на верхней панели:

class CustomBarView extends scheduler.views["modes/timeline/bar"] {
  config() {
    const ui = super.config();
 
    const rich = ui.elements[1];
    rich.options.push({
      id: "quarter",
      value: "Quarter"
    });
 
    return ui;
  }
}

Related sample:  Scheduler: adding new scale types

Как выделить дни, на которые запланировано больше одного события

Нам нужно переопределить scheduler.views["modes/timeline/chart"].

1.Найдём события, которые пересекаются друг с другом:

Создайте какую-либо коллекцию, чтобы хранить в ней координаты пересечений (подойдет обычный массив):

ClearCollections() {
  super.ClearCollections();
 
  this._intersections = [];
}

Проверьте все события, если они пересекаются с теми, которые уже были обработаны:

getIntersectionDates(task) {
  const intersections = [];
 
  this._processed.forEach(obj => {
    if (obj.section == task.section) {
      if (this.CheckDatesIntersection(obj, task)) {
        const intersection = {
          start: Math.max(obj.$x, task.$x),
          end: Math.min(obj.$x + obj.$w, task.$x + task.$w),
          section: task.section,
        };
        /* здесь сохраняются все пересечения для одного и того же юнита 
        (чем больше событий, тем ярче цвет);
        если вам это не нужно, можете добавить проверку и удалять пересечения 
        в те дни, где есть уже хотя бы одно событие */
        intersections.push(intersection);
      }
    }
  });
 
  return intersections;
}
 
CheckDatesIntersection(a, b) {
  return a.id !== b.id && b.$x < a.$x + a.$w && b.$x + b.$w > a.$x;
}

Вызовите этот метод после того, как события были обработаны перед отрисовкой:

RefreshTask(updID) {
  super.RefreshTask(updID);
 
  const t = this.GetEvent(updID);
  this._intersections.push(...this.getIntersectionDates(t));
}

2.Отрисуем подсвечивание:

RenderEvents() {
  super.RenderEvents();
 
  let html = "";
  const sheight = this.GetSectionHeight();
  this._intersections.forEach(intersection => {
    const x = intersection.start;
    const x1 = intersection.end;
    const y = this.Bars.getItemNode(intersection.section).offsetTop;
    html += `<div class="intersection" style="top:${y}px; left:${x}px; width:${x1 -
      x}px; height:${sheight}px; position:absolute;"></div>`;
  });
  this._DataObj.insertAdjacentHTML("afterbegin", html);
}

3.Добавим стили:

.webix_scheduler_timeline_bars .intersection,
.webix_scheduler_timeline_touch_bars .intersection {
  background: #abdbe3;
  pointer-events: none; /* ! важно добавить, чтобы все действия с таймлайном 
                           были доступны там, где добавились элементы для подсветки  */
}

Related sample:  Scheduler: Timeline with highlighted overlaps

Наверх