ในบทเรียนนี้เราจะติดตาม guideline for developing a plugin เพื่อพัฒนาปลั๊กอินดาวน์โหลด PDF Datalist Action ของเรา โปรดอ้างอิงถึงบทช่วยสอนแรกสุด วิธีการพัฒนา Bean Shell Hash Variable สำหรับขั้นตอนรายละเอียดเพิ่มเติม

1.ปัญหาคืออะไร?

ราต้องการความสามารถในการดาวน์โหลดข้อมูลแบบฟอร์มเป็นไฟล์ PDF จากดาต้าลิสต์

2.ความคิดของคุณในการแก้ปัญหาคืออะไร?

เราจะพัฒนา Datalist Action Plugin เพื่อแสดงปุ่มเพื่อสร้างไฟล์ PDF ของแบบฟอร์ม

3.อินพุตที่จำเป็นสำหรับปลั๊กอินของคุณคืออะไร?

เพื่อพัฒนาปลั๊กอิน PDF Download Datalist Action เราจะพิจารณาให้สิ่งต่อไปนี้เป็นอินพุต

  1. Form ID : แบบฟอร์มที่จะใช้ในการสร้างไฟล์ PDF
  2. Record ID Column : ใช้รหัสของแถวดาต้าลิสต์หรือค่าคอลัมน์เพื่อโหลดเรคคอร์ด
  3. Formatting options : ตัวเลือกในการจัดรูปแบบและปรับแต่งเอาต์พุต PDF

4. ผลลัพธ์และผลลัพธ์ที่คาดหวังจากปลั๊กอินของคุณคืออะไร?

เมื่อ PDF Download Datalist Action ถูกใช้เป็นแอ็คชันแถวของ datalist หรือคอลัมน์แอ็คชัน ผู้ใช้ปกติจะเห็นลิงค์เพื่อดาวน์โหลดไฟล์ PDF ในทุก ๆ แถวของดาต้าลิสต์ เมื่อคลิกที่ลิงค์ ไฟล์ PDF จะถูกถามให้ดาวน์โหลดสำหรับแถวที่ระบุ

เมื่อมีการใช้ปลั๊กอินสำหรับดาต้าลิสต์หลายแถว (การดำเนินการรายการทั้งหมด) ไฟล์ซิปที่มีไฟล์ PDF ที่สร้างขึ้นทั้งหมดของทุกแถวที่เลือกจะได้รับแจ้งให้ดาวน์โหลดเมื่อมีการคลิกปุ่ม

5. มีทรัพยากร / API ที่สามารถนำกลับมาใช้ใหม่ได้หรือไม่?

เพื่อพัฒนาปลั๊กอิน PDF Download Datalist Action. เราสามารถใช้วิธีการใน FormPdfUtil เพื่อสร้างแบบฟอร์ม PDF. เรายังสามารถอ้างถึง source code ของปลั๊กอินการดำเนินการลบแบบฟอร์มข้อมูลดาต้าลิสต์ นอกจากนั้นเรายังสามารถอ้างถึง เครื่องมือส่งออกแบบฟอร์มอีเมล์ (Export Form Email Tool) ในตัวเลือกคุณสมบัติปลั๊กอินที่เราสามารถให้ในปลั๊กอินเนื่องจากเครื่องมือส่งออกฟอร์มอีเมลใช้วิธีการในฟอร์ม PdfUtil ด้วยเช่นกัน

6. เตรียม environment ของคุณเพื่อการพัฒนา

เราจำเป็นต้องให้ซอร์สโค้ด Joget Workflow ของเราพร้อมและสร้างโดยทำตาม this guideline

บทช่วยสอนต่อไปนี้จัดทำขึ้นด้วย Macbook Pro และ  Joget Source Code is version 5.0.0. โปรดดูที่ แนวทางสำหรับการพัฒนาปลั๊กอิน บทความสำหรับคำสั่งแพลตฟอร์มอื่น ๆ

สมมติว่าไดเรกทอรีโฟลเดอร์ของเรามีดังนี้

- Home
  - joget
    - plugins
    - jw-community
      -5.0.0

The "plugins" directory เป็นโฟลเดอร์ที่เราจะสร้างและจัดเก็บปลั๊กอินของเราและ "jw-community" directory เป็นที่เก็บ  Source code Joget Workflow.

เรียกใช้คำสั่งต่อไปนี้เพื่อสร้างโครงการ maven ใน "plugins" directory.

cd joget/plugins/
~/joget/jw-community/5.0.0/wflow-plugin-archetype/create-plugin.sh org.joget.tutorial download_pdf_datalist_action 5.0-SNAPSHOT

Then, the shell script will ask us to key in a version number for the plugin and ask us for a confirmation before it generates the maven project.

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" ที่ปรากฏในเครื่องของเราและโฟลเดอร์ "download_pdf_datalist_action" ที่สร้างในโฟลเดอร์ "plugins"

เปิดโครงการ maven ด้วย IDE ที่คุณชื่นชอบ เราจะใช้ NetBeans.  

7. เริ่มโค้ด!

a. การขยาย abstract class ของประเภทปลั๊กอิน

สร้าง "DownloadPdfDatalistAction" ภายใต้ "org.joget.tutorial" package. จากนั้น ขยายคลาสด้วย org.joget.apps.datalist.model.DataListActionDefault abstract class. โปรดดูที่ Datalist Action Plugin.

b. การดำเนินการของ abstract methods ทั้งหมด

ตามปกติเราต้องใช้วิธีนามธรรมทั้งหมด เราจะใช้วิธี AppPluginUtil.getMessage เพื่อสนับสนุน i18n และการใช้ตัวแปร MESSAGE_PATH คงที่สำหรับ message resource bundle directory.

Implementation of all basic abstract methods
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". Let us create a directory "resources/properties" ภายใต้ "download_pdf_datalist_action/src/main" directory. หลังจากสร้าง directory, สร้างไฟล์ชื่อ "downloadPdfDatalistAction.json" ในโฟลเดอร์ "properties" 

ในไฟล์ตัวเลือกคุณสมบัติ เราจะต้องให้ตัวเลือกดังต่อไปนี้ โปรดทราบว่าเราสามารถใช้ "@@message.key@@" syntax เพื่อสนับสนุน 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);
        }
    }

c. จัดการ dependency libraries ของปลั๊กอินคุณ

ปลั๊กอินของเรากำลังใช้ javax.servlet.http.HttpServletRequest และ javax.servlet.http.HttpServletResponse class, ดังนั้นเราต้องเพิ่ม jsp-api library ไปที่ไฟล์ 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. เตรียมปลั๊กอิน internationalization (i18n) ของคุณให้พร้อม

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

สร้าง directory, "resources/messages", ภายใต้ "download_pdf_datalist_action/src/main" directory. จากนั้นสร้างไฟล์ "DownloadPdfDatalistAction.properties" ในโฟลเดอร์ ,ในไฟล์ properties เพิ่ม message keys และ its label as below.

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 Framework

ต่อไปเราจะต้องลงทะเบียนคลาสปลั๊กอินของเราในคลาส Activator (สร้างอัตโนมัติในแพ็คเกจคลาสเดียวกัน) เพื่อบอก Felix Framework ว่านี่เป็นปลั๊กอิน

    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-5.0.0.jar" ที่สร้างขึ้นภายใต้ไดเรกทอรี "download_pdf_datalist_action / target"

จากนั้นลองอัปโหลดปลั๊กอินไปที่ Manage Plugins. หลังจากอัปโหลดไฟล์ jar ตรวจสอบอีกครั้งว่ามีการอัปโหลดและเปิดใช้งานปลั๊กอินอย่างถูกต้อง

จากนั้นมาลองในหนึ่งในนักดาต้า คุณสามารถดูปลั๊กอินใหม่ของเราที่แสดงใน "การทำงาน" ใน ตัวสร้างดาตาลิสต์ (Datalist Builder).

เมื่อเราลากและวางการกระทำ "ดาวน์โหลด PDF" ลงในผืนผ้าใบตัวสร้างดาต้าลิสต์เราสามารถแก้ไขการกระทำได้ หน้าการกำหนดค่าต่อไปนี้จะแสดงตามคำนิยามตัวเลือกคุณสมบัติของเรา

 

ลองเพิ่มการกระทำ "ดาวน์โหลด PDF" เป็นการกระทำแถวและการกระทำแบบรายการทั้งหมดสำหรับการทดสอบ เราสามารถเห็นปุ่ม "ดาวน์โหลด" แสดงอย่างถูกต้องในหน้าจอ userview ด้านล่าง

มื่อคลิกการกระทำแถวจะมีการดาวน์โหลดไฟล์ PDF

เมื่อมีการคลิกการกระทำทั้งรายการไฟล์ซิปจะถูกดาวน์โหลด

8. ขั้นต่อไป แชร์หรือขาย

คุณสามารถดาวน์โหลด source code จาก download_pdf_datalist_action.zip.

หากต้องการดาวน์โหลด jar ปลั๊กอินที่พร้อมใช้งานโปรดค้นหาที่ http://marketplace.joget.org/.

 

 

 

เกี่ยวกับตัวเลือกคุณสมบัติปลั๊กอินที่เราสามารถให้ในปลั๊กอินในขณะที่เครื่องมือส่งออกอีเมลฟอร์มกำลังใช้วิธีการใน FormPdfUtil เช่นกัน
  • No labels