MyHome Custom UI

 

Overview

The CumulusPro My Home form normally shows the tabs, fields (optionally in multiple columns) and tables that have been defined in the Workflow Configuration. While this makes it very quick and cost effective to create forms, there is a limit to the flexibility of this user interface.

 

MyHome Custom UI allows you  now display a custom UI which replaces the default UI provided by Straatos. This provids maximum flexibility in displaying the process data to the user while having the authentication and task queue managed by Straatos.

 

The custom UI can replace the form section, form section and Buttons or form section, buttons and viewer.

 

The custom UI can be provided either as a link in the configuraiton or a ZIP file with the website can be updated.

 

User Task Configuration

In the process designer, click on a user task and then on settings

In the first field 'Mode' select 'Task (form based)'

Expand the section 'My Home User Interface

Options:

 

Custom UI Positions

In the MyHome User interface section, 3 different positions can be selected to show the custom UI. The below screenshots show which area is replaced by the custom UI:

 

None

no custom user interface, the generated MyHome form is displayed

Form

The area below the buttons and the Form, History, Collaboration and Attachments tabs is replaced by the Custom UI.

Form and Buttons

Everything on the right of the viewer is replaced by the Custom UI

Form, button and viewer

Everything, including the viewer is replaced by the Custom UI

 

Creating a Custom UI

 

The Custom UI will be embedded in an iframe.

There are two ways to implement the Custom UI:

  1. Client side only code (e.g. HTML, JavaScript, CSS) that is hosted by CumulusPro. The code needs to be packaged as a ZIP file containing all files, including a file named index.html in the root of the ZIP file. The ZIP file can be uploaded using the button Upload ZIP. The first time the ZIP file is uploaded, the Custom UI URL should be empty and will be set automatically.
  2. An externally hosted application. Examples of this include full stack implementations like an Angular 5 CLI user interface with REST services implemented in Java Spring boot or a Microsoft dot net (core) backend. In this case, the Custom UI URL should be set to point to the index.html (or similar) of the website.

An API has been defined to communicate between MyHome and the Custom UI JavaScript code. The API is available in JavaScript and in Angular 5 TypeScript.

 

JavaScript MyHome API

The JavaScript API consists of a file named cumuluspro-api.js. At this time, the file needs to be included in the Custom UI application (either part of the ZIP file or available on the externally hosted server).

The API needs to be included at the end of the HTML document, but before the code that uses the API (in this example named custom.js):

<script src="https://cdnjs...min.js" integrity="..." crossorigin="anonymous"></script>

<script src="cumuluspro-api.js"></script>
<script src="custom.js"></script>

</body>
</html>

To request the My Home environment, the custom.js first needs to call the initialize function:

(function() {
    ...
    CPMyHome.initialize(function () {
    ...
        var variables = CPMyHome.document.variables;
    ...
    });
})();

The initialize function takes a callback as parameter. Once the initialize function has retrieved the My Home environment, the callback is called. In the callback function, the API functions and fields defined in CPMyHome can be used.

The following variables are available in CPMyHome:

The following event functions are available in CPMyHome:

The following one-way functions are available in CPMyHome:

This is the definition of CPMyHome.document (CPDocument) and related objects:

CPActivityInstance: function (activityInstance) {
    if (activityInstance) {
        this.activityId = activityInstance.activityId;
        this.activityName = activityInstance.activityName;
        this.duration = activityInstance.duration;
        this.startDateTime = activityInstance.startDateTime;
        this.endDateTime = activityInstance.endDateTime;
        this.triggerUserId = activityInstance.triggerUserId;
        this.triggerUserName = activityInstance.triggerUserName;
        this.workState = activityInstance.workState;
    }
},

CPDocumentPage: function (documentPage) {
    if (documentPage) {
        this.orientation = documentPage.orientation;
        this.originalURL = documentPage.originalURL;
        this.pageNumber = documentPage.pageNumber;
        this.pagePreviewURL = documentPage.pagePreviewURL;
        this.pdfPageURL = documentPage.pdfPageURL;
        this.thumbnailURL = documentPage.thumbnailURL;
    }
},

CPAdditionalData: function (additionalData) {
    if (additionalData) {
        this.key = additionalData.key;
        this.url = additionalData.url;
    }
},

CPDocumentComment: function (comment) {
    if (comment) {
        this.comment = comment.comment;
        this.name = comment.name;
        this.timestampDate = comment.timestampDate;
    }
},

CPDocument: function (document) {
    if (document) {
        this.id = document.id;

        this.startDateTime = document.startDateTime;
        this.variables = document.variables; // { var1: var1Value, var2: var2Value, ... }
        this.internals = document.internals; // { var1: var1Value, var2: var2Value, ... }

        if (document.activityInstances) {
            this.activityInstances = document.activityInstances.map(function (a) {
                return new CPMyHome.CPActivityInstance(a);
            });
        }
        
        this.originalURL = document.originalURL;

        if (document.documentPages) {
            this.documentPages = document.documentPages.map(function (p) {
                return new CPMyHome.CPDocumentPage(p);
            });
        }

        if (document.additionalData) {
            this.additionalData = document.additionalData.map(function (a) {
                return new CPMyHome.CPAdditionalData(a);
            });
        }

        if (document.comments) {
            this.comments = document.comments.map(function (c) {
                return new CPMyHome.CPDocumentComment(c);
            })
        }
    }
},

 

A partial example implementation of custom.js:

(function() {
    CPMyHome.initialize(function () {
        CPMyHome.collapseTasks();

        // Override Complete button behaviour
        CPMyHome.registerSubmitHandler(submitHandler);
        CPMyHome.registerDeleteHandler(deleteHandler)

        var variables = CPMyHome.document.variables;

        var value = variables.Variable1; // Variable1 is defined as Workflow index field

        // ...

        var table = CPMyHome.document.internals._table;

        table.body.lines.forEach(function (line) {
            var columnValue = line.ColumnName.value;
        });
    });

    // Handle Complete / Custom button event; either Submit claim or Save
    function submitHandler(button, ignoreValidation, stepName, status) {
        if (status == 'reject') {
            $('#message-text').val('');
            $('#reject-modal').modal('show');
        } else {
            submit(status, stepName);
        }
    }

    // Handle Complete / Custom button event; either Submit claim or Save
    function submit(status, stepName) {

        // ...

        CPMyHome.submitTask({
            Variable1: 'value 1',
            Variable2: 'value 2',
            _moveToStep: stepName,
            _status: status,
            _userId: status == 'reject' ? CPMyHome.document.internals._authAccountId : null
        });
    }

    function deleteHandler() {
        if (confirm('Do you want to delete this task?')) {
            CPMyHome.submitTask({
                _moveToStep: 'delete'
            });
        }
    }

    $('#reject-claim-button').on('click', function () {
        CPMyHome.addComment($('#message-text').val());

        $('#reject-modal').modal('hide');

        submit('reject');
    });
})();

 

TypeScript MyHome API

The TypeScript API consists of a file named myhome.service.ts and should be included in the Angular project and registered in app.module.ts:


@NgModule({
  declarations: [
    ...
  ],
  imports: [
    BrowserModule,
    ...
  ],
  providers: [MyHomeService],
  bootstrap: [AppComponent]
})
export class AppModule { }

To request the My Home environment, the custom.js first needs to call the initialize function:

import { Component, OnInit } from '@angular/core';
import { MyHomeService } from '../services/myhome.service';

@Component({
  selector: 'app-homepage',
  templateUrl: './homepage.component.html',
  styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
  title = 'app';

  constructor(public myhomeService: MyHomeService) { }

  ngOnInit() {
    this.myhomeService.initialize().subscribe(
      _ => {
        console.log('initialized', this.myhomeService.document);
      }
    );
  }
}

Once the initialize function has retrieved the My Home environment, the API functions and fields defined in MyHomeService can be used in the subscribed code.

The following variables are available in myhomeService:

The following event functions are available in myhomeService:

The following one-way functions are available in myhomeService:

The following code shows definitions of document / CPDocument and SubmitParameter:

 

export namespace MyHomeService {
  export class SubmitParameter {
    public button: string;
    public ignoreValidation: boolean;
    public stepName: string;
    public status: string;

    constructor(button: string, ignoreValidation: boolean, stepName: string, status: string) {
      this.button = button;
      this.ignoreValidation = ignoreValidation;
      this.stepName = stepName;
      this.status = status;
    }
  }

  export class CPActivityInstance {
    public activityId: string;
    public activityName: string;
    public duration: number;
    public startDateTime: Date;
    public endDateTime: Date;
    public triggerUserId: number;
    public triggerUserName: string;
    public workState: string;

    constructor(activityInstance?: any) {
      if (activityInstance) {
        this.activityId = activityInstance.activityId;
        this.activityName = activityInstance.activityName;
        this.duration = activityInstance.duration;
        this.startDateTime = activityInstance.startDateTime;
        this.endDateTime = activityInstance.endDateTime;
        this.triggerUserId = activityInstance.triggerUserId;
        this.triggerUserName = activityInstance.triggerUserName;
        this.workState = activityInstance.workState;
      }
    }
  }

  export class CPDocumentPage {
    orientation: string;
    originalURL: string;
    pageNumber: number;
    pagePreviewURL: string;
    pdfPageURL: string;
    thumbnailURL: string;

    constructor(documentPage?: any) {
      if (documentPage) {
        this.orientation = documentPage.orientation;
        this.originalURL = documentPage.originalURL;
        this.pageNumber = documentPage.pageNumber;
        this.pagePreviewURL = documentPage.pagePreviewURL;
        this.pdfPageURL = documentPage.pdfPageURL;
        this.thumbnailURL = documentPage.thumbnailURL;
      }
    }
  }

  export class CPAdditionalData {
    key: string;
    url: string;

    constructor(additionalData?: any) {
      if (additionalData) {
        this.key = additionalData.key;
        this.url = additionalData.url;
      }
    }
  }

  export class CPDocumentComment {
    comment: string;
    name: string;
    timestampDate: string;

    constructor(comment?: any) {
      if (comment) {
        this.comment = comment.comment;
        this.name = comment.name;
        this.timestampDate = comment.timestampDate;
      }
    }
  }

  export class CPDocument {
    id: number;
    startDateTime: Date;
    variables: any; // { var1: var1Value, var2: var2Value, ... }
    internals: any; // { var1: var1Value, var2: var2Value, ... }
    activityInstances: CPActivityInstance[];
    originalURL: string;
    documentPages: CPDocumentPage[];
    additionalData: CPAdditionalData[];
    comments: CPDocumentComment[];

    if (document) {
      this.id = document.id;

      this.startDateTime = document.startDateTime;
      this.variables = document.variables;
      this.internals = document.internals;

      if (document.activityInstances) {
        this.activityInstances = document.activityInstances.map(function (a) {
          return new CPActivityInstance(a);
        });
      }


      this.originalURL = document.originalURL;

      if (document.documentPages) {
        this.documentPages = document.documentPages.map(function (p) {
          return new CPDocumentPage(p);
        });
      }

      if (document.additionalData) {
        this.additionalData = document.additionalData.map(function (a) {
          return new CPAdditionalData(a);
        });
      }

      if (document.comments) {
        this.comments = document.comments.map(function (c) {
          return new CPDocumentComment(c);
        })
      }
    }
  }
}

 

ES6 MyHome API (React)

The ES6 API consists of a file named myhomeservice.js. The file should be added to the custom project’s ‘src’ or any sub- directory. The API can then be imported (depending on the location) using:

import {MyHomeService} from "./myhomeservice";

A basic set-up for React has been made using the ‘create-react-app’ npm package which provides a basic project structure:

my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   └── favicon.ico
│   └── index.html
│   └── manifest.json
└── src
    └── App.css
    └── App.js
    └── App.test.js
    └── index.css
    └── index.js
    └── logo.svg
    └── registerServiceWorker.js

The MyHomeService object can be instantiated in index.js to be able to pass it to multiple components using props. The following code shows an example of this:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import {MyHomeService} from "./myhomeservice";

let myHomeService = new MyHomeService();

ReactDOM.render(<App myHomeService={myHomeService} />, document.getElementById('root'));
registerServiceWorker();

 

The MyHomeService object can then be used in the App component as followed:

import React, { Component } from 'react';
import logo from '../../logo.svg';
import './App.css';

class App extends Component {

  constructor(props) {
    super(props);
    this.myHomeService = props.myHomeService;
  }
  render() { 
    return (      
      <div></div>
    );
  }
}

export default App;

To request the My Home environment, the MyHomeService object needs to initialize. This is done using it’s initialize function. Initialize expects a callback to be passed to be able to send an update when finished. An example of the initialization process is shown in the following code:

import React, { Component } from 'react';
import logo from '../../logo.svg';
import './App.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.myHomeService = props.myHomeService;

    this.state = {
      initialized: false
    };

    this.myHomeService.initialize(() => {
      this.setState({
        initialized: true
      });

      this.myHomeService.collapseTasks();
    });
  }
  render() { 
    return (
      <div>{this.state.initialized ? 'Initialized for document ID: ' + this.myHomeService.document.id : 'Initializing...'}</div>
    );
  }
}

export default App;

After initialization, the MyHomeService object can be used to get certain variables or call various function as described in the TypeScript portion of this document. An example of this is shown in the follow code (only the render function is show for brevity):

render() { 
  return (      
    <div>{this.state.initialized ? 'Initialized for document ID: ' + this.myHomeService.document.id : 'Initializing...'}</div>

    {this.state.initialized && [
      <div>
        <button type="button" onClick={ () => {
          this.myHomeService.showOriginalDocument();
          this.myHomeService.expandViewerHalfWidth();
        }}>Show Original Document & expand half width</button>
      </div>,
      <div>
        <button type="button" onClick={ () => {this.myHomeService.expandViewerHalfWidth()}}>Expand Viewer Half Width</button>
      </div>,
      <div>
        <button type="button" onClick={ () => {this.myHomeService.expandViewerFullWidth()}}>Expand Viewer Full Width</button>
      </div>,
      <div>
        <button type="button" onClick={ () => {
          this.myHomeService.collapseViewer();
          this.myHomeService.clearViewer();
        }}>Collapse & Clear Viewer</button>
      </div>
    ]}
  );
}

Create your own Knowledge Base