Joget DX 8 Stable Released
The stable release for Joget DX 8 is now available, with a focus on UX and Governance.
在本教程中,我们将遵循 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作为消息资源包目录。
现在,我们必须为管理员用户创建一个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/上找到它 。