Skip to main content
Nintex Community Menu Bar

How to Include DHTMLX Gantt Chart Into Skuid Pages

  • July 8, 2024
  • 6 replies
  • 21 views
  • Translate

Forum|alt.badge.img+5

On a project I’m working on I had to embed a gantt chart. After some research I found a few JS gantt libraries, which didn’t turn out of much value. Until I found the DHTMLX Gantt Chart library. It’s for free and a pro-version can be bought (I just used the free version, it offers all the functionality I needed).

In order to embed the gantt chart you will have to do the following:

  1. Download and unzip the DHTMLX Gantt Chart (you can get it here)

  2. Upload the libraries and the css you need to use (in my case dhtmlxgantt.css, dhtmlxgantt.js, ext/dhtmlxgantt_marker.js, ext/dhtmlxgantt_tooltip.js) as static resource (if you want you can also upload any theme from skins/ but then you don’t need the standard css).

  3. Include the static resources in your skuid pages and make snippet in which you initialise the chart.

  4. Make a template field with the following content (very important: do not select a model! If you do so, the chart will be rendered every time you change some values on the gantt chart. And you might want to alter the style):

In my case, the gantt chart is on a tab view. That’s why there is a script-tag required. If you have the gantt chart on a page which loads on pageload, you can just delete that script and make an inline snippet.
5. Create a snippet where you initialise the chart (I’m not going to post the whole script at once, I will just guide you through it step by step):
5.1: Declare stuff you will use later (like models, objects, arrays and so on…). :

var $ = skuid.$, data = [], //data array later used to display on the gantt chart, contains objects startDate = new Date(), //date declarations in order to display the format in the european way endDate = new Date(), duration = 0, //duration of the task displayed on the gantt chart (in days) progress = 0, //the progress of each task ganttItem = {}, //an empty object (for now) which contains all the properties for the gantt chart to display itemsModel = skuid.$M('the\_project\_tasks'), tasksModel = skuid.$M('the\_project\_substasks'), projectModel = skuid.$M('the\_project\_model'), //the top most item (in the picture "Test Dev Project") project = projectModel.getFirstRow(), projectStart = new Date(project.Project\_Start\_\_c), projectEnd = new Date(project.Project\_End\_\_c), one\_day=1000\*60\*60\*24; //one day in ms (used for duration calculation) projectStart.setHours(0); //"format" the start and end date. The gantt chart shows hours, so if you need to display the hours you can leave that out projectStart.setMinutes(0); projectEnd.setHours(24); projectEnd.setMinutes(0);

5.2: Declare a function which assembles the data items:

//returns an object with all the properties needed for the gantt chartfunction
assembleDataObject(start, end, text, comp, id, parent, color){
    endDate = new Date(end);  startDate = new Date(start);     progress = comp / 100; 

    if(!isNaN(startDate.getTime()) && !isNaN(endDate.getTime())){ //this'll prevent the gantt chart from breaking and displaying errors (about start date being not a date). If you can guarantee that there won't be empty dates you can leave that out
        startDate.setHours(0);
        startDate.setMinutes(0);
        endDate.setHours(24);
        endDate.setMinutes(0);
    return {
        id: id,
        text: text,
        start\_date: startDate, //the start date
        duration: calculateDuration(startDate, endDate), //there is no end date, just a number of days (duration) which the chart then automatically calculates the end date
        progress: comp / 100, //the progress is a decimal number 1 = 100%, 0 = 0%
        open: false, //defines wether the task is collapsed on rendering or not
        parent: parent, //the parent task
        color: color //the color in which the task is displayed (hex or some other value)
    };
    }
}

5.3: Declare some other utility functions:

//requires start and end to be a js date
function calculateDuration(start, end){
    return Math.ceil((end.getTime() - start.getTime()) / (one\_day));
}
function updateSowModels(){
    skuid.$M('SowItems').updateData();
    skuid.$M('SowTasks').updateData();
}
//returns an object with the appropriate sow model and record
function getSowRecordAndModel(id){
    var retObj = {},
        project = projectModel.getRowById(id),
        sowItem = itemsModel.getRowById(id),
        sowTask = tasksModel.getRowById(id);
    if(project){
        retObj.model = projectModel;
        retObj.record = project;
        retObj.prog = project.Items\_Complete\_Percent\_\_c;
        retObj.noOfComps = project.No\_Project\_Items\_\_c;
        retObj.text = 'Name';
        retObj.start = 'Project\_Start\_\_c';
        retObj.end = 'Project\_End\_\_c';
    } else if(sowItem){
        retObj.model = itemsModel;
        retObj.record = sowItem;
        retObj.prog = sowItem.Components\_Complete\_\_c;
        retObj.noOfComps = sowItem.No\_Components\_\_c;
        retObj.text = 'Project\_Component\_\_c';
        retObj.start = 'Component\_Start\_Date\_\_c';
        retObj.end = 'Component\_End\_Date\_\_c';
    } else if(sowTask){
        retObj.model = tasksModel;
        retObj.record = sowTask;
        retObj.comp = 'Complete\_\_c';
        retObj.text = 'Name';
        retObj.start = 'Item\_Start\_Date\_\_c';
        retObj.end = 'Item\_End\_Date\_\_c';
    }
    return retObj;
}

5.4: Initialise the data:

//push the project data as overall parent object
data.push({
    id: project.Id,
    text: project.Name,
    start\_date: projectStart,
    duration: calculateDuration(projectStart, projectEnd),
    progress: project.Items\_Complete\_Percent\_\_c / 100,
    color: '#3d99da', //give it a default good looking color
    open: false //is not opened
});
//iterate over all sow-items and generate the data objects for all items
$.each(itemsModel.getRows(), function(index, item){
    ganttItem = assembleDataObject(item.Component\_Start\_Date\_\_c, item.Component\_End\_Date\_\_c, item.Project\_Component\_\_c, item.Components\_Complete\_\_c, item.Id, project.Id, item.Item\_Color\_\_c);
    if(ganttItem){
        data.push(ganttItem);
    }
});
//iterate over all sow-tasks and generate the data objects for all tasks
$.each(tasksModel.getRows(), function(index, task){
    ganttItem = assembleDataObject(task.Item\_Start\_Date\_\_c, task.Item\_End\_Date\_\_c, task.Name, task.Complete\_\_c, task.Id, task.Project\_Detail\_\_c, task.Project\_Detail\_\_r.Item\_Color\_\_c);
    if(ganttItem){
        data.push(ganttItem);
    }
});
var tasks = {
    data: data //there are some other properties on that object which I don't use (or not yet at least...)
};

5.5: Set chart properties, tooltips, templates, other styles, etc…

//convert the date to a appropriate format
gantt.config.date\_grid = '%d.%m.%Y';
//disable resize
gantt.config.drag\_resize = false;
//disable moving around the tasks
gantt.config.drag\_move = false;
//disallow dragging around tasks, only allow progress to be altered
gantt.attachEvent("onAfterTaskDrag", function(id, mode, e){
    if(mode == 'progress'){
        var sow = getSowRecordAndModel(id),
            ganttTask = gantt.getTask(id),
            comp = Math.round((ganttTask.progress \* 100)\*100) / 100;
        if(sow.comp == 'Complete\_\_c'){
            sow.model.updateRow(sow.record, {'Complete\_\_c': comp});
            sow.model.save({'callback': function(){
                updateSowModels();
                gantt.message(ganttTask.text + ': progress updated to ' + comp + '%');
            }});
            return true;
        } else {
            ganttTask.progress = sow.prog / 100;
            gantt.updateTask(id);
            gantt.message({
                type: 'error',
                text: "The Progress of SOW-Items or Dev-Projects can't be updated"
            });
            return false;
        }
    }
    return false;
});
//after edit/view details with double click save the changes
gantt.attachEvent("onLightboxSave", function(id, task, is\_new){
    var sow = getSowRecordAndModel(id), updates = {}, start = new Date(task.start\_date), end = new Date(task.end\_date);
    start.setHours(8);
    end.setHours(17);
    updates[sow.text] = task.text;
    updates[sow.start] = start;
    updates[sow.end] = end;
    sow.model.updateRow(sow.record, updates);
    sow.model.save({'callback': function(){
        gantt.message(task.text + ': changes saved');
        updateSowModels();
    }});
    return true;
});
//configure tooltip
gantt.templates.tooltip\_text = function(start, end, task){
    var startDate = new Date(start),
        endDate = new Date(end),
        sow = getSowRecordAndModel(task.id),
        noOfComps = '';
    if(sow.noOfComps){
        noOfComps = " **Number of Subtasks:**" + sow.noOfComps + "
";
    }
    endDate.setDate(endDate.getDate() - 1);
    return " **" + task.text+"**
" +
    noOfComps +
    " **Duration:**" + task.duration + " Days
" +
    " **Complete:**" + Math.round((task.progress \* 100)\*100) / 100 + "%
" +
    " **Start-Date:**" + skuid.time.formatDate('dd.mm.yy', startDate) + "
" +
    " **End-Date:**" + skuid.time.formatDate('dd.mm.yy', endDate);
};
//mark weekends with a special color
gantt.templates.scale\_cell\_class = function(date){
    if(date.getDay() === 0 || date.getDay() == 6){
        return "weekend";
    }
};
gantt.templates.task\_cell\_class = function(item,date){
    if(date.getDay() === 0 || date.getDay() == 6){ 
        return "weekend" ;
    }
};

5.6. (Finally) Initialise the gantt chart and set a marker on the current date:

//init the gantt chart
gantt.init("gantt\_chart");
//display the data on the chart
gantt.parse(tasks);
//gantt.scrollTo(gantt.posFromDate(new Date()), 0);
//after 250ms click on the expand icon (this is needed in order to display the data after rendering. There's a bug which prevents the data from being rendered until a DOM-change occurs...)
setTimeout(function(){
    $('.gantt\_open').click();
}, 250);
var date\_to\_str = gantt.date.date\_to\_str(gantt.config.task\_date),
    id = gantt.addMarker({start\_date: new Date(), css: "today", text: 'Today'});
setInterval(function(){
    var today = gantt.getMarker(id);
    today.start\_date = new Date();
    today.title = date\_to\_str(today.start\_date);
    gantt.updateMarker(id);
}, 60000);

If someone has more questions on how to do it (or wants the complete script) just ask me.

Cheers

Did this topic help you find an answer to your question?
This topic has been closed for comments

6 replies

Forum|alt.badge.img+20

You are the man David!

Translate

Forum|alt.badge.img+5

Wanna see some live demo?

Translate

Forum|alt.badge.img+20

Yes I would. Sometime Thursday or Friday. Ping me on Skype to make plans.

Translate

Forum|alt.badge.img+5

Will do

Translate

Forum|alt.badge.img+5

Note:
The export functions are a little strange.
You have to include the following library as external resource (you could also download the file and upload it as static resource): https://export.dhtmlx.com/gantt/api.js and then make a snippet to run on button click (for each one you need/want) containing just one of the following function calls: gantt.exportToPDF(), gantt.exportToPNG() or gantt.exportToExcel() and execute the snippets on button click. Unfortunately there is no better solution to this.
The export functions will open a new tab (external webpage) and generate the file you selected.

Translate

Forum|alt.badge.img+17
  • Nintex Employee
  • 3763 replies
  • July 8, 2024

David this is super cool! 

Translate

Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie Settings