WorkflowServer: Integration

Reading time: 7 minutes

WorkflowServer: Integration

Source Code

Feel free to download the source code.

Intro

When launching a new product we're always trying to anticipate all its potential use cases. But with WorkflowServer we could not even imagine that our clients would give up the embedded control panel and start using WorkflowServer as one of back-end services. That is why I have decided to write an article on integrating WorkflowDesigner from WorkflowServer into an html-page or React application.

Preparation

Launch WorkflowServer as described here or here, if you prefer docker.

So, you have launched WorkflowServer at: http://localhost:8077. We will use this address in all our examples for this article.

Before version 2.4.3 WorkflowServer did not support CORS customization, that is why it was rather complicated to connect the designer from other applications. Now WorkflowServer supports this feature6 and CORS requests are allowed by default.

Integration to HTML-page

Let's look at the simplest example, when you need to integrate WorkflowServer into an html-page and let your users browse and edit document flow scheme. To learn more how WorkflowDesigner works read this section.

Download WorkflowEngine Core archive from our downloads page. You will find all necessary resources in the Designer folder (scripts, styles, images). Connect them to your page. Use http://localhost:8077/DesignerAPI to connect to WorkflowServer.

For more convenience let's give access to the scheme by name from the schemecode parameter from url.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>OptimaJet WorkflowServer Integration HTML Sample</title>
    <link rel="Shortcut Icon" href="favicon.ico" type="image/x-icon">
</head>
<body>
    <script src="scripts/jquery.js" type="text/javascript"></script>
    <script src="scripts/semantic.min.js" type="text/javascript"></script>
    <script src="scripts/konva.min.js" type="text/javascript"></script>
    <script src="scripts/ace.js" type="text/javascript"></script>
    <script src="scripts/moment-with-locales.min.js" type="text/javascript"></script>
    <script src="scripts/jquery.auto-complete.min.js" type="text/javascript"></script>
    <script src="scripts/workflowdesigner.min.js" type="text/javascript"></script>

    <link href="css/semantic.min.css" rel="stylesheet" type="text/css" />
    <link href="css/workflowdesigner.css" rel="stylesheet" type="text/css" />

    <form action="" id="uploadform" method="post" enctype="multipart/form-data" onsubmit="tmp()" style="padding-bottom: 8px;">
        <div>
            <a href="javascript:OnNew()" class="ui secondary button">New scheme</a>
            <a href="javascript:OnSave()" class="ui secondary button">Save scheme</a>
            <a href="javascript:DownloadScheme()" class="ui primary button">Download XML</a>
            <a href="javascript:SelectScheme()" class="ui secondary button">Upload XML</a>
        </div>
        <input type="file" name="uploadFile" id="uploadFile" style="display:none" onchange="javascript: UploadScheme();">
    </form>
    <div id="wfdesigner" style="min-height:600px"></div>

    <script>
        var QueryString = function () {
            // This function is anonymous, is executed immediately and
            // the return value is assigned to QueryString!
            var query_string = {};
            var query = window.location.search.substring(1);
            var vars = query.split("&");
            for (var i = 0; i < vars.length; i++) {
                var pair = vars[i].split("=");
                // If first entry with this name
                if (typeof query_string[pair[0]] === "undefined") {
                    query_string[pair[0]] = pair[1];
                    // If second entry with this name
                } else if (typeof query_string[pair[0]] === "string") {
                    var arr = [query_string[pair[0]], pair[1]];
                    query_string[pair[0]] = arr;
                    // If third or later entry with this name
                } else {
                    query_string[pair[0]].push(pair[1]);
                }
            }
            return query_string;
        }();

        var schemecode = QueryString.schemecode;
        if(schemecode == undefined || schemecode == ""){
            schemecode = 'TestScheme';
        }
        var processid = QueryString.processid;
        var graphwidth = 1200;
        var graphminheight = 600;
        var graphheight = graphminheight;

        var wfdesigner = undefined;
        function wfdesignerRedraw() {
            var data;

            if (wfdesigner != undefined) {
                data = wfdesigner.data;
                wfdesigner.destroy();
            }

            wfdesigner = new WorkflowDesigner({
                name: 'simpledesigner',
                apiurl: 'http://localhost:8077/DesignerAPI',
                renderTo: 'wfdesigner',
                imagefolder: 'images/',
                graphwidth: graphwidth,
                graphheight: graphheight
            });

            if (data == undefined) {
                var isreadonly = false;
                if (processid != undefined && processid != '')
                    isreadonly = true;

                var p = { schemecode: schemecode, processid: processid, readonly: isreadonly };
                if (wfdesigner.exists(p))
                    wfdesigner.load(p);
                else
                    wfdesigner.create();
            }
            else {
                wfdesigner.data = data;
                wfdesigner.render();
            }
        }

        $(window).resize(function () {
            if (window.wfResizeTimer) {
                clearTimeout(window.wfResizeTimer);
                window.wfResizeTimer = undefined;
            }
            window.wfResizeTimer = setTimeout(function () {
                var w = $(window).width();
                var h = $(window).height();

                if (w > 300)
                    graphwidth = w - 40;

                if (h > 300)
                    graphheight = h - 250;

                if (graphheight < graphminheight)
                    graphheight = graphminheight;

                wfdesignerRedraw();
            }, 150);

        });

        $(window).resize();

    function DownloadScheme(){
        wfdesigner.downloadscheme();
    }

    function SelectScheme(type) {
        var file = $('#uploadFile');
        file.trigger('click');
    }

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

    function OnSave() {
        wfdesigner.schemecode = schemecode;

        var err = wfdesigner.validate();
        if (err != undefined && err.length > 0) {
            alert(err);
        }
        else {
            wfdesigner.save(function () {
                alert('The scheme is saved!');
            });
        }
    }
    function OnNew() {
        wfdesigner.create();
    }
    </script>
</body>
</html>

Open this page in the browser, for example with shemecode=test: index.html?schemecode=test. Now you can create and edit the document flow schrme. Let's create several states and push the Save scheme button, the scheme will be saved in WorkflowServer.

WorkflowServer

Now open WorkflowServer http://localhost:8077/?apanel=workflow&aid=test to see the scheme you have worked with previously.

WorkflowServer

If you need to get any information for your system from WorkflowServer control panel, use the following code:

$.ajax({
    url: 'http://localhost:8077/configapi?operation=load',
    async: true,
    success: function (response) {
        console.log(response.item);                
    }
});

The data set from WorkflowServer can be found in the response.item variable.
WorkflowServer

Find the link to the source code in the beginning of this article, see HTMLSample folder.

Integration to React app

At the moment, we can't use WorkflowDesigner as a React-component, that is why we suggest using the following code to create your own React-component containing WorkflowDesigner:

import React from "react";

export default class WFEComponent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {};
  }

  componentDidMount() {
    var data = undefined;
    if (this.wfdesigner != undefined) {
      data = wfdesigner.data;
      wfdesigner.destroy();
    }

    this.wfdesigner = new WorkflowDesigner({
      name: 'simpledesigner',
      apiurl: 'http://localhost:8077/designerapi',
      renderTo: 'wfdesigner',
      imagefolder: '/images/',
      graphwidth: 1200,
      graphheight: 600
    });

    var QueryString = this.getQueryString();
    var schemecode = QueryString.schemecode;
    var processid = QueryString.processid;
    if(schemecode == undefined || schemecode == ""){
        schemecode = 'TestScheme';
    }

    if (data == undefined) {
      var isreadonly = false;
      if (processid != undefined && processid != '')
          isreadonly = true;

      var p = { schemecode: schemecode, processid: processid, readonly: isreadonly };
      if (this.wfdesigner.exists(p))
        this.wfdesigner.load(p);
      else
        this.wfdesigner.create();
    }
    else {
      this.wfdesigner.data = data;
      this.wfdesigner.render();
    }
  }

  render (){
    return (<div id={"wfdesigner"}/>);
  }

  getQueryString() {
    // This function is anonymous, is executed immediately and
    // the return value is assigned to QueryString!
    var query_string = {};
    var query = window.location.search.substring(1);
    var vars = query.split("&");
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split("=");
        // If first entry with this name
        if (typeof query_string[pair[0]] === "undefined") {
            query_string[pair[0]] = pair[1];
            // If second entry with this name
        } else if (typeof query_string[pair[0]] === "string") {
            var arr = [query_string[pair[0]], pair[1]];
            query_string[pair[0]] = arr;
            // If third or later entry with this name
        } else {
            query_string[pair[0]].push(pair[1]);
        }
    }
    return query_string;
  }
}

Find the link to the source code in the beginning of this article, see ReactSample folder. Execute the following commands to launch the example:

npm install
npm start

Open http://localhost:8091/?schemecode=test to see the test scheme from WorkflowServer. WorkflowServer

If you need to get any information for your system from WorkflowServer control panel, use the following code:

$.ajax({
    url: 'http://localhost:8077/configapi?operation=load',
    async: true,
    success: function (response) {
        console.log(response.item);                
    }
});

The data set from WorkflowServer can be found in the response.item variable.
WorkflowServer

Integration to Angular

See the example of integration the designer into an Angular project on our page here and here on Github.

You can integrate these examples with WorkflowServer. To do that use apiurl:http://localhost:8077/designerapi when initializing WorkflowDesigner.

WorkflowAPI and CallbackAPI

I have not described how to call commands and process return calls in this article. These topics are well covered in our documentation here and here.

Conclusion

This was one of the most unexpected ways of using WorkflowServer. But even such an unconventional case works perfectly. You can be sure that various variants of integration are available to you when you use WorkflowServer.

Many thanks to Doug who with his questions and suggestions inspired me to write this article.

Top