At the start of 2015, we wanted to renew our Gantt section in order to improve its usability and maintainability over time while at the same time adding a number features. We needed the ability for our GeoPal jobs to be dragged and dropped around a timeline view, along with showing data in both grid and map views. As developing a complete Gantt library out of nowhere is not an easy task, we decided to evaluate a number of 3rd party JavaScript libraries. The library was to be both easy to work with and powerful enough to ensure that our extensive wish list could be implemented.

After evaluating what a number of technologies theoretically allowed us to do, we decided to further investigate Bryntum's library, as it was both specifically made with ExtJS in mind and incredibly powerful, with a host of features designed to cater for a wide array of requirements.

We first developed a “Demo” scheduler, in order to understand not only what could be done “on paper” but how we could perform certain operations and how it would have looked, helping ourselves with the great examples on their site in order to get up and running as fast as possible. This activity led us to understand that what we really needed was a “Scheduler” library, rather than a full-blown “Gantt” library. It also showed that Bryntum's technology was an exact match to our requirements and, in fact, all of them could be implemented with relative ease.

There are several reasons for this:

  1. Integrating into existing ExtJS pages, even though those pages might be complex with border layouts and complex panels and maps presented no problem for the library
  2. A massive amount of complexity related to timeline views we no longer had to worry about
  3. There is a huge number of options in the library that gave us great flexibility when working with GeoPal job data.

This new section, that we named “Planner”, has since then been released, many of our customers favour this solution and the technology behind it proved to be of very high quality. While it's inevitable that individual needs drive technological choices, Bryntum's is certainly suitable for a lot of purposes and it turned out to be the best choice to embed GeoPal Job Scheduling functionality on our system.

Our old Gantt section (Picture 1) was based on jQuery and developed several years ago. The developed functionality was perfect for the company objectives and the customers we had. Over time, with the technology improving and the company growing, it started to show its age and a number of features that we - as users - are now accustomed to expect from modern web applications were completely missing. Furthermore, users’ feedback over time revealed that while the tool was fine for certain scenarios it was far less useful for other ones.

Among the most missed features, there was the ability to view more than one day at any given time and to view some key information regarding the jobs and the assigned employees. Also, we wanted each job to display a possibly different set of data based on a customised template, as some of our customers wanted to see the address where the job was to be executed whereas other ones were more interested in the asset being serviced. Finally, we wanted the ability to view the same set of data not only in a Gantt fashion but also in a Grid and Map fashion.

We, therefore, decided to set-up a “live” demo with some pre-made data (Picture 2), in order to see how much of what we wanted to do could actually be done. The demo was prepared as much as possible following best practices in order to tap into the full potential of the technology while ensuring that future upgrades would not require too much maintenance.

As soon as we started developing it’s been immediately clear that setting up the basic Scheduler was a matter of an hour or so and that the rest of the time would have been used to add the multitude of additional features we wanted, whether they were related to the Gantt view or to the Grid/Map views.

When it came to the Gantt, the features with most visual impact are probably related to the borders around a task and the very rich Staff/Employee column. The Staff/Employee column was dead easy to implement (Snippets 1 to 3), as Bryntum’s Scheduler features a “columns” property that works almost exactly as ExtJS’s grid “columns” property. All we had to do was basically set-up a renderer and specify it during the creation of the SchedulerGrid object.

The custom visualisation of an event was also similarly easy, as the SchedulerGrid provides a dedicated property named “eventBodyTemplate”.

Snippet 1: Initialisation of our variant of SchedulerGrid. Note the use of “renderer”

initComponent: function() {

Ext.apply(this, {

plugins: [

Ext.create('Geopal.plugin.TodayLine', {})


columns: [


header : 'Staff',

width : 150,

dataIndex : 'Name',

renderer : Geopal.view.Renderers.GanttResourceTemplateRenderer,

scope : this





//var eventStore = this.getEventStore();

//this.mon(eventStore, 'datachanged', this.handleEventStoreDataChanged, this);


Snippet 2: the “Renderers” singleton object.

Ext.define("Geopal.view.Renderers", {

singleton: true,

requires: [




GanttEventTemplateRenderer: Ext.emptyFn,


GanttResourceTemplateRenderer: function(value, metaData, record, rowIndex, colIndex) {

var dt = record.get("LastKnownLocationTimestamp");

var online = record.get("Online");


return Geopal.view.XTemplates.GanttResourceTemplate.apply(Ext.applyIf({

LastKnownLocationTimestamp: Ext.Date.format(dt, "Y-m-d H:i"),

OnlineIconClass: online ? "online-icon" : "offline-icon",

LastKnownLocationClass: (dt.getTime() > (new Date()).getTime() - 300000) ? "g-recentdata" : "g-olddata"

}, record.getData()));



UnplannedJobEventRenderer: function(value, metaData, record, rowIndex, colIndex, store, view) {

return Geopal.view.XTemplates.UnplannedJobTemplate.apply(record.getData());



Snippet 3: an extract of the XTemplates object containing the template we used to render an employee

Ext.define("Geopal.view.XTemplates", {

singleton: true,

GanttResourceTemplate: new Ext.XTemplate(

'<tpl for=".">',

'<table width="100%" height="100%" cellpadding="0" cellspacing="0"><tbody>',


' <td style="height: 24px; width: 32px;">',

' <i class="fa fa-wifi fa-lg {OnlineIconClass}"></i>',

' </td>',

' <td><strong>{Name}</strong><br/>{Mobile}</td>',



' <td colspan="2" valign="bottom" height="100%" ',

' style="color: {LocationColor}">{LastKnownLocation}</td>',



' <td colspan="2" style="font-size: 8px; text-align: right;',

' padding-top: 3px; color: {LocationColor};">{LastKnownLocationTimestamp}</td>',






The results of this 3-weeks long effort produced a very interesting concept, from which we took most features, ditched some and added some more. Among the most interesting features we didn’t implement in the final version there is the ability to specify certain areas as “not available” (example: Holidays). Among the features we added later, there is support for overlapping jobs (only partially implemented and not correctly working in the technology demo) and the ability to filter the displayed employees “inline”. The overlapping jobs feature works by figuring out which jobs overlap and then applying a nice offset, mainly with the objective to don’t let some jobs to go unnoticed. This feature (Snippets 4 and 5) also was very easy to implement.

Snippet 4: shows how to offset overlapping jobs.

Ext.define('', {

extend : "",

model : "GeoPal.model.planner.SchedulerPlannedJob",


constructor: function() {


this.on('load', this._onSchedulerJobsLoad, this);



_onSchedulerJobsLoad: function(store, records, successful) {

if(!successful) {




var overlapGroups = [];

var scanned = [];


for(var i = 0; i < records.length; i++) {

var record = records[i];

if(!Ext.isDefined(scanned[record.get("Id")])) {

var overlapping = this._getOverlappingRecords(record, records, i);

if (overlapping.length > 1) {

var group = {

records: overlapping



for (var c = 0; c < overlapping.length; c++) {

scanned[overlapping[c].get("Id")] = group;







for(var i = 0; i < overlapGroups.length; i++) {

var group = overlapGroups[i];

for(var c = 0; c < group.records.length; c++) {

var record = group.records[c];

record.OverlapDepth = c;

// needed to trigger the update on the class


// needed as the above will mark the record dirty even though it's not

record.dirty = false;






_getOverlappingRecords: function(target, records, startIndex) {

var results = [];

var START = "StartDateTime";

var END = "EndDateTime";

var EMPLOYEE = "AssignedTo";

for(var i = startIndex; i < records.length; i++) {

var record = records[i];

if(record.get(EMPLOYEE) == target.get(EMPLOYEE) && record.get(START) <= target.get(END) && record.get(END) >= target.get(START)) {




return results;



_generateCls: function(rec) {

var overlapCls = Ext.isDefined(rec.OverlapDepth) ? (" g-overlap-depth-" + rec.OverlapDepth + (rec.OverlapDepth > 2 ? " g-overlap-depth-deep" : "")) : "";

return overlapCls;




Snippet 5: CSS needed to offset the overlapping jobs

.collapsed .g-overlap-depth-0 {

margin-top: 2px;

margin-left: 2px;



.collapsed .g-overlap-depth-1 {

margin-top: 4px;

margin-left: 4px;



.collapsed .g-overlap-depth-2 {

margin-top: 6px;

margin-left: 6px;



.collapsed .g-overlap-depth-deep {

margin-top: 8px;

margin-left: 8px;


Just like all our web application sections, the Planner is also, of course, under continuous development and we have internal backlog tickets with new amazing features we would like to implement over time.

With the knowledge we now have of Bryntum’s technology and the direct experience on its potential we may not know when we will be able to implement all the goodness we have in mind, but we know that it will entirely possible as the technology has so far proven to be both reliable and very powerful so stay tuned: we have great plans!

Full Sample Code: Link

Share This

Make the Connection!

We work with your company to implement workforce mobility solutions that transform the efficiency of your field operations. Get in touch to start developing your own solution today.