Hello everyone,
I’m just starting out, and I would really appreciate your help.
I’m trying to use BPMN.io via a CDN, and I want to create custom elements.
At the moment, I’ve added the element to the palette, and I can also display a preview using drag-and-drop. However, I’m unable to render the custom element on the canvas.
I’ll share my code below for review. If possible, please guide me through this.
Test online: https://codesandbox.io/p/sandbox/4k4l4t
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Custom Elements</title>
<link
rel="stylesheet"
href="https://unpkg.com/bpmn-js@17.11.1/dist/assets/diagram-js.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/bpmn-js@17.11.1/dist/assets/bpmn-font/css/bpmn.css"
/>
<style>
.bpmn-task.trapezoid {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 50'%3E%3Cpolygon points='15,50 85,50 65,0 35,0' fill='%23000'%3E%3C/polygon%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
.bpmn-task.rectangle {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 50'%3E%3Crect width='100' height='50' fill='%23000'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
.bpmn-task.ellipse {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 50'%3E%3Cellipse cx='50' cy='25' rx='50' ry='25' fill='%23000'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
.bpmn-task.triangle {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 50'%3E%3Cpolygon points='50,0 0,50 100,50' fill='%23000'%3E%3C/polygon%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
#canvas {
width: 100%;
height: 600px;
border: 1px solid lightgray;
}
</style>
<script src="https://unpkg.com/bpmn-js@17.11.1/dist/bpmn-modeler.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/snapsvg@0.5.0/dist/snap.svg-min.js"></script>
</head>
<body>
<!-- Canvas Container -->
<div id="canvas"></div>
<script>
// Custom Renderer
class CustomRenderer {
constructor(eventBus, bpmnRenderer) {
this.bpmnRenderer = bpmnRenderer;
// eventBus.on("element.changed", (event) => {
// console.log("element ", event.element, " changed");
// });
eventBus.on("render.shape", (event) => {
const { gfx, element } = event;
if (element.type === "custom:rectangle") {
return this.drawRectangle(gfx, element);
} else if (element.type === "custom:ellipse") {
return this.drawEllipse(gfx, element);
} else if (element.type === "custom:triangle") {
return this.drawTriangle(gfx, element);
} else if (element.type === "custom:trapezoid") {
return this.drawTrapezoid(gfx, element);
}
});
}
drawRectangle(parentGfx, element) {
const rect = document.createElementNS(
"http://www.w3.org/2000/svg",
"rect"
);
rect.setAttribute("width", 100);
rect.setAttribute("height", 50);
rect.setAttribute("fill", "transparent");
rect.setAttribute("stroke", "black");
parentGfx.appendChild(rect);
return rect;
}
drawEllipse(parentGfx, element) {
const ellipse = document.createElementNS(
"http://www.w3.org/2000/svg",
"ellipse"
);
ellipse.setAttribute("cx", 50);
ellipse.setAttribute("cy", 25);
ellipse.setAttribute("rx", 50);
ellipse.setAttribute("ry", 25);
ellipse.setAttribute("fill", "transparent");
ellipse.setAttribute("stroke", "black");
parentGfx.appendChild(ellipse);
return ellipse;
}
drawTriangle(parentGfx, element) {
const polygon = document.createElementNS(
"http://www.w3.org/2000/svg",
"polygon"
);
polygon.setAttribute("points", "50,0 100,100 0,100");
polygon.setAttribute("fill", "transparent");
polygon.setAttribute("stroke", "black");
parentGfx.appendChild(polygon);
return polygon;
}
drawTrapezoid(parentGfx, element) {
const polygon = document.createElementNS(
"http://www.w3.org/2000/svg",
"polygon"
);
polygon.setAttribute("points", "25,0 75,0 100,50 0,50");
polygon.setAttribute("fill", "transparent");
polygon.setAttribute("stroke", "black");
parentGfx.appendChild(polygon);
return polygon;
}
}
CustomRenderer.$inject = ["eventBus", "bpmnRenderer"];
// Custom Palette Provider
class CustomPaletteProvider {
constructor(bpmnFactory, palette, create, elementFactory) {
this.bpmnFactory = bpmnFactory;
this.create = create;
this.elementFactory = elementFactory;
palette.registerProvider(this);
}
getPaletteEntries() {
const { bpmnFactory, create, elementFactory } = this;
const createShapeAction = (type) => (event) => {
const shape = elementFactory.createShape({
type: `custom:${type}`,
businessObject: {
suitable: type,
},
});
create.start(event, shape);
};
return {
"create.rectangle": {
group: "customElement",
className: "bpmn-task rectangle",
title: "Create rectangle",
action: {
dragstart: createShapeAction("rectangle"),
click: createShapeAction("rectangle"),
},
},
"create.ellipse": {
group: "customElement",
className: "bpmn-task ellipse",
title: "Create ellipse",
action: {
dragstart: createShapeAction("ellipse"),
click: createShapeAction("ellipse"),
},
},
"create.triangle": {
group: "customElement",
className: "bpmn-task triangle",
title: "Create triangle",
action: {
dragstart: createShapeAction("triangle"),
click: createShapeAction("triangle"),
},
},
"create.trapezoid": {
group: "customElement",
className: "bpmn-task trapezoid",
title: "Create trapezoid",
action: {
dragstart: createShapeAction("trapezoid"),
click: createShapeAction("trapezoid"),
},
},
};
}
}
CustomPaletteProvider.$inject = [
"bpmnFactory",
"palette",
"create",
"elementFactory",
];
var diagramXML = `<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1"/>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
<dc:Bounds x="173" y="102" width="36" height="36"/>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>`;
// Integrating custom rules and modules
var bpmnModeler = new BpmnJS({
container: "#canvas",
additionalModules: [
{
__init__: ["customRenderer", "customPaletteProvider"],
customRenderer: ["type", CustomRenderer],
customPaletteProvider: ["type", CustomPaletteProvider],
},
],
});
function logXMLOnChange(bpmnModeler) {
const eventBus = bpmnModeler.get("eventBus");
eventBus.on("commandStack.changed", function () {
bpmnModeler.saveXML({ format: true }).then(function (result) {
console.log("Updated XML after change:", result.xml);
});
});
}
// Import BPMN diagram
bpmnModeler
.importXML(diagramXML)
.then(function () {
console.log("Diagram imported successfully!");
logXMLOnChange(bpmnModeler);
})
.catch(function (err) {
console.error("Error importing diagram:", err);
});
</script>
</body>
</html>