Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

In this tutorial, we will following the guideline of developing a plugin to develop our Gantt Chart Userview Menu plugin. Please also refer to the very first tutorial How to develop a Bean Shell Hash Variable for more details steps.

...

The following of this tutorial is prepared with a Macbook Pro and Joget Source Code version 5.0.0. Please refer to Guideline of for developing a plugin for other platform command.

...

We should get "BUILD SUCCESS" message shown in our terminal and a "gantt_cahrtchart_menu" folder created in "plugins" folder.

Open the maven project with your favour favourite IDE. I will be using NetBeans.  

...

Then, we have to do a UI for admin user to provide inputs for our plugin. In getPropertyOptions method, we already specify have already specifed our Plugin Properties Options definition file is locate at "/properties/ganttChartMenu.json". Let us create a directory "resources/properties" under "gantt_chart_menu/src/main" directory. After create the directory, create a file named "ganttChartMenu.json" in the "properties" folder.

...

Code Block
languagejs
 [{
    title : '@@userview.ganttchart.config@@',
    properties : [{
        name : 'id',
        label : 'Id',
        type : 'hidden'
    },
    {
        name : 'customId',
        label : '@@userview.ganttchart.customId@@',
        type : 'textfield',
        regex_validation : '^[a-zA-Z0-9_]+$',
        validation_message : '@@userview.ganttchart.invalidId@@'
    },
    {
        name : 'label',
        label : '@@userview.ganttchart.label@@',
        type : 'textfield',
        required : 'True'
    },
    {
        name : 'title',
        label : '@@userview.ganttchart.title@@',
        type : 'textfield'
    },
    {
        name : 'binder',
        label : '@@userview.ganttchart.binder@@',
        type : 'elementselect',
        required : 'True',
        options_ajax : '[CONTEXT_PATH]/web/property/json/getElements?classname=org.joget.apps.datalist.model.DataListBinderDefault',
        url : '[CONTEXT_PATH]/web/property/json[APP_PATH]/getPropertyOptions'
    },
    {
        label : '@@userview.ganttchart.mapping@@',
        type : 'header'
    },
    {
        name : 'category',
        label : '@@userview.ganttchart.mapping.category@@',
        type : 'textfield',
        required : 'True'
    },
    {
        name : 'task',
        label : '@@userview.ganttchart.mapping.task@@',
        type : 'textfield',
        required : 'True'
    },
    {
        name : 'activity',
        label : '@@userview.ganttchart.mapping.activity@@',
        type : 'textfield',
        required : 'True'
    },
    {
        name : 'fromDate',
        label : '@@userview.ganttchart.mapping.fromDate@@',
        type : 'textfield',
        required : 'True'
    },
    {
        name : 'toDate',
        label : '@@userview.ganttchart.mapping.toDate@@',
        type : 'textfield',
        required : 'True'
    },
    {
        name : 'dateFormat',
        label : '@@userview.ganttchart.mapping.dateFormat@@',
        type : 'textfield',
        required : 'True',
        value : 'yyyy-MM-dd'
    },
    {
        name : 'taskId',
        label : '@@userview.ganttchart.mapping.taskId@@',
        type : 'textfield'
    },
    {
        name : 'cssClass',
        label : '@@userview.ganttchart.mapping.cssClass@@',
        type : 'textfield'
    }]
},
{
    title : '@@userview.ganttchart.advanced@@',
    properties : [
    {
        name : 'itemsPerPage',
        label : '@@userview.ganttchart.itemsPerPage@@',
        type : 'textfield',
        required : 'True',
        value : '20'
    },
    {
        name : 'navigate',
        label : '@@userview.ganttchart.navigate@@',
        type : 'selectbox',
        required : 'True',
        value : 'scroll',
        options : [{
            value : 'buttons',
            label : '@@userview.ganttchart.navigate.buttons@@'    
        },
        {
            value : 'scroll',
            label : '@@userview.ganttchart.navigate.scroll@@'    
        }]
    },
    {
        name : 'scale',
        label : '@@userview.ganttchart.scale@@',
        type : 'selectbox',
        required : 'True',
        value : 'days',
        options : [{
            value : 'hours',
            label : '@@userview.ganttchart.scale.hours@@'    
        },
        {
            value : 'days',
            label : '@@userview.ganttchart.scale.days@@'    
        },
        {
            value : 'weeks',
            label : '@@userview.ganttchart.scale.weeks@@'    
        },
        {
            value : 'months',
            label : '@@userview.ganttchart.scale.months@@'    
        }]
    },
    {
        name : 'maxScale',
        label : '@@userview.ganttchart.maxScale@@',
        type : 'selectbox',
        required : 'True',
        value : 'months',
        options : [{
            value : 'hours',
            label : '@@userview.ganttchart.scale.hours@@'    
        },
        {
            value : 'days',
            label : '@@userview.ganttchart.scale.days@@'    
        },
        {
            value : 'weeks',
            label : '@@userview.ganttchart.scale.weeks@@'    
        },
        {
            value : 'months',
            label : '@@userview.ganttchart.scale.months@@'    
        }]
    },
    {
        name : 'minScale',
            label : '@@userview.ganttchart.minScale@@',
        type : 'selectbox',
        required : 'True',
        value : 'monthshours',
        options : [{
            value : 'hours',
            label : '@@userview.ganttchart.scale.hours@@'    
        },
        {
            value : 'days',
            label : '@@userview.ganttchart.scale.days@@'    
        },
        {
            value : 'weeks',
            label : '@@userview.ganttchart.scale.weeks@@'    
        },
        {
            value : 'months',
            label : '@@userview.ganttchart.scale.months@@'    
        }]
    },
    {
        name : 'useCookie',
        label : '@@userview.ganttchart.useCookie@@',
        type : 'checkbox',
        options : [{
            value : 'true',
            label : ''    
        }]
    },
    {
        name : 'scrollToToday',
        label : '@@userview.ganttchart.scrollToToday@@',
        type : 'checkbox',
        options : [{
            value : 'true',
            label : ''    
        }]
    },
    {
        name : 'onItemClick',
        label : '@@userview.ganttchart.onItemClick@@',
        type : 'codeeditor',
        mode : 'javascript',
        value : '//console.log(data); //data obj hold all the columns value of a row'
    },
    {
        name : 'onAddClick',
        label : '@@userview.ganttchart.onAddClick@@',
        type : 'codeeditor',
        mode : 'javascript',
        value : '//console.log(datetime); //the DateTime in ms for the clicked Cell\n//console.log(rowId); //the row ID of clicked Cell'
    },
    {
        name : 'onRender',
        label : '@@userview.ganttchart.onRender@@',
        type : 'codeeditor',
        mode : 'javascript',
        value : '//console.log("chart rendered");'
    },
    {
        name : 'customHeader',
        label : '@@userview.ganttchart.customHeader@@',
        type : 'codeeditor',
        mode : 'html'
    },
    {
        name : 'customFooter',
        label : '@@userview.ganttchart.customFooter@@',
        type : 'codeeditor',
        mode : 'html'
    }]
}]

...

After verify the static HTML is working in our plugin, we can further enhance it by adding data to the view. Now, modify your getRenderPage method and ganttChart.ftl template as below.

Code Block
languagejava
 
Code Block
<link href="${request.contextPath}/plugin/org.joget.tutorial.GanttChartMenu/lib/jquery.gantt/css/style.css" rel="stylesheet" type="text/css" />
<script src="${request.contextPath}/plugin/org.joget.tutorial.GanttChartMenu/lib/jquery.gantt/js/jquery.fn.gantt.min.js"></script>
<div class="gantt_chart_menu_body">
    <#if element.properties.title?? ><h3>${element.properties.title!}</h3></#if>
    ${element.properties.customHeader!}
    <div class="gantt"></div>
    <script>   @Override
    public String getRenderPage() {
        Map model = new HashMap();
        model.put("request", getRequestParameters());
        model.put("element", this);
        
        $(function() {
   //populate data in JSON format
         "use strict";
 model.put("data", getData());
        
        PluginManager pluginManager = $(".gantt"PluginManager)AppUtil.getApplicationContext().gantt({
getBean("pluginManager");
        String content = pluginManager.getPluginFreeMarkerTemplate(model, getClass().getName(), "/templates/ganttChart.ftl", MESSAGE_PATH);
     source: ${data!},
  return content;
    }
     
    months: [@@userview.ganttChart.months.label@@],
  protected String getData() {
        String json     dow: [@@userview.ganttChart.dow.label@@],= "[]"; 
        
        itemsPerPage:try ${element.properties.itemsPerPage!},
            DataListCollection data   navigate: "${element.properties.navigate!}",= null;
            String idColumn   scale: "${element.properties.scale!}",= getPropertyString("taskId");
            
    maxScale: "${element.properties.maxScale!}",
       //get the binder
            Object binderData minScale:= "${element.properties.minScale!}",
getProperty("binder");
            if (binderData != null && binderData instanceof waitText: "@@userview.ganttChart.waitText@@",Map) {
                Map onItemClick:bdMap function= (dataMap) {binderData;
                if (bdMap != null && bdMap.containsKey("className")  ${element.properties.onItemClick!}&& !bdMap.get("className").toString().isEmpty()) {
                },
    PluginManager pluginManager = (PluginManager) AppUtil.getApplicationContext().getBean("pluginManager");
         onAddClick: function(datetime, rowId) {
        DataListBinder binder = (DataListBinder) pluginManager.getPlugin(bdMap.get("className").toString());
        ${element.properties.onAddClick!}
            
    },
                onRender:if function(binder != null) {
                    ${element.properties.onRender!}
    Map bdProps = (Map) bdMap.get("properties");
        },
                useCookie: <#if element.properties.useCookie! == 'true'>true<#else>false</#if>,binder.setProperties(bdProps);
                        
                scrollToToday:  <#if element.properties.scrollToToday! == 'true'>true<#else>false</#if>
   data = binder.getData(null, bdProps, new DataListFilterQueryObject[0], null, null, null, }null);
              });
       </script>
   if ${element.properties.customFooter!}
</div>

c. Manage the dependency libraries of your plugin

Our plugin is using javax.servlet.http.HttpServletRequest and javax.servlet.http.HttpServletResponse class, so we will need to add jsp-api library to our POM file.

Code Block
languagexml
<!-- Change plugin specific dependencies here -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.0</version>
</dependency>
<!-- End change plugin specific dependencies here -->

d. Make your plugin internationalization (i18n) ready

We are using i18n message key in getLabel and getDescription method. We also used i18n message key in our properties options definition as well. So, we will need to create a message resource bundle properties file for our plugin.

Create directory "resources/messages" under "gantt_chart_menu/src/main" directory. Then, create a "GanttChartMenu.properties" file in the folder. In the properties file, let add all the message keys and its label as below.

Code Block
 org.joget.tutorial.GanttChartMenu.pluginLabel=Gantt Chart Menu
org.joget.tutorial.GanttChartMenu.pluginDesc=Display data in a Gantt Chart view
userview.ganttchart.config=Configure Gantt Chart Menu
userview.ganttchart.customId=Custom ID
userview.ganttchart.invalidId=Only alpha-numeric and underscore characters allowed
userview.ganttchart.label=Label
userview.ganttchart.title=Page Title
userview.ganttchart.binder=Data Binder
userview.ganttchart.mapping=Column to Data Mappings
userview.ganttchart.mapping.category=Category (column ID)
userview.ganttchart.mapping.task=Task (column ID)
userview.ganttchart.mapping.activity=Activity (column ID)
userview.ganttchart.mapping.fromDate=Activity From Date (column ID)
userview.ganttchart.mapping.toDate=Activity To Date (column ID)
userview.ganttchart.mapping.dateFormat=Date format for Activity From/To Date
userview.ganttchart.mapping.taskId=Task Id (column ID)
userview.ganttchart.mapping.cssClass=Status (column ID, use as CSS class for styling)
userview.ganttchart.advanced=Advanced
userview.ganttchart.itemsPerPage=Item pre page
userview.ganttchart.navigate=Navigator
userview.ganttchart.navigate.buttons=Buttons
userview.ganttchart.navigate.scroll=Scroll
userview.ganttchart.scale=Default Scale
userview.ganttchart.scale.hours=Hours
userview.ganttchart.scale.days=Days
userview.ganttchart.scale.weeks=Weeks
userview.ganttchart.scale.months=Months
userview.ganttchart.maxScale=Maximum Scale
userview.ganttchart.minScale=Minimum Scale
userview.ganttchart.useCookie=Use cookie for storing chart states
userview.ganttchart.scrollToToday=Auto scroll to today after rendered
userview.ganttchart.onItemClick=On Item Clicked Event (Javascript)
userview.ganttchart.onAddClick=On Empty Space Clicked Event (Javascript)
userview.ganttchart.onRender=On Rendered Event (Javascript)
userview.ganttchart.customHeader=Custom Header (HTML)
userview.ganttchart.customFooter=Custom Footer (HTML)

e. Register your plugin to Felix Framework

We will have to register our plugin class in Activator class (Auto generated in the same class package) to tell Felix Framework that this is a plugin.

Code Block
languagejava
    public void start(BundleContext context) {
        registrationList = new ArrayList<ServiceRegistration>();
        //Register plugin here
        registrationList.add(context.registerService(GanttChartMenu.class.getName(), new GanttChartMenu(), null));
    }

f. Build it and testing 

Let build our plugin. Once the building process is done, we will found a "gantt_chart_menu-5.0.0.jar" file is created under "gantt_chart_menu/target" directory.

(idColumn.isEmpty()) {
                            idColumn = binder.getPrimaryKeyColumnName();
                        }
                    }
                }
            }
            
            JSONArray dataArry = new JSONArray();
            String dateFormat = getPropertyString("dateFormat");
            SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
            
            if (data != null && !data.isEmpty()) {
                String currentCategory = null;
                int cat_count = 0;
                String currentTask = null;
                int task_count = 0;
                int act_count = 0;
                
                JSONObject taskObj = null;
                JSONArray taskValuesArray = null;
                for (Object r : data) {
                    String id = getValue(r, idColumn);
                    String cat = getValue(r, getPropertyString("category"));
                    String task = getValue(r, getPropertyString("task"));
                    String act = getValue(r, getPropertyString("activity"));
                    String formDate = getValue(r, getPropertyString("fromDate"));
                    String toDate = getValue(r, getPropertyString("toDate"));
                    String status = getValue(r, getPropertyString("cssClass"));
                    
                    if (currentTask == null || !currentTask.equals(task) || (currentTask.equals(task) && !cat.equals(currentCategory))) {
                        currentTask = task;
                        if (taskObj != null) {
                            taskObj.put("values", taskValuesArray);
                            dataArry.put(taskObj);
                        }
                        taskObj = new JSONObject();
                        taskValuesArray = new JSONArray();
                        taskObj.put("desc", task);
                        taskObj.put("id", id);
                        task_count++;
                    }
                    
                    if (currentCategory == null || !currentCategory.equals(cat)) {
                        currentCategory = cat;
                        taskObj.put("name", cat);
                        cat_count++;
                    }
                    
                    JSONObject valueObj = new JSONObject();
                    JSONObject actObj = new JSONObject();
                    act_count++;
                    
                    actObj.put("taskId", id);
                    actObj.put("category", cat);
                    actObj.put("task", task);
                    actObj.put("activity", act);
                    actObj.put("formDate", formDate);
                    actObj.put("toDate", toDate);
                    actObj.put("status", status);
                    
                    valueObj.put("dataObj", actObj);
                    valueObj.put("label", act);
                    valueObj.put("from", "/Date("+sdf.parse(formDate).getTime()+")/");
                    valueObj.put("to", "/Date("+sdf.parse(toDate).getTime()+")/");
                    valueObj.put("customClass", "cat_"+cat_count+" task_"+task_count+" act_"+act_count+" "+status.replace(" ", "_"));
                    taskValuesArray.put(valueObj);
                }
                if (taskObj != null) {
                    taskObj.put("values", taskValuesArray);
                    dataArry.put(taskObj);
                }
            }
            
            return dataArry.toString();
        } catch (Exception e) {
            LogUtil.error(GanttChartMenu.class.getName(), e, json);
        }
        
        return json;
    }
    
    protected String getValue(Object o, String name) {
        String value = "";
        
        try {
            Object v = LookupUtil.getBeanProperty(o, name);
            if (v != null) {
                return v.toString();
            }
        } catch (Exception e) {
            LogUtil.error(GanttChartMenu.class.getName(), e, name);
        }
        
        return value;
    }
Code Block
<link href="${request.contextPath}/plugin/org.joget.tutorial.GanttChartMenu/lib/jquery.gantt/css/style.css" rel="stylesheet" type="text/css" />
<script src="${request.contextPath}/plugin/org.joget.tutorial.GanttChartMenu/lib/jquery.gantt/js/jquery.fn.gantt.min.js"></script>
<div class="gantt_chart_menu_body">
    <#if element.properties.title?? ><h3>${element.properties.title!}</h3></#if>
    ${element.properties.customHeader!}
    <div class="gantt"></div>
    <script>
        $(function() {
            "use strict";
            $(".gantt").gantt({
                source: ${data!},
                months: [@@userview.ganttChart.months.label@@],
                dow: [@@userview.ganttChart.dow.label@@],
                itemsPerPage: ${element.properties.itemsPerPage!},
                navigate: "${element.properties.navigate!}",
                scale: "${element.properties.scale!}",
                maxScale: "${element.properties.maxScale!}",
                minScale: "${element.properties.minScale!}",
                waitText: "@@userview.ganttChart.waitText@@",
                onItemClick: function (data) {
                    ${element.properties.onItemClick!}
                },
                onAddClick: function(datetime, rowId) {
                    ${element.properties.onAddClick!}
                },
                onRender: function() {
                    ${element.properties.onRender!}
                },
                useCookie: <#if element.properties.useCookie! == 'true'>true<#else>false</#if>,
                scrollToToday: <#if element.properties.scrollToToday! == 'true'>true<#else>false</#if>
            });
         });
    </script>
    ${element.properties.customFooter!}
</div>

c. Manage the dependency libraries of your plugin

Our plugin is using some libraries, we have to add all of them to our POM file.

Code Block
languagexml
        <!-- Change plugin specific dependencies here -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20080701</version>
        </dependency>
        <dependency>
            <groupId>displaytag</groupId>
            <artifactId>displaytag</artifactId>
            <version>1.2</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>jcl104-over-slf4j</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- End change plugin specific dependencies here -->

d. Make your plugin internationalization (i18n) ready

We are using i18n message key in getLabel and getDescription method. We also used i18n message key in our properties options definition as well. So, we will need to create a message resource bundle properties file for our plugin.

Create directory "resources/messages" under "gantt_chart_menu/src/main" directory. Then, create a "GanttChartMenu.properties" file in the folder. In the properties file, let add all the message keys and its label as below.

Code Block
org.joget.tutorial.GanttChartMenu.pluginLabel=Gantt Chart Menu
org.joget.tutorial.GanttChartMenu.pluginDesc=Display data in a Gantt Chart view
userview.ganttchart.config=Configure Gantt Chart Menu
userview.ganttchart.customId=Custom ID
userview.ganttchart.invalidId=Only alpha-numeric and underscore characters allowed
userview.ganttchart.label=Label
userview.ganttchart.title=Page Title
userview.ganttchart.binder=Data Binder
userview.ganttchart.mapping=Column to Data Mappings
userview.ganttchart.mapping.category=Category (column ID)
userview.ganttchart.mapping.task=Task (column ID)
userview.ganttchart.mapping.activity=Activity (column ID)
userview.ganttchart.mapping.fromDate=Activity From Date (column ID)
userview.ganttchart.mapping.toDate=Activity To Date (column ID)
userview.ganttchart.mapping.dateFormat=Date format for Activity From/To Date
userview.ganttchart.mapping.taskId=Task Id (column ID)
userview.ganttchart.mapping.cssClass=Status (column ID, use as CSS class for styling)
userview.ganttchart.advanced=Advanced
userview.ganttchart.itemsPerPage=Item pre page
userview.ganttchart.navigate=Navigator
userview.ganttchart.navigate.buttons=Buttons
userview.ganttchart.navigate.scroll=Scroll
userview.ganttchart.scale=Default Scale
userview.ganttchart.scale.hours=Hours
userview.ganttchart.scale.days=Days
userview.ganttchart.scale.weeks=Weeks
userview.ganttchart.scale.months=Months
userview.ganttchart.maxScale=Maximum Scale
userview.ganttchart.minScale=Minimum Scale
userview.ganttchart.useCookie=Use cookie for storing chart states
userview.ganttchart.scrollToToday=Auto scroll to today after rendered
userview.ganttchart.onItemClick=On Item Clicked Event (Javascript)
userview.ganttchart.onAddClick=On Empty Space Clicked Event (Javascript)
userview.ganttchart.onRender=On Rendered Event (Javascript)
userview.ganttchart.customHeader=Custom Header (HTML)
userview.ganttchart.customFooter=Custom Footer (HTML)
userview.ganttChart.months.label="January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
userview.ganttChart.dow.label="S", "M", "T", "W", "T", "F", "S"
userview.ganttChart.waitText=Loading...

e. Register your plugin to Felix Framework

We will have to register our plugin class in Activator class (Auto generated in the same class package) to tell Felix Framework that this is a plugin.

Code Block
languagejava
    public void start(BundleContext context) {
        registrationList = new ArrayList<ServiceRegistration>();
        //Register plugin here
        registrationList.add(context.registerService(GanttChartMenu.class.getName(), new GanttChartMenu(), null));
    }

f. Build it and testing 

Let build our plugin. Once the building process is done, we will found a "gantt_chart_menu-5.0.0.jar" file is created under "gantt_chart_menu/target" directory.

Then, let upload the plugin jar to Manage Plugins. After upload the jar file, double check the plugin is uploaded and activated correctly.

Image Added

Open a userview, you will see the new plugin is added under "Marketplace". Drag it to one of your Userview Category.

Image Added

Edit the properties of the Gantt Chart Menu.

Image Added

I selected "Form Data Binder" as "Data Binder" for testing. Fill all the mappings to corresponding Field Id/Column Id.

Image Added

Configure binder.

Image Added

Advanced setting to configure the gantt chart.

Image Added

Writing some css styling in "Custom Footer (HTML)" option to give different colors for different status.

Image Added

Populate some data to the form for testing.

Image Added

The end resultThen, let upload the plugin jar to Manage Plugins. After upload the jar file, double check the plugin is uploaded and activated correctly.

8. Take a step further, share it or sell it

You can download the source code from gantt_chart_menu.zip.

The test app for this tutorial is APP_testGanttChart-1-20151106194035.jwa.

To download the ready-to-use plugin jar, please find it in http://marketplace.joget.org/.

...