Skip to content

Commit

Permalink
refactor: directly override shape.createSvgCanvas
Browse files Browse the repository at this point in the history
Previously, this was done by modifying the shape prototype.
The new implementation limits side effects on application that use mxGraph directly, and don't modify code we don't own.

The change is validated by integration tests that check that the bpmn-id and the CSS classes in the DOM are correctly
set on both shapes/edges and labels.
This is also validated with the elements-identification.html demo. The CSS class is correctly applied to the label.
  • Loading branch information
tbouffard committed Mar 1, 2024
1 parent 9c8cb9a commit fd9a1ec
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 49 deletions.
7 changes: 7 additions & 0 deletions src/component/mxgraph/BpmnCellRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { mxCellState, mxImageShape, mxShape } from 'mxgraph';
import { mxgraph, mxRectangle } from './initializer';
import { MxGraphCustomOverlay } from './overlay/custom-overlay';
import { OverlayBadgeShape } from './overlay/shapes';
import { overrideCreateSvgCanvas } from './shape/utils';

export class BpmnCellRenderer extends mxgraph.mxCellRenderer {
constructor(private iconPainter: IconPainter) {
Expand Down Expand Up @@ -90,6 +91,12 @@ export class BpmnCellRenderer extends mxgraph.mxCellRenderer {
if ('iconPainter' in shape) {
shape.iconPainter = this.iconPainter;
}
overrideCreateSvgCanvas(shape);
return shape;
}

override createLabel(state: mxCellState, value: string): void {
super.createLabel(state, value);
overrideCreateSvgCanvas(state.text);
}
}
50 changes: 1 addition & 49 deletions src/component/mxgraph/config/ShapeConfigurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ limitations under the License.
import type { mxShape } from 'mxgraph';

import { ShapeBpmnElementKind } from '../../../model/bpmn/internal';
import { mxgraph, mxCellRenderer, mxConstants, mxSvgCanvas2D } from '../initializer';
import { computeAllBpmnClassNamesOfCell } from '../renderer/style-utils';
import { mxCellRenderer, mxConstants, mxSvgCanvas2D } from '../initializer';
import {
BusinessRuleTaskShape,
CallActivityShape,
Expand Down Expand Up @@ -83,7 +82,6 @@ const registerShapes = (): void => {
export default class ShapeConfigurator {
configureShapes(): void {
this.initMxSvgCanvasPrototype();
this.initMxShapePrototype();
registerShapes();
}

Expand Down Expand Up @@ -132,50 +130,4 @@ export default class ShapeConfigurator {
return css;
};
}

private initMxShapePrototype(): void {
// The following is copied from the mxgraph mxShape implementation then converted to TypeScript and enriched for bpmn-visualization
// It is needed for adding the custom attributes that permits identification of the BPMN elements in the DOM
mxgraph.mxShape.prototype.createSvgCanvas = function () {
const canvas = new mxSvgCanvas2D(this.node, false);
canvas.strokeTolerance = this.pointerEvents ? this.svgStrokeTolerance : 0;
canvas.pointerEventsValue = this.svgPointerEvents;
const off = this.getSvgScreenOffset();

if (off == 0) {
this.node.removeAttribute('transform');
} else {
this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')');
}

// START bpmn-visualization CUSTOMIZATION
// add attributes to be able to identify elements in DOM
if (this.state?.cell) {
// 'this.state.style' = the style definition associated with the cell
// 'this.state.cell.style' = the style applied to the cell: 1st element: style name = bpmn shape name
const cell = this.state.cell;
// dialect = strictHtml is set means that current node holds an HTML label
const allBpmnClassNames = computeAllBpmnClassNamesOfCell(cell, this.dialect === mxConstants.DIALECT_STRICTHTML);
const extraCssClasses = this.state.style[BpmnStyleIdentifier.EXTRA_CSS_CLASSES];
if (typeof extraCssClasses == 'string') {
allBpmnClassNames.push(...extraCssClasses.split(','));
}

this.node.setAttribute('class', allBpmnClassNames.join(' '));
this.node.dataset.bpmnId = this.state.cell.id;
}
// END bpmn-visualization CUSTOMIZATION
canvas.minStrokeWidth = this.minSvgStrokeWidth;

if (!this.antiAlias) {
// Rounds all numbers in the SVG output to integers
canvas.format = function (value: string) {
// eslint-disable-next-line unicorn/prefer-number-properties -- mxGraph code
return Math.round(parseFloat(value));
};
}

return canvas;
};
}
}
67 changes: 67 additions & 0 deletions src/component/mxgraph/shape/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright 2024 Bonitasoft S.A.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import type { mxShape } from 'mxgraph';

import { mxConstants, mxSvgCanvas2D } from '../initializer';
import { computeAllBpmnClassNamesOfCell } from '../renderer/style-utils';
import { BpmnStyleIdentifier } from '../style';

export const overrideCreateSvgCanvas = function (shape: mxShape): void {
// The following is copied from the mxgraph mxShape implementation then converted to TypeScript and enriched for bpmn-visualization
// It is needed for adding the custom attributes that permits identification of the BPMN elements in the DOM
shape.createSvgCanvas = function () {
const canvas = new mxSvgCanvas2D(this.node, false);
canvas.strokeTolerance = this.pointerEvents ? this.svgStrokeTolerance : 0;
canvas.pointerEventsValue = this.svgPointerEvents;
const off = this.getSvgScreenOffset();

if (off == 0) {
this.node.removeAttribute('transform');
} else {
this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')');
}

// START bpmn-visualization CUSTOMIZATION
// add attributes to be able to identify elements in DOM
if (this.state?.cell) {
// 'this.state.style' = the style definition associated with the cell
// 'this.state.cell.style' = the style applied to the cell: 1st element: style name = bpmn shape name
const cell = this.state.cell;
// dialect = strictHtml is set means that current node holds an HTML label
const allBpmnClassNames = computeAllBpmnClassNamesOfCell(cell, this.dialect === mxConstants.DIALECT_STRICTHTML);
const extraCssClasses = this.state.style[BpmnStyleIdentifier.EXTRA_CSS_CLASSES];
if (typeof extraCssClasses == 'string') {
allBpmnClassNames.push(...extraCssClasses.split(','));
}

this.node.setAttribute('class', allBpmnClassNames.join(' '));
this.node.dataset.bpmnId = this.state.cell.id;
}
// END bpmn-visualization CUSTOMIZATION
canvas.minStrokeWidth = this.minSvgStrokeWidth;

if (!this.antiAlias) {
// Rounds all numbers in the SVG output to integers
canvas.format = function (value: string) {
// eslint-disable-next-line unicorn/prefer-number-properties -- mxGraph code
return Math.round(parseFloat(value));
};
}

return canvas;
};
};

0 comments on commit fd9a1ec

Please sign in to comment.