在本教程中,我们将遵循开发插件的 指导方针 来开发我们的甘特图用户视图菜单插件。 有关更多详细信息步骤,请参阅第一个教程 如何开发一个Bean Shell哈希变量插件。
1.什么问题?
我想在甘特图视图中显示收集的表单数据。我可以选择使用 贾斯珀报告用户视图菜单来实现它,但它看起来不错,缺乏交互控制。然后,我发现这个 由Tait Brown 根据MIT许可开发的Jquery Gantt Chart库 ,看起来比使用Jasper Report的甘特图更好看,效果更好。我想用它来显示我收集的表单数据。
2.你有什么想法来解决这个问题?
我们可以开发一个 Userview菜单插件 来使用 Jquery甘特图库来显示收集的表单数据。
3.你的插件需要什么输入?
要开发一个甘特图Userview菜单插件,我们可以考虑提供以下作为输入。
- 数据绑定器:我们可以重用数据绑定器来检索数据
- 数据映射:将检索数据从数据列表绑定到映射库的数据格式
- 格式化选项:用于格式化和自定义甘特图的选项。
4.你的插件的输出和预期结果是什么?
通过参考库演示,我们可以快速地出来一个静态的HTML页面,如下图所示。由于这是一个Joget插件教程,所以我们不会详细介绍静态HTML页面的编码。你可以参考 static.zip。我们预计我们的用户视图页面可以将我们收集的数据显示为静态的HTML页面。
5.有没有可重用的资源/ API?
如果您不熟悉 FreeMaker 语法,则在继续之前,您应该查看其文档。
6.准备你的开发环境
我们需要始终准备好Joget Workflow Source Code,并按照这个指导方针建立起来 。
本教程的以下内容是使用Macbook Pro和Joget源代码5.0.0版编写的。 其他平台命令请参考 如何开发插件。
让我们说我们的文件夹目录如下。
- Home
- joget
- plugins
- jw-community
-5.0.0
“plugins”目录是我们要创建和存储我们所有插件的文件夹,“jw-community”目录是Joget Workflow源代码存储的地方。
运行以下命令在“plugins”目录下创建一个maven项目。
cd joget/plugins/ ~/joget/jw-community/5.0.0/wflow-plugin-archetype/create-plugin.sh org.joget.tutorial gantt_chart_menu 5.0.0
然后,shell脚本将要求我们为您的插件输入一个版本,并在生成maven项目之前要求我们确认。
Define value for property 'version': 1.0-SNAPSHOT: : 5.0.0 [INFO] Using property: package = org.joget.tutorial Confirm properties configuration: groupId: org.joget.tutorial artifactId: gantt_chart_menu version: 5.0.0 package: org.joget.tutorial Y: : y
我们应该在终端上显示“BUILD SUCCESS”消息,并在“plugins”文件夹中创建一个“gantt_chart_menu”文件夹。
用你最喜欢的IDE打开maven项目。我将使用 NetBeans。
7. Just code it!
a. 扩展插件类型的抽象类
在“org.joget.tutorial”包下创建一个“GanttChartMenu”类。然后,使用org.joget.apps.userview.model.UserviewMenu 抽象类来扩展 该类。请参阅用户 视图菜单插件。
b. 实现所有的抽象方法
像往常一样,我们必须执行所有的抽象方法。我们将使用AppPluginUtil.getMessage方法来支持i18n,并使用常量变量MESSAGE_PATH作为消息资源包目录。
然后,我们必须为管理员用户提供一个UI来为我们的插件提供输入。在getPropertyOptions方法中,我们已经指定了我们的 插件属性选项和配置 定义文件位于“/properties/ganttChartMenu.json”。让我们在“gantt_chart_menu / src / main”目录下创建一个目录“resources / properties”。创建目录后,在“properties”文件夹中创建一个名为“ganttChartMenu.json”的文件。
在属性定义选项文件中,我们需要提供如下的选项。请注意,我们可以在我们的属性选项中使用“@@ message.key @@”语法来支持i18n。
[{
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 : 'hours',
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'
}]
}]
完成收集输入的属性选项后,我们可以使用getRenderPage方法的插件的主要方法。通常情况下,我会先详细地将数据填充到视图中,首先将getRenderPage的静态内容先构建并测试插件。这一切都很好,只有我们试图添加数据到视图。
@Override
public String getRenderPage() {
Map model = new HashMap();
model.put("request", getRequestParameters());
model.put("element", this);
PluginManager pluginManager = (PluginManager)AppUtil.getApplicationContext().getBean("pluginManager");
String content = pluginManager.getPluginFreeMarkerTemplate(model, getClass().getName(), "/templates/ganttChart.ftl", null);
return content;
}
然后,让我们创建一个位于“/templates/ganttChart.ftl”的模板文件。让我们在“gantt_chart_menu / src / main”目录下创建一个目录“resources / templates”。创建目录后,在“模板”文件夹中创建一个名为“ganttChartMenu.json”的文件。
将我们之前创建的静态HTML放到模板文件中,如下所示。记得把所有的依赖关系的JavaScript库和图像放在“gantt_chart_menu / src / main / resources / resources”下,并相应地更改url。你的项目目录应该看起来像下面的图片。
<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">
<h3>Plugin Development</h3>
<div class="gantt"></div>
<script>
$(function() {
"use strict";
$(".gantt").gantt({
source: [{
name: "Sprint 0",
desc: "Analysis",
values: [{
from: "/Date(1320192000000)/",
to: "/Date(1322401600000)/",
label: "Requirement Gathering",
customClass: "ganttRed"
}]
},{
name: " ",
desc: "Scoping",
values: [{
from: "/Date(1322611200000)/",
to: "/Date(1323302400000)/",
label: "Scoping",
customClass: "ganttRed"
}]
},{
name: "Sprint 1",
desc: "Development",
values: [{
from: "/Date(1323802400000)/",
to: "/Date(1325685200000)/",
label: "Development",
customClass: "ganttGreen"
}]
},{
name: " ",
desc: "Showcasing",
values: [{
from: "/Date(1325685200000)/",
to: "/Date(1325695200000)/",
label: "Showcasing",
customClass: "ganttBlue"
}]
},{
name: "Sprint 2",
desc: "Development",
values: [{
from: "/Date(1326785200000)/",
to: "/Date(1325785200000)/",
label: "Development",
customClass: "ganttGreen"
}]
},{
name: " ",
desc: "Showcasing",
values: [{
from: "/Date(1328785200000)/",
to: "/Date(1328905200000)/",
label: "Showcasing",
customClass: "ganttBlue"
}]
},{
name: "Release Stage",
desc: "Training",
values: [{
from: "/Date(1330011200000)/",
to: "/Date(1336611200000)/",
label: "Training",
customClass: "ganttOrange"
}]
},{
name: " ",
desc: "Deployment",
values: [{
from: "/Date(1336611200000)/",
to: "/Date(1338711200000)/",
label: "Deployment",
customClass: "ganttOrange"
}]
},{
name: " ",
desc: "Warranty Period",
values: [{
from: "/Date(1336611200000)/",
to: "/Date(1349711200000)/",
label: "Warranty Period",
customClass: "ganttOrange"
}]
}],
navigate: "scroll",
maxScale: "hours",
itemsPerPage: 10
});
});
</script>
</div>
现在,为了测试目的,我们可以跳到 c。管理你的插件的依赖库,d。让你的插件国际化(国际化), 例如。将您的插件注册到Felix Framework和 f。构建它并测试, 然后在测试之后继续下面的内容。你会在你的用户视图中看到类似于下面的内容。
在验证静态HTML在我们的插件中工作之后,我们可以通过向视图添加数据来进一步增强它。现在,修改您的getRenderPage方法和ganttChart.ftl模板如下。
@Override
public String getRenderPage() {
Map model = new HashMap();
model.put("request", getRequestParameters());
model.put("element", this);
//populate data in JSON format
model.put("data", getData());
PluginManager pluginManager = (PluginManager)AppUtil.getApplicationContext().getBean("pluginManager");
String content = pluginManager.getPluginFreeMarkerTemplate(model, getClass().getName(), "/templates/ganttChart.ftl", MESSAGE_PATH);
return content;
}
protected String getData() {
String json = "[]";
try {
DataListCollection data = null;
String idColumn = getPropertyString("taskId");
//get the binder
Object binderData = getProperty("binder");
if (binderData != null && binderData instanceof Map) {
Map bdMap = (Map) binderData;
if (bdMap != null && bdMap.containsKey("className") && !bdMap.get("className").toString().isEmpty()) {
PluginManager pluginManager = (PluginManager) AppUtil.getApplicationContext().getBean("pluginManager");
DataListBinder binder = (DataListBinder) pluginManager.getPlugin(bdMap.get("className").toString());
if (binder != null) {
Map bdProps = (Map) bdMap.get("properties");
binder.setProperties(bdProps);
data = binder.getData(null, bdProps, new DataListFilterQueryObject[0], null, null, null, null);
if (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;
}
<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. 管理你的插件的依赖库
我们的插件正在使用一些库,我们必须将它们全部添加到我们的POM文件中。
<!-- 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. 让你的插件国际化(国际化)
我们在getLabel和getDescription方法中使用i18n消息密钥。我们还在我们的属性选项定义中使用了i18n消息密钥。所以,我们需要为我们的插件创建一个消息资源包属性文件。
在“gantt_chart_menu / src / main”目录下创建目录“resources / messages”。然后,在文件夹中创建一个“GanttChartMenu.properties”文件。在属性文件中,让我们添加所有的消息键和它的标签如下。
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. 注册你的插件Felix框架
我们将不得不在Activator类(在同一个类包中自动生成)中注册我们的插件类,以告诉Felix框架这是一个插件。
public void start(BundleContext context) {
registrationList = new ArrayList<ServiceRegistration>();
//Register plugin here
registrationList.add(context.registerService(GanttChartMenu.class.getName(), new GanttChartMenu(), null));
}
f. 建设和测试
让我们建立我们的插件。一旦构建过程完成,我们将在“gantt_chart_menu / target”目录下创建一个“gantt_chart_menu-5.0.0.jar”文件。
然后,让插件jar上传到 管理插件。上传jar文件后,仔细检查插件是否正确上传和激活。
打开一个用户视图,你会看到新的插件被添加到“市场”下。将其拖到您的一个Userview类别.
编辑甘特图菜单的属性。
我选择“表单数据包装器”作为“数据包装器”进行测试。将所有映射填充到相应的字段标识/列标识。
配置选项。
高级设置来配置甘特图。
在“自定义页脚(HTML)”选项中编写一些css样式,为不同的状态提供不同的颜色。
将一些数据填充到表单中进行测试。
最终的结果。
8. 再进一步,分享或出售
您可以从gantt_chart_menu.zip下载源代码 。
本教程的测试应用程序是 APP_testGanttChart-1-20151106194035.jwa。
要下载现成的插件jar,请在http://marketplace.joget.org/上找到它 。









