Customize User Interface and Flow-Apps
Setting Up the Project Structure
We create a simple Typescript
application packaged as a UMD
module using Rollup.js.
We recommend adding the flowable work-scripts
dependency to your frontend project, this way everything that you need in terms of Typescript configuration or frontend build will be done for you.
The main application is ready to side-load external applications, by
injecting a custom universal module definition (UMD
) commonly called custom.js
.
This document
assumes you have already created a Java application serving the front end
component, included in the flowable-work-frontend
jar.
The first step is to create an empty npm
or yarn
project with a src
folder.
Then the work-scripts
dependency can be added.
yarn add @flowable/work-scripts
This package will make available for you an executable tool to run the build, at this point your package.json
file should look like the next (note the main
and script.build
properties):
{
...
"main": "src/index.tsx",
"scripts": {
...
"build": "flowbuild build"
},
"dependencies": {
"@flowable/work-scripts": "^3.7.0"
}
...
}
To bundle the frontend you will need to execute the next command:
yarn build
This will generate custom.js
and custom.css
files in /dist
, in addition to copy some static assets like ./src/fonts/**/*
.
Copy the generated files from /dist
into your Flowable web folder (e.g. /src/main/resources/static/ext
) and the new components will be injected into the Work UI.
Basic Application Structure
Your application module exports four objects:
A list of applications
The application sorting
A list of additional translations to be merged with existing ones
Other customizations such as form details, form translations, custom header, etc.
To do that, create a file in src/index.tsx
with the following content:
const applications: ExternalApplication[] = [];
const applicationSorting: { [appId: string]: number } = {};
const translations = {};
export default { applications, applicationSorting, translations };
Alternative configuration for the frontend build
If for some reason @flowable/work-scripts
dependency cannot be used, the most important configuration files that need to be present are the following Typescript configuration:
"compilerOptions": {
"jsx": "react",
"module": "esNext",
"moduleResolution": "node",
"outDir": "./dist",
"preserveConstEnums": true,
"strict": true,
"strictFunctionTypes": false,
"target": "es5",
"sourceMap": true,
"importHelpers": true,
"resolveJsonModule": true,
"esModuleInterop": true
}
and the following Rollup.js
configuration:
export default {
input: "src/index.tsx",
output: {
name: "flowable.externals",
file: "./dist/custom.js",
format: "umd",
sourcemap: true,
globals: {
react: "flowable.React",
"react-dom": "flowable.ReactDOM",
"react-router": "flowable.ReactRouter",
"@flowable/forms": "flowable.Forms",
"@flowable/work": "flowable.Components",
},
},
plugins: [
json(),
sass({
output: "./dist/custom.css",
runtime: node_sass,
options: { outputStyle: production ? "compressed" : "expanded" },
}),
copy({
targets: [{ src: "./src/fonts/", dest: "./dist" }],
}),
resolve(),
commonjs(),
typescript(),
production && uglify(),
sourceMaps(),
copy({
targets: [{ src: "dist/*", dest: "../src/main/resources/static/ext" }],
}),
],
external: [
"react",
"react-dom",
"react-router",
"@flowable/forms",
"@flowable/work",
"@flowable/work-scripts",
],
};
Typescript and Rollup.js are totally optional and you can use any other language or module bundler as long as it marks the following names as externals:
React is available at
flowable.React
andflowable.ReactDOM
.Flowable Forms are available at
flowable.Forms
.Flowable common components are registered at
flowable.Components
.
Add a Flow-App using Custom React Components
You can create Flow-Apps directly using Flowable Design, but there are cases where you might want to do more fine-grained customization or add your own components.
Creating the First Application
Let us add a simple application displaying a very basic React component, defined as:
import React from 'react';
import classNames from 'classnames';
import { ExternalAppComponentProps } from '@flowable/work';
import './ghostApp.scss';
export type GhostsAppProps = ExternalAppComponentProps & {};
export const GhostsApp = (props: GhostsAppProps) => (
<div className={classNames('flo-ghostsApp', { 'flo-ghostsApp--engage': props.features.conversations })}>
<div className="flo-ghostsApp__title">Ghosts Application</div>
<div className="flo-ghostsApp__data">{JSON.stringify(props, null, 2)}</div>
</div>
);
Custom Flow-Apps are just regular React components and they follow the same behavior and lifecycle. Render will be only requested when parent props change, as expected.
The sample application dumps all the props received as JSON, with the following information:
export type ExternalAppComponentProps = {
// Current logged-in user
currentUser: CurrentUser;
// Set of configured URLs for the platform services (you can define your own)
endpoints: { [key: string]: string };
// Set of feature toggles enabled/disabled for the current user
features: { [key: string]: boolean };
};
Exposing the Application
To expose the application and make it visible under the left menu bar,
you need to define a few properties, such as the name or the icon.
Modify the file at src/index.tsx
to do that:
const applications: ExternalApplication[] = [
{
applicationId: 'ghosts01',
label: 'Ghosts!',
labelKey: 'ghosts:title',
icon: 'skull-crossbones/solid',
component: (props: ExternalAppComponentProps) => <GhostsApp {...props} />,
}
];
Once loaded, your application is available at /ghosts01
.
Rendering a Form Instead of a Full-fledged Component
Instead of defining a component
, you can also provide a property named
form
, that can either receive a form key or the complete form definition:
form: () => 'A10_joinParticipantActionForm'
Alternatively, using the form definition:
form: () => ({ rows: [ ... ] })
Make sure also to remove the component
property, since it is
used instead if defined.
Customizing the Title of the Application
By default, the application title is inherited by the host application. If you want to change the application title for your Flow-App, you can provide a function that is called from the host application:
title: ({ applicationId }: { applicationId: string }) => `Ghosts - ${applicationId}`
Customizing the Icon
We support two icon packs, Font Awesome 5 Pro
(FA5) and Feather. The variants for FA5 can be referred by
appending /solid
, /light
or /regular
where /regular
is the default.
Icon names from FA5 have a higher priority than the ones from Feather,
for backward compatibility reasons.
Adding Sub-applications
You can also define sub-applications, by filling the sub
property:
const applications: ExternalApplication[] = [
{
applicationId: 'ghosts01',
label: 'Ghosts!',
labelKey: 'ghosts:title',
icon: 'skull-crossbones/solid',
component: (props: ExternalAppComponentProps) => <GhostsApp {...props} />,
sub: [
{
applicationId: 'rockets',
label: 'Some Rockets',
labelKey: 'pirates:rockets.title',
icon: 'rocket/solid',
component: () => <div>Rockets App</div>
}
]
}
];
Notice that when there are sub-applications, the first one loads by default unless the base URL is defined:
When the URL is
#/ghosts01
, theGhostApp
component is loaded.When the URL is
#/ghosts01/rockets
, a simplediv
is rendered.When you transition from other applications to the Ghosts one, it loads the first application at
#/ghosts01/rockets
.
We recommend setting the parent application component to be the same as the first application.
Changing Application Sorting
The applicationSorting
property allows for the definition of custom
application sorting. You can also override existing sort order in Work or Engage.
For instance, if you want to place your recently created Ghosts application
as the first one, followed by contacts and putting Engage always at the end:
const applicationSorting = {
pirates: 1,
contacts: 3,
conversations: 800
};
By default there is a gap of 10 units for existing applications, with the following values:
conversations: 20,
work: 30,
tasks: 40,
contacts: 70,
reports: 80,
templateManagement: 100
Translations
Adding Application Translations
Applications support defining a labelKey
that is used to retrieve the
label displayed from the translation bundles. You can get different
translations by modifying the translations
property:
const applications: ExternalApplication[] = [
{
applicationId: 'eggplants',
labelKey: 'eggplants:title',
...
}
];
const translations = {
'en-US': {
eggplants: {
title: 'Eggplants'
}
},
'en-UK': {
eggplants: {
title: 'Aubergine'
}
},
'es-ES': {
eggplants: {
title: 'Berenjenas'
},
}
};
Adding Form Engine Translations
By providing the formTranslations
object to the external customisations, you can easily add your own Forms
translations.
//index.ts
...
const translations = {
...
};
const formTranslations = {
en: {
dtable: {
actionShowFilters: 'Show filters',
actionClearFilters: 'Clear filters'
},
validation: {
isRequired: 'Field must not be empty',
minLength: 'Field length must be longer or equal to {{extraSettings.minLength}}',
maxLength: 'Field length must be smaller or equal to {{extraSettings.maxLength}}',
minValue: 'Field must be a number greater than or equal to {{extraSettings.min}}'
}
}
};
...
export default {
...
translations,
formTranslations,
...
};
This will create form translations for most common language variations (e.g. 'en_us', 'en_uk', 'en_gb', etc.)
If you want to override one particular variation you can do it by adding it to the formTranslations
object.
...
const formTranslations = {
en: {
dtable: {
actionShowFilters: 'Show filters',
actionClearFilters: 'Clear filters'
},
validation: {
isRequired: 'Field must not be empty',
minLength: 'Field length must be longer or equal to {{extraSettings.minLength}}',
maxLength: 'Field length must be smaller or equal to {{extraSettings.maxLength}}',
minValue: 'Field must be a number greater than or equal to {{extraSettings.min}}'
}
},
'en_gb': {
validation: {
isRequired: 'This field is mandatory'
}
}
};
...
In this example the value validation.isRequired
will be "Field must not be empty" for every language variation except in en_gb
case, that will be overwritten by "This field is mandatory".
NOTE
All variation keys are treated as lowercase underscored strings. E.g: en-GB
or en-gb
keys will be parsed automatically to en_gb
.
Important: If more than one variation key is provided for the same language variation, the effective translations will be set as the key that is lowest in the formTranslations
object.
You can find the complete list of values that can be overwritten here
const formTranslations = {
...
'en_gb': {
validation: {
isRequired: 'This field is mandatory'
}
},
// this second variation will be parsed to 'en_gb'
// and set as the effective translation.
'en-GB': {
validation: {
isRequired: 'This field must be completed'
}
}
};
...
Customize the Default CSS Stylesheet
Flowable CSS should be customized through user themes.
The currentUser.theme
property returned by the back end is used by a CSS
class injected in one of the parent DOM nodes. This means it might exist
as many CSS themes as theme configurations exist, e.g., user-1
might be
configured with theme-1
, user-2
with theme-2
, and so on.
There are specific recommended steps to follow to achieve this successfully:
Configure user theme
Customize the theme stylesheet
Fonts customization
Logo customization
Configure User Themes
User themes should be configured as part of a user configuration in the
tenant setup file. For instance, based on the following user configuration,
the user Shane
is associated with the my-custom-theme
:
{
"firstName": "Shane",
"lastName": "Bowen",
"login": "shane.bowen",
"personalNumber": "12345678",
"technicalNumber": "123456",
"entityId": "HK-030",
"language": "en",
"theme": "my-custom-theme",
"userDefinitionKey": "user-client-advisor"
}
This results in a CSS
class applied to the parent DOM component as
follows (note the theme-
prefix applied to the specified theme name):
<div class="flw-root-app theme-my-custom-theme">
...
</div>
Customize the Theme in Flowable
Option 1 - Customize the Theming Configuration
The Theming Configuration can be configured during Runtime, by adding the allowedFeature
= themeManagement
.
Then the Theme Management app appears in Flowable and the logos as well as several colors can be set.
Furthermore, the Theming configuration can also be set in the backend, by adding a JSON configuration file to following
path in your classpath resources/com/flowable/themes/custom
.
The JSON configuration file for the theming has the following format:
{
"overwrite": true,
"tenantIds": ["acme", "megacorp"],
"themes": [
{
"name": "flowable-theming",
"options": {
"logoUrl": "data:image/svg+xml;base64,<base64-encoded-image>",
"logoUrlFull": "data:image/svg+xml;base64,<base64-encoded-image>",
"subMenu": "#0b0f18",
"topNavBar": "#ffffff",
"switcherItemFgHover": "#ccd1d6",
"switcherSelectedBgHover": "#216c7d",
"subMenuSelectedItemBorder": "#b03b39",
"subMenuSelectedItemBg": "#111e2c",
"switcherItemFg": "#b0b8bf",
"switcherBase": "#111e2c",
"switcherSelectedFg": "#ffffff",
"switcherActionHover": "#e54d42",
"switcherSelectedBg": "#1d5d6d",
"subMenuSelectedItemFg": "#ffffff",
"subMenuItemFgHover": "#ccd1d6",
"switcherAction": "#b03b31",
"subMenuItemFg": "#b0b8bf",
"buttonBg": "#E1E1E1",
"buttonFg": "#333333",
"buttonHoverBg": "#eeeeee",
"buttonHoverFg": "#333333",
"buttonPrimaryBg": "#1d5d6d",
"buttonPrimaryFg": "#ffffff",
"buttonPrimaryHoverBg": "#226e81",
"buttonPrimaryHoverFg": "#ffffff",
"flwColorLightgray": "#cccccc",
"flwFontSans": "-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif",
"flwFormsBackgroundColor": "#FFFFFF",
"flwFormsBlueBackgroundColor": "#d8eafc",
"flwFormsBorderColor": "#e5e5e5",
"flwFormsColor": "#0ac3db",
"flwFormsColorHover": "#0998aa",
"flwFormsColorSecondary": "#538187",
"flwFormsDebugBorder": "#ff0097",
"flwFormsErrorColor": "#f0506e",
"flwFormsGreenBackgroundColor": "#edfbf6",
"flwFormsLabelTextColor": "#333333",
"flwFormsMutedBackgroundColor": "#f8f8f8",
"flwFormsMutedTextColor": "#999999",
"flwFormsOrangeBackgroundColor": "#fef5ee",
"flwFormsOrangeColor": "#faa05a",
"flwFormsRedBackgroundColor": "#fef4f6",
"flwFormsSelectedRowBackgroundColor": "#FFFFDD",
"flwFormsShadowColor": "#000000",
"flwFormsSuccessColor": "#32d296",
"flwFormsTextColor": "#666666",
"flwGutter": "6px",
"flwText2Xl": "27px",
"flwText3Xl": "30.6px",
"flwTextBase": "14px",
"flwTextLg": "21px",
"flwTextMed": "13px",
"flwTextSm": "12px",
"flwTextTiny": "10px",
"flwTextXl": "22.95px",
"flwTextXs": "11px",
"fontFamily": "Roboto"
}
}
]
}
The option overwrite
configures if the server needs to overwrite the themes
on server startup (and restart). This configuration is optional and can also
be configured globally by setting the following application property to true
or
false
- flowable.platform.theme.force-overwrite
.
The tenantIds
is a list of tenants where this theming configuration becomes
applicable. This is also an optional configuration. Without setting any tenantIds
the theming becomes applicable overall tenants.
The themes
is a list of themes which is represented in the example
above with one element of a theme configuration.
Option 2 - Customize the Theme Stylesheet
Custom CSS can be implemented with CSS or Sass which is an
extension of CSS. Any .css
or .scss
file found under /frontend
can be
imported and bundled as part of the application CSS.
In your CSS you can override the CSS variables like this.
:root {
--flw-font-family: "Roboto";
--flw-switcher-base: #111e2c;
--flw-switcher-item-color: #b0b8bf;
--flw-switcher-item-color-hover: #ccd1d6;
--flw-switcher-selected-bgcolor: #1d5d6d;
--flw-switcher-selected-bgcolor-hover: #216c7d;
--flw-switcher-selected-color: #fff;
--flw-subMenu-bgcolor: #0b0f18;
--flw-subMenu-item-color: #b0b8bf;
--flw-subMenu-item-color-hover: #ccd1d6;
--flw-subMenu-selected-bgcolor: #111e2c;
--flw-subMenu-selected-color: #fff;
--flw-subMenu-selected-color-border: #b03b39;
--flw-subMenu-action-color: #b03b39;
--flw-subMenu-action-color-hover: #e54d42;
--flw-navigationBar-bgcolor: #fff;
--flw-color-lightgray: #cccccc;
--flw-button-bg: #e1e1e1;
--flw-button-fg: #333333;
--flw-button-hover-bg: #d4d4d4;
--flw-button-hover-fg: #333333;
--flw-button-primary-bg: #1d5d6d;
--flw-button-primary-fg: #ffffff;
--flw-button-primary-hover-bg: #226e81;
--flw-button-primary-hover-fg: #ffffff;
--flw-forms-background-color: #ffffff;
--flw-forms-blue-background-color: #d8eafc;
--flw-forms-border-color: #e5e5e5;
--flw-forms-color: #1e87f0;
--flw-forms-color-hover: #0f6ecd;
--flw-forms-color-secondary: #538187;
--flw-forms-debug-border: #ff0097;
--flw-forms-error-color: #f0506e;
--flw-forms-green-background-color: #edfbf6;
--flw-forms-label-text-color: #333333;
--flw-forms-muted-background-color: #f8f8f8;
--flw-forms-muted-text-color: #999999;
--flw-forms-orange-background-color: #fef5ee;
--flw-forms-orange-color: #faa05a;
--flw-forms-red-background-color: #fef4f6;
--flw-forms-selected-row-background-color: #ffffdd;
--flw-forms-shadow-color: #000000;
--flw-forms-success-color: #32d296;
--flw-forms-text-color: #666666;
}
Notice. For backwards compatibility we still support changing some variables by importing and calling the SASS mixin theme(...)
. This mechanism is deprecated and support will be removed from version 3.10.0
.
Export/Import a theme customization
The Theme Customizations can be exported to a Json file using the Export button
from the Theme card action menu.
The generated JSON file has the following format:
{
"logoUrl": "data:image/svg+xml;base64,<base64-encoded-image>",
"logoUrlFull": "data:image/svg+xml;base64,<base64-encoded-image>",
"subMenu": "#0b0f18",
"topNavBar": "#ffffff",
"switcherItemFgHover": "#ccd1d6",
"switcherSelectedBgHover": "#216c7d",
"subMenuSelectedItemBorder": "#b03b39",
"subMenuSelectedItemBg": "#111e2c",
"switcherItemFg": "#b0b8bf",
"switcherBase": "#111e2c",
"switcherSelectedFg": "#ffffff",
"switcherActionHover": "#e54d42",
"switcherSelectedBg": "#1d5d6d",
"subMenuSelectedItemFg": "#ffffff",
"subMenuItemFgHover": "#ccd1d6",
"switcherAction": "#b03b31",
"subMenuItemFg": "#b0b8bf",
"buttonBg": "#E1E1E1",
"buttonFg": "#333333",
"buttonHoverBg": "#eeeeee",
"buttonHoverFg": "#333333",
"buttonPrimaryBg": "#1d5d6d",
"buttonPrimaryFg": "#ffffff",
"buttonPrimaryHoverBg": "#226e81",
"buttonPrimaryHoverFg": "#ffffff",
"flwColorLightgray": "#cccccc",
"flwFontSans": "-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif",
"flwFormsBackgroundColor": "#FFFFFF",
"flwFormsBlueBackgroundColor": "#d8eafc",
"flwFormsBorderColor": "#e5e5e5",
"flwFormsColor": "#0ac3db",
"flwFormsColorHover": "#0998aa",
"flwFormsColorSecondary": "#538187",
"flwFormsDebugBorder": "#ff0097",
"flwFormsErrorColor": "#f0506e",
"flwFormsGreenBackgroundColor": "#edfbf6",
"flwFormsLabelTextColor": "#333333",
"flwFormsMutedBackgroundColor": "#f8f8f8",
"flwFormsMutedTextColor": "#999999",
"flwFormsOrangeBackgroundColor": "#fef5ee",
"flwFormsOrangeColor": "#faa05a",
"flwFormsRedBackgroundColor": "#fef4f6",
"flwFormsSelectedRowBackgroundColor": "#FFFFDD",
"flwFormsShadowColor": "#000000",
"flwFormsSuccessColor": "#32d296",
"flwFormsTextColor": "#666666",
"flwGutter": "6px",
"flwText2Xl": "27px",
"flwText3Xl": "30.6px",
"flwTextBase": "14px",
"flwTextLg": "21px",
"flwTextMed": "13px",
"flwTextSm": "12px",
"flwTextTiny": "10px",
"flwTextXl": "22.95px",
"flwTextXs": "11px",
"fontFamily": "Roboto"
}
Is it possible to import a Custom Theme Json file using the Import button
in the theme management view.
Notice. Once the file has been uploaded, the user will be prompted to add a name for the new theme and any existing theme with the same name will be overridden.
Font Customization
Fonts are exposed through a Sass file within the ../lib
folder. This way
the same fonts can be easily used for custom components:
@import "../lib/_fonts.scss";
.my-custom-component {
font-family: $flowable-fonts-roboto;
...
}
...
Customize Font Family
We can also set the font family for the whole application. As mentioned above using the theme()
mixin you can set the font family between the native supported fonts, e.g:
@import "~@flowable/work/theme.scss";
@include theme((... fontFamily: "Roboto", ...));
- Supported Font Families:
Roboto
,Fira Sans
.
Adding External Font Families
As part of font family customizations, we can add font families that are not natively supported in Flowable Front-End.
To add it, we can create a ./src/fonts/
folder and then copy the font files inside. You can organize these font families creating a folder for each one with the font family name, e.g: ./src/fonts/DancingScript
To make use of this font we need to expose it through the @font-face
API. E.g:
@import '~@flowable/work/theme.scss';
@font-face {
font-family: 'DancingScript';
src: url('src/fonts/DancingScript/DancingScript-Regular.ttf') format('truetype');
}
...
@include theme(
(
...
fontFamily: 'DancingScript',
...
)
);
The fonts
folder will be copied to dist
folder as static assets when we execute work-scripts flowbuild build
command to generate custom.js
and custom.css
files. All generated files should be copied to the server external folder as usual.
NOTE
For a complete list of free font families, you can visit Google Fonts page.
You can learn more about @font-face
API and browser support here.
Logo and Favicon Images
The favicon can be customized by adding the favicon.ico
file into following
classpath folder /src/main/resources/public
.
The application logo is customized by overwriting certain CSS classes with the code
below. The image files should be available under the resources folder in the web
application, e.g., /src/main/resources/static/ext
.
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
/* SideBar logo */
.flw-switcher__nav {
&__logo {
background-image: url("./logo.png");
background-size: auto;
svg {
display: none;
}
}
&__collapsed .flw-switcher__nav__logo {
background-image: url("./logo_min.png");
background-size: auto;
}
}
/* Login logo */
.flw-login__logo {
background-image: url("./logo.png");
background-size: auto;
svg {
display: none;
}
}
}
The desktop notification logo can be overwritten by adding a custom image file in the
configured folder path for this, there is an application property to specify the
notification logo path: flowable.frontend.notificationLogoUrl
. As fallback, the
Flowable logo is used if no notification.png
file is found.
Customizing Tabs for Details Forms
The details forms for processes, tasks, and cases are customized by
exposing a property named detailCustomizations
when you define a
custom Flow-App. Please refer to this document
to learn how can you define custom Flow-Apps using regular React components.
We start by defining a function that gets the element loaded and the detail form type and returns a promise with the tab customizations:
export type Element = Case | Process | Task;
export type DetailCustomizationType = 'case' | 'process' | 'task';
export type DetailCustomizations = {
externalTabs: { [tabId: string]: ExternalTab };
tabOverrides: { [tabId: string]: Partial<ExternalTab> };
};
export const detailCustomizations = (element: Element, type: DetailCustomizationType): Promise<DetailCustomizations> => {
if (type === 'case') {
return caseDetailCustomizations(element);
} else if (type === 'process') {
return processDetailCustomization(element);
} else if (type === 'task') {
return taskDetailCustomizations(element);
}
return Promise.resolve(emptyDetailCustomizations);
};
That function does no modifications to existing tabs, but we use it as starting point for the following sections. Notice that depending on the detail form type, we are using a different function. For instance, let us create a function to customize task details tabs:
const taskDetailCustomizations = (element: Element): Promise<DetailCustomizations> => {
return Promise.resolve({
externalTabs: {
...
},
tabOverrides: {
...
}
});
};
Notice that the details customization should return an object that has
two entries externalTabs
, to add a new tab to the detail pages, and
tabOverrides
to modify the properties of existing tabs.
The details customization function should be exposed from your
custom.js
module:
export default { applications, applicationSorting, translations, detailCustomizations };
How to Add a Tab
It is as simple as adding the tab definition to the externalTabs
property:
const taskDetailCustomizations = (element: Element): Promise<DetailCustomizations> => {
return Promise.resolve({
externalTabs: {
ghosts: {
label: 'Ghosts',
icon: 'skull',
component: () => <div>Ghosts</div>
}
}
});
};
Tab definitions support the following properties:
export type ExternalTab = {
tabId?: string;
label: string;
icon: string;
order?: number;
hidden?: boolean;
component?: () => JSX.Element;
form?: () => FormLayout | string;
};
Conditionally Rendering Tab Contents
In some cases, you might want to check some properties before loading or
displaying the tab. Remember the function receives an element
parameter
with the details for the task, process or case being displayed.
In the following example we change the tab label when the process is completed:
const processDetailCustomization = (element: { endTime?: string }): Promise<DetailCustomizations> => {
const processIsComplete = element && !!element.endTime;
return Promise.resolve({
externalTabs: {
orders: {
label: processIsComplete ? 'Current Orders' : 'Completed Orders',
icon: 'shopping-cart',
order: 1,
component: () => <div className="flo-ordersTab">Orders</div>
}
},
tabOverrides: {}
});
};
How to Change the Icon or Label of an Existing Tab
If one wants to change the Work Form tab, altering the default order, label and icon it can be done by providing the new values:
tabOverrides: {
workForm: {
label: 'Stranger Things',
icon: 'ghost',
order: 25
}
}
The properties you defined in the tabOverrides
overwrite existing
ones before loading the tab.
For instance, if you want to hide a
the sub-items tab, add a hidden: true
to the definition:
{
subItems: {
hidden: true
}
}
Show folders in Documents Tab
By default the "Folders" switcher in the Documents tab is disabled. To change this default behaviour you need to define an overwrite in the componentDefaultProps
section of tabOverrides
as shown below.
{
externalTabs: {
...
},
tabOverrides: {
documents: {
label: 'Pre-configured Documents',
componentDefaultProps: {
showFolders: true
}
},
...
}
}
Tab Identifiers Reference
For each one of the detail pages for process, cases, and tasks this is the list of identifiers used for customizations:
Case details
Open tasks:
task
Work form:
workForm
Sub-items:
subItems
Documents attached:
documents
History:
history
Process details
Work form:
workForm
Sub-items:
subItems
Documents attached:
documents
History:
history
Task details
Form:
taskForm
Involved people:
people
Sub Tasks:
subTasks
Documents attached:
documents
History:
history
Customizing Headers for Cases, Processes, Tasks and Case View
The headers for Cases, Processes, Tasks and Case View can be customized by exposing a
property named headerCustomizations
.
The object headerCustomizations
could look like this:
const headerCustomizations = {
tasks: {
['humanTask1']: (props: { task?; actions?; stages?; payload?: Payload }) => {
return (
<div>
<div>my custom TASK header for humanTask1</div>
<div>{props.task?.name}</div>
</div>
);
},
['task-key-2']: 'md'
},
casePages: {
['myCustomView']: (props: { caseInstance; actions; stages; payload }) => {
return (
<div>
my custom header <br /> actions:{JSON.stringify(props.actions)}
<br /> stages: {JSON.stringify(props.stages)}
<br /> case: {JSON.stringify(props.caseInstance)}
</div>
);
},
global: 'md'
},
cases: {
global: (props: { actions?; stages?; payload?: Payload }) => <div>my custom header</div>,
['casePageModel']: (props: { caseInstance?; actions?; stages?; payload?: Payload }) => {
return (
<div>
<div>my custom CASE header for casePageModel changed</div>
<div>{props.caseInstance?.name}</div>
</div>
);
}
['casePageModel2']: 'xlg'
},
processes: {
global: 'sm'
}
};
There are four main sections: cases, processes, tasks and casePages. For each of those we have the option to define a custom header at two levels:
- definition key: will be applied only for that case/process/task definition
global
: will be applied to all other case/process/task
For setting a custom header we have two options: passing a react component
or a size of the header (sm
, md
, xlg
).
In the case of the react component, Flowable Work will pass as parameters the current task/case, related actions, stages and payload.
const headerCustomizations = {
tasks: {
['humanTask1']: (props: { task?; actions?; stages?; payload?: Payload }) => {
return (
<div>
<div>my custom TASK header for humanTask1</div>
<div>{props.task?.name}</div>
</div>
);
},
['task-key-2']: 'md'
}
};
The header customization function should be exposed from your
custom.js
module:
export default { headerCustomizations };
Customizing the application header
Similar to the rest of customizations, the top navigation bar or application header can be replaced by a custom react component and exposed as customTopNavigationBar
. The only requirement for the component is to accepct children
as prop, this way we can easily inject the user profile component. In order to show the top navigation bar, the feature flag topNavigationBar
should be enabled.
Custom Component
import React from 'react';
import './customTopNavigationBar.scss';
type CustomTopNavigationBarProps = {
children?: React.ReactNode;
};
export const CustomTopNavigationBar = (props: CustomTopNavigationBarProps) => {
const { children } = props;
return (
<div className="custom-top-navigation-bar">
<div>
<img src="https://robohash.org/customnav" />
</div>
<div>External Custom Navigation Top Bar</div>
{children}
</div>
);
};
index.tsx
import { CustomTopNavigationBar as customTopNavigationBar } from './customizations/CustomTopNavigationBar/CustomTopNavigationBar';
...
export default {
...
customTopNavigationBar
};
Http Client Custom Configuration
It is possible to customize the configuration of the http client used by the frontend application using the httpClientCustomConfiguration
extension point that expose the axios instance.
To add a custom configuration to the http client you can assign a function to the httpClientCustomConfiguration
extension point in the custom.js
file and use interceptors to intercept and/or mutate outgoing requests or incoming responses.
In the example below two interpectors are added to the axios instance, the first add a custom header to all the outgoing requests, the second intercept all the incoming responses and in case of a 401 error will redirect to an external IDM:
window.flowable.httpClientCustomConfiguration = function(io) {
//add custom headers
io.interceptors.request.use(function(config) {
config.headers = { ...config.headers, 'X-Requested-With': 'XMLHttpRequest'};
return config;
});
//errorHandler
io.interceptors.response.use(
function(r) {
return r;
},
function errorHandler(err) {
if (statusCodeIs(err, 401)) {
window.location.href='<external IDM>';
}
}
);
};
function statusCodeIs(error, code) {
return (error && error.response && error.response.status === code) || (error && error.status === code) || false;
}