Flowable Enterprise Documentation

Flowable Enterprise Documentation

  • How to
  • Modeler
  • Administrator
  • Developer
  • User

›Developer Guide

Flowable Front End Developer Guides

  • Flowable Front-End Developer Guides

Developer Guide

  • Customize User Interface and Flow-Apps
  • Add a Flow-App with Flowable Design
  • Customize with Work Components
  • Form Expressions
  • Engage Embedded
  • Case View

Flowable Forms

  • Flowable Forms

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:

  1. A list of applications

  2. The application sorting

  3. A list of additional translations to be merged with existing ones

  4. 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 and flowable.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:

  1. When the URL is #/ghosts01, the GhostApp component is loaded.

  2. When the URL is #/ghosts01/rockets, a simple div is rendered.

  3. 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.

Expanded

Expanded

Expanded

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.

Export button

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.

Import button

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;
}
← Flowable Front-End Developer GuidesAdd a Flow-App with Flowable Design →
  • Setting Up the Project Structure
    • Basic Application Structure
    • Alternative configuration for the frontend build
  • Add a Flow-App using Custom React Components
    • Creating the First Application
    • Exposing the Application
    • Rendering a Form Instead of a Full-fledged Component
    • Customizing the Title of the Application
    • Customizing the Icon
    • Adding Sub-applications
    • Changing Application Sorting
  • Translations
    • Adding Application Translations
    • Adding Form Engine Translations
  • Customize the Default CSS Stylesheet
  • Configure User Themes
    • Customize the Theme in Flowable
    • Export/Import a theme customization
    • Font Customization
    • Customize Font Family
    • Adding External Font Families
    • Logo and Favicon Images
  • Customizing Tabs for Details Forms
    • How to Add a Tab
    • Conditionally Rendering Tab Contents
    • How to Change the Icon or Label of an Existing Tab
    • Show folders in Documents Tab
    • Tab Identifiers Reference
  • Customizing Headers for Cases, Processes, Tasks and Case View
  • Customizing the application header
  • Http Client Custom Configuration
Flowable Enterprise Documentation
Documentation
UsersModelersAdministratorsDevelopers
Community
Enterprise ForumEnterprise BlogOpen Source ForumOpen Source Blog
Follow @FlowableGroup
More
DisclaimerPoliciesGitHubStar
Copyright © 2021 Flowable AG