Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Technically, RFE Forms is a React component that requires the following properties(props):

Mapping Controls

Fields are mapped to their controls via a registry system. This is highly extensible and easy to override the default controls.

Code Block
languagejson
{
  "label":"Which of the following prevention services was the client referred to?",
  "type":"obs",
  "questionOptions":{
    "rendering":"checkbox",
    "concept":"5f394708-ca7d-4558-8d23-a73de181b02d",
    "answers": [...]
  },
  "id":"referredToPreventionServices"
}

The field above has the rendering of type checkbox, the engine will simply check the registry for this kind of control and instantiates an instance of this control.

Submission Handlers

These handle submission at the field level. The engine doesn’t know how fields aggregate or compose primitive values into objects that the backend expects ie. an Observation. The form engine by default defines a set of base handlers eg. ObsSubmissionHandler, EncounterLocationSubmissionHandler etc.

These are also mapped using the registry system based on the type property of a form field. On form submission, the engine simply collects all field submissions and aggregates them into some object that OpenMRS understands.

Extending the Registry

The engine exposes an API to work with the registry.

  • addHandler(handler: HandlerRegistryItem) Adds submission handler to the handlers registry

    Code Block
    languagetsxjson
    import { SomeCustomHandler } from "./handlers";
    import { addHandler } from "openmrs-ohri-form-engine-lib";
     
    // Add to registryaddHandlerregistry
    addHandler({
      id: "customHandler",
      component: SomeCustomHandler,
      type: "someCustomType",
    });
  • addvalidator(validator: ValidatorRegistryItem)

    Adds validator to the validators registry

    Code Block
    languagetsxjson
    import { SomeCustomValidator } from "./validators";
    import { addvalidator } from "openmrs-ohri-form-engine-lib";
     
    // Add to registryaddvalidatorregistry
    addvalidator({
      id: "customValidator",
      component: SomeCustomValidator,
      type: "customValidatorType",
    });
  • addFieldComponent(control: ControlRegistryItem)

    Adds custom control to the field controls registry

    Code Block
    languagetsxjson
    import { BootstrapDropdown } from "./controls";
    import { addFieldComponent } from "openmrs-ohri-form-engine-lib";
     
    // Add to registryaddFieldComponentregistry
    addFieldComponent({
      id: "bootstrapDropdown",
      component: BootstrapDropdown,
      type: "bootstrapDropdownType",
    });

Post Submission Actions

The form-engine supports post-submission actions. A post action is a JS class or function that implements this interface. This functional interface requires implementing the applyAction(formSession, config) function which has the form session context plus an adhoc config. The config makes the actions more generic and configurable hence the ability of reusing the same action for different use-cases.

How do we register an action?

The engine utilises the registerPostSubmissionActionfunction to register a post submission action.

...

HERE is an example of an action that links mother to infant post delivery and here is how it's registered.

Code Block
languagetsxjson
   registerPostSubmissionAction({
    name: 'MotherToChildLinkageSubmissionAction',
    load: () => import('./post-submission-actions/mother-child-linkage-action'),
  });

Take note that the registration happens in the app's index within the seeder func startupApp.

...

Code Block
languagejson
"availableIntents":[
    {
      "intent":"*",
      "display":"Labour & Delivery Form"
    }
  ],
  "processor":"EncounterFormProcessor",
  "uuid":"1e5614d6-5306-11e6-beb8-9e71128cae77",
  "referencedForms":[],
  "encounterType":"6dc5308d-27c9-4d49-b16f-2c5e3c759757",
  "postSubmissionActions": [{"actionId": "MotherToChildLinkageSubmissionAction", "config": { "targetQueue": "Pre-Counselling", "isOptional": true }}],
  "allowUnspecifiedAll":true,
  "formOptions": {
    "usePreviousValueDisabled": "true"
  }

NOTE: We can also use the old way of linking post actions we didn't support configuring the actions then, see below. (The form engine still supports the old old way for linking actions)

Code Block
languagejson
  "availableIntents": [
    {
      "intent": "*",
      "display": "Labour & Delivery Form"
    }
  ],
  "processor": "EncounterFormProcessor",
  "uuid": "1e5614d6-5306-11e6-beb8-9e71128cae77",
  "referencedForms": [],
  "encounterType": "6dc5308d-27c9-4d49-b16f-2c5e3c759757",
  "postSubmissionActions": [
    "MotherToChildLinkageSubmissionAction",
    "ArtSubmissionAction"
  ],
  "allowUnspecifiedAll": true,
  "formOptions": {
    "usePreviousValueDisabled": "true"
  }

Program Enrolment

The ProgramEnrollmentSubmissionAction is a generic post submission action that automatically enrols a patient to a specified program upon successful submission of an enncounter. This is passed through the form JSON and the action can either be enabled or disabled based on the expression passed as the enabled flag. Below is an example of TB Program enrolment in the TB Case enrolment Form.

Code Block
languagejson
   "postSubmissionActions": [
    {
      "actionId": "ProgramEnrollmentSubmissionAction",
      "enabled":"tbProgramType === '160541AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'",
      "config": {
      "enrollmentDate": "tbRegDate",
      "programUuid": "58005eb2-4560-4ada-b7bb-67a5cffa0a27",
      "completionDate": "outcomeTBRx"
    }
  },
  {
    "actionId": "ProgramEnrollmentSubmissionAction",
    "enabled":"tbProgramType === '160052AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'",
    "config": {
    "enrollmentDate": "tbRegDate",
    "programUuid": "00f37871-0578-4ebc-af1d-e4b3ce75310d",
    "completionDate": "outcomeTBRx"
  }
}
  ]

We can add as many post submission actions as we want to one form and only those whose enabled flag evaluates to true will be processed.

Confirmation Modal

The repeating rendering type lets you replicate instances of a question. Such replicas that were created can also be deleted. When deleting a replica, a confirmation modal should pop up asking the user to confirm the action.

...

Panel
panelIconIdatlassian-warning
panelIcon:warning:
bgColor#FFBDAD

The implementation of the confirmation modal must be registered and implemented within the frontend module/package that has the form-engine-lib linked.

In your frontend module, you must have the implementation for the confirmation modal like such:

Code Block
languagetsx
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ModalHeader, ModalBody, ModalFooter, Button } from '@carbon/react';

interface DeleteModalProps {
  onConfirm: () => void;
  onCancel: () => void;
}

const DeleteModal: React.FC<DeleteModalProps> = ({ onConfirm, onCancel }) => {
  const { t } = useTranslation();

  return (
    <React.Fragment>
      <ModalHeader
        closeModal={onCancel}
        title={t('deleteQuestionConfirmation', 'Are you sure you want to delete this question?')}
      />
      <ModalBody>
        <p>{t('deleteQuestionExplainerText', 'This action cannot be undone.')}</p>
      </ModalBody>
      <ModalFooter>
        <Button kind="secondary" onClick={onCancel}>
          {t('cancel', 'Cancel')}
        </Button>
        <Button kind="danger" onClick={onConfirm}>
          {t('deleteQuestion', 'Delete question')}
        </Button>
      </ModalFooter>
    </React.Fragment>
  );
};

export default DeleteModal;

The modal must be registered in the routes.json of your frontend module in the property modals. For example,

Code Block
languagejson
"modals": [
    {
      "name": "form-engine-delete-question-confirm-modal",
      "component": "deleteQuestionModal"
    }
  ],

Update the index.ts of your module to include the export of the confirmation modal, as such:

Code Block
languagetypescript
export const deleteQuestionModal = getAsyncLifecycle(
  () => import('./form-renderer/repeat/delete-question-modal.component'),
  options,
);

Finally, update the FormEngine component to pass along a prop called handleConfirmQuestionDeletion. This prop should contain a function that has been defined and implemented in the frontend module and triggers the modal using showModal:

Code Block
languagetsx
const handleConfirmQuestionDeletion = useCallback(() => {
    return new Promise<void>((resolve, reject) => {
      const dispose = showModal('form-engine-delete-question-confirm-modal', {
        onCancel() {
          dispose();
          reject();
        },
        onConfirm() {
          dispose();
          resolve();
        },
      });
    });
  }, []);
  
  render(
    <FormEngineComponent
    ....
    handleConfirmQuestionDeletion={handleConfirmQuestionDeletion}
    />
  )
Info

If a confirmation modal is not implemented, the form-engine-lib fallbacks to deleting without showing the user any modal, but it is recommended to follow the instructions above and implement a modal if used in a package where the end user fills in the forms.
An example implemented in esm-patient-chart can be used as a reference.