ในบทเรียนนี้เราจะติดตาม guideline for developing a plugin เพื่อพัฒนาปลั๊กอินดาวน์โหลด PDF Datalist Action ของเรา โปรดอ้างอิงถึงบทช่วยสอนแรกสุด วิธีการพัฒนา Bean Shell Hash Variable สำหรับขั้นตอนรายละเอียดเพิ่มเติม
1.ปัญหาคืออะไร?
ราต้องการความสามารถในการดาวน์โหลดข้อมูลแบบฟอร์มเป็นไฟล์ PDF จากดาต้าลิสต์
2.ความคิดของคุณในการแก้ปัญหาคืออะไร?
เราจะพัฒนา Datalist Action Plugin เพื่อแสดงปุ่มเพื่อสร้างไฟล์ PDF ของแบบฟอร์ม
3.อินพุตที่จำเป็นสำหรับปลั๊กอินของคุณคืออะไร?
เพื่อพัฒนาปลั๊กอิน PDF Download Datalist Action เราจะพิจารณาให้สิ่งต่อไปนี้เป็นอินพุต
- Form ID : แบบฟอร์มที่จะใช้ในการสร้างไฟล์ PDF
- Record ID Column : ใช้รหัสของแถวดาต้าลิสต์หรือค่าคอลัมน์เพื่อโหลดเรคคอร์ด
- 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.
ตอนนี้เราต้องสร้าง 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/.