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.

Frontend

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

  • JQuery;
  • JQuery UI — this library allows you to draw scheme parts editing forms;
  • konva.js — this library allows you to draw the visual part of the scheme, i.e. activities and transitions;
  • ace.js — is used for syntax highlighting in the designer’s built-in code editor;
  • json5.js — a JSON extension that makes process values easier to write and modify;
  • workflowdesigner.min.js — the designer itself.

You’d better add mode-csharp.js, mode-json.js,worker-json.js, and theme-monokai.js files to the folder where the ace.js library is located. It is not necessary to provide links to them on the web page. All these scripts are used for correct syntax highlighting.

You should also add two style sheets:

  • workflowdesigner.css — the designer's style sheet;
  • link to the jquery UIstyle sheet — you can download any theme here.

Besides, put designer's icons into the Images folder.

If you downloaded the workflow-engine-net-core.zip archive, you will find the abovementioned Javascript libraries and images in the Designer folder. You can download Jquery UI via the link above or grab it from our sample archive.

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

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

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

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;
  • imagefolder: '/Images/' — path to the Icons folder;
  • graphwidth — designer's canvas width;
  • graphheight — designer's canvas height.

You can also define optional settings:

wfdesigner = new WorkflowDesigner({
    mode: 'readonly',
    //mode: 'printable'
    notrendertoolbar : true,
    notshowwindows : true,
    disableobjectmovements : true
});
  • mode: 'readonly' — enables the read-only mode, all objects are not available for editing, objects' graph is not available for change;
  • notrendertoolbar : true — toolbar is not rendered in the read-only mode;
  • notshowwindows : true — object properties that are generally shown via a double click on Activity or Transition are restricted in the read-only mode;
  • disableobjectmovements : true — 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.

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 p = { schemecode: schemecode, processid: processid};
    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. 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:

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

        var parameters = new NameValueCollection {Request.Params};

        if(Request.HttpMethod.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
        {
            var allKeys = parameters.AllKeys;
            foreach (var key in Request.Form.AllKeys)
            {
                if (!allKeys.Contains(key))
                        parameters.Add(Request.Form);
            }
        }

        var res = WorkflowInit.Runtime.DesignerAPI(parameters, filestream, true);

        if (parameters["operation"].ToLower() == "downloadscheme")
            return File(Encoding.UTF8.GetBytes(res), "text/xml", "scheme.xml");
        else if (operation == "downloadschemebpmn")
            return File(UTF8Encoding.UTF8.GetBytes(res), "text/xml", "scheme.bpmn");
        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 eariler 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 parameters = new NameValueCollection {context.Request.Params};

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

        var res = WorkflowInit.Runtime.DesignerAPI(parameters, filestream, true);
        if (parameters["operation"].ToLower() == "downloadscheme")
        {
            context.Response.ContentType = "file/xml";
            context.Response.AddHeader("Content-Disposition", "attachment; filename=schema.xml");
            context.Response.BinaryWrite(Encoding.UTF8.GetBytes(res));
            context.Response.End();
        }
        else if (pars["operation"].ToLower() == "downloadschemebpmn")
        {
            context.Response.ContentType = "file/xml";
            context.Response.AddHeader("Content-Disposition", "attachment; filename=schema.bpmn");            
            context.Response.BinaryWrite(UTF8Encoding.UTF8.GetBytes(res));
            context.Response.End();
        }
        else
        {
            context.Response.Write(res);
            context.Response.End();
        }
    }
}
Top