【JavaFx 构建Bpmn-JS 设计器】
【JavaFx 构建Bpmn-JS 设计器】
-
简介
bpmn-js设计器web端组件非常多各式各样的, bpmn-js也提供很多实例,但是都只是功能实现,完全拿来使用可能并不能满足需求,经过一段时间的资料收集与研究,在javafx中实现了bpmn设计器组件功能的实现.现将核心实现分享与大家,希望共同探讨研究.
-
实例效果
当前JavaFx 构建Bpmn-JS 设计器效果如下:
-
引入框架
- Bpmn资源:
https://bpmn.io/toolkit/bpmn-js/ - Bpmn examples:
https://github.com/bpmn-io/bpmn-js-examples
- Bpmn资源:
-
功能实现
-
bpmn-designer Bpmn 设计器组件实现
-
项目结构
-
BpmnComponent 组件实现
package com.liangchao.cloud.bpmn;import javafx.concurrent.Worker;import javafx.scene.Node;import javafx.scene.web.WebView;import javafx.util.Callback;import netscape.javascript.JSObject;import java.io.BufferedReader;import java.io.File;import java.io.FileReader;public class BpmnComponent { privateWebView webView; private static final String HTML_PATH = "/public/index.html"; privateBpmnScript bpmnScript;//用户回调函数管理 public BpmnComponent() { this.webView = new WebView(); } /** * 获取组件对象 */ public Node getNode() { // 测试阶段 // VBox vBox = new VBox(); // vBox.getChildren().addAll( // new ToolBar( // new Button("创建") {{ //setOnAction(event -> { // createNewDiagram(); //}); // }}, // new Button("放大") {{ //setOnAction(event -> { // zoomInAction(); //}); // }}, // new Button("缩小") {{ //setOnAction(event -> { // zoomOutAction(); //}); // }}, // new Button("还原") {{ //setOnAction(event -> { // zoomResetAction(); //}); // }}, // new Button("保存BPMN") {{ //setOnAction(event -> { // savaBpmnXmlAction(); //}); // }}, // new Button("保存SVG") {{ //setOnAction(event -> { // savaBpmnPictureAction(); //}); // }}, // new Button("获取bpmn数据") {{ //setOnAction(event -> { // getBpmnXmlAction(new Callback() { // @Override // public Object call(Object param) { // System.out.println(param.toString()); // return true; // } // }); //}); // }}, // new Button("导入BPMN") {{ //setOnAction(event -> { // String xml = file(new File("C:\\Users\\Administrator\\Desktop\\qingjia.bpmn")); // openDiagram(xml); //}); // }} // ) // ); // // vBox.getChildren().add(webView); // return vBox; // 正式阶段 return webView; } /** * 初始化节点数据 */ public BpmnComponent binds() { // 使用默认BpmnScript if (this.bpmnScript == null) { this.bpmnScript = new BpmnScriptImpl(webView); } // 初始化 webView webView.getEngine().setOnAlert(event -> { System.out.println(event.getData()); }); webView.getEngine().load(this.getClass().getResource(HTML_PATH).toExternalForm()); webView.getEngine().getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> { if (newValue == Worker.State.SUCCEEDED) { JSObject win = (JSObject) webView.getEngine().executeScript("window"); // 然后把应用程序对象设置成为js对象 win.setMember("bpmnScript", bpmnScript); } }); return this; } /** * 执行保存当前bpmn数据对象 */ public void savaBpmnXmlAction() { this.bpmnScript.executeScript("savaBpmnXml"); } /** * 执行保存当前bpmn 图片数据对象 */ public void savaBpmnPictureAction() { this.bpmnScript.executeScript("savaBpmnPicture"); } /** * 创建bpmn对象 */ public void createNewDiagram() { this.bpmnScript.executeScript("createNewDiagram"); } /** * 放大 */ public void zoomInAction() { this.bpmnScript.executeScript("zoomInAction"); } /** * 缩小 */ public void zoomOutAction() { this.bpmnScript.executeScript("zoomOutAction"); } /** * 还原 */ public void zoomResetAction() { this.bpmnScript.executeScript("zoomResetAction"); } /** * 用户获取bpmn xml数据处理 */ public void getBpmnXmlAction(Callback callback) { if (callback != null) { this.bpmnScript.setGetBpmnXmlCallback(callback); this.bpmnScript.executeScript("getBpmnXml"); } } /** * 用户导入bomn */ private void openDiagram(String xml) { this.bpmnScript.executeScript("openDiagram", xml); } /** * 导入bpmn数据 */ public void openDiagramAction(String file) { openDiagram(file(new File(file))); } /** * 导入bpmn数据 */ public void openDiagramAction(File file) { openDiagram(file(file)); } /** * 获取bpmn数据 */ public String file(File file) { String result = ""; try { //构造一个BufferedReader类来读取文件 BufferedReader br = new BufferedReader(new FileReader(file)); String s = null; //使用readLine方法,一次读一行 while ((s = br.readLine()) != null) { result = result + "\n" + s; } br.close(); } catch (Exception e) { e.printStackTrace(); } return result; }}
-
BpmnScript 调用Bpmn 页面脚本
package com.liangchao.cloud.bpmn;import javafx.util.Callback;/** * Bpmn 回调接口 */public interface BpmnScript { /** * 执行js调用函数 * * @param method 函数名称 * @param param 参数 */ Object executeScript(String method, String... param); /** * 保存bpmn xml 数据 * * @param xml bpmn 数据 */ boolean savaBpmn(String xml); /** * 获取bpmn xml 数据 * * @param xml bpmn xml 数据 */ void getBpmnXml(String xml); /** * 获取bpmn xml 数据 用户端回调函数处理 * * @param callback 回调处理 */ void setGetBpmnXmlCallback(Callback callback); /** * 保存bpmn svg 数据 * * @param data svg数据 */ boolean savaBpmnSvg(String data); /** * WebView 页面消息返回调用请求处理 * * @param msg 消息 */ void msg(String... msg);}
-
BpmnScript 抽象类 AbstractBpmnScript
package com.liangchao.cloud.bpmn;import javafx.concurrent.Worker;import javafx.scene.Node;import javafx.scene.web.WebView;import javafx.stage.FileChooser;import java.io.File;import java.io.UnsupportedEncodingException;import java.net.URLDecoder;public abstract class AbstractBpmnScript implements BpmnScript { private WebView node; protected void setNode(WebView node) { this.node = node; } /** * 执行js调用函数 * * @param method 函数名称 * @param param 参数 */ public Object executeScript(String method, String... param) { if (!node.getEngine().getLoadWorker().getState().equals(Worker.State.SUCCEEDED)) { throw new RuntimeException("WebView State :" + node.getEngine().getLoadWorker().getState() + ", 状态不可用"); } String jsHandle = ""; for (Object object : param) { if (object != null) { try { // 进行转码处理 jsHandle += "," + "\'" + URLDecoder.decode(object.toString().replaceAll("\r|\n", ""), "UTF-8") + "\'"; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } jsHandle = method + "(" + (jsHandle.length() > 1 ? jsHandle.substring(1) : "") + ")"; System.out.println("JS 调用函数:" + jsHandle); return node.getEngine().executeScript(jsHandle); } public Object fileChooser(String title, File openFile, Type type, FileChooser.ExtensionFilter... filters) { return fileChooser(this.node, title, openFile, type, filters); } /** * 创建 FileChooser * * @param node 绑定对象 * @param title 标题 * @param openFile 默认打开位置 * @param type 打开类型 * @param filters //过滤 */ public static Object fileChooser(Node node, String title, File openFile, Type type, FileChooser.ExtensionFilter... filters) { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(title); fileChooser.setInitialDirectory(openFile == null ? new File(System.getProperty("user.home")) : openFile ); if (filters != null) { fileChooser.getExtensionFilters().addAll(filters); } Object files = null; switch (type) { case OPEN: files = fileChooser.showOpenDialog(node.getScene().getWindow()); break; case OPEN_MULTIPLE: files = fileChooser.showOpenMultipleDialog(node.getScene().getWindow()); break; case SAVA: files = fileChooser.showSaveDialog(node.getScene().getWindow()); break; } return files; } public enum Type { /** * 打开 */ OPEN, /** * 打开多个 */ OPEN_MULTIPLE, /** * 保存 */ SAVA; }}
-
Bpmn-js 属性面板组件核心调用js处理
import $ from 'jquery';import BpmnModeler from 'bpmn-js/lib/Modeler';import {BpmnPropertiesPanelModule, BpmnPropertiesProviderModule, CamundaPlatformPropertiesProviderModule} from 'bpmn-js-properties-panel';import diagramXML from '../resources/newDiagram.bpmn';/** ************************************* **//** 属性面板组件**//** ************************************* **/// ZeebePropertiesProviderModule// import ZeebeBpmnModdle from 'zeebe-bpmn-moddle/resources/zeebe.json'import CamundaBpmnModdle from 'camunda-bpmn-moddle/resources/camunda.json'import customTranslate from './i18n/customTranslate/customTranslate';/** ************************************* **//** 国际化组件 **//** ************************************* **/var customTranslateModule = { translate: ['value', customTranslate]};/** ************************************* **//** bpmn 创建对象 **//** ************************************* **/var container = $('#js-drop-zone');var canvas = $('#js-canvas');var bpmnModeler = new BpmnModeler({ container: canvas, propertiesPanel: { parent: '#js-properties-panel' }, additionalModules: [ BpmnPropertiesPanelModule, BpmnPropertiesProviderModule, // ZeebePropertiesProviderModule CamundaPlatformPropertiesProviderModule, // 国际化 customTranslateModule ], moddleExtensions: { // zeebe: ZeebeBpmnModdle camunda: CamundaBpmnModdle }});// 移除样式 with-diagramcontainer.removeClass('with-diagram');/** * 创建新Bpmn */function createNewDiagram() { openDiagram(diagramXML);}/** * 打开bpmn xml文件 */async function openDiagram(xml) { try { await bpmnModeler.importXML(xml); container .removeClass('with-error') .addClass('with-diagram'); } catch (err) { container .removeClass('with-diagram') .addClass('with-error'); container.find('.error pre').text(err.message); console.error(err); }}function registerFileDrop(container, callback) { function handleFileSelect(e) { e.stopPropagation(); e.preventDefault(); var files = e.dataTransfer.files; var file = files[0]; var reader = new FileReader(); reader.onload = function (e) { var xml = e.target.result; callback(xml); }; reader.readAsText(file); } function handleDragOver(e) { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy. } container.get(0).addEventListener('dragover', handleDragOver, false); container.get(0).addEventListener('drop', handleFileSelect, false);}// file drag / drop ///// check file api availabilityif (!window.FileList || !window.FileReader) { window.alert( 'Looks like you use an older browser that does not support drag and drop. ' + 'Try using Chrome, Firefox or the Internet Explorer > 10.');} else { registerFileDrop(container, openDiagram);}// bootstrap diagram functions$(function () { // 绑定创建bpmn $('#js-create-diagram').click(function (e) { e.stopPropagation(); e.preventDefault(); createNewDiagram(); });});/** * 保存bpmn */async function exportDiagram() { try { var result = await bpmnModeler.saveXML({format: true}); // console.log('DIAGRAM', result.xml); return result.xml; } catch (err) { console.error('could not save BPMN 2.0 diagram', err); } return null;}/** * 保存 SVG */async function exportSvgDiagram() { try { const {svg} = await bpmnModeler.saveSVG(); var encodedData = encodeURIComponent(svg); // return 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData; return encodedData; } catch (err) { console.error('Error happened saving SVG: ', err); } return null;}/***********************************************//** 提升作用域绑定windows ************************//***********************************************/window.createNewDiagram = function () { createNewDiagram();}/** * 绑定createNewDiagram */BpmnFactory.createNewDiagram = function () { createNewDiagram();}/** * 绑定重置大小 */BpmnFactory.zoomResetAction = function () { bpmnModeler.get("zoomScroll").reset();}/** * 绑定放大 */BpmnFactory.zoomInAction = function () { bpmnModeler.get("zoomScroll").stepZoom(1);}/** * 绑定缩小 */BpmnFactory.zoomOutAction = function () { bpmnModeler.get("zoomScroll").stepZoom(-1);}/** * 绑定打开Bpmn数据文件 * @param xml bpmn Xml数据文件 */BpmnFactory.openXmlDiagram = function (xml) { openDiagram(xml);}/** * 保存bpmn xml 数据 * @returns 返回bpmn xml 数据 */BpmnFactory.savaBpmnXml = function () { return exportDiagram();}/** * 保存bpmn svg 图片 * @returns 返回bpmn svg 图片 */BpmnFactory.savaBpmnSvg = function () { return exportSvgDiagram();}/** * 打开bpmn数据文件 * @param xml bpmn 数据 */BpmnFactory.openDiagram = function (xml) { openDiagram(xml);}
-
-
Bpmn JavaFx 调用 bpmn-designer组件
package com.liangchao.cloud.ui.controller.approve;import com.liangchao.cloud.bpmn.AbstractBpmnScript;import com.liangchao.cloud.bpmn.BpmnComponent;import com.liangchao.cloud.bpmn.BpmnScriptImpl;import com.liangchao.cloud.utils.javafx.SupperController;import com.liangchao.cloud.utils.javafx.Win;import de.felixroske.jfxsupport.FXMLController;import javafx.fxml.FXML;import javafx.scene.Node;import javafx.scene.control.TextArea;import javafx.scene.input.MouseEvent;import javafx.scene.layout.Priority;import javafx.scene.layout.VBox;import javafx.stage.FileChooser;import java.io.File;import java.io.UnsupportedEncodingException;/** * BPMN 设计器 */@FXMLControllerpublic class BpmnDesignerUIController extends SupperController { @FXML private VBox webViewBox; private BpmnComponent bpmnComponent; public void getBpmnXmlAction(MouseEvent mouseEvent) { bpmnComponent.getBpmnXmlAction(param -> { Win.open(new TextArea(param.toString()) {{ setPrefWidth(700); setPrefHeight(400); }}, Win.SwitchBtn.OK); return true; }); } public void importBpmnAction(MouseEvent mouseEvent) throws UnsupportedEncodingException { File file = (File) BpmnScriptImpl.fileChooser(webViewBox, "选择Bpmn文件", null, AbstractBpmnScript.Type.OPEN, new FileChooser.ExtensionFilter("Bpmn", "*.bpmn")); if (file != null) { bpmnComponent.openDiagramAction(file); } } @Override public void initNodesBus() { webViewBox.getChildren().clear(); bpmnComponent = new BpmnComponent().binds(); Node node = bpmnComponent.getNode(); webViewBox.getChildren().add(node); VBox.setVgrow(node, Priority.ALWAYS); } public void newBpmnAction(MouseEvent mouseEvent) { bpmnComponent.createNewDiagram(); } public void savaBpmnSvgAction(MouseEvent mouseEvent) { bpmnComponent.savaBpmnPictureAction(); } public void savaBpmnXmlAction(MouseEvent mouseEvent) { bpmnComponent.savaBpmnXmlAction(); } public void zoomInAction(MouseEvent mouseEvent) { bpmnComponent.zoomInAction(); } public void zoomOutAction(MouseEvent mouseEvent) { bpmnComponent.zoomOutAction(); } public void zoomResetAction(MouseEvent mouseEvent) { bpmnComponent.zoomResetAction(); }}
-
- 项目缺陷
该功能组件虽然实现了,但是放在JavaFx中由于WebView解析问题,可能产生卡顿,效率等问题,需要优化WebView组件.
- 完整dome视频欣赏
该项目实现功能已经生成视频,地址:https://www.kuaishou.com/f/X2iaKlRJG1l26d 有兴趣的可以去看看.
二维码扫描直接查看: