Mats Bryntse
3 October 2014

Writing a custom plugin for Ext Gantt 3.x

Since the Gantt 3.0 release is getting closer, it’s time to get a bit more familiar with it. Ext Gantt […]

Since the Gantt 3.0 release is getting closer, it’s time to get a bit more familiar with it. Ext Gantt is currently in Beta-1 stage and the next beta is coming soon. In this post we’ll look at how to write a custom plugin which visually groups child task of a parent task. Here’s an image of what it should achieve.

Writing a plugin skeleton

Writing an Ext JS 5 plugin is a very simple task, and it should be done in the same way as in Ext JS 4. We simply subclass the Ext.AbstractPlugin class and implement the init method called during initialization.

[crayon striped=”false” lang=”js” nums=”false” toolbar=”false”]
Ext.define(“MyApp.TaskArea”, {
extend : ‘Ext.AbstractPlugin’,

init : function (panel) {
// TODO: cool things
}
});
[/crayon]

In the init method, we have access to the Gantt panel to which the plugin instance is attached. Here we will do any setup that needs to be done for our plugin. In our case, we first need some sort of Template that we will use for the rendering. We can simply put this directly on the class prototype. This template needs to be used as the parentTaskBodyTemplate which controls the inner content of every parent task. We simply append a bit of markup to each existing parent task to make sure we don’t break the default rendering.

[crayon striped=”false” lang=”js” nums=”false” toolbar=”false”]
Ext.define(“MyApp.TaskArea”, {
extend : ‘Ext.AbstractPlugin’,

tpl : ‘

‘,

init : function (panel) {
var bodyTpl = (panel.parentTaskBodyTemplate || Gnt.template.ParentTask.prototype.innerTpl) + this.tpl;

Ext.apply(panel.getSchedulingView(), {
parentTaskBodyTemplate : bodyTpl
});

this.panel = panel;
this.taskStore = panel.getTaskStore();
}
});
[/crayon]

We also save a reference to the Gantt panel and its task store since we need them later.

Template and rendering

Next step is to do the actual sizing of the rendered custom elements in the view. To do this, we need to listen to a few different events of the Gantt view as you can see below.

[crayon striped=”false” lang=”js” nums=”false” toolbar=”false”]
Ext.define(“MyApp.TaskArea”, {
extend : ‘Ext.AbstractPlugin’,

init : function (panel) {

this.panel = panel;
this.taskStore = panel.getTaskStore();

panel.getSchedulingView().on({
refresh : this.repaintAllAreas,

// These are also fired on expand / collapse
itemupdate : this.repaintAllAreas,
itemremove : this.repaintAllAreas,
itemadd : this.repaintAllAreas,
scope : this
});
},

repaintAllAreas : function() {}
});
[/crayon]

As you can see, we want to react to each full refresh, row update, row removal and to each new row being added. For each such event we simply resize all visible task group elements, all we need to do is to set a new proper height, which we can get by asking the view for the last child of each parent (recursively). Below is the full implementation, which in total is only 70 lines of JS and a little CSS. You can try the example out yourself here:

[crayon striped=”false” lang=”js” nums=”false” toolbar=”false”]
Ext.define(“MyApp.TaskArea”, {
extend : ‘Ext.AbstractPlugin’,

tpl : ‘

‘,

init : function (panel) {
var bodyTpl = (panel.parentTaskBodyTemplate || Gnt.template.ParentTask.prototype.innerTpl) + this.tpl;

Ext.apply(panel.getSchedulingView(), {
parentTaskBodyTemplate : bodyTpl
});

this.panel = panel;
this.taskStore = panel.getTaskStore();

panel.getSchedulingView().on({
refresh : this.repaintAllAreas,

// These are also fired on expand / collapse
itemupdate : this.repaintAllAreas,
itemremove : this.repaintAllAreas,
itemadd : this.repaintAllAreas,
scope : this
});
},

getLastVisibleChild : function (parentNode) {
var result;

if (!parentNode || parentNode.isLeaf() || !parentNode.isExpanded()) {
result = parentNode;
}
else {
result = this.getLastVisibleChild(parentNode.lastChild);
}

return result;
},

repaintAllAreas : function () {
var view = this.panel.getSchedulingView();
var viewNodes = this.panel.getSchedulingView().getNodes();

Ext.Array.each(viewNodes, function (domNode) {
var node = view.getRecord(domNode);

if (node && !node.isRoot() && !node.isLeaf() && !node.isMilestone() && node.isExpanded()) {
this.refreshAreaSize(node);
}
}, this);
},

refreshAreaSize : function (parentTask) {

var view = this.panel.getSchedulingView();
var el = view.getElementFromEventRecord(parentTask);
var areaEl = el.down(‘.gnt-taskarea’);
var store = view.store;
var lastVisibleChild = this.getLastVisibleChild(parentTask);
var height = 0;

if (lastVisibleChild && lastVisibleChild !== parentTask) {
var indexDelta = view.store.indexOf(lastVisibleChild) – view.store.indexOf(parentTask);

height = indexDelta * view.timeAxisViewModel.getViewRowHeight() – 5;
}

areaEl.setHeight(height);
}
});
[/crayon]

A little CSS to finish it off:

[crayon striped=”false” lang=”css” nums=”false” toolbar=”false”]
.gnt-taskarea {
position : absolute;
top : 100%;
left : 0;
margin-left : -1px;
background-color : inherit;
border-width : 0;
opacity : 0.2;
filter : alpha(opacity=20);
width : inherit;
border-radius : 4px;
z-index : -1;
pointer-events : none;
}

.gnt-taskarea-inner {
width : 100%;
border-radius : inherit;
border-width : 1px;
border-style : solid;
border-color : inherit;
height : 100%;
opacity : 0.7;
filter : alpha(opacity=70);
background-color : transparent;
}
[/crayon]

Summing up

Time to give this example a try! Note that it is based on Ext JS 5 and our first Gantt 3 beta release. We hope this plugin can be useful for you and that it demonstrates how easy it is to take control over the Gantt rendering process to customize the appearance of the chart. Let us know what you think and if you have other ideas of useful plugins. Happy hacking!

Mats Bryntse

Ext Gantt