如何开发一个下载 PDF 数据列表的插件

Skip to end of metadata



Go to start of metadata


在本教程中,我们将遵循 guideline for developing a plugin 来开发一个下载PDF 数据列表插件.有关更多详细信息步骤,请参阅第一个教程 如何开发一个Bean Shell Hash Variable插件.


1.什么问题?

我们需要能够从datalist下载PDF格式的表格数据。


2.如何解决问题?

我们将开发一个  Datalist Action插件  来显示一个按钮来生成一个表单PDF文件。 


3.你的插件需要什么输入?

要开发一个PDF下载Datalist Action插件,我们将考虑提供以下内容作为输入。

  1. 表单ID:将用于生成PDF文件的表单。
  2. 记录标识列:使用数据列行的标识或列值来加载记录。
  3. 格式化选项:用于格式化和自定义PDF输出的选项。 

4.你的插件的输出和预期结果是什么?

当“PDF Download Datalist Action”用作数据列行操作或列操作时,普通用户将在数据列表的每一行中看到链接以下载PDF文件。一旦链接被点击,一个PDF文件将被提示下载该特定的行。

当插件用于多个datalist行(整个列表操作)时,包含所有生成的每个选定行的PDF文件的zip文件将被点击按钮时提示下载。


5.是否有任何资源/ API可以重复使用?

要开发PDF下载Datalist Action插件,我们可以重用FormPdfUtil中的方法   来生成PDF格式的表单。我们也可以参考 Datalist Form Data Delete Action插件的  源代码。除此之外,我们可以参考  导出表单电子邮件工具  ,了解我们可以在插件中提供哪种插件属性选项,因为导出表单电子邮件工具也使用FormPdfUtil中的方法。


6.准备你的开发环境

我们需要始终准备好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。 


7. 开始编码吧!

a. 继承插件抽象类

在“org.joget.tutorial”包下创建一个“DownloadPdfDatalistAction”类。然后,使用org.joget.apps.datalist.model.DataListActionDefault抽象类来扩展  该类。请参阅  Datalist Action插件


b. 实现所有的抽象方法

像往常一样,我们必须执行所有的抽象方法。我们将使用AppPluginUtil.getMessage方法来支持i18n,并使用常量变量MESSAGE_PATH作为消息资源包目录。

Implementation of all basic abstract methods

 Expand source


现在,我们必须为管理员用户创建一个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, nullnullnull);

    }

    String footer = null;

    if (!getPropertyString("footerHtml").isEmpty()) {

        footer = getPropertyString("footerHtml");

        footer = AppUtil.processHashVariable(footer, nullnullnull);

    }

     

    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);

    }

}

c. 管理你的插件的依赖库

我们的插件使用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 -->

d. 让你的插件国际化(国际化)准备就绪

我们在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?

e.注册你的插件到Felix框架

接下来,我们将需要在Activator类(在同一个类包中自动生成)中注册我们的插件类,以告诉Felix框架这是一个插件。

public void start(BundleContext context) {

    registrationList = new ArrayList<ServiceRegistration>();

    //Register plugin here

    registrationList.add(context.registerService(DownloadPdfDatalistAction.class.getName(), new DownloadPdfDatalistAction(), null));

}

f. 构建并测试

让我们建立我们的插件。一旦构建过程完成,我们将在“download_pdf_datalist_action / target”目录下找到一个“download_pdf_datalist_action-5.0.0.jar”文件。

然后,让我们上传插件jar到  管理插件。上传jar文件后,再次检查插件是否正确上传并激活。

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

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

 

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

当点击时,下载pdf。

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

8. 再进一步,分享或出售

您可以从download_pdf_datalist_action.zip下载源代码  。

要下载现成的插件jar,请在http://marketplace.joget.org/上找到它  。

文档更新时间: 2018-11-06 06:16   作者:李庆