How-tos

You can read about general customization rules in the corresponding article.

Adding columns to project tree

To add a custom column(s) create a new class and inherit it from the default one (gantt.views.tree). Inside the config() method split the default UI and add configuration for the new column:

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 field comes from dataset
      width: 40,
    });
 
    if (!compact) ui.width += 40;
 
    return ui;
  }
}

Then, override the default class via the corresponding property:

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

Related sample:  Gantt: Extra Column

Adding custom task tooltip

To add a rich tooltip for tasks, create a new class and inherit it from the default one (gantt.views["chart/bars"]). Inside the config() method delete the default tooltip and provide a new one with desired data template to display:

class CustomBarsView extends gantt.views["chart/bars"] {
  config() {
    const ui = super.config();
 
    // delete default 'overflow' tooltip
    const tpl = ui.cells[1].type.templateStart;
    ui.cells[1].type.templateStart = function(obj) {
      return tpl(obj).replace("webix_tooltip=", "");
    };
 
    // set rich 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;
}

Then, override the default class via the corresponding property:

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

Related sample:  Gantt: Custom Tooltip

Implementing scales zoom

In implement zooming functionality for scales, you should provide configuration for the new scale and refresh Gantt widget.

First, define a function that will reset scales according to the mode set:

function resetScales(v) {
  let scales, cellWidth;
 
  if (v == "1") { //days
    scales = [{ unit: "day", format: "%d" }];
    cellWidth = 200;
  } else { //weeks
    scales = [
      { unit: "week", format: function(start) {
          // format logic
      }},
      { unit: "day", format: "%d" },
    ];
    cellWidth = 70;
  }
 
  const current = $$("gantt1").getService("local").getScales();
 
  $$("gantt1").getService("local").setScales( 
    current.start, 
    current.end, 
    cellWidth, 
    current.cellHeight,
    scales
  );
 
  $$("gantt1").$app.refresh();
}

Then, create a toolbar with 2 radio buttons that will trigger the resetScales function upon toggling:

webix.ui({
  rows: [
    {
      view: "toolbar",
      elements: [
        {
          view: "radio",
          options: [
            { id: "1", value: "days" },
            { id: "2", value: "weeks" },
          ],
          on: {
            onChange: resetScales,
          }
        }
      ]
    }
    // Gantt config
  ],
});

Related sample:  Gantt: Scale Zoom

Info popup and wide form

This example shows how to get rid of the right panel and:

  • display a small information popup instead of the right panel
  • open a big form for editing in place of the Tasks area

The changes concern desktop mode only, the compact view remains unchanged.

Preparing Popup

Task Info view will be placed into a popup, so we need to make several adjustments beforehand:

  • set paddings and sizes of UI elements
  • hide some details (e.g. successors, predecessors...) from template to keep it small
  • change the "Edit" button action, so that it can open the form in place of the chart
class CustomInfo extends gantt.views["task/info"] {
   // styling
  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;
  }
  // "Edit" button action
  EditTask() {
    if (!this.Compact) {
      this.getParentView().Hide();
      this.app.show("top/task.form");
    } else super.EditTask();
  }
 // less details
  InfoTemplate(obj) {
    obj.targets = obj.sources = [];
    delete obj.details;
    return super.InfoTemplate(obj);
  }
}

After that create the InfoPopup class with a Popup component that contains the above CustomInfo view. Also, define methods to show/hide the popup:

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

Showing and Hiding Info Popup

The next step is to show and hide the Info Popup upon clicking the tasks. It can be implemented by redefining the default gantt.views["chart/bars"] class:

class CustomBarsView extends gantt.views["chart/bars"] {
  config() {
    const ui = super.config();
    // show InfoPopup on clicking the task
    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);
    // create a popup window
    this.Info = this.ui(InfoPopup);
    // handle hide event
    this.on(this.app, "edit:stop", () => {
      this.State.selected = this.State.parent = null;
      this.Info.Hide();
    });
  }
}

Preparing Form View

If needed, you can split Form UI into columns and show the Notes field on the right side. Also, as the form now overlaps Tasks area, on-change editing can be removed.

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;
 
        //form with 3 columns
        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;
    }
}

After that, adjust saving logic for the CustomForm:

  • when users click on the "Done" button, the data must be saved and form must be closed;
  • when users click on the "Close" button, you can ask them whether to save the changes, if any;
// "Done" button action
 Done(exit) {
    // save data if a new task is created or the existing one is updated
    if (this.State.selected === "$temp" || this.Form.isDirty())
      this.UpdateTask().then(() => this.Back(exit));
    else this.Back(exit);
 }
// "Close" icon action
Close() {
  // ask to save form data in case of changes
  if (this.Form.isDirty()) {
    webix.confirm("Save changes?")
   .then(() => this.Done(true))
               .catch(() => this.Back(true));
  } else this.Back(true);
}
// clear the form and show the Tasks area
Back(exit) {
    if (!this.Compact) {
      this.Form.clear();
      this.State.selected = this.State.parent = null;
      this.app.show("top/chart");
    } else super.Back(close);
}

Tuning Navigation

To be able to show the form instead of Tasks area, you need to replace a static subview with a dynamic one and then show Tasks area during initialization.

class CustomTopView extends gantt.views.top {
  config() {
      const ui = super.config();
       // insert dynamic subview to show chart or form
       ui.cells[0].cols[2] = { $subview: true };
       return ui;
   }
   init(view) {
        super.init(view);
        //show chart
        this.show("chart/bars");
    }
  }

Then you will be able to open the form there from the CustomInfo:

// EditTask method from the CustomInfo class described above
this.app.show("top/task.form");

And open a form when the new task is created:

// if new task is created, open the form
ShowTask(path) {
     if (this.Compact) super.ShowTask(path);
    else if (path === "form")
        this.show("task.form");
}
// preserve default hide logic only for compact mode
HideTask() {
       if (this.Compact);
         super.HideTask();
}

Finally, override the default classes via the corresponding property:

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: Info Window and Wide Form

Back to top