Core Concepts

Forms

A form is a collection of fields classified through pages, sections and questions. These fields are rendered differently based on their associated question type and rendering. For example, a text field will behave differently from a dropdown.

Forms are described using JSON schemas that conform to the O3 standard JSON schema spec. This standard JSON schema ensures that schemas built using either the Angular or React form engines have parity. Below is an example of a form rendered using the Form Engine:

{ "name": "Hello world form", "pages": [], "uuid": "ff0933fb-20bd-4e44-a3e8-4073e9801ceb", "encounterType": "d105cbc3-728d-4d11-9ed3-637bf1a4e8a6", "availableIntents": [] }

O3 ships a form builder frontend module that users can leverage to build forms of arbitrary complexity. These schemas can then be published for use in the Patient Chart to collect data.

Forms can be in any one of the following states:

  • published - can be accessed in both the form builder and the Patient Chart.

  • unpublished - can only be accessed in the form builder. The Patient Chart forms workspace only loads published forms.

  • retired - cannot be accessed in either the form builder or the Patient Chart.

Properties of a form

A form schema comprises the following properties:

Element

Type

Description

Element

Type

Description

name

string

The name of the form. This is required and should be unique within the system.

uuid

string

The unique form identifier

encounterType

string

The encounter type uuid associated with the form’s encounter. This defines the context in which the form is used.

inlineRendering

single-line / multi-line / automatic

The inline rendering mode

pages

Array<Section>

A collection of pages that make the form. Each page contains sections and questions.

availableIntents

Array<Intent>

A list of intents supported by this form. Intents define specific actions associated with the form.

Additional form properties

Validators

array of objects

These are the conditions that validate the input data.

Default

string/ number/boolean,/object

Default values for fields in the form.

Required

boolean

Fields that must be filled out or selected.

historicalExpression

string

Expression or data related to historical information.

Hide

object

The option to hide certain parts of the form or fields.

CalculatedExpressions

string

Expressions that calculate values based on inputs.

questionInfo

string

These are additional information or context about a question.

answers

array of objects

Responses or answers provided in the form.

orderType

string

Type of order or sequence for form elements.

selectableOrders

array of objects

Orders that can be selected or applied.

conceptMappings

array of objects

This is the mapping of concepts to specific form elements.

Concept ID

string

Identifier for a specific concept within the form.

Pages

A page is a collection of related sections. A typical page definition consists of a label, inlineRendering (optional) and a list of sections. The engine uses the page’s label to identify it from other pages; that being said, it’s mandatory to keep the page’s label unique. Below is an example of a form with one page and section. The section has 4 questions labelled:

{ "name": "HTS Retrospective Form", "pages": [ { "label": "Eligibility Screening", "sections": [ { "label": "Testing history", "isExpanded": "true", "questions": [ { "label": "What is the reason for conducting the HIV test?", "type": "obs", "questionOptions": { "rendering": "select", "concept": "ce3816e7-082d-496b-890b-a2b169922c22", "answers": [ { "concept": "7398c91a-8db8-480d-8130-1a92cc208ded", "label": "Inconclusive HIV Result", "conceptMappings": [] }, { "concept": "a6ad599d-2bc4-47b7-81fe-a38e88867c1d", "label": "Self Initiative", "conceptMappings": [] }, { "concept": "0e65e5fd-a1d8-4730-a991-75a1d703cba6", "label": "HIV Self Test Positive", "conceptMappings": [] }, { "concept": "6e768c50-a239-45ff-9920-2c6a9352320e", "label": "Index Client Testing", "conceptMappings": [] }, { "concept": "cb099534-b609-4561-9d4c-dd2fc74cedaf", "label": "Assisted Partner Notification (APN)", "conceptMappings": [] }, { "concept": "5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "label": "Other" } ] }, "id": "reasonForTesting" }, { "label": "Has the client ever been tested for HIV?", "type": "obs", "questionOptions": { "rendering": "radio", "concept": "1492AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "answers": [ { "label": "Yes", "concept": "cf82933b-3f3f-45e7-a5ab-5d31aaee3da3" }, { "label": "No", "concept": "488b58ff-64f5-4f8a-8979-fa79940b1594" }, { "concept": "1067AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "label": "Unknown" } ] }, "id": "everTestedForHIV" }, { "label": "How recently has the client been tested for HIV within any of the following periods?", "type": "obs", "questionOptions": { "rendering": "select", "concept": "e7947a45-acff-49e1-ba1c-33e43a710e0d", "answers": [ { "concept": "909edba5-c9b1-44e3-92ee-f95269964fe1", "label": "0-3 Months", "conceptMappings": [] }, { "concept": "8df190d5-7a65-4b53-9b4a-b35b9cf400b1", "label": "3-6 Months", "conceptMappings": [] }, { "concept": "c85e6df3-7184-400e-a686-f41aaae08113", "label": "6-12 Months", "conceptMappings": [] }, { "concept": "8de5bf3f-8058-4735-ae50-b5a986b2362b", "label": "More Than 12 Months", "conceptMappings": [] }, { "concept": "1067AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "label": "Unknown" } ] }, "id": "durationSinceLastHIVTest" }, { "label": "What was the result of the last HIV test?", "type": "obs", "questionOptions": { "rendering": "select", "concept": "159427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "answers": [ { "concept": "6378487b-584d-4422-a6a6-56c8830873ff", "label": "Positive", "conceptMappings": [] }, { "concept": "664AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "label": "Negative" }, { "concept": "7398c91a-8db8-480d-8130-1a92cc208ded", "label": "Inconclusive", "conceptMappings": [] }, { "concept": "1067AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "label": "Unknown" } ] }, "id": "lastHIVTestResult" } ] } ] } ], "uuid": "xxxx", "encounterType": "79c1f50f-f77d-42e2-ad2a-d29304dde2fe" }

Here's how this page definition gets rendered:

In practice, your form will likely have more than one page. You can cycle through the pages by infinitely scrolling or selecting the desired page from the left sidebar. Here's how a form with multiple pages would look like:

image-20240418-155120.png

 

Sections


A section is an element of a form schema that groups related questions. Sections will be rendered in "expanded mode" by default. Set isExpanded to false in the section definition if you want the section rendered in "collapsed mode".

Below is an example of a basic section:

{ "label": "Testing history", "isExpanded": true, "questions": [ { "label": "What was the result of the last HIV test?", "type": "obs", "questionOptions": { "rendering": "select", "concept": "159427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "answers": [ { "concept": "6378487b-584d-4422-a6a6-56c8830873ff", "label": "Positive" }, { "concept": "664AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "label": "Negative" }, { "concept": "7398c91a-8db8-480d-8130-1a92cc208ded", "label": "Inconclusive" }, { "concept": "1067AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "label": "Unknown" } ] }, "id": "lastHIVTestResult" } ] }

Subforms


Forms can be broken up into smaller, distinct units that can be reused within other forms and even programme areas. Subforms also enable mixing encounter types. For example, the parent form can post to an Assessment encounter type, but an embedded form for lab requests can post to a Lab test encounter type, while providing a single user experience. Subforms can be defined through pages, this gives us the flexibility of arbitrary positioning subforms anywhere within the parent form.

The isSubform attribute explicitly tells the engine that this page is a subform. The subform property is a manifestation of the embedded form, it defines meta properties of the subform like the subform target intent, parent behaviour override(s) like the readonly behaviour and form namespace.

A page is used when defining a collection of related sections while a subform is used when embedding different form to the current form.

Questions


A question is the smallest unit of a form, and enables a single piece of data to be collected.

Question properties


Here's a reference of the various properties you can specify in a question definition:

 

image-20240418-155756.png

 

  • label: The actual content of the question. This label is what gets rendered as the question label. If no label is specified, the "display" value of the concept is used (which is generally the preferred name for the concept in the current locale)

  • type: Provides information of how the submission value will be processed. It helps the engine map the target submission handler to a field. Currently supported types include: obs | obsGroup | encounterLocation

    • obs: questions of this type will yield an observation as a submit-able.

    • obsGroup: questions of this type will yield an obs group as a submit-able.

    • encounterLocation: questions of this type will capture a location that will be associated to the current encounter.

  • questionOptions: An object defining other properties of a question:

    • rendering: The field type of the question. Currently supported types include: select | text | date | number | checkbox | radio | repeating | group | content-switcher | encounter-location | textarea | toggle | fixed-value read more about supported types.

    • concept: The concept UUID or concept reference mapping in the format "source:term" (for example "CIEL:1234") of the backing concept for this field.

    • defaultValue: The default value to be associated to this question.

    • answers: An array of answers scoped to a question. An answer definition consists of a concept UUID or mapping and label pair. If no label is specified, the "display" value of the concept is used. Below is an example of answers to a Current HIV status question:

  • questionInfo: A property that recieves a string containing additional information regarding the field. When hovered over, it displays a tooltip containing the information passed.

 

 

  • isHidden: A boolean value that determines field visibility. In most cases, this value is driven by hide expressions.

  • hide: You can use this to define logic for hiding a question based on certain conditions. To do so, you provide a JavaScript expression that evaluates to a boolean value:

  • required: If set true, that form field is considered to be mandatory. Defaults to false.

  • unspecified: If set true, the form engine will render a widget(as part of this field) that can be used to mark this field as unspecified. By default, a mandatory field can't pass validation without a valid value. However, think of a scenario where it’s almost impossible to provide a value eg. when filling out a form in retrospective where a value wasn’t collected on the paper form. For such scenarios, a required field can be marked as unspecified.

What is the value of Unspecified?

Unspecified does not create an observation in the database. It is the equivalent of a NULL value in database systems (but does not create a NULL value). Unspecified is not a concept. It is rendered by the form engine and not persisted in the database.

  • validators: An array in which you provide validation logic for the specific question. Learn more about validators.

  • behaviours: An array of supported behaviours. Learn more about form behaviours.

  • isTransient The RFE supports the ability to make a form field transient. Making a field transient means that the data entered into the form is not saved permanently. This functionality can be easily integrated into the JSON format, as demonstrated below.

Components


There may be situations where you might want to separate commonly-used form logic into separate reusable bits. In such cases, you can structure that logic as a component form. Components can therefore be thought of as reusable forms that carry domain-specific information. Imagine a situation where you're creating many forms for use in a Point of Care setting. You might find that multiple forms might need to have sections for collecting pre-clinic Review information. This pre-clinic Review information could include details such as:

  • Current HIV status

  • Whether a visit was scheduled or not

  • Reasons for a visit

  • Current visit type

  • Patient's insurance information

Now imagine having to define all of these sections and their accompanying questions in each of your forms. Components are the perfect tool for such situations.

Creating Components


To create a component, follow the same procedure of creating a form(opens in a new tab) with the exception being at the point of saving.

  1. It is advisable to prefix the name of the component with component_. For example, if you're creating a component for pre-clinic review, you could name it component_pre-clinic-review.

  2. From the dropdown of encounters, you MUST select the encounter type Component as shown below:

 

 

After saving, the component will be available in the list of forms and you can filter forms if you only want to view saved components.

 

 

The resulting component json will be as shown below;

Referencing Components


You can reference components that have already been saved in the system by adding it the referencedForms key to your json form.

Available visual options

  • form : The alias of the referenced component form as defined in the referencedForms key

  • page : The page label of the referenced component form

  • section : The section label of the referenced component form to be displayed

  • excludeQuestions : An array of question id(s) to be excluded from the referenced section of component form

Example

Below is the complete json form with 2 components referenced;