Multiple Form Engines in one Activity

Use case: Displaying 2 Form Engines using the same data from 1 activity but load 2 different UI Models. eg: Loading 1 fully CosmoClaims form and 1 CosmoClaimsMetadata form these 2 Forms using the same data but it has different UI Model.

Known Issue: Rendering 2 Form Engines with FormEngineViews.FormEngine doesn’t work → the 1st UI Model loaded will be used in both Form Engine.

Solution: Create 2 containers:

  • 1 return FormEngineViews.FormEngine (default wrapper in bap-client for form-engine)
  • the other one using your own connected Form Engine which load the 2nd UI Model.

Your custom connected form engine function looks like this:

type StateProps2 = Partial<DefaultStateProps>;

interface OwnProps2 extends View, Partial<Config> {
	readonly activity: Activity;
}

const FormEngineView2 = connect<StateProps2, DefaultDispatchProps, OwnProps2, object>(
	function mapStateToProps(state: object, ownProps: OwnProps2) {
		const dataHolder = ActivitySelectors.dataHolderByDescriptor(
			ownProps.activity.id,
			ownProps.activity.descriptor)(state);
		const document = dataHolder
		&& dataHolder.loadingState === "loaded"
		&& Activity.Data.SingleDocumentData.isInstance(dataHolder.data)
			? dataHolder.data.document
			: {};
		const sceneReference = ViewSelectors.sceneReferenceByActivityId(ownProps.activity.id)(state);
		if (sceneReference === undefined) {
			throw new Error();
		}

		const documentModelTuple = ModelSelectors.documentModelTuple({ sceneReference })(state);
		if (documentModelTuple === undefined || documentModelTuple.loadingState !== "loaded") {
			return {};
		}
		const { documentModel, validatorProvider } = documentModelTuple.data;
		const respondToComplaintFormModel = ModelSelectors.formModel({
			sceneReference: sceneReference,
			modelDescriptor: {name: "AnalyzeRootCause"}
		})(state);
		if (respondToComplaintFormModel === undefined || respondToComplaintFormModel.loadingState !== "loaded") {
			return {};
		}
		const formModel = respondToComplaintFormModel.data as unknown as FormModel;
		if (false === FormModel.isInstance(formModel)) {
			return {};
		}
		const engineState = createEngineStore(
			{
				data: {
					document
				},
				models: {
					documentModel,
					validatorProvider,
					formModel
				},
				ui: {
					readonly: true
				},
				locale: LocaleSelectors.locale()(state)
			}
		);
		return defaultMapStateToProps(engineState, {});
	},
	FormEngineActions.mapDispatchToProps
)(function Engine(props: FormEngineRenderer.PropsType): JSX.Element | null {
	return Object.keys(props).indexOf("state") > 0
		? <ScrollHandler models={props.state.models} uiState={props.state.ui} {...props}>
			<FormEngineContentBoxRenderer {...props}>
				<FormEngineRenderer {...props} />
			</FormEngineContentBoxRenderer>
		</ScrollHandler>
		: <div>LOADING</div>;
});

Your 2 containers look like this:

function FirstFormEngine(props: Props): JSX.Element {
	return <FormEngineViews.FormEngine activity={props.activity} name={"FirstFormEngine"} />;
}

function SecondFormEngine(props: Props) {
	return <div>
		<FormEngineView2 name={"customFE2"}
						activity={props.activity}/>
		</div>;
}

Your container factory look like this:

export const ViewsMap: { [name: string]: React.ComponentType<View> } = {
	FirstFormEngine: FirstFormEngine,
	SecondFormEngine: ConnectedMultipleFormEngineOneActivity
};

Your application model looks like this:

{
			name: "Multiple-fe-1-activity",
			menu: {
				name: "Multi FE in 1 Activity",
				label: {
					en: "Multi FE in 1 Activity",
					de: "Multi FE in 1 Activity"
				},
				state: {
					test: "multiple-form-engine",
					instance: "Test"
				}
			},
			flows: [
				{
					name: "welcome",
					scenes: [
						{
							name: "about",
							matchConditions: [
								{
									key: "test",
									mustEqual: "multiple-form-engine"
								},
								{
									key: "instance",
									isSet: true
								}
							],
							sceneChange: {
								onEnter: [
									{
										type: "REGION_CLEAR"
									},
									{
										type: "VIEW_ADD",
										container: "FirstFormEngine",
										models: [
											{ name: "RespondToComplaint", modelType: "form"}
										]
									},
									{
										type: "VIEW_ADD",
										container: "SecondFormEngine",
										models: [
											{ name: "AnalyzeRootCause", modelType: "form"}
										]
									}
								]
							}
						}
					]
				}
			]
		}

with example above you can see:

  • FirstFormEngine container loads RespondToComplaint Ui Model,
  • SecondFormEngine container load AnalyzeRootCause from the code in the connected function. The declaration of the Form Model has to be included in application model in order to tell bap-client load it.

Example is tested with

  • bap-client: 5.1.0
  • bap-form-engine: 27.1.0

I hope it helps. In case you have further problem just comment in this article or call me via jabber :wink:

Cheers,
Tuan Do