Skip to main content

Framework guides

info

This content is intended for advanced Studio users.

This page covers integrating Form renderer across four environments. Jump to the section for your framework:

Framework-agnostic​

This section covers integrating Form renderer in plain JavaScript applications and any framework not covered by the Angular, React, or Vue sections.

Include Form renderer​

After installing the package, reference the script and stylesheet in your HTML entry file:

<head>
<script src="/path-to-package/form-renderer.js"></script>
<link rel="stylesheet" type="text/css" href="/path-to-package/styles.css" media="screen"/>
</head>

Configure​

Set properties directly on the form-renderer element reference:

<form-renderer id="fr"></form-renderer>
const fr = document.getElementById('fr');

fr.ehrServerUrl = 'https://better-ehr-server.com/rest/v1';
fr.credentials = { username: 'user', password: 'pwd' };
fr.formMetadata = { name: 'my-form', version: '1.0.0' };
fr.compositionId = '28e813d9-5caf-4302-8274-3966a5b1f819::user::1';
good to know

version in formMetadata is optional. If omitted, the latest published version of the form is used.

Compositions and compositionIds​

  • Passing composition fills the form with the provided composition object.
  • Passing compositionId fetches the composition from the server and fills the form.
  • Passing both causes compositionId to take priority; the provided composition is ignored.
  • Passing neither renders an empty form.
  • Passing null as compositionId renders an empty form.

Configuring EhrFormContext​

Pass a context object to configure renderer behaviour. For the full list of options, see Configuration.

fr.context = {
language: 'en',
readonly: false,
territory: 'SI',
presentationMode: true,
showAllPages: false,
dateFormat: 'dd/MM/yyyy',
autoPrefill: {
enabled: true,
mode: 'LATEST_COMPOSITION',
customAqlView: 'my-custom-aqlview-to-prefill-data'
}
};

Credentials​

Basic Auth​

fr.credentials = { username: 'user', password: 'pswd' };
fr.ehrServerUrl = 'https://better-ehr-server.com/rest/v1';

OAuth 2.0​

Provide a credentials object with authType: 'oauth2' and a token:

fr.credentials = {
authType: 'oauth2',
token: 'a2lkIiA6ICJfRjRVUENFYUg4Nmk5'
};
fr.ehrServerUrl = 'https://better-ehr-server.com/rest/v1';

When the token expires, Form renderer emits onRequestUpdatedToken. Call fr.updateToken(token) within 5000ms:

fr.addEventListener('onRequestUpdatedToken', () => {
fr.updateToken('FTYXEyaDdnanFvWDZVIn0.eyJqdGkiOiIzMGU2OTc5ZS00ZTAz');
});

Alternatively, set a new credentials object:

fr.credentials = {
authType: 'oauth2',
token: 'FTYXEyaDdnanFvWDZVIn0.eyJqdGkiOiIzMGU2OTc5ZS00ZTAz'
};

Call methods​

function onValidateForm() {
const validationModel = fr.validate(true);
console.log(validationModel);
}

function onSetFieldValue() {
const scriptApi = fr.getScriptApi();
scriptApi.setFieldValue('path/to/form/element', 'value');
}

Handle events​

fr.addEventListener('onValueChange', function(event) {
console.log('onValueChange', event.detail);
});

fr.addEventListener('onFormRendered', function(event) {
console.log('onFormRendered', event.detail);
});

fr.addEventListener('eventDispatched', function(event) {
console.log('eventDispatched', event.detail);
});

When onValueChange fires, fr.composition is also updated:

fr.addEventListener('onValueChange', function() {
console.log('composition', fr.composition);
});

Viewing the current composition​

console.log(fr.composition);

Saving a composition​

Both save approaches require an ehrId passed via formEnvironment:

fr.formEnvironment = {
variables: [{ name: 'ehrId', value: '000-92ac-4bf6-bfed-f0d4b9f6' }]
};

Using saveComposition via the Script API:

const api = fr.getScriptApi();

async function save() {
const response = await api.saveComposition();
if (!response.success) throw response.error;
if (response.commitData) {
response.commitData.forEach(commit => console.log(commit.id));
} else {
console.log(response.uid);
}
}

Pass false to skip validation: api.saveComposition(false).

Using a save dependency on the form:

fr.addEventListener('onCompositionSaved', (response) => {
// handle SaveCompositionResponse in response.detail
});

Updating a composition​

Provide ehrId via formEnvironment and a compositionId:

fr.formEnvironment = {
variables: [{ name: 'ehrId', value: '000-92ac-4bf6-bfed-f0d4b9f6' }]
};
fr.compositionId = 'f8805e7e-0000-1111-222c-74ca6b0a53e9::test::1';
async function update() {
const response = await fr.getScriptApi().updateComposition();
if (!response.success) throw response.error;
console.log(response.uid);
}

SaveCompositionResponse​

// Success
response = { success: true, uid: '000-92ac-4bf6-bfed-f0d4b9f6' };

// Error
response = { success: false, error: { elementId, message, code, status } };

onPageChanged event​

onPageChanged fires when the user navigates between pages in a multi-page form:

fr.addEventListener('onPageChanged', function(event) {
console.log('onPageChanged', event);
});

Event detail shape:

id: string;
name: string;
firstPage?: boolean;
hideInPresentationMode?: boolean;
valid?: boolean;

Adding custom translations​

Use setTranslation(lang, keyVals) after onFormRendered fires. For the full list of available keys, see Localisation.

<form-renderer id="fr"></form-renderer>

<script>
const fr = document.getElementById('fr');
fr.addEventListener('onFormRendered', function() {
fr.setTranslation('no', {
'boolean.presentation.false': 'Usann',
'boolean.presentation.no': 'Nei',
'boolean.presentation.true': 'Sann',
'boolean.presentation.yes': 'Ja'
});
});
</script>

Angular​

This section covers integrating Form renderer in Angular applications.

Setup​

Add CUSTOM_ELEMENTS_SCHEMA​

Form renderer is a Web Component. Add CUSTOM_ELEMENTS_SCHEMA to prevent Angular from rejecting the <form-renderer> custom element.

Standalone components (Angular 15+):

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@Component({
// ...
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})

Module-based setup:

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
// ...
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})

Add the script to angular.json​

"scripts": [
"node_modules/@better/form-renderer/form-renderer.js"
]

Add the stylesheet to angular.json​

Add the stylesheet after your global styles:

"styles": [
"src/styles.scss",
"node_modules/@better/form-renderer/styles.css"
]

Configure​

app.component.ts:

export class MyComponent {
ehrServerUrl = 'https://better-ehr-server.com/rest/v1';
credentials = { username: 'user', password: 'pswd' };
formMetadata = { name: 'my-form', version: '1.0.0' };
context = { language: 'en', readonly: false };
compositionId = '28e813d9-5caf-4302-8274-3966a5b1f819::user::1';
}

app.component.html:

<form-renderer
[formMetadata]="formMetadata"
[ehrServerUrl]="ehrServerUrl"
[credentials]="credentials"
[context]="context"
[compositionId]="compositionId">
</form-renderer>
good to know

version in formMetadata is optional. If omitted, the latest published version of the form is used.

Compositions and compositionIds​

  • Passing composition fills the form with the provided composition object.
  • Passing compositionId fetches the composition from the server and fills the form.
  • Passing both causes compositionId to take priority; the provided composition is ignored.
  • Passing neither renders an empty form.
  • Passing null as compositionId renders an empty form.

Configuring EhrFormContext​

For the full list of context options, see Configuration.

context = {
language: 'en',
readonly: false,
territory: 'SI',
presentationMode: true,
showAllPages: false,
dateFormat: 'dd/MM/yyyy',
autoPrefill: { enabled: true, mode: 'LATEST_COMPOSITION' }
};

Credentials​

Basic Auth​

credentials = { username: 'my_user', password: 'my_password' };

OAuth 2.0​

<form-renderer [credentials]="credentials"
(onRequestUpdatedToken)="provideNewToken()">
</form-renderer>
credentials = {
authType: 'oauth2',
token: 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIdasiwia2lkIiA6ICJiA'
};

When the token expires, call updateToken via @ViewChild:

<form-renderer #fr [credentials]="credentials"
(onRequestUpdatedToken)="provideNewToken()">
</form-renderer>
export class MyComponent {
@ViewChild('fr', { static: false }) fr: ElementRef;

credentials = { authType: 'oauth2', token: 'eyJhbGci...' };

async provideNewToken() {
const refreshedToken = await /* fetch token */;
this.fr.nativeElement.updateToken(refreshedToken);
}
}

Call methods​

Use @ViewChild to access the element and call methods:

<form-renderer #fr ...></form-renderer>
export class MyComponent {
@ViewChild('fr', { static: false }) private formRenderer: ElementRef;

onValidateForm(): void {
const validationModel = this.formRenderer.nativeElement.validate(true);
}

onSetFieldValue(): void {
const scriptApi = this.formRenderer.nativeElement.getScriptApi();
scriptApi.setFieldValue('path/to/form/element', 'value');
}
}

Handle events​

export class MyComponent {
valueChange($event): void {
console.log('onValueChange', $event.detail);
}

formRendered($event): void {
console.log('onFormRendered', $event.detail);
}
}
<form-renderer #fr
(onValueChange)="valueChange($event)"
(onFormRendered)="formRendered($event)">
</form-renderer>

Viewing the current composition​

@ViewChild('fr', { static: false }) private formRenderer: ElementRef;

console.log(this.formRenderer.nativeElement.composition);

Saving a composition​

Both approaches require ehrId via formEnvironment:

<form-renderer [formEnvironment]="environment"></form-renderer>
environment = {
variables: [{ name: 'ehrId', value: '000-92ac-4bf6-bfed-f0d4b9f6' }]
};

Using saveComposition via the Script API:

async customSaveFunction() {
const response = await this.fr.nativeElement.getScriptApi().saveComposition();
if (!response.success) throw response.error;
if (response.commitData) {
response.commitData.forEach(commit => console.log(commit.id));
} else {
console.log(response.uid);
}
}

Using a save dependency on the form:

<form-renderer [formEnvironment]="environment"
(onCompositionSaved)="handleSavedComposition($event)">
</form-renderer>
handleSavedComposition($event) {
if (!$event.detail.success) throw $event.detail.error;
console.log($event.detail.uid);
}

Updating a composition​

<form-renderer [formEnvironment]="environment"
[compositionId]="compositionId" #fr>
</form-renderer>
<button (click)="updateFunction()">Update</button>
compositionId = 'f8805e7e-0000-1111-222c-74ca6b0a53e9::test::1';

async updateFunction() {
const response = await this.fr.nativeElement.getScriptApi().updateComposition();
if (!response.success) throw response.error;
console.log(response.uid);
}

onPageChanged event​

pageChange(event) { console.log(event); }
<form-renderer #fr (onPageChanged)="pageChange($event)"></form-renderer>

Adding custom translations​

Use setTranslation(lang, keyVals) after onFormRendered fires. For the full list of available keys, see Localisation.

@ViewChild('fr', { static: false }) private formRenderer: ElementRef;

this.formRenderer.nativeElement.setTranslation('no', {
'boolean.presentation.false': 'Usann',
'boolean.presentation.no': 'Nei',
'boolean.presentation.true': 'Sann',
'boolean.presentation.yes': 'Ja'
});

React​

This section covers integrating Form renderer in React applications.

Setup​

Copy form-renderer.js and all style files from node_modules/@better/form-renderer into your project's public directory. Then reference them in index.html:

<head>
<link rel="stylesheet" href="%PUBLIC_URL%/styles.css">
<script src="%PUBLIC_URL%/form-renderer.js"></script>
</head>

Configure​

Access the element via a ref handler. The ref={this.handleRef} assignment is required.

import { Component } from 'react';

class App extends Component {
formMetadata = { name: 'my-form', version: '1.0.0' };
ehrServerUrl = 'https://better-ehr-server.com/rest/v1';
credentials = { username: 'user', password: 'pswd' };
context = { language: 'en', readonly: false };
compositionId = '28e813d9-5caf-4302-8274-3966a5b1f819::user::1';

componentDidMount() {
this.component.formMetadata = this.formMetadata;
this.component.ehrServerUrl = this.ehrServerUrl;
this.component.credentials = this.credentials;
this.component.context = this.context;
this.component.compositionId = this.compositionId;
}

handleRef = component => {
this.component = component;
};

render() {
return (
<div className="App">
<form-renderer ref={this.handleRef}></form-renderer>
</div>
);
}
}

export default App;
good to know

version in formMetadata is optional. If omitted, the latest published version of the form is used.

Compositions and compositionIds​

  • Passing composition fills the form with the provided composition object.
  • Passing compositionId fetches the composition from the server and fills the form.
  • Passing both causes compositionId to take priority; the provided composition is ignored.
  • Passing neither renders an empty form.
  • Passing null as compositionId renders an empty form.

Configuring EhrFormContext​

For the full list of context options, see Configuration.

componentDidMount() {
this.component.context = {
language: 'en',
readonly: false,
territory: 'SI',
presentationMode: true,
showAllPages: false,
dateFormat: 'dd/MM/yyyy',
autoPrefill: { enabled: true, mode: 'LATEST_COMPOSITION' }
};
}

Credentials​

Basic Auth​

credentials = { username: 'my_user', password: 'my_password' };

OAuth 2.0​

credentials = {
authType: 'oauth2',
token: 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIdasiwia2lkIiA6ICJiA'
};

When the token expires, listen for onRequestUpdatedToken and call updateToken:

provideNewToken = async () => {
const refreshedToken = await /* fetch token */;
this.component.updateToken(refreshedToken);
};

componentDidMount() {
this.component.addEventListener('onRequestUpdatedToken', () => this.provideNewToken());
}

Alternatively, set a new credentials object:

this.component.credentials = { authType: 'oauth2', token: refreshedToken };

Call methods​

onValidateForm = () => {
const validationModel = this.component.validate(true);
};

onSetFieldValue = () => {
const scriptApi = this.component.getScriptApi();
scriptApi.setFieldValue('path/to/form/element', 'value');
};

Handle events​

Pass event listeners as arrow functions in componentDidMount. onValueChange fires on initial render as well as on each subsequent change.

componentDidMount() {
this.component.addEventListener('onValueChange', (event) => this.valueChange(event));
this.component.addEventListener('onFormRendered', (event) => this.formRendered(event));
}

valueChange = (event) => {
console.log('composition', this.component.composition);
};

formRendered = (event) => {
console.log('onFormRendered', event.detail);
};

Viewing the current composition​

logger = () => {
console.log(this.component.composition);
};

Saving a composition​

Provide ehrId via formEnvironment in componentDidMount:

environment = {
variables: [{ name: 'ehrId', value: '000-92ac-4bf6-bfed-f0d4b9f6' }]
};

Using saveComposition via the Script API:

customSaveFunction = async () => {
const response = await this.component.getScriptApi().saveComposition();
if (!response.success) throw response.error;
console.log(response.uid);
};

Using a save dependency on the form:

componentDidMount() {
this.component.addEventListener('onCompositionSaved', (event) =>
this.handleSavedComposition(event)
);
}

handleSavedComposition(event) {
if (!event.detail.success) throw event.detail.error;
console.log(event.detail.uid);
}

Updating a composition​

componentDidMount() {
this.component.compositionId = 'f8805e7e-0000-1111-222c-74ca6b0a53e9::test::1';
this.component.formEnvironment = this.environment;
}

updateFunction = async () => {
const response = await this.component.getScriptApi().updateComposition();
if (!response.success) throw response.error;
console.log(response.uid);
};

onPageChanged event​

pageChange = (event) => { console.log(event); };

componentDidMount() {
this.component.addEventListener('onPageChanged', (event) => this.pageChange(event));
}

Adding custom translations​

Use setTranslation(lang, keyVals) after onFormRendered fires. For the full list of available keys, see Localisation.

componentDidMount() {
this.component.addEventListener('onFormRendered', (event) => {
this.component.setTranslation('no', {
'boolean.presentation.false': 'Usann',
'boolean.presentation.no': 'Nei',
'boolean.presentation.true': 'Sann',
'boolean.presentation.yes': 'Ja'
});
});
}

Vue​

This section covers integrating Form renderer in Vue.js applications. For further details on using Web Components in Vue, see the official Vue documentation.

Setup​

Configure the Vue compiler​

Tell Vue to treat form-renderer as a custom element so it does not try to resolve it as a Vue component.

Vite (vite.config.js):

import vue from '@vitejs/plugin-vue';

export default {
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.includes('-')
}
}
})
]
};

Vue CLI (vue.config.js):

module.exports = {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => ({
...options,
compilerOptions: {
isCustomElement: tag => tag.startsWith('ion-')
}
}));
}
};

Include the script and stylesheet​

Copy form-renderer.js from node_modules to your Vue public directory and reference it in index.html:

<script src="path/form-renderer.js"></script>
<link rel="stylesheet" href="path/styles.css">

Configure​

Use Vue's dot-binding syntax (.property="value") to pass properties to the element. The dot prefix is required.

Template:

<template>
<form-renderer
.formMetadata="formMetadata"
.ehrServerUrl="ehrServerUrl"
.credentials="credentials"
.context="context"
.compositionId="compositionId">
</form-renderer>
</template>

Script:

<script>
export default {
data() {
return {
ehrServerUrl: 'https://better-ehr-server.com/rest/v1',
credentials: { username: 'user', password: 'pswd' },
formMetadata: { name: 'my-form', version: '1.0.0' },
context: { language: 'en', readonly: false },
compositionId: '28e813d9-5caf-4302-8274-3966a5b1f819::user::1'
};
}
};
</script>
good to know

version in formMetadata is optional. If omitted, the latest published version of the form is used.

Compositions and compositionIds​

  • Passing composition fills the form with the provided composition object.
  • Passing compositionId fetches the composition from the server and fills the form.
  • Passing both causes compositionId to take priority; the provided composition is ignored.
  • Passing neither renders an empty form.
  • Passing null as compositionId renders an empty form.

Configuring EhrFormContext​

For the full list of context options, see Configuration.

context: {
language: 'en',
readonly: false,
territory: 'SI',
presentationMode: true,
showAllPages: false,
dateFormat: 'dd/MM/yyyy',
autoPrefill: { enabled: true, mode: 'LATEST_COMPOSITION' }
}

Credentials​

Basic Auth​

credentials: { username: 'my_user', password: 'my_password' }

OAuth 2.0​

<form-renderer .credentials="credentials"
.(onRequestUpdatedToken)="provideNewToken">
</form-renderer>
credentials: {
authType: 'oauth2',
token: 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIdasiwia2lkIiA6ICJiA'
}

When the token expires, call updateToken via the stored ref:

<form-renderer ref="fr"
.credentials="credentials"
.(onRequestUpdatedToken)="provideNewToken()">
</form-renderer>
let form;

export default {
mounted() { form = this.$refs.fr; },
methods: {
async provideNewToken() {
const refreshedToken = await /* fetch token */;
form.updateToken(refreshedToken);
}
}
};

Call methods​

<form-renderer ref="fr"></form-renderer>
let form;

export default {
mounted() { form = this.$refs.fr; },
methods: {
onValidateForm() {
const validationModel = form.validate(true);
},
onSetFieldValue() {
const scriptApi = form.getScriptApi();
scriptApi.setFieldValue('path/to/form/element', 'value');
}
}
};

Handle events​

<form-renderer ref="fr"
.(onValueChange)="valueChange"
.(onFormRendered)="formRendered">
</form-renderer>
let form;

export default {
mounted() { form = this.$refs.fr; },
methods: {
valueChange(event) {
console.log('composition', form.composition);
},
formRendered(event) {
console.log('onFormRendered', event.detail);
}
}
};

Viewing the current composition​

mounted() {
form = this.$refs.fr;
console.log(form.composition);
}

Saving a composition​

Provide ehrId via formEnvironment:

<form-renderer .formEnvironment="environment"></form-renderer>
environment: {
variables: [{ name: 'ehrId', value: '000-92ac-4bf6-bfed-f0d4b9f6' }]
}

Using saveComposition via the Script API:

<form-renderer ref="fr" .formEnvironment="environment"></form-renderer>
<button @click="customSaveFunction">Save</button>
async customSaveFunction() {
const response = await form.getScriptApi().saveComposition();
if (!response.success) throw response.error;
console.log(response.uid);
}

Using a save dependency on the form:

<form-renderer ref="fr"
.formEnvironment="environment"
.(onCompositionSaved)="handleSavedComposition">
</form-renderer>
handleSavedComposition(event) {
if (!event.detail.success) throw event.detail.error;
console.log(event.detail.uid);
}

Updating a composition​

<form-renderer ref="fr"
.formEnvironment="environment"
.compositionId="compositionId">
</form-renderer>
<button @click="updateFunction">Update</button>
async updateFunction() {
const response = await form.getScriptApi().updateComposition();
if (!response.success) throw response.error;
console.log(response.uid);
}

onPageChanged event​

<form-renderer ref="fr" .(onPageChanged)="pageChange"></form-renderer>
pageChange(event) { console.log(event); }

Adding custom translations​

Use setTranslation(lang, keyVals) after onFormRendered fires. For the full list of available keys, see Localisation.

<form-renderer ref="fr" @onFormRendered="formRendered"></form-renderer>
let form;

export default {
mounted() { form = this.$refs.fr; },
methods: {
formRendered(event) {
form.setTranslation('no', {
'boolean.presentation.false': 'Usann',
'boolean.presentation.no': 'Nei',
'boolean.presentation.true': 'Sann',
'boolean.presentation.yes': 'Ja'
});
}
}
};

Frequently asked questions (FAQs)​

How do I include Form renderer in a plain JavaScript application?

Reference form-renderer.js and styles.css in the <head> of your HTML entry file. Then use the <form-renderer> custom element in your markup and configure it by setting properties on the element reference retrieved with document.getElementById.

Why do I need CUSTOM_ELEMENTS_SCHEMA in Angular?

Form renderer is a Web Component. Angular requires CUSTOM_ELEMENTS_SCHEMA to recognise and render custom HTML elements like <form-renderer> without throwing a template compilation error.

How do I access the form-renderer element in Angular?

Use the @ViewChild decorator with a template reference variable to get the element reference, then access .nativeElement to call methods or set properties.

How do I include Form renderer in a React project?

Copy form-renderer.js and the style files from node_modules/@better/form-renderer into your public directory, then reference them in index.html using %PUBLIC_URL%.

How do I access form-renderer properties and methods in React?

Use a ref handler function assigned to the ref prop of the <form-renderer> element. Access the component via this.component inside componentDidMount and other class methods.

Why do I need to configure isCustomElement in Vue?

Vue needs to be told that form-renderer is a custom element, not an unknown Vue component. The isCustomElement compiler option prevents Vue from attempting to resolve it as a component and throwing a warning.

How do I access the form-renderer element in Vue?

Add ref="fr" to the <form-renderer> element, then access it via this.$refs.fr in the mounted hook and store it in a variable for use across methods.

How do I handle OAuth token expiry?

Listen for onRequestUpdatedToken and call updateToken(newToken) on the renderer element within 5000ms. Alternatively, assign a new credentials object to the renderer. See the credentials section for your framework for the exact syntax.

How do I save a composition programmatically?

Call saveComposition() via getScriptApi(). The method returns a promise resolving to a SaveCompositionResponse. Pass false to skip form validation before saving.

How do I add custom translations?

Call setTranslation(lang, keyVals) after the onFormRendered event fires. Pass an ISO 639-1 language code and an object of key-value overrides. See Localisation for the full list of available keys.