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 |
---|---|---|
| string | The name of the form. This is required and should be unique within the system. |
| string | The unique form identifier |
| string | The encounter type |
| single-line / multi-line / automatic | The inline rendering mode |
| Array<Section> | A collection of pages that make the form. Each page contains sections and questions. |
| Array<Intent> | A list of intents supported by this form. Intents define specific actions associated with the form. |
Additional form properties
| array of objects | These are the conditions that validate the input data. |
| string/ number/boolean,/object | Default values for fields in the form. |
| boolean | Fields that must be filled out or selected. |
| string | Expression or data related to historical information. |
| object | The option to hide certain parts of the form or fields. |
| string | Expressions that calculate values based on inputs. |
| string | These are additional information or context about a question. |
| array of objects | Responses or answers provided in the form. |
| string | Type of order or sequence for form elements. |
| array of objects | Orders that can be selected or applied. |
| array of objects | This is the mapping of concepts to specific form elements. |
| 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 section
s. 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:
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:
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 | encounterLocationobs
: questions of thistype
will yield an observation as a submit-able.obsGroup
: questions of thistype
will yield an obs group as a submit-able.encounterLocation
: questions of thistype
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 aCurrent 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 settrue
, that form field is considered to be mandatory. Defaults to false.unspecified
: If settrue
, 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 validvalue
. 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, arequired
field can be marked asunspecified
.
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
: Anarray
in which you provide validation logic for the specific question. Learn more about validators.behaviours
: Anarray
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.
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 itcomponent_pre-clinic-review
.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 thereferencedForms
keypage
: The page label of the referenced component formsection
: The section label of the referenced component form to be displayedexcludeQuestions
: 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;