Integration of BPMN.IO into Meteor


#1

I am trying to integrate a modelling tool app which is available as a node module. Using 1.3 has made this a lot easier but I still can’t get it to work as expected.

Running in node, this is what is meant to happen when selecting a component:

Unfortunately when running in Meteor no selection is possible, and an exception when adding new components from the palette to the canvas.

This is based on example code located at https://github.com/bpmn-io/bpmn-js-examples/tree/master/modeler.

The sample code running in node looks like this:

‘use strict’;

var fs = require(‘fs’);

var $ = require(‘jquery’),
BpmnModeler = require(‘bpmn-js/lib/Modeler’);

var container = $(’#js-drop-zone’);

var canvas = $(’#js-canvas’);

var renderer = new BpmnModeler({ container: canvas });

var newDiagramXML = fs.readFileSync(__dirname + ‘/…/resources/newDiagram.bpmn’, ‘utf-8’);

function createNewDiagram() {
openDiagram(newDiagramXML);
}

function openDiagram(xml) {

renderer.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');
}

});
}

function saveSVG(done) {
renderer.saveSVG(done);
}

function saveDiagram(done) {

renderer.saveXML({ format: true }, function(err, xml) {
done(err, xml);
});
}

function registerFileDrop(container, callback) {

function handleFileSelect(e) {
console.log(e);
e.stopPropagation();
e.preventDefault();

var files = e.dataTransfer.files;

var file = files[0];
console.log(file);

var reader = new FileReader();

reader.onload = function(e) {

  var xml = e.target.result;

  callback(xml);
};

reader.readAsText(file);

}

function handleDragOver(e) {
console.log(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 {
console.log(“registerFileDrop”);
registerFileDrop(container, openDiagram);
}

// bootstrap diagram functions

$(document).on(‘ready’, 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) {
console.log(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
  });
} else {
  link.removeClass('active');
}

}

var _ = require(‘lodash’);

var exportArtifacts = _.debounce(function() {

saveSVG(function(err, svg) {
  setEncoded(downloadSvgLink, 'diagram.svg', err ? null : svg);
});

saveDiagram(function(err, xml) {
  setEncoded(downloadLink, 'diagram.bpmn', err ? null : xml);
});

}, 500);

renderer.on(‘commandStack.changed’, exportArtifacts);
});

My attempt at the equivalent Meteor code looks like this:

‘use strict’;

var $ = require(‘jquery’);
var BpmnModeler = require(‘bpmn-js/lib/Modeler’);

var newDiagramXML;
Template.index.onCreated(function() {
Meteor.call(‘readFile’, ‘resources/newDiagram.bpmn’, function(err, data) {
if (err) {
console.log(err);
return;
}
newDiagramXML = data;
});

});

var container;
var canvas;
var bpmnModeler;
var fileDropRegistered = false;
Template.index.onRendered(function() {
container = $(’#js-drop-zone’);
canvas = $(’#js-canvas’);
bpmnModeler = new BpmnModeler({ container: canvas });

function registerFileDrop(container, callback) {

  function handleDragOver(e) {
    e.stopPropagation();
    e.preventDefault();

    e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
  }

  function handleFileSelect(e) {
    console.log(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);
    };

    console.log(file);
    if(file) {
     reader.readAsText(file);
    }
  }
  container.get(0).addEventListener('dragover', handleDragOver, false);
  container.get(0).addEventListener('drop', handleFileSelect, false);

}

// 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 if (fileDropRegistered == false) {
fileDropRegistered = true;
registerFileDrop(container, openDiagram);
}

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
  });
} else {
  link.removeClass('active');
}

}

var downloadLink = $(’#js-download-diagram’);
var downloadSvgLink = $(’#js-download-svg’);
var debounce = require(‘lodash/function/debounce’);

var exportArtifacts = debounce(function() {

saveSVG(function(err, svg) {
  setEncoded(downloadSvgLink, 'diagram.svg', err ? null : svg);
});

saveDiagram(function(err, xml) {
  setEncoded(downloadLink, 'diagram.bpmn', err ? null : xml);
});

}, 500);

bpmnModeler.on(‘commandStack.changed’, exportArtifacts);
// bpmnModeler.on(‘commandStack.changed’, exportArtifacts);

});

function createNewDiagram() {
openDiagram(newDiagramXML);
}

function openDiagram(xml) {
bpmnModeler.importXML(xml, function(err) {
if (err) {
container
.removeClass(‘with-diagram’)
.addClass(‘with-error’);

  container.find('.error pre').text(err.message);
  console.error(err);
} else {
  console.log('success!');
  container
    .removeClass('with-error')
    .addClass('with-diagram');
}

});
}

function saveSVG(done) {
bpmnModeler.saveSVG(done);
}

function saveDiagram(done) {
bpmnModeler.saveXML({ format: true }, function(err, xml) {
done(err, xml);
});
}

////// file drag / drop ///////////////////////

// bootstrap diagram functions

$(document).on(‘ready’, function() {

$(’#js-create-diagram’).click(function(e) {
e.stopPropagation();
e.preventDefault();

createNewDiagram();

});

$(’.buttons a’).click(function(e) {
console.log(e);
if (!$(this).is(’.active’)) {
e.preventDefault();
e.stopPropagation();
}
});
});

A good deal of the code has to go into Template.index.onRendered as the otherwise many of the variables will be undefined when they are evaluated.

The main problem seems to be that in Meteor code is executing when it shouldn’t and not executing when it should. For example the when dragging a new item onto the canvas an exception is thrown as the code is executing unexpectedly.

What I would like to understand is how to structure the code in Meteor so that the diagram editor behaves as expected.


#2

hello @michaelrb , have you solved this problem already? we’e got the same problem…


#3

hy @michaelrb and @dawn I created a package which wraps the bpmn tools and easily integrates the modeler via spacebars template.

Checkout: https://atmospherejs.com/jkuester/bpmntools

I could also need some support for further testing, maintenance and integration of the other examples into their own templates, so we can create a 1:1 version of the bpmn-js-examples repo.


#4

To those who are still looking into this topic there is an update on that.

  • First, you should learn to integrate the modeler via npm so you can keep it updated. In the future I will release a gist on this

  • I created a new package, that wraps the modeler as plugin for aldeed:autoform

  • There is a node-js bpmn-engine that has great test coverage and works even with my most complex processes. It is maintained and updated regularly.

  • In the mid future I will get deeper involved into BPMN on a theoretical level, which gives me the ability to write a BPMN Validation engine, based on the BPMN Ruleset, which is by the camunda bpmn-moddle just implicit for now in terms of structural rules. To gain validation of a process however, it is also required to implement a validation for BPMN process rules.