org.jbpm.designer.public.js.Plugins.stepThroughPlugin.js Maven / Gradle / Ivy
/*
* TODO interaction format step through <=> oryx should be json!!!
*/
/**
* Copyright (c) 2008, Christoph Neijenhuis, modified by Kai Schlichting
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
**/
/**
* @namespace Oryx name space for plugins
* @name ORYX.Plugins
**/
Ext.namespace("ORYX.Plugins");
ORYX.Plugins.AbstractStepThroughPlugin = ORYX.Plugins.AbstractPlugin.extend({
construct: function() {
// Call super class constructor
arguments.callee.$.construct.apply(this, arguments);
this.facade.offer({
'name': ORYX.I18N.StepThroughPlugin.stepThrough,
'functionality': this.load.bind(this),
'group': ORYX.I18N.StepThroughPlugin.group,
'icon': ORYX.BASE_FILE_PATH + "images/control_play.png",
'description': ORYX.I18N.StepThroughPlugin.stepThroughDesc,
'index': 1,
'toggle' : true,
'minShape': 0,
'maxShape': 0
});
this.facade.offer({
'name': ORYX.I18N.StepThroughPlugin.undo,
'functionality': this.undo.bind(this),
'group': ORYX.I18N.StepThroughPlugin.group,
'icon': ORYX.BASE_FILE_PATH + "images/control_rewind.png",
'description': ORYX.I18N.StepThroughPlugin.undoDesc,
'index': 2,
'minShape': 0,
'maxShape': 0
});
},
showEnabled: function(shape, display){
// Creates overlay for an enabled shape
// display is beeing ignored
if (!(shape instanceof ORYX.Core.Shape)) {
return;
}
else if (this.isOrSplit(shape)) { //special handling for OR-Split
this.showEnabledOrSplit(shape);
return;
}
this.showPlayOnShape(shape);
},
showPlayOnShape: function(shape){
var attr;
if (shape instanceof ORYX.Core.Edge) {
attr = {
stroke: "green"
};
}
else {
attr = {
fill: "green"
};
}
var play = ORYX.Editor.graft("http://www.w3.org/2000/svg", null, ['path', {
"title": "Click the element to execute it!",
"stroke-width": 2.0,
"stroke": "black",
"d": "M0,-5 L5,0 L0,5 Z",
"line-captions": "round"
}]);
this.showOverlayOnShape(shape, attr, play);
},
showOverlayOnShape: function(shape, attributes, node){
this.hideOverlayOnShape(shape);
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_OVERLAY_SHOW,
id: "st." + shape.resourceId,
shapes: [shape],
attributes: attributes,
node: (node ? node : null),
nodePosition: shape instanceof ORYX.Core.Edge ? "END" : "SE"
});
},
hideOverlayOnShape: function(shape){
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_OVERLAY_HIDE,
id: "st." + shape.resourceId
});
},
// Pass preserveInitialMarking = true as config option, if initial marking
// should not be deleted
hideOverlays: function(preserveInitialMarking){
// hides all overlays
var els = this.facade.getCanvas().getChildShapes(true);
var el;
for (i = 0; i < els.size(); i++) {
el = els[i];
// This may send hide-events for objects that have no overlay
if (!(preserveInitialMarking && this.isStartNode(el))) {
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_OVERLAY_HIDE,
id: "st." + el.resourceId
});
}
}
},
/**
* Called when the user loads or unloads the plugin
* @methodOf ORYX.Plugins.StepThroughPlugin
*/
load: function(button, pressed){
this.initializeLoadButton(button, pressed);
this.togglePlugin(pressed);
},
togglePlugin: function(turn_on) {
if (turn_on) {
this.initialMarking = [];
if (this.getDiagramType() === "epc") {
this.prepareInitialMarking();
}
else { //only start immediately for bpmn diagrams and Petri nets
this.startAndCheckSyntax();
}
}
else {
// Reset vars
this.executionTrace = "";
this.rdf = undefined;
// Reset syntax checker errors
this.facade.raiseEvent({type: ORYX.Plugins.SyntaxChecker.RESET_ERRORS_EVENT});
this.onDeactivate();
}
if (this.active()) {
this.callback = this.doMouseUp.bind(this)
this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEUP, this.callback)
} else {
this.facade.unregisterOnEvent(ORYX.CONFIG.EVENT_MOUSEUP, this.callback)
this.callback = undefined;
}
},
// When plugin is deactivated through pressing the button
// Overwrite to implement custom behavior
onDeactivate: function(){
// Hide overlays
this.hideOverlays();
},
// Very evil, this method is a result from Oryx generalized plugin infrastructure
// Direct access to button needed, so that this can be done in construct method
initializeLoadButton: function(button, pressed){
if(this.loadButton !== button){
// Sets state of the loadButton and sets the model readonly
var toggleActive = function(active){
if(active){
//Set "readonly" (edges cannot be moved anymore)
this.facade.disableEvent(ORYX.CONFIG.EVENT_MOUSEDOWN);
} else {
this.facade.enableEvent(ORYX.CONFIG.EVENT_MOUSEDOWN);
}
}.createDelegate(this);
button.on('toggle', function(button, p){
toggleActive(p);
});
// Very evil!! The first time, toggle event isn't thrown
// Should be like that listeners can be defined earlier!!!
toggleActive(button, pressed);
}
this.loadButton = button;
},
active: function(){
return this.loadButton ? this.loadButton.pressed : false;
},
onSelectionChanged: function(){
if (this.active() && this.facade.getSelection().length > 0) {
// Stop the user from editing the diagram while the plugin is active
this.facade.setSelection([]);
}
},
//TODO generic function
getDiagramType: function(){
switch (this.facade.getCanvas().getStencil().namespace()) {
case "http://b3mn.org/stencilset/epc#":
return "epc";
case "http://b3mn.org/stencilset/bpmn#":
return "bpmn";
default:
return null;
}
},
showUsed: function(shape, display){
// Creates overlay for a shape that has been used and is not enabled
if (!(shape instanceof ORYX.Core.Shape))
return;
var attr;
if (shape instanceof ORYX.Core.Edge) {
attr = {
stroke: "mediumslateblue"
};
}
else {
attr = {
fill: "mediumslateblue"
};
}
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_OVERLAY_HIDE,
id: "st." + shape.resourceId
});
if (display != "-1" && display != "1") {
// Show the number
var text = ORYX.Editor.graft("http://www.w3.org/2000/svg", null, ['text', {
"style": "font-size: 16px; font-weight: bold;"
}, display]);
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_OVERLAY_SHOW,
id: "st." + shape.resourceId,
shapes: [shape],
attributes: attr,
node: text,
nodePosition: shape instanceof ORYX.Core.Edge ? "END" : "SE"
});
}
else {
// This is an XOR split, don't display number
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_OVERLAY_SHOW,
id: "st." + shape.resourceId,
shapes: [shape],
attributes: attr
});
}
}
});
ORYX.Plugins.PetriNetStepThroughPlugin = ORYX.Plugins.AbstractStepThroughPlugin.extend({
construct: function() {
// Call super class constructor
arguments.callee.$.construct.apply(this, arguments);
},
startAndCheckSyntax: function(){
this.facade.raiseEvent({
type: ORYX.Plugins.SyntaxChecker.CHECK_FOR_ERRORS_EVENT,
onErrors: function(){
Ext.Msg.alert("Syntax Check", "Some syntax errors have been found, please correct them!");
}.bind(this),
onNoErrors: function(){
if (this.initializeMarking()) {
this.firedTransitions = [];
this.showEnabledTransition();
}
else {
this.togglePlugin(false); // turn off
}
}.bind(this)
});
},
/**
* Initializes the number of tokens for each place. Additionally, if none of the places have a token, the initial places
* get one.
*/
initializeMarking: function(){
// monkeypatching place marking (add/remove tokens) towards new stencil set
// definitions from r3187
var markPlaces = function(shape, propertyValue) {
if(propertyValue == 0) {
shape.setProperty("oryx-numberoftokens_text", "");
shape.setProperty("oryx-numberoftokens_drawing", "0");
} else if(propertyValue == 1) {
shape.setProperty("oryx-numberoftokens_text", "");
shape.setProperty("oryx-numberoftokens_drawing", "1");
} else if(propertyValue == 2) {
shape.setProperty("oryx-numberoftokens_text", "");
shape.setProperty("oryx-numberoftokens_drawing", "2");
} else if(propertyValue == 3) {
shape.setProperty("oryx-numberoftokens_text", "");
shape.setProperty("oryx-numberoftokens_drawing", "3");
} else if(propertyValue == 4) {
shape.setProperty("oryx-numberoftokens_text", "");
shape.setProperty("oryx-numberoftokens_drawing", "4");
} else {
var tokens = parseInt(propertyValue, 10);
if(tokens && tokens > 0) {
shape.setProperty("oryx-numberoftokens_text", "" + tokens);
shape.setProperty("oryx-numberoftokens_drawing", "0");
} else {
shape.setProperty("oryx-numberoftokens_text", "");
shape.setProperty("oryx-numberoftokens_drawing", "0");
}
}
}
this.getPlaces().each(function(place){
if ("undefined" == typeof(place._setProperty_monkey)) {
place._setProperty_monkey = place.setProperty;
place.setProperty = function(p,a) {
if ("oryx-numberoftokens" == p) {
markPlaces(place, a)
}
place._setProperty_monkey.apply(place, arguments);
}
}
})
var anyTokenFound = 0;
this.getPlaces().each(function(place){
var tokens = parseInt(place.properties["oryx-numberoftokens"]);
if(isNaN(tokens)){
// All places which don't have any number of tokens, gets 0 tokens explicitly
place.setProperty("oryx-numberoftokens", 0);
} else if(tokens > 0){
anyTokenFound += tokens;
}
});
// If no place has any token, all incoming places gets tokens
if(0 == anyTokenFound){
this.getPlaces().each(function(place){
if(place.getIncomingShapes().length == 0){
place.setProperty("oryx-numberoftokens", 1);
}
});
Ext.Msg.show({
title:'No Tokens Found',
msg: 'Current marking of the Petri net doesn\'t contain any token. Tokens are added to the initial places of the net.',
buttons: Ext.Msg.OK,
icon: Ext.MessageBox.INFO
});
}
if(anyTokenFound > 3){
Ext.Msg.show({
title:'Too Many Tokens On Place',
msg: 'Places with more than 3 tokens aren\'t supported yet. Please avoid this scenario.',
buttons: Ext.Msg.OK,
icon: Ext.MessageBox.WARNING
});
}
return true;
},
doMouseUp: function(event, shape){
if (!(this.isTransition(shape))) return; // Can be a docker, place or something else
// assuming correct syntax
var enabled = this.getIncomingNodes(shape).all(function(place) {
return parseInt(place.properties["oryx-numberoftokens"]) > 0;
});
if (enabled) {
this.fireTransition(shape);
}
this.showEnabledTransition();
},
onDeactivate: function(){
// Hide overlays
this.hideOverlays();
// Undo all fired transitions so that the marking is the initial marking
while(this.firedTransitions.length !== 0){
this.undoLastFiredTransition();
}
this.facade.getCanvas().update(); //update markings
this.facade.raiseEvent({type: ORYX.Plugins.SyntaxChecker.RESET_ERRORS_EVENT});
},
fireTransition: function(transition){
this.firedTransitions.push(transition);
this.getIncomingNodes(transition).each(function(place){
this.removeToken(place);
}.bind(this));
this.getOutgoingNodes(transition).each(function(place){
this.addToken(place);
}.bind(this));
},
undoLastFiredTransition: function(){
var transition = this.firedTransitions.pop();
// If there is no transition anymore, just quit
if(!transition) return;
this.getIncomingNodes(transition).each(function(place){
this.addToken(place);
}.bind(this));
this.getOutgoingNodes(transition).each(function(place){
this.removeToken(place);
}.bind(this));
},
removeToken: function(place){
place.setProperty("oryx-numberoftokens", parseInt(place.properties["oryx-numberoftokens"])-1);
},
addToken: function(place){
var tokens = parseInt(place.properties["oryx-numberoftokens"]) +1;
place.setProperty("oryx-numberoftokens", tokens);
if(tokens > 3){
Ext.Msg.show({
title:'Too Many Tokens On Place',
msg: 'Places with more than 3 tokens aren\'t supported yet. Please avoid this scenario.',
buttons: Ext.Msg.OK,
icon: Ext.MessageBox.WARNING
});
}
},
showEnabledTransition: function(){
this.hideOverlays();
this.firedTransitions.each(function(transition){
this.showUsed(transition, "1");
}.bind(this));
this.getEnabledTransitions().each(function(transition){
this.showPlayOnShape(transition);
}.bind(this));
this.facade.getCanvas().update();
},
getTransitions: function(){
return this.facade.getCanvas().getChildShapes().select(function(shape){
return this.isTransition(shape);
}.bind(this));
},
isTransition: function(shape){
return shape instanceof ORYX.Core.Shape && shape.getStencil().id().search(/Transition/) > -1;
},
getPlaces: function(){
return this.facade.getCanvas().getChildShapes().select(function(shape){
return shape.getStencil().id().search(/Place/) > -1;
});
},
getIncomingNodes: function(node){
return node.getIncomingShapes().collect(function(arc){
return arc.getIncomingShapes();
}).flatten();
},
getOutgoingNodes: function(node){
return node.getOutgoingShapes().collect(function(arc){
return arc.getOutgoingShapes();
}).flatten();
},
getEnabledTransitions:function(){
return this.getTransitions().select(function(transition){
// Checks whether all incoming places have at least 1 token
return this.getIncomingNodes(transition).all(function(place){
return parseInt(place.properties["oryx-numberoftokens"]) > 0;
});
}.bind(this));
},
undo: function(){
this.undoLastFiredTransition();
this.showEnabledTransition();
}
});
/**
* Step Through Plugin
* @class ORYX.Plugins.StepThroughPlugin
* @constructor Creates new plugin instance
*/
ORYX.Plugins.StepThroughPlugin = ORYX.Plugins.AbstractStepThroughPlugin.extend({
/**
* My method!
* @methodOf ORYX.Plugins.StepThroughPlugin
*/
construct: function(facade){
this.el = undefined;
this.callback = undefined;
this.executionTrace = ""; // A string containing all objects that have been fired
this.rdf = undefined;
arguments.callee.$.construct.apply(this, arguments);
},
// Each start node and each start arc gets colored
prepareInitialMarking: function(){
this.startNodes = [];
Ext.each(this.facade.getCanvas().getChildShapes(true), function(shape){
if (this.isStartNode(shape)) {
this.startNodes.push(shape);
shape.initialMarkingFired = false;
this.showPlayOnShape(shape);
if (shape.getOutgoingShapes().size() == 1) {
this.showOverlayOnShape(shape.getOutgoingShapes()[0], {
stroke: "green"
});
shape.getOutgoingShapes()[0].initialMarking = true;
}
}
}
.createDelegate(this));
},
/* Returns true if node is start node */
isStartNode: function(shape){
return (shape.getStencil().id().search(/#Event$/) > -1) && shape.getIncomingShapes().length == 0 && shape.getOutgoingShapes().length == 1;
},
/* Returns true if source node from arc is start node */
isStartArc: function(shape){
return this.isStartNode(shape.getIncomingShapes()[0]);
},
isStartArcOrNode: function(shape){
return this.isStartNode(shape) || this.isStartArc(shape);
},
/* TODO this should be a general oryx helper method!! */
generateRDF: function(){
try {
var serialized_rdf = this.getRDFFromDOM();
// Firefox 2 to 3 problem?!
serialized_rdf = !serialized_rdf.startsWith("" + serialized_rdf : serialized_rdf;
}
catch (error) {
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_LOADING_DISABLE
});
Ext.Msg.alert(ORYX.I18N.Oryx.title, error);
}
this.rdf = serialized_rdf;
},
getRDF: function(){
if (this.rdf == undefined) {
this.generateRDF();
}
return this.rdf;
},
startAndCheckSyntax: function(){
this.postExecutionTrace({
checkSyntax: true,
onlyChangedObjects: false,
onSuccess: function(request){
//TODO use always json!!!! This is a bad hack!!!
if (request.responseText.startsWith("{")){ //seems to be json
var errors = Ext.decode(request.responseText).syntaxErrors;
// Show errors
this.facade.raiseEvent({
type: ORYX.Plugins.SyntaxChecker.SHOW_ERRORS_EVENT,
errors: errors
});
} else { //normal step through response
this.showObjectStates(request.responseText);
}
}.bind(this)
});
},
fireObject: function(objResourceId){
// Add this object to executionTrace
this.executionTrace += objResourceId + ";";
// Add selected edges for or split
if (this.isOrSplit(this.el)) {
//eliminate ;
this.executionTrace = this.executionTrace.substring(0, this.executionTrace.length - 1);
this.executionTrace += "#";
var outgoingEdges = new Ext.util.MixedCollection();
outgoingEdges.addAll(this.el.getOutgoingShapes());
var firingEdgesResourceIds = [];
outgoingEdges.filter("selectedForOrSplit", "true").each(function(edge){
firingEdgesResourceIds.push(edge.resourceId);
}
.createDelegate(this));
outgoingEdges.each(function(edge){
edge.selectedForOrSplit = false;
this.hideOverlayOnShape(edge);
}
.createDelegate(this));
this.executionTrace += firingEdgesResourceIds.join(",") + ";";
}
this.postExecutionTrace({
checkSyntax: false,
onlyChangedObjects: true,
onSuccess: function(request){
if (request.responseText != "") {
// successful
this.showObjectStates(request.responseText);
}
else {
// object couldn't be fired, remove it from executionTrace
this.removeLastFiredObject();
}
}
.bind(this)
});
},
doMouseUp: function(event, arg){
if (arg instanceof ORYX.Core.Shape) {
// if its an or split
if (arg instanceof ORYX.Core.Edge && this.isOrSplit(arg.getIncomingShapes()[0])) {
this.doMouseUpOnEdgeComingFromOrSplit(arg);
// else if its an epc start node
}
else if (arg instanceof ORYX.Core.Edge && this.getDiagramType() === "epc" && this.isStartNode(arg.getIncomingShapes()[0])) {
this.doMouseUpOnEdgeComingFromStartNode(arg);
}
else if (this.getDiagramType() === "epc" && this.isStartNode(arg)) {
arg.initialMarkingFired = true;
var edge = arg.getOutgoingShapes()[0];
this.hideOverlayOnShape(edge);
if (edge.initialMarking) {
this.showUsed(arg, "1");
this.initialMarking.push(arg.resourceId);
}
else {
this.hideOverlayOnShape(arg);
}
// If clicked node is the last startNode, activate real step through
var allStartNodesFired = true;
Ext.each(this.startNodes, function(startNode){
if (!startNode.initialMarkingFired) {
allStartNodesFired = false;
}
});
if (allStartNodesFired) {
this.startAndCheckSyntax();
}
}
else {
this.el = arg;
this.fireObject(this.el.resourceId);
}
}
},
showObjectStates: function(objs){
var objsAndState = objs.split(";");
for (i = 0; i < objsAndState.size(); i++) {
var objAndState = objsAndState[i].split(",");
if (objAndState.size() < 3) {
continue;
}
var obj = this.facade.getCanvas().getChildShapeByResourceId(objAndState[0]);
if (objAndState[2] == "t") { // Is enabled
this.showEnabled(obj, objAndState[1]);
}
else if (objAndState[1] != "0") { // has been used
this.showUsed(obj, objAndState[1]);
}
else { // Was enabled, has not been used
this.hideOverlayOnShape(obj);
}
}
},
doMouseUpOnEdgeComingFromOrSplit: function(edge){
var orSplit = edge.getIncomingShapes()[0];
if (edge.selectedForOrSplit) { //deselect edge
this.showOverlayOnShape(edge, {
stroke: "orange"
});
// Hide or-split overlay, if last edge has been deselected
var outgoingEdges = new Ext.util.MixedCollection();
outgoingEdges.addAll(orSplit.getOutgoingShapes());
if (outgoingEdges.filter("selectedForOrSplit", "true").length <= 1) { // > 1, because current edge is in this list
this.hideOverlayOnShape(orSplit);
}
}
else { //select edge
this.showOverlayOnShape(edge, {
stroke: "green"
});
this.showPlayOnShape(orSplit);
}
// toggle selection
edge.selectedForOrSplit = !edge.selectedForOrSplit;
},
// Toggles color and initialMarking value of start arcs
doMouseUpOnEdgeComingFromStartNode: function(edge){
edge.initialMarking = !edge.initialMarking;
if (edge.initialMarking) {
this.showOverlayOnShape(edge, {
stroke: "green"
});
}
else {
this.showOverlayOnShape(edge, {
stroke: "orange"
});
}
},
//checks whether shape is OR gateway and hasn't more than 1 outgoing edges
isOrSplit: function(shape){
return (shape.getStencil().id().search(/#(OR_Gateway|OrConnector)$/) > -1) && (shape.getOutgoingShapes().length > 1);
},
showEnabledOrSplit: function(shape){
Ext.each(shape.getOutgoingShapes(), function(edge){
Ext.apply(edge, {
selectedForOrSplit: false
});
this.showOverlayOnShape(edge, {
stroke: "orange"
});
}
.createDelegate(this));
},
removeLastFiredObject: function(){
// Removes last entry in execution trace
this.executionTrace = this.executionTrace.replace(/[^;]*;$/, "")
},
undo: function(){
if (!this.active())
return;
if (this.executionTrace !== "") {
this.removeLastFiredObject();
this.postExecutionTrace({
checkSyntax: false,
onlyChangedObjects: false,
onSuccess: function(request){
// Hide overlays because everything is drawn from scratch
this.hideOverlays(true);
// Draw new overlays
this.showObjectStates(request.responseText);
}
.bind(this)
});
}
else if (this.getDiagramType() === "epc") {
this.hideOverlays();
this.prepareInitialMarking(); // "reset" initial marking
}
},
/* Posts current execution trace to server for executing
* options is a hash with following keys:
* - onlyChangedObjects (boolean)
* - onSuccess (function with parameter request)
* - checkSyntax (boolean)
*/
postExecutionTrace: function(options){
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_LOADING_ENABLE,
text: ORYX.I18N.StepThroughPlugin.executing
});
//TODO merge in default options
new Ajax.Request(ORYX.CONFIG.STEP_THROUGH, {
method: 'POST',
asynchronous: false,
parameters: {
rdf: this.getRDF(),
checkSyntax: options.checkSyntax,
fire: this.executionTrace,
onlyChangedObjects: options.onlyChangedObjects,
initialMarking: this.initialMarking.join(";")
},
onSuccess: function(response){
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_LOADING_DISABLE
});
options.onSuccess(response);
}.createDelegate(this),
onFailure: function(){
//TODO raise error message?
this.facade.raiseEvent({
type: ORYX.CONFIG.EVENT_LOADING_DISABLE
});
}.createDelegate(this)
});
}
});
© 2015 - 2025 Weber Informatics LLC | Privacy Policy