1function extractXml(data) { 2 return new TextDecoder() 3 .decode(Uint8Array.from(atob(data), (c) => c.charCodeAt(0))) 4 .trim(); 5} 6 7function getErrorMessage(error, fallbackMessage) { 8 if (error instanceof Error && error.message) { 9 return error.message; 10 } 11 12 if (typeof error === "string" && error.length > 0) { 13 return error; 14 } 15 16 return fallbackMessage; 17} 18 19function getPluginRoot(target) { 20 if (!(target instanceof Element)) { 21 return null; 22 } 23 24 return target.closest(".plugin-bpmnio") ?? target; 25} 26 27function getStatusElement(target) { 28 const root = getPluginRoot(target); 29 if (!root) { 30 return null; 31 } 32 33 let status = root.querySelector(".plugin_bpmnio_status"); 34 if (!status) { 35 status = document.createElement("div"); 36 status.className = "plugin_bpmnio_status"; 37 root.append(status); 38 } 39 40 return status; 41} 42 43function clearStatusMessage(target) { 44 const status = getPluginRoot(target)?.querySelector(".plugin_bpmnio_status"); 45 if (status) { 46 status.remove(); 47 } 48} 49 50function showStatusMessage(target, message) { 51 const status = getStatusElement(target); 52 if (!status) { 53 return; 54 } 55 56 status.textContent = message; 57 status.style.color = "red"; 58} 59 60function showContainerError(container, message) { 61 clearStatusMessage(container); 62 container.textContent = message; 63 container.style.color = "red"; 64} 65 66function clearContainerError(container) { 67 container.style.color = ""; 68 clearStatusMessage(container); 69} 70 71function getLayerBounds(canvas) { 72 const layer = canvas?.getActiveLayer?.(); 73 if (!layer || typeof layer.getBBox !== "function") { 74 return null; 75 } 76 77 const bounds = layer.getBBox(); 78 if (!bounds) { 79 return null; 80 } 81 82 const values = [bounds.x, bounds.y, bounds.width, bounds.height]; 83 if (!values.every(Number.isFinite)) { 84 return null; 85 } 86 87 return bounds; 88} 89 90async function renderDiagram(xml, container, viewer, computeSizeFn) { 91 try { 92 clearContainerError(container); 93 await viewer.importXML(xml); 94 95 if (!computeSizeFn) return; 96 97 const zoom = getZoomFactor(container); 98 const layout = computeSizeFn(viewer, zoom); 99 if (!layout) return; 100 101 container.style.height = `${layout.scaledHeight}px`; 102 container.style.width = `${layout.scaledWidth}px`; 103 104 if (typeof layout.applyZoom === "function") { 105 layout.applyZoom(); 106 } 107 } catch (err) { 108 showContainerError( 109 container, 110 getErrorMessage(err, "Unable to render diagram.") 111 ); 112 } 113} 114 115function getZoomFactor(container) { 116 const zoom = Number.parseFloat(container.dataset.zoom ?? "1"); 117 118 if (!Number.isFinite(zoom) || zoom <= 0) { 119 return 1; 120 } 121 122 return zoom; 123} 124 125function computeBpmnDiagramSize(viewer, zoom) { 126 const canvas = viewer.get("canvas"); 127 const bboxViewport = getLayerBounds(canvas); 128 if (!bboxViewport) { 129 return undefined; 130 } 131 132 const width = bboxViewport.width + 4; 133 const height = bboxViewport.height + 4; 134 135 return { 136 width, 137 height, 138 scaledWidth: Math.max(width * zoom, 1), 139 scaledHeight: Math.max(height * zoom, 1), 140 applyZoom() { 141 canvas.resized(); 142 canvas.viewbox({ 143 x: bboxViewport.x - 2, 144 y: bboxViewport.y - 2, 145 width, 146 height, 147 }); 148 }, 149 }; 150} 151 152function computeDmnDiagramSize(viewer, zoom) { 153 const activeView = viewer.getActiveView(); 154 if (!activeView || activeView.type !== "drd") { 155 return undefined; 156 } 157 158 const activeEditor = viewer.getActiveViewer(); 159 160 const canvas = activeEditor?.get("canvas"); 161 const bboxViewport = getLayerBounds(canvas); 162 if (!bboxViewport) { 163 return undefined; 164 } 165 166 const width = bboxViewport.width + 4; 167 const height = bboxViewport.height + 4; 168 return { 169 width, 170 height, 171 scaledWidth: Math.max(width * zoom, 1), 172 scaledHeight: Math.max(height * zoom, 1), 173 applyZoom() { 174 canvas.resized(); 175 canvas.viewbox({ 176 x: bboxViewport.x - 2, 177 y: bboxViewport.y - 2, 178 width, 179 height, 180 }); 181 }, 182 }; 183} 184 185async function renderBpmnDiagram(xml, container) { 186 const BpmnViewer = window.BpmnJS?.Viewer; 187 if (typeof BpmnViewer !== "function") { 188 throw new Error("BPMN viewer library is unavailable."); 189 } 190 191 const viewer = new BpmnViewer({ container }); 192 193 return renderDiagram(xml, container, viewer, computeBpmnDiagramSize); 194} 195 196async function renderDmnDiagram(xml, container) { 197 const DmnViewer = window.DmnJSViewer; 198 if (typeof DmnViewer !== "function") { 199 throw new Error("DMN viewer library is unavailable."); 200 } 201 202 const viewer = new DmnViewer({ container }); 203 204 return renderDiagram(xml, container, viewer, computeDmnDiagramSize); 205} 206 207async function exportDataBase64(editor) { 208 try { 209 if (typeof editor?.saveXML !== "function") { 210 return null; 211 } 212 213 const options = { format: true }; 214 const result = await editor.saveXML(options); 215 const { xml } = result; 216 if (typeof xml === "string" && xml.length > 0) { 217 const encoder = new TextEncoder(); 218 const data = encoder.encode(xml); 219 return btoa(String.fromCharCode(...data)); 220 } 221 } catch { 222 return null; 223 } 224 225 return null; 226} 227 228function addFormSubmitListener(editor, container) { 229 const form = document.getElementById("dw__editform"); 230 if (!form) { 231 showStatusMessage(container, "Editor form is unavailable."); 232 return; 233 } 234 235 if (form.dataset.pluginBpmnioListenerBound === "true") { 236 return; 237 } 238 239 form.dataset.pluginBpmnioListenerBound = "true"; 240 form.addEventListener("submit", async (event) => { 241 if (form.dataset.pluginBpmnioSubmitting === "true") { 242 delete form.dataset.pluginBpmnioSubmitting; 243 return; 244 } 245 246 event.preventDefault(); 247 248 const field = form.querySelector('input[name="plugin_bpmnio_data"]'); 249 if (!field) { 250 showStatusMessage(container, "Diagram data field is unavailable."); 251 return; 252 } 253 254 clearStatusMessage(container); 255 const data = await exportDataBase64(editor); 256 if (!data) { 257 showStatusMessage(container, "Unable to save diagram changes."); 258 return; 259 } 260 261 field.value = data; 262 form.dataset.pluginBpmnioSubmitting = "true"; 263 264 if (typeof form.requestSubmit === "function") { 265 form.requestSubmit(event.submitter); 266 return; 267 } 268 269 delete form.dataset.pluginBpmnioSubmitting; 270 form.submit(); 271 }); 272} 273 274async function renderBpmnEditor(xml, container) { 275 const BpmnEditor = window.BpmnJS; 276 if (typeof BpmnEditor !== "function") { 277 throw new Error("BPMN editor library is unavailable."); 278 } 279 280 const editor = new BpmnEditor({ container }); 281 addFormSubmitListener(editor, container); 282 return renderDiagram(xml, container, editor, null); 283} 284 285async function renderDmnEditor(xml, container) { 286 const DmnEditor = window.DmnJS; 287 if (typeof DmnEditor !== "function") { 288 throw new Error("DMN editor library is unavailable."); 289 } 290 291 const editor = new DmnEditor({ container }); 292 addFormSubmitListener(editor, container); 293 return renderDiagram(xml, container, editor, null); 294} 295 296function startRender(fn, xml, container) { 297 Promise.resolve(fn(xml, container)).catch((error) => { 298 showContainerError( 299 container, 300 getErrorMessage(error, "Unable to initialize diagram.") 301 ); 302 }); 303} 304 305function safeRender(tag, type, fn) { 306 try { 307 const root = jQuery(tag); 308 const containerId = "." + type + "_js_container"; 309 const container = root.find(containerId)[0]; 310 if (!container) { 311 showStatusMessage(tag, "Diagram container is missing."); 312 return; 313 } 314 315 // avoid double rendering 316 if (container.children?.length > 0) return; 317 318 const dataId = "." + type + "_js_data"; 319 const data = root.find(dataId)[0]; 320 if (!data) { 321 showContainerError(container, "Diagram data is missing."); 322 return; 323 } 324 325 const xml = extractXml(data.textContent); 326 327 if (xml.startsWith("Error:")) { 328 showContainerError(container, xml); 329 return; 330 } 331 332 startRender(fn, xml, container); 333 } catch (err) { 334 showStatusMessage( 335 tag, 336 getErrorMessage(err, "Unable to initialize diagram.") 337 ); 338 } 339} 340 341jQuery(document).ready(function () { 342 jQuery("div[id^=__bpmn_js_]").each((_, tag) => 343 safeRender(tag, "bpmn", renderBpmnDiagram) 344 ); 345 jQuery("div[id^=__dmn_js_]").each((_, tag) => 346 safeRender(tag, "dmn", renderDmnDiagram) 347 ); 348 jQuery("div[id=plugin_bpmnio__bpmn_editor]").each((_, tag) => 349 safeRender(tag, "bpmn", renderBpmnEditor) 350 ); 351 jQuery("div[id=plugin_bpmnio__dmn_editor]").each((_, tag) => 352 safeRender(tag, "dmn", renderDmnEditor) 353 ); 354}); 355