Through the use of a custom renderer, and a custom replace menu provider I have successfully gotten the email task to load and save all attributes. Though I will note that I am using a custom property panel loader, the code I am unable to share.
I will list the code for the other custom components below though.
It is also important to note that in order for my custom attributes to save correctly I had to add my custom namespace to the header of namespaces.
Within a custom folder with my custom file:
Index.ts:
import CustomReplaceMenuProvider from './CustomReplaceMenuProvider';
import CustomRenderer from './CustomRenderer';
export default {
__depends__: ['popupMenu', 'bpmnReplace', 'modeling', 'bpmnFactory', 'eventBus', 'bpmnRenderer', 'textRenderer'],
__init__: ['customReplaceMenuProvider', 'CustomRenderer'],
customReplaceMenuProvider: ['type', CustomReplaceMenuProvider],
CustomRenderer: ['type', CustomRenderer]
}
email-task.json
{
"name": "MailTask",
"uri": "http://custom/schema/bpmn/moTask",
"prefix": "moTask",
"types": [
{
"name": "MailTask",
"superClass": [ "bpmn:Task" ],
"properties": [
{
"name": "mailtaskto",
"isAttr": true,
"type": "String"
},
{
"name": "mailtaskfrom",
"isAttr": true,
"type": "String"
},
{
"name": "mailtasksubject",
"isAttr": true,
"type": "String"
},
{
"name": "mailtaskcc",
"isAttr": true,
"type": "String"
},
{
"name": "mailtasktext",
"isAttr": true,
"type": "String"
},
{
"name": "mailtasktextvar",
"isAttr": true,
"type": "String"
}
]
}
]
}
Custom Renderer:
import inherits from 'inherits-browser';
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
import {
append as svgAppend,
create as svgCreate,
attr as svgAttr,
} from 'tiny-svg';
import { is } from 'bpmn-js/lib/util/ModelUtil';
export default function CustomRenderer(eventBus, textRenderer) {
BaseRenderer.call(this, eventBus, 1500);
this.canRender = function(element) {
return is(element, 'moTask:MailTask');
};
this.drawShape = function(parentNode, element) {
const rect = svgCreate('rect');
svgAttr(rect, {
width: 100,
height: 80,
rx: 10,
ry: 10,
stroke: 'black',
strokeWidth: 2,
fill: 'none'
});
svgAppend(parentNode, rect);
const imgAtters = svgCreate('image');
svgAttr(imgAtters, {
x: 5,
y: 5,
width: 20,
height: 20,
href: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M16.1 260.2c-22.6 12.9-20.5 47.3 3.6 57.3L160 376v103.3c0 18.1 14.6 32.7 32.7 32.7 9.7 0 18.9-4.3 25.1-11.8l62-74.3 123.9 51.6c18.9 7.9 40.8-4.5 43.9-24.7l64-416c1.9-12.1-3.4-24.3-13.5-31.2s-23.3-7.5-34-1.4l-448 256zm52.1 25.5L409.7 90.6 190.1 336l1.2 1L68.2 285.7zM403.3 425.4L236.7 355.9 450.8 116.6 403.3 425.4z'/%3E%3C/svg%3E"
});
svgAppend(parentNode, imgAtters);
const labelAttrs = {
box: {
width: 90,
height: 80
},
align: 'center-middle',
padding: 7,
fitBox: true
};
const text = textRenderer.createText(element.businessObject.name || 'Mail Task', labelAttrs);
svgAttr(text, { transform: 'translate(20, 0)', 'fill': 'rgb(34, 36, 42)' });
svgAppend(parentNode, text);
return rect;
}
}
inherits(CustomRenderer, BaseRenderer);
CustomRenderer.$inject = [ 'eventBus', 'textRenderer' ];
Custom Replace Menu Provider:
import { isAny } from 'bpmn-js/lib/util/ModelUtil';
import { assign } from 'lodash';
export default class CustomReplaceMenuProvider {
private readonly bpmnReplace;
private readonly popupMenu;
private readonly modeling;
private readonly bpmnFactory;
private readonly rules;
private readonly translate;
constructor(bpmnReplace, popupMenu, modeling, bpmnFactory, rules, translate) {
this.bpmnReplace = bpmnReplace;
this.popupMenu = popupMenu;
this.modeling = modeling;
this.bpmnFactory = bpmnFactory;
this.rules = rules;
this.translate = translate;
this.popupMenu.registerProvider('bpmn-replace', this);
}
getPopupMenuEntries(element) {
const entries = {};
if (isAny(element, [ 'bpmn:Task' ])) {
entries["replace-with-email-task"] = {
label: "Email Task",
className: "bpmn-icon-receive",
action: () => {
this.replaceElement(element, "moTask:MailTask", { moTask: "MailTask" });
},
};
}
return entries;
}
replaceElement(element, _newType, customOptions) {
const businessObject = this.bpmnFactory.create('moTask:MailTask');
this.modeling.updateProperties(element, {
'moTask:mailtaskto': '1',
'moTask:mailtaskfrom': '2',
'moTask:mailtasksubject': '3',
'moTask:mailtaskcc': '4',
'moTask:mailtasktext': '5',
'moTask:mailtasktextvar': '6'
});
const newElement = this.bpmnReplace.replaceElement(
element,
assign(
{ type: 'moTask:MailTask', businessObject },
customOptions
)
);
return newElement;
}
}
(CustomReplaceMenuProvider as any).$inject = [
'bpmnReplace',
'popupMenu',
'modeling',
'bpmnFactory',
'rules',
'translate',
];
Json properties for my custom property panel:
"MailTask": [
{
"id": "id",
"type": "String",
"title": "Id",
"value": "",
"description": "Unique identifier of the element.",
"popular": true
},
{
"id": "name",
"type": "String",
"title": "Name",
"value": "",
"description": "The descriptive name of the BPMN element.",
"popular": true,
"refToView": "text_name"
},
{
"id": "documentation",
"type": "Text",
"title": "Documentation",
"value": "",
"description": "Documentation of the BPMN element.",
"popular": true
},
{
"id": "mailtaskto",
"type": "String",
"title": "To",
"value": "",
"description": "The recipients if the e-mail. Multiple recipients are defined in a comma-separated list.",
"popular": true
},
{
"id": "mailtaskfrom",
"type": "String",
"title": "From",
"value": "",
"description": "The sender e-mail address. If not provided, the default configured from address is used.",
"popular": true
},
{
"id": "mailtasksubject",
"type": "String",
"title": "Subject",
"value": "",
"description": "The subject of the e-mail.",
"popular": true
},
{
"id": "mailtaskcc",
"type": "String",
"title": "Cc",
"value": "",
"description": "The cc's of the e-mail. Multiple recipients are defined in a comma-separated list",
"popular": true
},
{
"id": "mailtasktext",
"type": "String",
"title": "Content",
"value": "",
"description": "The content of the e-mail, in case one needs to send plain -rich e-mails. Can be used in combination with html, for e-mail clients that don't support rich content. The client will then fall back to this text-only alternative.",
"popular": true
},
{
"id": "mailtasktextvar",
"type": "String",
"title": "ContentVar",
"value": "",
"description": "The name of a process variable that holds the text that is the content of the e-mail, in case one needs to send plain -rich e-mails. Can be used in combination with html, for e-mail clients that don't support rich content. The client will then fall back to this text-only alternative.",
"popular": true
},
{
"id": "asynchronousdefinition",
"type": "Boolean",
"title": "Asynchronous",
"value": "false",
"description": "Define the activity as asynchronous.",
"popular": true
},
{
"id": "exclusivedefinition",
"type": "Boolean",
"title": "Exclusive",
"value": "false",
"description": "Define the activity as exclusive.",
"popular": true
},
{
"id": "executionlisteners",
"type": "multiplecomplex",
"title": "Execution listeners",
"value": "",
"description": "Listeners for an activity, process, sequence flow, start and end event.",
"popular": true
},
{
"id": "multiinstance_type",
"type": "multiinstance-type",
"title": "Multi-instance type",
"value": "",
"description": "Repeated activity execution (parallel or sequential) can be displayed through different loop types",
"popular": true,
"refToView": "multiinstance"
},
{
"id": "multiinstance_cardinality",
"type": "String",
"title": "Cardinality (Multi-instance)",
"value": "",
"description": "Define the cardinality of multi instance.",
"popular": true
},
{
"id": "multiinstance_collection",
"type": "String",
"title": "Collection (Multi-instance)",
"value": "",
"description": "Define the collection for the multi instance.",
"popular": true
},
{
"id": "multiinstance_variable",
"type": "String",
"title": "Element variable (Multi-instance)",
"value": "",
"description": "Define the element variable for the multi instance.",
"popular": true
},
{
"id": "multiinstance_condition",
"type": "String",
"title": "Completion condition (Multi-instance)",
"value": "",
"description": "Define the completion condition for the multi instance.",
"popular": true
},
{
"id": "isforcompensation",
"type": "Boolean",
"title": "Is for compensation",
"value": "false",
"description": "A flag that identifies whether this activity is intended for the purposes of compensation.",
"popular": true,
"refToView": "compensation"
}
],
within my main file:
bpmnJS: BpmnJS = new BpmnJS({
additionalModules: [
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
CustomReplaceModule
],
moddleExtensions: {
moTask: emailTask
}
});