How to customise the Patient Registration page?

Hi everyone,

We would like to “extend” the current Patient Registration page so that it contains more questions. The answers to those questions should ideally be obtained via concepts (as one would do when implementing HFE’s forms and using obs), unless there is another way to do this. What would be the way to go, could anyone point me to the development steps to follow?

It seems that the Ebola example is doing something similar using the principles outlined in the App Framework Documentation by @darius, but the master currently doesn’t build…

For example, when collecting the patient’s address, we need to retrieve the District, whose possible values should be picked up via a dropdown menu: what’s the simplest way to achieve this through a custom module?

Any help would be greatly appreciated!

2 Likes

My first question is, by district do you mean something other than a component of the patient’s address? If it’s part of the address, you might want to use the addresshierarchy module in conjunction with patient registration to capture this.

For capturing observations, I think that @mogoodrich can point you to a newer example than what I did in the Ebola module.

Thanks for this and for your guidance on IRC last night. @mogoodrich, as suggested by @darius, perhaps could you also shed some light on this thread? In particular could you point me to an example where the address section would be extended with a dropdown question listing the possible districts?

So here is what I did for a first attempt:

  1. I created a new Registration encounter type and I used its UUID in “registrationEncounter”, see my JSON below.
  2. I copied the following JSON in ./omod/src/main/resources/apps/testRegistration_app.json
[
    {
        "id": "mymodule.registrationapp.registerPatient",
        "instanceOf": "registrationapp.registerPatient",
        "label": "TEST Register Patient",
        "description": "Create a new Patient Record",
        "extensions": [
            {
                "id": "mymodule.registrationapp.registerPatient.homepageLink",
                "extensionPointId": "org.openmrs.referenceapplication.homepageLink",
                "type": "link",
                "label": "mymodule.app.registerPatient.label",
                "url": "registrationapp/registerPatient.page?appId=mymodule.registrationapp.registerPatient",
                "icon": "icon-user",
                "order": 1,
                "requiredPrivilege": "App: registrationapp.registerPatient"
            }
        ],
        "config": {
            "registrationEncounter": {
                "encounterType": "bfcf0344-f70b-455b-8f03-a270d810001e",
                "encounterRole": "a0b03050-c99b-11e0-9572-0800200c9a66"
            },
            "allowUnknownPatients": false,
            "allowManualIdentifier": false,
            "allowRetrospectiveEntry":true,
            "sections": [
                {
                    "id": "quickAssessment",
                    "label": "Quick Assessment",
                    "questions": [
                        {
                            "legend": "Weight",
                            "fields": [
                                {
                                    "type": "obs",
                                    "formFieldName": "obs.5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                                    "label": "Weight",
                                    "widget": {
                                        "providerName": "uicommons",
                                        "fragmentId": "field/text",
                                        "config": {
                                            "min": "0.5",
                                            "max": "199",
                                            "appendToValueDisplayed": "kg"
                                        }
                                    },
                                    "cssClasses": [ "number", "numeric-range" ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    }
]

There are two issues that I would like to report and get help with:

  1. This did not extend the existing Patient Registration app but added a new app with its own icon just next to it. How do I then disable/hide the original Patient Registration app? Or how do I make sure that the new one replaces the original one?

  2. I get an exception when clicking on my new app’s icon: org.openmrs.ui.framework.AttributeExpressionException: Failed checks: [min, max] (the following passed: [])

Looking into the above issue #2 in more details, and in particular at the field/text fragment, it seems to require a “min” and a “max” when the CSS class is “numeric-range”:

if (config.classes && config.classes.contains("numeric-range")) {
      config.require("min", "max")
}

… this is the case and it is precisely what is being done in the *_app.json. Could you let me know what is not correct with the above .json then?

Thanks again so much for you help!

At a glance maybe min and max are supposed to be numbers and not strings?

Thanks @darius but that does not seem to be the issue, this is the original (from Ebola example) problematic part:

"fields": [
    {
        "type": "obs",
        "formFieldName": "obs.5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
        "label": "Weight",
        "widget": {
            "providerName": "uicommons",
            "fragmentId": "field/text",
            "config": {
                "min": "0.5",
                "max": "199",
                "appendToValueDisplayed": "kg"
            }
        },
        "cssClasses": [ "number", "numeric-range" ]
    }
]

I tried Attempt 1 (as you suggested)

"fields": [
    {
        "type": "obs",
        "formFieldName": "obs.5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
        "label": "Weight",
        "widget": {
            "providerName": "uicommons",
            "fragmentId": "field/text",
            "config": {
                "min": 0.5,
                "max": 199,
                "appendToValueDisplayed": "kg"
            }
        },
        "cssClasses": [ "number", "numeric-range" ]
    }
]

Attempt 2 (since the “config” subsection appeared to be missing when debugging, see my comments further below)

"fields": [
    {
        "type": "obs",
        "formFieldName": "obs.5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
        "label": "Weight",
        "widget": {
            "providerName": "uicommons",
            "fragmentId": "field/text",
            "min": "0.5",
            "max": "199",
            "appendToValueDisplayed": "kg"
        },
        "cssClasses": [ "number", "numeric-range" ]
    }
]

Attempt 3 (just to be sure)

"fields": [
    {
        "type": "obs",
        "formFieldName": "obs.5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
        "label": "Weight",
        "widget": {
            "providerName": "uicommons",
            "fragmentId": "field/text",
            "min": 0.5,
            "max": 199,
            "appendToValueDisplayed": "kg"
        },
        "cssClasses": [ "number", "numeric-range" ]
    }
]

… to no avail.

The reason why I got rid of the config subsection in my trials is because while debugging the UI Framework I saw that indeed the “min” and “max” where missing in the AttributeHolder instance that is checked by AttributeHolderUtil:satisfied(…).

This, however, works (since I get rid of the class that triggers the test on “min” and “max” presence):

"fields": [
    {
        "type": "obs",
        "formFieldName": "obs.5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
        "label": "Weight",
        "widget": {
            "providerName": "uicommons",
            "fragmentId": "field/text",
            "config": {
                "min": "0.5",
                "max": "199",
                "appendToValueDisplayed": "kg"
            }
        }
    }
]

I don’t think I need this min/max feature currently so I’ll get on with my use case, but I though the whole above details could be useful to others. I am sure I will need more help, so I’ll be around here or on IRC.

@mogoodrich would you have an example on hands showing how the address section could be extended?

Thanks anyhow for all the great help and tips so far. Cheers.

More importantly, now I tried to use a custom .gsp fragment (simply copied from the Ebola example), placed in ./omod/src/main/webapp/fragments/field/ethnicity.gsp:

<p class="required">
    <label>
        ${config.label}
        <span>(${ ui.message("emr.formValidation.messages.requiredField.label") })</span>
    </label>
    <select name="${config.formFieldName}" size="3">
        <option value=""></option>
        <option value ="142177AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA">Suspected case</option>
        <option value ="159392AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA">Confirmed case</option>
    </select>
    <span class="field-error"></span>
</p>

within my app’s JSON configurator:

{
   "legend":"Ethnicity",
   "fields":[
      {
         "type":"obs",
         "formFieldName":"obs.162828AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
         "label":"Ethnicity?",
         "widget":{
            "providerName":"mymodule",
            "fragmentId":"field/ethnicity"
         }
      }
   ]
}

And upon clicking on the app OpenMRS is complaining about a missing controller:

org.openmrs.ui.framework.UiFrameworkException: No controller provider: mymodule
	at org.openmrs.ui.framework.fragment.FragmentFactory.getController(FragmentFactory.java:296)
	at org.openmrs.ui.framework.fragment.FragmentFactory.processThisFragment(FragmentFactory.java:146)
	at org.openmrs.ui.framework.fragment.FragmentFactory.process(FragmentFactory.java:116)
	at org.openmrs.ui.framework.page.PageContext.includeFragment(PageContext.java:75)
	at org.openmrs.ui.framework.UiUtils.includeFragment(UiUtils.java:155)

Could you point me to this controller in the Ebola example module?

Have you done the first two things mentioned in this wiki page?

  • Depending on the UI Framework module

  • Beans in webModuleApplicationContext.xml

Thanks Darius, that indeed solved the controller issue. I was able to append a new section to Patient Registration using concepts behind the question/answers. That’s a great step forward. Thanks so much!

If possible you could help others by writing a step by step guide for the same.

1 Like

@mksd To answer your questions:

  1. Are you using the Address Hierarchy module? In order to use dropdowns to select addresses, you will have to set up a hierarchy using the module as described here: https://wiki.openmrs.org/display/docs/Address+Hierarchy+Module. Then, after you have the hierarchy set up, you will want to use the PersonAddressWithHierarchy widget providered by the registration app (provider=“registrationapp”, fragmentId=“field/personAddressWithHierarchy”).

You may have to set some of the configuration parameters on the person address with hierarchy widget as well–there’s no good documentation of them, but the code for the widget can be found here:

  1. Unfortunately, there is no easy way to add “edit” functionality to observations added to the registration encounter as part of the registration workflow. What we’ve done in these cases is created separate Html Form Entry forms for editng. This is not ideal, because you have to maintain the content of the form in two places–in the *_app.json and in a separate html form, but it is all we have for now. What we did was defined the registration encounter as an html form, and then added an instance of the coreapps “mostRecentEncounter” app to the registration dashboard and associated it with our registration encounter type.

openmrs-module-addresshierarchy

No I wasn’t using it, thanks for pointing that out. I tried to import the sample address hierarchy text file (copy/pasting the 4 liner provided in the wiki) and, although I just pasted it using the pipe as delimiter… well basically it ignored the pipe and created a address hierarchy level for every single character in the file (I guess I forgot to escape it at first, my bad).

When trying to re-upload while overriding it fails and logs:

Unable to import address hierarchy file org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [delete from address_hierarchy_entry where address_hierarchy_entry_id=?]; constraint [null] … ERROR - ManageAddressHierarchyController.processAddressHierarchyUploadForm(210) |2015-07-30 23:43:15,962| Unable to import address hierarchy file org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [delete from address_hierarchy_entry where address_hierarchy_entry_id=?]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update at…

Hence my new questions :-/

  1. How do I delete this now to start over again?
  2. How do I change the address field to bring up something like ‘district’ as it is shown in the screenshots? Right now I only see the “usual”: Address, Address 2, City/Village… etc.

Edition

I guess I see the idea, I’ll come back to this after I have managed to fully figure out the above address challenge. But anyway the address will be editable right, it is only those fields where I used obs that will have to go the HFE way for editing?

@mogoodrich @darius

Ok I figured out, using Bahmni’s example how to import a CSV file correctly.

Here is how I changed my *_app.json file:

{
  "type": "personAddress",
  "label": "registrationapp.patient.address.question",
  "widget": {
      //"providerName": "uicommons",
      //"fragmentId": "field/personAddress"
      "providerName": "registrationapp",
      "fragmentId": "field/personAddressWithHierarchy"
  }
}

And here is the error log I get when trying to access the patient registration page after that: http://paste.ubuntu.com/11972180/

Clearly some controller fails to provide some name variable to the fragment, I’m not quite sure how to troubleshoot this right now, any help would be very welcome…

Have you set a “Mapped Address Field” for each level in the hierarchy as shown in the Bahmni example? It’s annoying to debug gsp because you can’t tell from the stack trace exactly where the problem is occurring, but it looks like the only places where a name field is being accessed in that page is addressfield.name.

For what it’s worth, I did see that in reference to map address fields in the Bahmni docs it says “From DB perspective this does not matter”… but, to be clear, it means that from a DB perspective it doesn’t matter which field you chose, but it does matters that you choose some field.

Yes you are right, I had figured that out as well, it was indeed coming from unset addressfield.name. But would you know how to delete an existing hierarchy?

@mogoodrich

Ok for now I could overwrite an existing hierarchy, which already helps. It would be good to be able to just delete it though.


I have a very important question left because this is a functional blocker: after setting up everything, I just couldn’t obtain a free field anymore (such as address1).

  • Either I map address1 in ‘Address Hierarchy Levels’ and it becomes constrained.
  • Or I don’t map it and it just doesn’t show up on the registration form.

How can I then combine a few constrained fileds (country, province, district) with one free field (address)?

Thanks for all your help so far!

Kind of hacky, but I think if you upload a blank csv file, it may just blow away the existing hierarchy, The other hacky alternative would be just to clear out all the rows in the address_hierarchy_entry and address_hierarchy_level table.

I believe for your other issue you need to add a list of the fields that allow manual entry like so:

widget : {
  "providerName": "uicommons",
   ...
  "config": {
     "manualFields": [
          "addressfield-name-of-field-that-allows-manual-entry"
      ]

   }
}

For now I have this:

"legend": "Person.address",
"fields": [
    {
        "type": "personAddress",
        "label": "registrationapp.patient.address.question",
        "widget": {
            "providerName": "registrationapp",
            "fragmentId": "field/personAddressWithHierarchy"
        }
    }
]

Should it become that?

"legend": "Person.address",
"fields": [
    {
        "type": "personAddress",
        "label": "registrationapp.patient.address.question",
        "widget": {
            "providerName": "registrationapp",
            "fragmentId": "field/personAddressWithHierarchy",
            "config": {
              "manualFields": [
                "Foobar"
              ]
            }
        }
    }
]

This doesn’t produce any result. I am sorry if this is obvious, I’m just really unsure about this config logic in the widget. But from the widget class that you pointed out above in the thread, it seems that it should be at the same level as fragmentId or providerName, which is why I put it there. I’m not sure if you really meant uicommons in your snippet, in which case I may need more info here I’m afraid.

Another issue is that the address template in Admin > Manage Address Template is reset to the default one each time I restart Jetty. Have you ever noticed anything like that?

Thanks for your responsivness.

EDIT Being a bit stuck, I tried to debug PersonAddressWithHierarchyWidget.java but this was never reached. When exactly is this Config object instanciated/read?

Sorry, I’ve been swamped with a lot of stuff, so haven’t had much of a chance to look into this, but, yes, the example you listed is more or less what I suggested. I must be missing something somewhere.

It’s been hard to point you to an example, because we have some java code that dynamically created the json structures that we use, so I was trying to quickly look through that to figure out the format. You can take a closer look here… hopefully it will help some!

1 Like

Whats the use of sections under config in the app.json file @darius

Cannot find the documentation for it

Say you want to change the address format when using hierarchy module , do you pass these parameters here?

@judy, the <sections> part adds additional “sections” which have “questions” and “fields” in them (see the wiki page about Simple Form UI for a description of section/question/field).

I don’t think configuring the registration page is properly documented anywhere, but it’s definitely possible to figure it out by looking at examples, as @mksd did earlier in this thread (and maybe he can share his examples).

In the Ebola module you can see examples of capturing person attributes, obs, and a custom widget:

PIH has done the most recent customizations (including adding some features that didn’t exist when I was doing the Ebola module configuration), but their code may be harder to read since it is written as Java rather than JSON.

2 Likes