在本教程中,我们将遵循 guideline for developing a plugin 来开发一个下载PDF 数据列表插件.有关更多详细信息步骤,请参阅第一个教程 如何开发一个Bean Shell Hash Variable插件.
我们需要能够从datalist下载PDF格式的表格数据。
我们将开发一个 Datalist Action插件 来显示一个按钮来生成一个表单PDF文件。
要开发一个PDF下载Datalist Action插件,我们将考虑提供以下内容作为输入。
当“PDF Download Datalist Action”用作数据列行操作或列操作时,普通用户将在数据列表的每一行中看到链接以下载PDF文件。一旦链接被点击,一个PDF文件将被提示下载该特定的行。
当插件用于多个datalist行(整个列表操作)时,包含所有生成的每个选定行的PDF文件的zip文件将被点击按钮时提示下载。
要开发PDF下载Datalist Action插件,我们可以重用FormPdfUtil中的方法 来生成PDF格式的表单。我们也可以参考 Datalist Form Data Delete Action插件的 源代码。除此之外,我们可以参考 导出表单电子邮件工具 ,了解我们可以在插件中提供哪种插件属性选项,因为导出表单电子邮件工具也使用FormPdfUtil中的方法。
我们需要始终准备好Joget Workflow Source Code,并按照这个指导方针建立起来 。
以下教程是用Macbook Pro编写的,Joget源代码是5.0.0版。其他平台命令请参阅 如何开发插件文章。
假设我们的文件夹目录如下所示。
- Home
- joget
- plugins
- jw-community
-5.0.0 |
“插件”目录是我们将创建和存储我们所有插件的文件夹,“jw-community”目录是Joget Workflow源代码的存储位置。
运行以下命令在“plugins”目录下创建一个maven项目。
cd joget/plugins/ ~/joget/jw-community/5.0.0/wflow-plugin-archetype/create-plugin.sh org.joget.tutorial download_pdf_datalist_action 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: download_pdf_datalist_action version: 5.0.0 package: org.joget.tutorial Y: : y |
我们应该在终端上显示“BUILD SUCCESS”消息,并在“plugins”文件夹中创建一个“download_pdf_datalist_action”文件夹。
用你最喜欢的IDE打开maven项目。我将使用 NetBeans。
在“org.joget.tutorial”包下创建一个“DownloadPdfDatalistAction”类。然后,使用org.joget.apps.datalist.model.DataListActionDefault抽象类来扩展 该类。请参阅 Datalist Action插件。
像往常一样,我们必须执行所有的抽象方法。我们将使用AppPluginUtil.getMessage方法来支持i18n,并使用常量变量MESSAGE_PATH作为消息资源包目录。
package org.joget.tutorial;
import org.joget.apps.app.service.AppPluginUtil;
import org.joget.apps.datalist.model.DataListActionDefault;
public class DownloadPdfDatalistAction extends DataListActionDefault {
private final static String MESSAGE_PATH = "messages/DownloadPdfDatalistAction";
public String getName() {
return "Download PDF Datalist Action";
}
public String getVersion() {
return "5.0.0";
}
public String getClassName() {
return getClass().getName();
}
public String getLabel() {
//support i18n
return AppPluginUtil.getMessage("org.joget.tutorial.DownloadPdfDatalistAction.pluginLabel", getClassName(), MESSAGE_PATH);
}
public String getDescription() {
//support i18n
return AppPluginUtil.getMessage("org.joget.tutorial.DownloadPdfDatalistAction.pluginDesc", getClassName(), MESSAGE_PATH);
}
public String getPropertyOptions() {
return AppUtil.readPluginResource(getClassName(), "/properties/downloadPdfDatalistAction.json", null, true, MESSAGE_PATH);
}
public String getLinkLabel() {
return getPropertyString("label"); //get label from configured properties options
}
public String getHref() {
return getPropertyString("href"); //Let system to handle to post to the same page
}
public String getTarget() {
return "post";
}
public String getHrefParam() {
return getPropertyString("hrefParam"); //Let system to set the parameter to the checkbox name
}
public String getHrefColumn() {
String recordIdColumn = getPropertyString("recordIdColumn"); //get column id from configured properties options
if ("id".equalsIgnoreCase(recordIdColumn) || recordIdColumn.isEmpty()) {
return getPropertyString("hrefColumn"); //Let system to set the primary key column of the binder
} else {
return recordIdColumn;
}
}
public String getConfirmation() {
return getPropertyString("confirmation"); //get confirmation from configured properties options
}
public DataListActionResult executeAction(DataList dataList, String[] rowKeys) {
throw new UnsupportedOperationException("Not supported yet.");
}
} |
现在,我们必须为管理员用户创建一个UI,为我们的插件提供输入。在getPropertyOptions方法中,我们已经指定了我们的 插件属性选项和配置定义文件位于“/properties/downloadPdfDatalistAction.json”。让我们在“download_pdf_datalist_action / src / main”目录下创建一个目录“resources / properties”。创建目录后,在“properties”文件夹中创建一个名为“downloadPdfDatalistAction.json”的文件。
在属性定义选项文件中,我们需要提供如下的选项。请注意,我们可以在我们的属性选项中使用“@@ message.key @@”语法来支持i18n。
[{
title : '@@datalist.downloadPdf.config@@',
properties : [{
name : 'label',
label : '@@datalist.downloadPdf.label@@',
type : 'textfield',
value : '@@datalist.downloadPdf.download@@'
},
{
name : 'formDefId',
label : '@@datalist.downloadPdf.form@@',
type : 'selectbox',
options_ajax : '[CONTEXT_PATH]/web/json/console/app[APP_PATH]/forms/options',
required : 'True'
},
{
name : 'recordIdColumn',
label : '@@datalist.downloadPdf.recordIdColumn@@',
description : '@@datalist.downloadPdf.recordIdColumn.desc@@',
type : 'textfield'
},
{
name : 'confirmation',
label : '@@datalist.downloadPdf.confirmationMessage@@',
type : 'textfield'
}]
},
{
title : '@@datalist.downloadPdf.advanced@@',
properties : [{
name : 'formatting',
label : '@@datalist.downloadPdf.formatting@@',
type : 'codeeditor',
mode : 'css'
},
{
name : 'headerHtml',
label : '@@datalist.downloadPdf.headerHtml@@',
type : 'codeeditor',
mode : 'html'
},
{
name : 'repeatHeader',
label : '@@datalist.downloadPdf.repeatHeader@@',
type : 'checkbox',
options : [{
value : 'true',
label : ''
}]
},
{
name : 'footerHtml',
label : '@@datalist.downloadPdf.footerHtml@@',
type : 'codeeditor',
mode : 'html'
},
{
name : 'repeatFooter',
label : '@@datalist.downloadPdf.repeatFooter@@',
type : 'checkbox',
options : [{
value : 'true',
label : ''
}]
},
{
name : 'hideEmptyValueField',
label : '@@datalist.downloadPdf.hideEmptyValueField@@',
type : 'checkbox',
options : [{
value : 'true',
label : ''
}]
},
{
name : 'showNotSelectedOptions',
label : '@@datalist.downloadPdf.showNotSelectedOptions@@',
type : 'checkbox',
options : [{
value : 'true',
label : ''
}]
}]
}] |
完成属性选项以收集输入后,我们可以对插件的主要方法(即executeAction方法)进行操作。
public DataListActionResult executeAction(DataList dataList, String[] rowKeys) {
// only allow POST
HttpServletRequest request = WorkflowUtil.getHttpServletRequest();
if (request != null && !"POST".equalsIgnoreCase(request.getMethod())) {
return null;
}
// check for submited rows
if (rowKeys != null && rowKeys.length > 0) {
try {
//get the HTTP Response
HttpServletResponse response = WorkflowUtil.getHttpServletResponse();
if (rowKeys.length == 1) {
//generate a pdf for download
singlePdf(request, response, rowKeys[0]);
} else {
//generate a zip of all pdfs
multiplePdfs(request, response, rowKeys);
}
} catch (Exception e) {
LogUtil.error(getClassName(), e, "Fail to generate PDF for " + ArrayUtils.toString(rowKeys));
}
}
//return null to do nothing
return null;
}
/**
* Handles for single pdf file
* @param request
* @param response
* @param rowKey
* @throws IOException
* @throws javax.servlet.ServletException
*/
protected void singlePdf(HttpServletRequest request, HttpServletResponse response, String rowKey) throws IOException, ServletException {
byte[] pdf = getPdf(rowKey);
writeResponse(request, response, pdf, rowKey+".pdf", "application/pdf");
}
/**
* Handles for multiple files download. Put all pdfs in zip.
* @param request
* @param response
* @param rowKeys
* @throws java.io.IOException
* @throws javax.servlet.ServletException
*/
protected void multiplePdfs(HttpServletRequest request, HttpServletResponse response, String[] rowKeys) throws IOException, ServletException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(baos);
try {
//create pdf and put in zip
for (String id : rowKeys) {
byte[] pdf = getPdf(id);
zip.putNextEntry(new ZipEntry(id+".pdf"));
zip.write(pdf);
zip.closeEntry();
}
zip.finish();
writeResponse(request, response, baos.toByteArray(), getLinkLabel() +".zip", "application/zip");
} finally {
baos.close();
zip.flush();
}
}
/**
* Generate PDF using FormPdfUtil
* @param id
* @return
*/
protected byte[] getPdf(String id) {
AppDefinition appDef = AppUtil.getCurrentAppDefinition();
String formDefId = getPropertyString("formDefId");
Boolean hideEmptyValueField = null;
if (getPropertyString("hideEmptyValueField").equals("true")) {
hideEmptyValueField = true;
}
Boolean showNotSelectedOptions = null;
if (getPropertyString("showNotSelectedOptions").equals("true")) {
showNotSelectedOptions = true;
}
Boolean repeatHeader = null;
if ("true".equals(getPropertyString("repeatHeader"))) {
repeatHeader = true;
}
Boolean repeatFooter = null;
if ("true".equals(getPropertyString("repeatFooter"))) {
repeatFooter = true;
}
String css = null;
if (!getPropertyString("formatting").isEmpty()) {
css = getPropertyString("formatting");
}
String header = null;
if (!getPropertyString("headerHtml").isEmpty()) {
header = getPropertyString("headerHtml");
header = AppUtil.processHashVariable(header, null, null, null);
}
String footer = null;
if (!getPropertyString("footerHtml").isEmpty()) {
footer = getPropertyString("footerHtml");
footer = AppUtil.processHashVariable(footer, null, null, null);
}
return FormPdfUtil.createPdf(formDefId, id, appDef, null, hideEmptyValueField, header, footer, css, showNotSelectedOptions, repeatHeader, repeatFooter);
}
/**
* Write to response for download
* @param response
* @param bytes
* @param filename
* @param contentType
* @throws IOException
*/
protected void writeResponse(HttpServletRequest request, HttpServletResponse response, byte[] bytes, String filename, String contentType) throws IOException, ServletException {
OutputStream out = response.getOutputStream();
try {
String name = URLEncoder.encode(filename, "UTF8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment; filename="+name+"; filename*=UTF-8''" + name);
response.setContentType(contentType+"; charset=UTF-8");
if (bytes.length > 0) {
response.setContentLength(bytes.length);
out.write(bytes);
}
} finally {
out.flush();
out.close();
//simply foward to a
request.getRequestDispatcher(filename).forward(request, response);
}
} |
我们的插件使用javax.servlet.http.HttpServletRequest和javax.servlet.http.HttpServletResponse类,因此我们需要将jsp-api库添加到我们的POM文件中。
<!-- 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 --> |
我们在getLabel和getDescription方法中使用i18n消息密钥。我们将在我们的属性选项定义中使用i18n消息密钥。然后,我们将需要为我们的插件创建一个消息资源包属性文件。
在“download_pdf_datalist_action / src / main”目录下创建一个目录“resources / messages”。然后,在文件夹中创建一个“DownloadPdfDatalistAction.properties”文件。在属性文件中,添加所有消息密钥及其标签,如下所示。
org.joget.tutorial.DownloadPdfDatalistAction.pluginLabel=Download PDF org.joget.tutorial.DownloadPdfDatalistAction.pluginDesc=Support to download form PDF from datalist datalist.downloadPdf.download=Download datalist.downloadPdf.config=Configure Download PDF Action datalist.downloadPdf.label=Label datalist.downloadPdf.form=Form datalist.downloadPdf.recordIdColumn=Record Id Column datalist.downloadPdf.recordIdColumn.desc=Default to the primary key of the configured binder datalist.downloadPdf.confirmationMessage=Confirmation Message datalist.downloadPdf.hideEmptyValueField=Hide field that without value datalist.downloadPdf.showNotSelectedOptions=Show unselected options for multi options field datalist.downloadPdf.advanced=Advanced datalist.downloadPdf.formatting=Formatting (CSS) datalist.downloadPdf.headerHtml=Header (HTML) datalist.downloadPdf.repeatHeader=Repeat header on every page? datalist.downloadPdf.footerHtml=Footer (HTML) datalist.downloadPdf.repeatFooter=Repeat footer on every page? |
接下来,我们将需要在Activator类(在同一个类包中自动生成)中注册我们的插件类,以告诉Felix框架这是一个插件。
public void start(BundleContext context) {
registrationList = new ArrayList<ServiceRegistration>();
//Register plugin here
registrationList.add(context.registerService(DownloadPdfDatalistAction.class.getName(), new DownloadPdfDatalistAction(), null));
} |
让我们建立我们的插件。一旦构建过程完成,我们将在“download_pdf_datalist_action / target”目录下找到一个“download_pdf_datalist_action-5.0.0.jar”文件。
然后,让我们上传插件jar到 管理插件。上传jar文件后,再次检查插件是否正确上传并激活。

那么,让我们来试一下datalist。您可以在Datalist Builder的 “操作”下看到我们的新插件。

一旦我们将“下载PDF”操作拖放到数据生成器画布中,我们就可以编辑该操作。以下配置页面将根据我们的属性选项定义显示。

我们添加“下载PDF”操作作为行操作,也是整个列表操作进行测试。我们可以在下面的userview截图中看到正确显示的“下载”按钮。

当点击时,下载pdf。

当整个列表动作被点击时,一个zip文件被下载。

您可以从download_pdf_datalist_action.zip下载源代码 。
要下载现成的插件jar,请在http://marketplace.joget.org/上找到它 。