Skip to main content

Designer

Examples of Designer integration

Designer

In order to include the visual scheme designer into your application you should create a Javascript object — WorkflowDesigner — on a web page where you want the designer to be displayed, and a function that will process requests from the designer and pass them to WorkflowRuntime.

Workflow Designer

Frontend

Add links to the following JavaScript libraries on the web page:

You should also add one style sheet:

Besides, put designer's templates into the templates folder.

If you downloaded the workflow-engine-net-core.zip archive, you will find the above-mentioned Javascript libraries and templates in the Designer folder. You can download Jquery via the link above.

If you installed the designer from NuGet, then you have all links already written in Designer/index.chtml, except for those to JQuery. You have to manually put links to those library versions that you have in the project.

Designer properties

Once all the necessary libraries are installed, create the JavaScript object WorkflowDesigner.

wfdesigner = new WorkflowDesigner({
name: 'simpledesigner',
apiurl: '/Designer/API',
renderTo: 'wfdesigner',
graphwidth: graphwidth,
graphheight: graphheight
});

The following settings are required:

  • apiurl: '/Designer/API' — path for submitting requests to the server part. In this particular case, it’s the DesignerController API method;
  • renderTo: 'wfdesigner' — ID of the div the scheme is drawn into;
  • graphwidth — designer's canvas width;
  • graphheight — designer's canvas height;

You can also define optional settings:

wfdesigner = new WorkflowDesigner({
templatefolder: '/templates/',
cacheLoadedFiles: false,
showSaveButton: true,
notrendertoolbar: true,
notshowwindows: true,
disableobjectmovements: true,
mode: 'printable',
});
  • templatefolder — path to the templates (all forms) folder;
  • cacheLoadedFiles — caches downloaded files (e.g. templates), enabled by default.
  • showSaveButton — shows the save button on the toolbar;
  • notrendertoolbar — toolbar is not rendered in the read-only mode;
  • notshowwindows — object properties that are generally shown via a double click on Activity or Transition are restricted in the read-only mode;
  • disableobjectmovements — object movement is not available in the read-only mode;
  • mode: 'printable' — enables print mode. Grid, toolbar and all control elements are hidden, while the canvas scales automatically to show the entire scheme on canvas. This mode allows you to render a pdf document directly from the browser.

You can also configure designer API call headers:

  1. Set up ajax pipeline in the main app class by handling the beforeSend event.

    window['$'].ajaxSetup({beforeSend: this.onSend});
  2. Add a handler function that adds custom headers to the request.

    // Example for bearer authorization
    function onSend(e, data) {
    const token = 'myToken';
    e.setRequestHeader('Authorization', `Bearer ${token}`);
    return true;
    }

Core WorkflowDesigner functions:

  • Scheme existence check: if the scheme already exists, it is opened, otherwise a new scheme is created.

    var schemecode = QueryString.schemecode;
    var processid = QueryString.processid;
    var readonly = processid !== undefined;
    var p = { schemecode: schemecode, processid: processid, readonly: readonly };
    if (wfdesigner.exists(p)) {
    wfdesigner.load(p);
    } else {
    wfdesigner.create();
    }

    Function exists(p) checks whether the scheme exists. Function load(p) loads the scheme to the designer. You can pass either the scheme code or process ID to both of them. If process ID is entered, then WorkflowDesigner opens the scheme associated with the process. If scheme code is passed, it takes the required one from the WorkflowScheme table. Yet, there is another way: you can enter the schemeid and retrieve the scheme from WorkflowProcessScheme. Parameter readonly defines the read-only mode for the scheme. When its enabled all objects are not available for editing. create() is used to create a new blank scheme.

  • Saving a scheme:

    wfdesigner.schemecode = schemecode;
    var err = wfdesigner.validate();
    if (err != undefined && err.length > 0) {
    alert(err);
    } else {
    wfdesigner.save(function () {
    alert('The scheme was saved!');
    });
    }

    First, assign the scheme code for storing the item in the WorkflowScheme table. Then invoke the validate() function to check the scheme against errors. Finally, invoke the save(success) function that receives a function once the scheme is successfully saved.

  • Uploading a scheme to designer:

    wfdesigner.uploadscheme($('#uploadform')[0], function () {
    alert('The file was uploaded!');
    });

    Call the uploadscheme(form, success) function, attaching a form that contains the uploaded file and function triggered by successful upload of the scheme. Keep in mind, that when this function is called, the scheme is loaded into designer object, but it is not saved in a database. Use the save() function to save your scheme.

    In this case, the scheme is not saved in the database.

  • Downloading a scheme from designer:

    wfdesigner.downloadscheme();

    The scheme is downloaded from designer to your computer as an XML file.

  • Deleting the designer object:

    wfdesigner.destroy();
  • Accessing the scheme displayed in designer:

    var data = wfdesigner.data;
  • Designer re-rendering:

    wfdesigner.render();
  • Switching designer's view mode:

    wfdesigner.readonlymode();
    wfdesigner.printablemode();
    wfdesigner.editablemode();

In order to add localization to designer you will have to add another JavaScript file — workflowdesigner.localization.js — that contains all the string constants that are used by the designer. If you want to translate the designer's interface to another language you have to do it yourself. This file should be included after workflowdesigner.min.js.

Backend

There should be a method transferring requests from designer to WorkflowRuntime on the server side.

Here is an example of such a method for ASP.NET MVC Core:

public class DesignerController : Controller
{
public IActionResult API()
{
Stream filestream = null;
var isPost = Request.Method.Equals("POST", StringComparison.OrdinalIgnoreCase);
if (isPost && Request.Form.Files != null && Request.Form.Files.Count > 0)
filestream = Request.Form.Files[0].OpenReadStream();

var pars = new NameValueCollection();
foreach (var q in Request.Query)
{
pars.Add(q.Key, q.Value.First());
}

if (isPost)
{
var parsKeys = pars.AllKeys;

foreach (var key in Request.Form.Keys)
{
if (!parsKeys.Contains(key))
{
pars.Add(key, Request.Form[key]);
}
}
}

var res = WorkflowInit.Runtime.DesignerAPI(pars, out bool hasError, filestream, true);

if (pars["operation"].ToLower() == "downloadscheme" && !hasError)
return File(Encoding.UTF8.GetBytes(res), "text/xml");
if (pars["operation"].ToLower() == "downloadschemebpmn" && !hasError)
return File(Encoding.UTF8.GetBytes(res), "text/xml");

return Content(res);
}
}

Here is an example of such a method for ASP.NET MVC:

public class DesignerController : Controller
{
public ActionResult API()
{
Stream filestream = null;
if (Request.Files.Count > 0)
filestream = Request.Files[0].InputStream;

var pars = new NameValueCollection();
pars.Add(Request.QueryString);

if (Request.HttpMethod.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
{
var parsKeys = pars.AllKeys;
foreach (string key in Request.Form.Keys)
{
if (!parsKeys.Contains(key))
{
pars.Add(key, Request.Unvalidated[key]);
}
}
}

var res = WorkflowInit.Runtime.DesignerAPI(pars, out bool hasError, filestream, true);
var operation = pars["operation"].ToLower();
if (operation == "downloadscheme" && !hasError)
return File(Encoding.UTF8.GetBytes(res), "text/xml");
else if (operation == "downloadschemebpmn" && !hasError)
return File(UTF8Encoding.UTF8.GetBytes(res), "text/xml");

return Content(res);
}
}

There is nothing spectacular in this function, the request parameters are transferred to the WorkflowInit.Runtime.DesignerAPI(parameters, filestream, true); function. You should combine the query string parameters with those transferred in the form, and pass them in the parameters variable. If the request contains a file, you get it streamed and pass in the filestream variable. The last parameter passed to DesignerApi is the one indicating whether the IsObsolete attribute will be assigned to the existing scheme after a new version is saved. If true, processes created earlier on scheme with the same code will be lazily updated.

Here is an example of a similar function where requests are processed by a web service. Such a method is suitable for ASP.NET Web Forms:

public class WFEDesigner : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
Stream filestream = null;
if (context.Request.Files.Count > 0)
filestream = context.Request.Files[0].InputStream;

var pars = new NameValueCollection
{
context.Request.QueryString
};

if (context.Request.HttpMethod.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
{
var parsKeys = pars.AllKeys;
foreach (var key in context.Request.Form.AllKeys)
{
if (!parsKeys.Contains(key))
{
pars.Add(key, context.Request.Unvalidated[key]);
}
}
}

var res = WorkflowInit.Runtime.DesignerAPI(pars, out bool hasError, filestream, true);

context.Response.Cache.SetNoStore();

if (pars["operation"].ToLower() == "downloadscheme" && !hasError)
{
context.Response.ContentType = "file/xml";
context.Response.BinaryWrite(Encoding.UTF8.GetBytes(res));
context.Response.End();
}
else if (pars["operation"].ToLower() == "downloadschemebpmn" && !hasError)
{
context.Response.ContentType = "file/xml";
context.Response.BinaryWrite(Encoding.UTF8.GetBytes(res));
context.Response.End();
}
else
{
context.Response.Write(res);
context.Response.End();
}
}
}