# Embedding Eximee Forms as a web component

The Eximee platform Forms presentation module can be launched in the variant:

* a standalone single page application hosted as a dedicated web page, embedded within a webview or iframe,
* a web component library for embedding in any existing website or web application.

The forms application is functionally responsible for:

* the presentation and handling of forms defined using low-code in the Eximee platform,
* each form has a dynamic structure consisting of components and one or more pages defined using low-code,
* Eximee forms manage form state, handle user interactions, and navigation within the presented form,
* the application can use browser URL navigation, exclusively using the part after #, or in-memory navigation that does not affect the browser URL state.

The documentation below describes how to embed and integrate Eximee forms using the web component library.

## General assumptions

To launch a form:

* JavaScript files providing the component implementation must be included in the website,
  * component resources are served by the Eximee platform in a version compatible with the platform server,
  * the specific names and addresses of resources result from the manifest hosted together with the other static platform files,
* ensure correct CORS and CSP headers between the Eximee platform domains and the host application,
  * in particular, the ability to fetch static resources/assets of the form application in the host application,
* ensure access to the Eximee platform REST API from the host application domain,
* create a DOM element for the component in HTML or programmatically in JavaScript,
* initiate form loading using the programmatic JavaScript API.

## Technologies used

The application is built using Angular version 20.x.x (subject to regular updates).

And exposes a web component according to the web component specification in terms of:

* custom elements,
* shadow dom.

The web component can be embedded inside an open Shadow DOM, however this must be taken into account when embedding the styles from the bundleStats.json manifest into the DOM (described below).

## Impact on the application's global execution context by zone.js

The application relies on the availability of a globally loaded zone.js library in a version compatible with the Angular version (and delivered together with Angular/Angular CLI).

The zone.js library is the basis of the Angular framework and is commonly used in applications built with it.

The library works by monkey patching asynchronous user interaction APIs with the page in order to support UI change detection during user interactions.

The Eximee forms application is currently not adapted to work in zoneless mode, and any adaptation would require dedicated work on the Eximee platform side.

However, previous experience shows that in the case of:

* using the web component inside a host application using a compatible version of zone.js (e.g. written in Angular),
* using the web component inside a host application not using zone.js (e.g. written in Vue or React).

We have not observed conflicts or operational issues with either of these applications (host, Eximee). However, the need to verify compatibility of libraries embedded in the running application should be kept in mind.

## Impact on the application's global execution context by polyfills

The web component library relies on the availability of (and includes, if not available) the following polyfills:

* core-js/shim from core-js,
* @webcomponents/webcomponentsjs/custom-elements-es5-adapter.js from @webcomponents/webcomponentsjs,
* web-components/webcomponents-loader from polymer.

All polyfill dependencies comply with polyfill delivery standards, i.e.

* they do not override native solutions available in the browser,
* they load only if they provide functionality not available natively and not provided by other methods (e.g. by the host application),
* they are provided by commonly used open source libraries.

In practice, the indicated polyfills should not change behavior in the case of:

* using modern browsers,
* using a host application that uses modern web frameworks (such as Angular).

## Resource manifest and release cycle

The web component library resources depend on the current version of the Eximee platform deployed in a given environment.

To make dependency management easier, the platform hosts a manifest file describing the resources that need to be included on the page to launch the web component.

The manifest is in JSON format and looks like this, for example:

```json
{
  "format": 3,
  "scripts": [
    "polyfills.125595b8f8d58bce.js",
    "main.922b3d09b697675a.js"
  ],
  "globalStyles": [
    "global-styles.a9c4b7e18d2f03ab.css"
  ],
  "styles": [
    "styles.6e50ddf23fe0e270.css"
  ]
}
```

The manifest contains three types of resources, which must be loaded in different ways:

* scripts – JavaScript scripts; they should be embedded in the `<head>` of the page hosting the web component (in `<script>`),
* globalStyles – global style sheets; they should be embedded in the `<head>` of the page hosting the web component (in `<link>`). They contain only resources unrelated to layout or the appearance of DOM elements, such as font definitions (`@font-face`). They will not affect the styling of elements in the existing host application,
* styles – component-specific style sheets; they should be attached directly to the component's DOM element (or its shadowRoot, if the web component is embedded in Shadow DOM).

All resource names contain hashes based on file contents. This makes it possible at the same time to:

* ensure loading of the correct file corresponding to a specific system version,
* reuse files from cache if their content does not change between versions.

Resource embedding can be done:

* dynamically by the application's frontend before launching the form,
* as part of server-side rendering of the page hosting the host application (recommended).

Note: if the form web component is embedded within nodes under an open shadow DOM, it is necessary to embed the style links inside the appropriate shadowRoot. Styles embedded in the head of the entire document will not be able to style the component inside the shadow DOM in the standard way.

## Creating a form instance

### Embedding the component and launching the form

The component can be embedded in the DOM using the component's HTML tag or programmatically using the JavaScript API:

```typescript
var form = document.createElement("ex-forms-form");
document.body.appendChild(form)
```

The form component can be embedded anywhere in the application's DOM structure.

Once embedded and after obtaining a reference to the element, it is possible to launch the form using the loadForm method as shown in the example:

```typescript
container.loadForm({
  formId: 'demoFormularzJakoWebcomponent', /* form identifier */
  baseHref: '/api',  /* path where the Eximee REST API exposed by proxy-pass is available */
  onError: function () {
    alert('An error occurred while processing the application');
  }
});
```

It is possible to pass additional parameters to the load form method, which will be described in the next sections of the documentation and provided in the reference API at the end of the document.

One such parameter is the ability to pass business parameters for launching a specific form, for example:

```typescript
container.loadForm({
  formId: 'demoFormularzJakoWebcomponent', /* form identifier */
  baseHref: '/api',  /* path where the Eximee REST API exposed by proxy-pass is available */
  data: JSON.stringify({'param1': 'value1'}), /* Additional business parameters feeding the form as a key-value object serialized to a JSON string  */
  onError: function () {
    alert('An error occurred while processing the application');
  }
});
```

### Extending REST communication headers (including auth headers for API Gateway)

In many deployments, it is necessary to extend REST API request headers, especially in applications that handle user authentication and rely on authorization control at the API Gateway level (e.g. OIDC tokens).

For this purpose, it is possible to specify a function that creates headers which will be added to each REST request:

```typescript
form.loadForm({
    formId: 'demoFormularzJakoWebcomponent',
    additionalRequestHeaders: () => ({
        'X-Custom-Header': 'value'
    })
})
```

The method creating the headers is called each time, immediately before the request to the REST API is made. The headers are not stored between calls, which is particularly important e.g. for OAuth headers that may change due to token refresh during form handling.

### Preserving form state across page refreshes / host application navigation

The complete form state and user-entered data are stored on the Eximee platform server side within the user session.

This means that it is possible to restore/resume an active user form even after navigation in the host application or a full page refresh in the browser.

The user form on the server side is distinguished based on:

* the session identifier in the cookie,
* the form instance identifier in the session based on the form instance number (formInstanceNumber).

Assuming cookie handling is guaranteed and the cookie will not be deleted (except when functional requirements require such deletion), restoring the form requires storing its instance number.

The form number can be passed to the load form method and retrieved from the instance after it has been launched. Assuming the host application has a clear way to store this value (e.g. in the page query, server-side, etc.), it is possible to write:

```typescript
let formInstanceNumber: string | undefined = restoreFormInstanceNumber();
form.loadForm({
    formId: 'demoFormularzJakoWebcomponent',
    formInstanceNumber: formInstanceNumber,
    onLoaded: (result, config) => storeFormInstanceNumber(result.data.formModel.formNumber),
});

```

## Access to the Eximee platform REST infrastructure

The form presentation component requires access to the REST API served by the Eximee platform instance. All REST API endpoints are already hosted and exposed for standalone webforms instances (e.g. for web applications, webview embedding, or iframe).

The recommended solution (most often chosen by customers) is to configure a proxy pass in the host application domain to forward traffic to the Eximee infrastructure. This solution offers the simplest configuration in terms of infrastructure and security headers, as well as access control to the infrastructure, because the REST API is handled within the same domain on which the page/application of the host is presented.

Alternatively, a configuration with direct connections between the host application domain and the Eximee REST server is possible. However, this requires additional security/header configuration and verification of individual environments through testing.

It is also possible to develop a different communication mechanism, especially one in which the host application intermediates every REST call. However, this requires analysis and development of a solution specific to the given deployment and involves planning additional development work on the platform.

## Form application cookies

The application uses cookies describing:

* the user session,
* session affinity parameters for load balancers.

Cookies are created automatically by the server and the infrastructure (load balancers) and are configured according to the parameters of the specific environment.

## Known functional limitations

* The library assumes that only one form is presented on a single screen at the same time, and attempting to display two parallel form instances may cause errors.
* Changing the parameters of an initialized form requires reinitialization and means preparing a new instance (without the data previously entered by the user).

## Component interface

```typescript
export interface FormWebcomponentApi {
    loadForm(config: LoadFormConfig): void;
    hasActiveForm(): boolean;
 
    cancelCurrentForm(): void;
    handleAction(action: ExAction): void;
    proceedCurrentForm(): void;
    backCurrentForm(): void;
    isLastVisiblePage(): boolean;
    isFirstVisiblePage(): boolean;
    shouldShowForwardButton(): boolean;
    shouldShowBackwardButton(): boolean;
    getForwardButtonLabel(): string;
    onShowSpinner(callback: () => void): void;
    onHideSpinner(callback: () => void): void;
    getTranslation(key: string): string;
}
 
export interface LoadFormConfig extends LoadConfig {
    formId: string;
}
 
export interface LoadConfig {
    formInstanceNumber?: string;
    processId?: string;
    baseHref?: string;
    accessToken?: string;
    tokenType?: string;
    readonly?: boolean;
    data?: string;
    shadowRoot?: ShadowRoot;
    scrollOnErrorOffset?: number;
    additionalRequestHeaders?: () => { [header: string]: string };
    onLoaded?: (result: unknown, config: LoadConfig) => void;
    onActionDispatched?: (result: unknown, config: LoadConfig) => void;
    onCancelled?: (config: LoadConfig) => void;
    onSaved?: (result, config: LoadConfig) => void;
    onDraftSaved?: (result, config: LoadConfig) => void;
    onPageChanged?: (result, config: LoadConfig) => void;
    onModelChanged?: (result, config: LoadConfig) => void;
    onPageValidationErrors?: (result, config: LoadConfig) => void;
    onError?: (error, config: LoadConfig) => void;
    onAppEvent?: (event, config: LoadConfig) => void;
    onComponentValueChanged?: (result: unknown, config: LoadConfig) => void;
    onShowSpinner?: (immediate: boolean ) => void;
    onHideSpinner?: () => void;
    onClosed?: (config: LoadConfig) => void;
}
 
export interface ExAction {
    sourceId: string;
    event: ExActionEvent | string;
    detail?: object | string | number | boolean;
}
 
export enum ExActionEvent {
    SAVE = 'SAVE',
    CLOSE = 'CLOSE',
    NEXT = 'NEXT',
    EDIT = 'EDIT',
    CLICK_MORE_INFO = 'CLICK_MORE_INFO',
    CALL = 'CALL',
    CHECK = 'CHECK',
    UNCHECK = 'UNCHECK',
    CLICK = 'CLICK',
    ON_EXIT = 'ON_EXIT',
    TOOLTIP_CLICKED = 'TOOLTIP_CLICKED',
    AUTOCOMPLETE_NO_MATCH_BUTTON_CLICKED = 'AUTOCOMPLETE_NO_MATCH_BUTTON_CLICKED',
    EXPAND_STATEMENT = 'EXPAND_STATEMENT',
    ON_PAGE_ENTER = 'ON_PAGE_ENTER',
    POI_SELECTED = 'POI_SELECTED',
    HIDDEN = 'HIDDEN',
    CLOSE_POPUP = 'CLOSE_POPUP',
    RETRY = 'RETRY',
    SAVE_DRAFT = 'SAVE_DRAFT',
    VALUE_CHANGED = 'VALUE_CHANGED',
    FORWARD_PAGE = 'FORWARD_PAGE',
    BACKWARD_PAGE = 'BACKWARD_PAGE',
    PARK_FORM_WITH_PROVIDED_HASH = 'PARK_FORM_WITH_PROVIDED_HASH',
    SHOW_POPUP = 'SHOW_POPUP',
    SAVE_POPUP = 'SAVE_POPUP',
    TOGGLE = 'TOGGLE',
    CLEAR_UPLOAD_FILE = 'CLEAR_UPLOAD_FILE',
    REDIRECT_TO_RETURN_URL = 'REDIRECT_TO_RETURN_URL',
    REDIRECT = 'REDIRECT',
    CHECK_FED_STATEMENT = 'CHECK_FED_STATEMENT',
    POPUP_SAVED = 'POPUP_SAVED',
    POPUP_HIDDEN = 'POPUP_HIDDEN',
    START_PROCESS = 'START_PROCESS',
    COMPLETE_USER_TASK = 'COMPLETE_USER_TASK',
    TILE_CLICKED = 'TILE_CLICKED',
    START_APPLICATION = 'START_APPLICATION',
    EXIT_CONFIRMED = 'EXIT_CONFIRMED'
}
```

## Usage examples

The platform deployed in test environments hosts an example HTML page embedding a form using the web component:

* directly in the page DOM,
* wrapped in a Shadow Root.

Knowing the Eximee platform address, both examples can be viewed at https\://\[environment-address]/webcomponent/\[deployment-theme]-webcomponent.html

HTML example:

```html
<html>
<head>
    <script>
        /*  Method for launching the application directly in the DOM tree. */
        function initFormPlain() {
            // Prepare the component in the DOM tree
            const formWrapper = document.getElementById('form-wrapper');
            const formWebcomponent = document.createElement("ex-forms-form");
            formWrapper.appendChild(formWebcomponent);
 
            // Launch the form instance
            formWebcomponent.loadForm({
                formId: 'demoFormularzJakoWebcomponent',
                baseHref: '/api',
                onError: function () {
                    alert('An error occurred while processing the application');
                }
            });
        }
    </script>
</head>
<body>
    <div id="form-wrapper"></div>
    <script src="polyfills.5310c9e539f37fb1.js" type="module"></script>
    <script src="main.71e5244a2a4d4f3d.js" type="module"></script>
</body>
</html>
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.eximee.com/documentation/documentation-en/wprowadzenie/architektura-platformy/moduly-wykonawcze/eximee-forms/osadzanie-eximee-forms-jako-webcomponent.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
