OK wall of code time:
import diagramStyle from 'bpmn-js/dist/assets/diagram-js.css'; // eslint-disable-line no-unused-vars
import bpmnStyle from 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import icons from 'bpmn-font/dist/css/bpmn-embedded.css'; // eslint-disable-line no-unused-vars
import commentsStyle from 'bpmn-js-embedded-comments/assets/comments.css';
import propertiesStyle from 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css';
import appStyle from './css/app.css';
// Yes this is technically loaded twice, but but the load in html makes it easier to find where the css items are to edit and debug.
// Since it is loaded here though that means the app.css is technically the last thing to set any CSS rule thus I can more easily override
// classes and rules set in the other CSS files previously loaded.
import $ from 'jquery';
// import CustomModeler from './custom-modeler'; // This already calls BpmnModeler inside of itself.
// import CustomModeler from './custom-modeler-reorganized'; // This already calls BpmnModeler inside of itself.
import BpmnModeler from 'bpmn-js/lib/Modeler';
import diagramXML from '../resources/newDiagram.bpmn';
import EmbeddedComments from 'bpmn-js-embedded-comments';
// import nyanObjectModule from './custom-modeler-reorganized/nyan';
import propertiesPanelModule from 'bpmn-js-properties-panel';
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/bpmn';
import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda';
import qaPackage from './moddleExtensions/qaPackage.json'
import urlRefPackage from './moddleExtensions/urlRefPackage.json'
import colorPickerModule from './color-picker';
import { b64EncodeUnicode, b64DecodeUnicode} from './functions/b64_unicode.js';
import { apiGet, apiPost } from './functions/apiGetPost.js';
var container = $('#js-drop-zone');
// window.modeler = new CustomModeler({
window.modeler = new BpmnModeler({
container: '#js-canvas',
propertiesPanel: {
parent: '#js-properties'
},
additionalModules: [
// nyanObjectModule,
colorPickerModule,
EmbeddedComments,
propertiesPanelModule,
propertiesProviderModule
],
moddleExtensions: {
camunda: camundaModdleDescriptor,
qa: qaPackage,
urlRef: urlRefPackage
},
keyboard: {
bindTo: document
}
});
window.modelerEventBus = modeler.get('eventBus');
window.modelerChanged = false;
window.currentProject = { username: "", foldername: "", filename: "" };
window.file_set_username = file_set_username;
function file_set_username(newName, event) {
// console.log("file_set_username: "+newName);
// console.log(event);
window.currentProject.username = newName;
document.getElementById("leftSidebar-username").value = newName;
}
window.file_set_foldername = file_set_foldername;
function file_set_foldername(newName, event) {
// console.log("file_set_foldername: "+newName);
// console.log(event);
window.currentProject.foldername = newName;
document.getElementById("leftSidebar-foldername").value = newName;
}
window.file_set_filename = file_set_filename;
function file_set_filename(newName, event) {
// console.log("file_set_filename: "+newName);
// console.log(event);
window.currentProject.filename = newName;
document.getElementById("leftSidebar-filename").value = newName;
if (modeler._definitions) { modeler._definitions.$attrs['diagram-name'] = newName; }
let diagramName = document.getElementById("diagramName")
diagramName.value = newName;
diagramName.size = newName.trim().length;
}
window.file_clean_up = file_clean_up;
function file_clean_up() {
file_set_username(window.currentProject.username.trim());
file_set_foldername(window.currentProject.foldername.trim());
file_set_filename(window.currentProject.filename.trim());
}
function createNewDiagram() {
openDiagram(diagramXML);
}
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
modeler.on('element.click', function(event) {
// console.log("modeler on element click");
let element = event.element,
moddle = modeler.get('moddle'),
// the underlaying BPMN 2.0 element
businessObject = element.businessObject,
analysis,
score,
message,
url_ref;
function getExtension(element, type) {
if (!element.extensionElements) {
return null;
}
console.log(element.extensionElements);
return element.extensionElements.values.filter(function(e) {
return e.$instanceOf(type);
})[0];
}
// analysis = getExtension(businessObject, 'qa:AnalysisDetails');
// console.log(analysis);
// if (analysis) {
// score = businessObject.suitable;
// if (isNaN(score)) {
// message = 'No suitability score yet, dblclick to assign one';
// } else {
// message = 'Diagram element has a suitability score of ' + score;
// }
// if (analysis) {
// message += '\n Last analyzed at ' + analysis.lastChecked;
// }
// window.alert(message);
// }
url_ref = getExtension(businessObject, 'url:ref');
if (!url_ref) {
url_ref = moddle.create('url:ref');
if (!businessObject.extensionElements) {
businessObject.extensionElements = moddle.create('bpmn:ExtensionElements');
}
url_ref.location = "https://www.google.com/";
businessObject.extensionElements.get('values').push(url_ref);
console.log(businessObject);
}
});
modeler.on('import.parse.complete', ({ error, definitions, context }) => {
// manipulate definitions before import
if(definitions.id === "sample-diagram") definitions.id = guid();
// console.log(definitions);
// document.querySelector('#diagramName').innerText = definitions.$attrs['diagram-name'] || "Diagram Name";
let newName = definitions.$attrs['diagram-name'] || "Diagram Name";
file_set_filename(newName);
file_clean_up();
// make sure to return the manipulated defintions
return definitions;
});
// This takes care of updates and changes to the name of the document. I.E. the file name.
// document.getElementById("diagramName").addEventListener("input", updateFileName, false);
// function updateFileName(event) { modeler._definitions.$attrs['diagram-name'] = document.querySelector('#diagramName').innerText; }
function openDiagram(xml) {
modeler.importXML(xml, function(err) {
if (err) {
container
.removeClass('with-diagram')
.addClass('with-error');
container.find('.error pre').text(err.message);
console.error(err);
} else {
container
.removeClass('with-error')
.addClass('with-diagram');
$('.show-with-diagram').addClass('shown-with-diagram').removeClass('show-with-diagram');
}
modeler.on('saveXML.start', ({ definitions }) => {
definitions.$attrs['diagram-name'] = window.currentProject.filename.trim();
return definitions;
});
modeler.on('saveXML.serialized', ({ xml }) => {
//this is where we will send it to the database to save
//console.log(xml);
return xml;
});
modeler.get('canvas').zoom('fit-viewport');
});
}
function saveSVG(done) {
modeler.saveSVG(done);
}
function saveDiagram(done) {
modeler.saveXML({ format: true }, function(err, xml) {
done(err, xml);
});
}
function loadFromDB(event) {
let prompt_filename = window.prompt("Filename to load:");
prompt_filename = prompt_filename.trim();
if (prompt_filename === "") return;
let data_id = {
"csrfToken": csrfToken,
"bpmn_username": window.currentProject.username.trim() || "jeremy.mone",
"bpmn_foldername": window.currentProject.foldername.trim() || "testFolder2",
"bpmn_filename": prompt_filename.trim()
};
apiPost("api/api.php?args=/bpmn_load/true", JSON.stringify(data_id),
function(result){ // Success
// console.log('success!');
// console.log(result);
let bpmn_json = JSON.parse(result.responseText);
// console.log(bpmn_json);
// console.log(bpmn_json.results[0].data.length);
// console.log(bpmn_json.results[0].data);
// console.log(atob(bpmn_json.results[0].data));
// let loaded_xml = decodeURIComponent(atob(bpmn_json.results[0].data));
let loaded_xml = b64DecodeUnicode(bpmn_json.results[0].data);
// console.log(loaded_xml);
openDiagram(loaded_xml);
file_set_username(bpmn_json.results[0].username);
file_set_foldername(bpmn_json.results[0].foldername);
file_set_filename(bpmn_json.results[0].filename);
file_clean_up();
},
function(result){ // Fail
console.log('The request failed!');
console.log(xhr);
}
);
}
// Added a button in html and this is the setting up and function for the click action of the button to save to db.
function saveToDB(event) {
// console.log("saveToDB", event);
modeler.saveXML({ format: true }, function(err, xml) {
if (err) { console.log(err); } // We got an error so we show an error.
else if (xml) { // We got data back. Assuming this is good.
// var bpmn_base64_xml = btoa(encodeURIComponent(xml));
var bpmn_base64_xml = b64EncodeUnicode(xml);
// console.log(bpmn_base64_xml.length);
file_clean_up();
let data_output = {
"csrfToken": csrfToken,
"bpmn_username": window.currentProject.username.trim() || "jeremy.mone",
"bpmn_foldername": window.currentProject.foldername.trim() || "testFolder2",
"bpmn_filename": window.currentProject.filename.trim(),
"bpmn_data": bpmn_base64_xml
};
// apiGet("api/api.php?args=/bpmn_load/true", function(result){
// console.log('success!', result);
// let bpmn_json = JSON.parse(result.responseText);
// console.log(bpmn_json);
// },
// function(result){
// console.log('The request failed!');
// console.log(xhr);
// });
apiPost("api/api.php?args=/bpmn_update/true", JSON.stringify(data_output),
function(result){ // Success
// console.log('success!');
// console.log(result);
let bpmn_json = JSON.parse(result.responseText);
// console.log(bpmn_json);
},
function(result){ // Fail
console.log('The request failed!');
console.log(xhr);
}
);
} else { // We got nothing on either err or xml. No idea what would do this, but noting something wrong.
console.error("saveToDB - modeler.saveXML: err and xml both empty or invalid.");
}
});
}
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 availability
if (!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() {
$('#js-create-diagram').click(function(e) {
e.stopPropagation();
e.preventDefault();
createNewDiagram();
});
var downloadLink = $('#js-download-diagram');
var downloadSvgLink = $('#js-download-svg');
$('.buttons a').click(function(e) {
if (!$(this).is('.active')) {
e.preventDefault();
e.stopPropagation();
}
});
function setEncoded(link, name, data) {
var encodedData = encodeURIComponent(data);
if (data) {
link.addClass('active').attr({
'href': 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData,
'download': name.replace(/[\^\/:\*\?"<>\|]/, '-').replace(/ /g, '_')
});
} else {
link.removeClass('active');
}
}
var exportArtifacts = debounce(function() {
// console.log("exportArtifacts called");
modelerChanged = true;
saveSVG(function(err, svg) {
setEncoded(downloadSvgLink, `${window.currentProject.filename.trim()}.svg`, err ? null : svg);
});
saveDiagram(function(err, xml) {
setEncoded(downloadLink, `${window.currentProject.filename.trim()}.bpmn`, err ? null : xml);
});
}, 500);
$("#js-load-from-db").on("click", loadFromDB);
$("#js-save-to-db").on("click", saveToDB);
// This hopefully captures the proper data for things that may not naturally trigger the commandStack.changed event.
$("#js-download-diagram").on("mouseenter", function() {
if (modelerChanged) { exportArtifacts(); }
// console.log("#js-download-diagram mouseenter");
});
$("#js-download-svg").on("mouseenter", function() {
if (modelerChanged) { exportArtifacts(); }
// console.log("#js-download-svg mouseenter");
});
modeler.on('comments.updated', exportArtifacts);
modeler.on('commandStack.changed', exportArtifacts);
modelerEventBus.on("element.click", function(e) {
// e.element = the model element
// e.gfx = the graphical element
// console.log("element.click on", e);
modeler.get('comments').collapseAll();
});
});
// helpers //////////////////////
function debounce(fn, timeout) {
var timer;
return function() {
if (timer) { clearTimeout(timer); }
timer = setTimeout(fn, timeout);
};
}
In that wall of code I have defined two moddle extensions:
{
"name": "QualityAssurance",
"uri": "http://some-company/schema/bpmn/qa",
"prefix": "qa",
"xml": {
"tagAlias": "lowerCase"
},
"types": [
{
"name": "AnalyzedNode",
"extends": [
"bpmn:FlowNode"
],
"properties": [
{
"name": "suitable",
"isAttr": true,
"type": "Float"
}
]
},
{
"name": "AnalysisDetails",
"superClass": [ "Element" ],
"properties": [
{
"name": "lastChecked",
"isAttr": true,
"type": "String"
},
{
"name": "nextCheck",
"isAttr": true,
"type": "String"
},
{
"name": "comments",
"isMany": true,
"type": "Comment"
}
]
},
{
"name": "Comment",
"properties": [
{
"name": "author",
"isAttr": true,
"type": "String"
},
{
"name": "text",
"isBody": true,
"type": "String"
}
]
}
],
"emumerations": [],
"associations": []
}
{
"name": "UrlReference",
"uri": "http://url_reference",
"prefix": "url",
"xml": {
"tagAlias": "lowerCase"
},
"types": [
{
"name": "ref",
"superClass": [ "Element" ],
"properties": [
{
"name": "location",
"isAttr": true,
"type": "String"
}
]
}
],
"emumerations": [],
"associations": []
}
QA is just from the example to see that it works, and it does. Saves out, and loads in with no issue.
The one I made called URL REF seems to work, and it shows up in the BPMN XML that it saves out when I click to save it. Yet when I try to load it I get this error:
read.js:677 could not parse node
handleError @ read.js:677
handleOpen @ read.js:778
(anonymous) @ read.js:828
parse @ index.esm.js:1005
Parser.parse @ index.esm.js:297
(anonymous) @ read.js:847
setTimeout (async)
defer @ read.js:81
./node_modules/moddle-xml/lib/read.js.Reader.fromXML @ read.js:843
./node_modules/bpmn-moddle/lib/bpmn-moddle.js.BpmnModdle.fromXML @ bpmn-moddle.js:56
./node_modules/bpmn-js/lib/Viewer.js.Viewer.importXML @ Viewer.js:168
openDiagram @ app.js:182
reader.onload @ app.js:320
load (async)
handleFileSelect @ app.js:318
read.js:678 Error: unknown type <url:Ref>
at Registry../node_modules/moddle/lib/registry.js.Registry.mapTypes (registry.js:159)
at Registry../node_modules/moddle/lib/registry.js.Registry.getEffectiveDescriptor (registry.js:184)
at BpmnModdle../node_modules/moddle/lib/moddle.js.Moddle.getType (moddle.js:97)
at ElementHandler../node_modules/moddle-xml/lib/read.js.ElementHandler.getPropertyForNode (read.js:404)
at ElementHandler../node_modules/moddle-xml/lib/read.js.ElementHandler.handleChild (read.js:458)
at ElementHandler../node_modules/moddle-xml/lib/read.js.BaseElementHandler.handleNode (read.js:257)
at handleOpen (read.js:775)
at read.js:828
at parse (index.esm.js:1005)
at Parser.parse (index.esm.js:297)
handleError @ read.js:678
handleOpen @ read.js:778
(anonymous) @ read.js:828
parse @ index.esm.js:1005
Parser.parse @ index.esm.js:297
(anonymous) @ read.js:847
setTimeout (async)
defer @ read.js:81
./node_modules/moddle-xml/lib/read.js.Reader.fromXML @ read.js:843
./node_modules/bpmn-moddle/lib/bpmn-moddle.js.BpmnModdle.fromXML @ bpmn-moddle.js:56
./node_modules/bpmn-js/lib/Viewer.js.Viewer.importXML @ Viewer.js:168
openDiagram @ app.js:182
reader.onload @ app.js:320
load (async)
handleFileSelect @ app.js:318
I don’t really know why. I guess that I am defining improperly the URL REF definition. Yet that seems odd because it seems to create the xml items without issue, but it can’t seem to understand what they are on load.
This is the file I am trying to load in that produces the error:
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="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" xmlns:url="http://url_reference" xmlns:qa="http://some-company/schema/bpmn/qa" id="6d9687b2-4884-6387-8109-a0afe142b518" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" diagram-name="test_2018-12-14-Dec">
<bpmn2:process id="Process_1" isExecutable="false">
<bpmn2:startEvent id="StartEvent_1">
<bpmn2:extensionElements>
<url:ref location="https://www.google.com/" />
</bpmn2:extensionElements>
<bpmn2:outgoing>SequenceFlow_1a2fxeb</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:task id="Task_0fa8dnk">
<bpmn2:extensionElements>
<qa:analysisDetails lastChecked="2015-01-20" nextCheck="2015-07-15">
<qa:comment author="Klaus">Our operators always have a hard time to figure out, what they need to do here.</qa:comment>
<qa:comment author="Walter">I believe this can be split up in a number of activities and partly automated.</qa:comment>
</qa:analysisDetails>
</bpmn2:extensionElements>
<bpmn2:incoming>SequenceFlow_1a2fxeb</bpmn2:incoming>
<bpmn2:outgoing>SequenceFlow_0adlmng</bpmn2:outgoing>
</bpmn2:task>
<bpmn2:endEvent id="EndEvent_0cl9wo8">
<bpmn2:incoming>SequenceFlow_0adlmng</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="SequenceFlow_1a2fxeb" sourceRef="StartEvent_1" targetRef="Task_0fa8dnk" />
<bpmn2:sequenceFlow id="SequenceFlow_0adlmng" sourceRef="Task_0fa8dnk" targetRef="EndEvent_0cl9wo8" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="412" y="240" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_0fa8dnk_di" bpmnElement="Task_0fa8dnk">
<dc:Bounds x="505" y="218" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0cl9wo8_di" bpmnElement="EndEvent_0cl9wo8">
<dc:Bounds x="670" y="240" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1a2fxeb_di" bpmnElement="SequenceFlow_1a2fxeb">
<di:waypoint x="448" y="258" />
<di:waypoint x="505" y="258" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0adlmng_di" bpmnElement="SequenceFlow_0adlmng">
<di:waypoint x="605" y="258" />
<di:waypoint x="670" y="258" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
As you can see… the QA parts are still in there, and the newly created URL REFs are in there. I don’t seem to get an error for the QA on the load, but the URL REFs do error on load. I feel like I am doing the same thing in both cases… and I don’t know why one works, and the other doesn’t.
If you need me to grab in more code and drop it, then I can.
Any help or suggestions on this?
x Jeremy M.