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.
In the process designer, click on a user task and then on settings
.png?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9kemY4dnF2MjRlcWhnLmNsb3VkZnJvbnQubmV0L3VzZXJmaWxlcy8zMzg1LzUwNDYvY2tmaW5kZXIvaW1hZ2VzL2ltYWdlKDY1KS5wbmciLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE3NjU4NjAyMDR9fX1dfQ__&Signature=N1OuzuVtmXuUuxbJ9fvVvvZInUlEnFloz92hpRL1KjNDrWgTKwd66~f6UsOzuYHtDya3KgQJh5UHFFPVvTFnqPXRcfpxq45o~2gy~j-ocZ8CPg0PVfHlo-aqSmWzjIqsIjgDWjqwpPgO2OkXPSKA4qRWZxdZcKn--MF9JO2U~wcK9UvAl-3qIu1KS~zFfNGixokv3RZ8KjSoW~hltMOi3OBY~gdno7fgq9LWb7XaUWj5Qpb6otj2S~XO3nFyWs3xH-ZLhKTa3HhxV4z7vK2hHqqcyjYM1Dnr~spNyhpJ1~Yek2d-1VZAklBy0anDSSjJtdujAAzxdiwmVhTLSe8BMA__&Key-Pair-Id=K2TK3EG287XSFC)
In the first field 'Mode' select 'Task (form based)'
Expand the section 'My Home User Interface
.png?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9kemY4dnF2MjRlcWhnLmNsb3VkZnJvbnQubmV0L3VzZXJmaWxlcy8zMzg1LzUwNDYvY2tmaW5kZXIvaW1hZ2VzL2ltYWdlKDY2KS5wbmciLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE3NjU4NjAyMDR9fX1dfQ__&Signature=lNImb90RA4awt2yuIlLK0asvTj-cSZbVimYygYht5JLUNMUbe29GGlVsFhi~A~hsuvtAlDJKodnegZ6BQwnY-rgPaDgHcf44HJdipywIMY8rAIcuo3mqbZ6g5v3joffGMV~5ZjlJ~nzZ24ER1S6kIM6xXk93Y3l22sLLH3fCzO3GHsEc4eEnMl4x0qrcoFb8T71zquHIVXOPnl3y4cMXvIUPUu4L8seTV64sQ40nDorukJmdfef8yMFU4Hv564W8o7hfiPBQNKy0tz4Q7OjwICtmumobyzma5jx~pFYwvCb6RYBDzJ5piLkyU9jaRrKtG4MGHaLTAI-Q4nGWjUQRBQ__&Key-Pair-Id=K2TK3EG287XSFC)
Options:
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:
no custom user interface, the generated MyHome form is displayed
.png?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9kemY4dnF2MjRlcWhnLmNsb3VkZnJvbnQubmV0L3VzZXJmaWxlcy8zMzg1LzUwNDYvY2tmaW5kZXIvaW1hZ2VzL2ltYWdlKDY3KS5wbmciLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE3NjU4NjAyMDR9fX1dfQ__&Signature=d8kpWo4BNJn9a5GrXUGgjJ-ete2xzmu6E09O07PSBLaD4TrHUjV-UQKYMxZ3hU1dYu7oDREh144Br0ryTyuCvK2cJZ~pyAsh4F8rt4HDFurWGI2C8X00YSlu7kmQmkecWOYtAK35HPYpfxLHaG7vVjUOrpy6mnTp3~nxWWXGN9t0u9mtcL89Ztrw8hUGDSsCCTgBWIJXyECV7aM-GO1LN7AOXTXThhV~DKDW-CjiAE-WLD0Q4epEmiRkMzPYDJuvzDB4yuS-RtF4-Fg4bl8UmY2ZvZZYPNn4BDMLW0wSRMx0dfQBwH5ZpsOWhOAO6tqlTLm2Oqmc5dTe6to7dS0JYw__&Key-Pair-Id=K2TK3EG287XSFC)
The area below the buttons and the Form, History, Collaboration and Attachments tabs is replaced by the Custom UI.
.png?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9kemY4dnF2MjRlcWhnLmNsb3VkZnJvbnQubmV0L3VzZXJmaWxlcy8zMzg1LzUwNDYvY2tmaW5kZXIvaW1hZ2VzL2ltYWdlKDY4KS5wbmciLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE3NjU4NjAyMDR9fX1dfQ__&Signature=BixampB67~W0GdulW2K-h2Yc3foXlu7FsdrJEgzepPDzo8CRZzxtmgPWWE8YYrbaM~rMIvGwLJfoG5pJ7eVj5dNT8TDz8oOcvIp7dJscqNi2ySk0xD1qY4W4hepaJKpSZU2t-Ej39zBLF2JNA0PSY3A0xqcHrszv3rrerzXdv8jmsbounkZah7b8k-YN8qHO1dDPasx4-~jqYhxmncWaCf~4z~qMcEAK2iO3oZvqwSS3IgOhV~qbqCrVki-u6TSJRMELi9tCsmtgov7iHAeqKVAVVzMXpNRFRHIKExk19~DiUsgBJnFsvtHX2xsjlvwdoAJIL7uP8pGmG7-ggJqLPw__&Key-Pair-Id=K2TK3EG287XSFC)
Everything on the right of the viewer is replaced by the Custom UI
.png?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9kemY4dnF2MjRlcWhnLmNsb3VkZnJvbnQubmV0L3VzZXJmaWxlcy8zMzg1LzUwNDYvY2tmaW5kZXIvaW1hZ2VzL2ltYWdlKDY5KS5wbmciLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE3NjU4NjAyMDR9fX1dfQ__&Signature=ekM9q7kA8Bmd47BM6wp1erEJ-9JvwR0ZOznbSHJH7kbbCCiPExmBPjeDT9I97-DPywoHSZk7QvMJWllfxYxz5FI6fJVUDH3tpZVbzEznlfKWMbIG4TTpxN-JGhUWDjdf4cvS~Rm7qB4me-SGBrNOClZkdqW6HnC~dyAp4lr7F8jYYIK46PEV8l7H3lnRJUhvbx6gTbisWAuGD8nsld6NntWI-yS1uSROB9Oi11-BFxHnGAsGsVGHJBoutMfMsOVrUQRld8hWvyrAga~S5~hyvIinsP3bUU1DEBXS27ec7GfawOFr4e3LjFOrjdiisxtaWRgpMqQYKQmkGM8prNfSAA__&Key-Pair-Id=K2TK3EG287XSFC)
Everything, including the viewer is replaced by the Custom UI
.png?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9kemY4dnF2MjRlcWhnLmNsb3VkZnJvbnQubmV0L3VzZXJmaWxlcy8zMzg1LzUwNDYvY2tmaW5kZXIvaW1hZ2VzL2ltYWdlKDcwKS5wbmciLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE3NjU4NjAyMDR9fX1dfQ__&Signature=QHqubTgD~sWvx-BDYRV8CImLsTzh-CznS3r0CtD3AWvF2smfpu2ElC8CkoFo0PbAzGkf7i9TkllwFkXg~adCZeUo32kYmx-rOeL2jBxa1UIIHrKpHEPmknFoLn~X~VdZj6xAEXKZAigpDP41nN1eIFMlGC8A90v2MgC~AVFOasJbi9bc9HV9SLBSk1ayqT7bP4EHagV4lmPUgp~GQ4O6auj2wDh9Nqfjb7XmEH0vy4XrQsdxJxXicP26XEsJTX4H2o~CagYyGFnGNkyJVOSWrCW2lzfFIWWdQvD3qmLmAiit-NnbqTusdvY4aYBVEgA7tEWme7poHl3uf88aE6Gnng__&Key-Pair-Id=K2TK3EG287XSFC)
The Custom UI will be embedded in an iframe.
There are two ways to implement the Custom UI:
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.
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');
});
})();
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);
})
}
}
}
}
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>
]}
);
}