Dynamic Autocomplete Widget And DynamicRepeat Tag

Note that this page refers to a Google Summer of Code project that has not been incorporated into the main Html Form Entry line--so this feature is not currently avaiable.  Please see the Html Form Entry reference guide to for the list of currently available tags: HTML Form Entry Module HTML Reference

For details about this project, see: 

Error rendering macro 'jira' : Unable to locate Jira server for this macro. It may be due to Application Link configuration.

(This is an enhancement to the HTML FormEntry.You would have to be a familiar with the implementation and functionality of HTML FormEntry to understand the "How did i do" section's)

What does this do?

In HTML FormEntry  you have to specify all the field's in the form in it's design phase and after it is saved it is similar to a printed paper. Consider an example of an intake form in which you need to fill out the symptoms of a patient and only a couple of text boxes are given and the symptoms are four, what do we do?? we write the extra symptoms below the text boxes.This enhancement helps you exactly in the same way by letting you write the symptoms while filling the form.If there are many form elements that need to be repeated then the second part would help duplicating them dynamically.

What are these:

As the heading of this page suggests, we are having two different tags.

(i) Dynamic Autocomplete Widget.

(ii) Dynamic Repeat Tag.

First one being enhancement to the autocomplete widget and the second is an independent tag.

Though both are providing similar functionality, they have different applications.

1.Dynamic Autocomplete Widget


You can try out the following code(go to administration->manage HTML forms->New HTML form->(give some example data) and save-> just select the whole code below and paste it in the text area provided in the page )  For Dynamic Autocomplete Functionality:

<htmlform>
    <macros>
        paperFormId = (Fill this in)
        headerColor =#009d8e
        fontOnHeaderColor = white
    </macros>

<style>

.section {
            border: 1px solid $headerColor;
            padding: 2px;
            text-align: left;
            margin-bottom: 1em;
        }
        .sectionHeader {
            background-color: $headerColor;
            color: $fontOnHeaderColor;
            display: block;
            padding: 2px;
            font-weight: bold;
       }
        table.baseline-aligned td {
            vertical-align: baseline;
        }   

    </style>

 <span style="float:right">Paper Form ID: $paperFormId</span>
    <h2>sample (v1)</h2>

    <section headerLabel="1. Encounter Details">
        <table>
            <tr>
                <td>Date:</td>
                <td><encounterDate default="today"/></td>
            </tr>
            <tr>
                <td>Location:</td>
                <td><encounterLocation/></td>
            </tr>
            <tr>
                <td>Provider:</td>
                <td><encounterProvider/></td>
            </tr>
        </table>
<fieldset>
    <legend>Symptoms:</legend>
        <obs conceptId="1272" answerClasses="Symptom,Finding" style="autocomplete" selectMulti="true"/>
</fieldset>
    </section>

    <section headerLabel="2. Demographic Information">
        <table>
            <tr>
                <td>Name:</td>
                <td><lookup expression="patient.personName"/></td>
            </tr>
            <tr>
                <td>Old Identification Number:</td>
                <td><lookup complexExpression="#foreach( $patId in $patientIdentifiers.get('Old Identification Number') ) $patId #end "/></td>
            </tr>
            <tr>
                <td>OpenMRS Identification Number:</td>
                <td><lookup complexExpression="#foreach( $patId in $patientIdentifiers.get('OpenMRS Identification Number') ) $patId #end "/></td>
            </tr>
            <tr>
                <td>Gender:</td>
                <td><lookup expression="patient.gender"/></td>
            </tr>
            <tr>
                <td>Birthdate:</td>
                <td>
                    <lookup complexExpression="#if( \$patient.birthdateEstimated ) ~#end"/> <lookup expression="patient.birthdate"/>
                    (Age: <lookup expression="patient.age"/>)
                </td>
            </tr>
        </table>
    </section>

    <submit/>
</htmlform>

output:

The entered xml would generate the following html: 

You can search and add the concepts by selecting the concept and clicking add beside it : (to remove any added concept just click the remove button beside the concept)

In View mode the form would be looking as:

Let us edit the entered form a little by removing and adding some concepts :

In this particular example let us assume there are some changes in the symptoms and deviated trachea as entered earlier is a false symptom. we'll remove the deviated trachea by clicking on the

remove button beside it.

 
Now let us assume there is a failure of the regimen  provided so we need to add it in the symptoms so we search for regimen failure and add it.Save changes.

How did i do it:(Workflow)


1. Created a new widget called DynamicAutocomplete Widget which has a similar functionality as of a normal autocomplete widget but it generates extra html in case of View/Edit mode.

2.It is registered as obsSubmissionElement.

3. The HTML management are handled by (button.addConceptButton).click function for creating the html when a concept is added by clicking add, when a concept is removed it is simply removed and both the times the refresh function is called.

4. The whole widget is enclosed in a division, so when any event is called such as add or remove, there is a count variable associated with the division which is updated in refresh function.

5. When the form is submitted the obsSubmissionElement handles the submission part by repeating the normal submission process for the count stored.

6. While in View/Edit mode the formEntryConcept is made responsible for fetching the list of values stored in the database corresponding to the question concept by using a new method removeExistingObs(concept) instead of removeExistingObs(concept,ansconcept) which returns only a single concept.(as the whole implementation till now had been for a form element having only a single answer).The list is used by the obsSubmissionElement which in turn sends it to the widget which generates the respective html.

7. While in Edit mode it is a little tricky for saving the modified list as earlier there was only a single answer so if there was any change in it the earlier obs is voided and a new obs is saved. As for the voiding the earlier obs and adding new obs part remains the same but now while saving we compare two lists of obs, one from the database and one from the user side if a database obs is not present then it is voided and if any extra obs is there in the user side list, then a a new obs is created.

2.Dynamic Repeat Tag


You can try out the following code(go to administration->manage HTML forms->New HTML form->(give some example data) and save-> just select the whole code below and paste it in the text area provided in the page )  For Dynamic Autocomplete Functionality:

The adding and removing usage is similar to Dynamic Autocomplete i.e clicking on add adds the selected obs and clicking removes them. Difference is that the in Dynamic Autocomplete the selected concept is shown only as a label and in this it provides the same functionality of the original widget i.e you can even modify them.

<htmlform>
    <macros>
        paperFormId = (Fill this in)
        headerColor =#009d8e
        fontOnHeaderColor = white
    </macros>

<style>

.section {
            border: 1px solid $headerColor;
            padding: 2px;
            text-align: left;
            margin-bottom: 1em;
        }
        .sectionHeader {
            background-color: $headerColor;
            color: $fontOnHeaderColor;
            display: block;
            padding: 2px;
            font-weight: bold;
       }
        table.baseline-aligned td {
            vertical-align: baseline;
        }   

    </style>

 <span style="float:right">Paper Form ID: $paperFormId</span>
    <h2>sample (v1)</h2>

    <section headerLabel="1. Encounter Details">
        <table>
            <tr>
                <td>Date:</td>
                <td><encounterDate default="today"/></td>
            </tr>
            <tr>
                <td>Location:</td>
                <td><encounterLocation/></td>
            </tr>
            <tr>
                <td>Provider:</td>
                <td><encounterProvider/></td>
            </tr>
        </table>
<fieldset>

  <legend>PRIMARY REGIMEN:</legend>

<dynamicRepeat>

<obsgroup groupingConceptId="1155">

ANTIRETROVIRAL DRUG RECOMMENDED:<obs conceptId="1088" answerClasses="Drug" style="autocomplete"/>

ANTIRETROVIRAL DOSE QUANTIFICATION: <obs conceptId="1181"/>

 </obsgroup>    

</dynamicRepeat>

</fieldset>
    </section>

    <section headerLabel="2. Demographic Information">
        <table>
            <tr>
                <td>Name:</td>
                <td><lookup expression="patient.personName"/></td>
            </tr>
            <tr>
                <td>Old Identification Number:</td>
                <td><lookup complexExpression="#foreach( $patId in $patientIdentifiers.get('Old Identification Number') ) $patId #end "/></td>
            </tr>
            <tr>
                <td>OpenMRS Identification Number:</td>
                <td><lookup complexExpression="#foreach( $patId in $patientIdentifiers.get('OpenMRS Identification Number') ) $patId #end "/></td>
            </tr>
            <tr>
                <td>Gender:</td>
                <td><lookup expression="patient.gender"/></td>
            </tr>
            <tr>
                <td>Birthdate:</td>
                <td>
                    <lookup complexExpression="#if( \$patient.birthdateEstimated ) ~#end"/> <lookup expression="patient.birthdate"/>
                    (Age: <lookup expression="patient.age"/>)
                </td>
            </tr>
        </table>
    </section>

    <submit/>
</htmlform>

The above Xml would be generating the following html:


 In Enter mode this would look as shown above and when the elements in it are selected and add is clicked it would duplicate the whole form elements as shown below:


In the View mode it would be looking a shown below:
In Edit Mode: It would have a similar look as of enter mode though,the newly added obs sets would be having the remove option the earlier added obs group set wont be having the remove ability.If you do want to remove them you can remove the values and save the form and they will be voided.(and you can even change the existing ons set to void the earlier ones and add new ones.)

How did i do it:(Workflow)


1. Created a dynamicRepeat tag handler which is derived from repeatControllerAction responsible for handling whatever is inside of <dynamicRepeat></dynamicRepeat>.

*2. *When ever the <dynamicRepeat> tag is encountered the tag handler creates a container division which cnsists of a hiddenfield widget(basically for holding the count value) and a template division which consists of the elements to be duplicated when the add button is clicked.

*3. *After the division is added beginDynamicRepeat which sets inDynamicRepeat variable in formEntryContext as true. So,when the inDynamicRepeat is true what ever widgets are registered they are registered with an added tail of -template to their fieldname. And as soon as the elements are completed in dynamicrepeat endDynamicRepeat is called which sets the inDynamicRepeat as false.

4. There is one other function called in the tag handler that is session.getSubmissionController().startRepeat(), this is called so that all the actions in these are not added in the formSubmissionActions but instead in the repeatControllerActions.

5. After the html is displayed at the user end, when the add button is clicked there is a function duplicateTemplate() in htmlformentry.jsp which is responsible for generating the html for the newly created elements by cloning the template division and replacing all the "template" in id's and names attribute values to the count value in the hidden field widget of the respective container division and replacing the add button with the remove button and binding it with the respective function.

6. For handling the remove there is a removeFunction() in htmlformentry.jsp this is responsible for removing the whole duplicated template and refreshing the values of every element respectively.For e.g. if there are elements with names and id's w8-0, w8-1, w8-2, w8-3, when a remove is called on w8-1 then the whole values are replaced with w8-0, w8-1, w8-2 for w8-0, w8-2, w8-3  respectively.The hidden field widget is also updated.

7. When the form is submitted when it encounters <dynamicRepeat> as the elements in it are stored in repeatControllerActions it is repeated the count(value stored in hidden field widget) number of times and all the elements in the template division having template in the id's are replaced with the iteration number and submission is handled by calling it's respective submission element with the new field name provided so it gets the value stored in the new field name.All of the point's till now are only for the enter mode(excepting for the similar functionality part in Edit mode).

8. Anything added via the dynamicRepeat tag will be stored in the database.But after it is stored in the database there will be no trace that they have been saved via the <dynamicRepeat> tag.

9. For the View/Edit modes i call applyDynamicTags() of htmlformentry generator, where it checks for the number of obsgroups in the formEntryContext's existinObsGroup varible and repeats the XML in the <dynamicRepeat></dynamicRepeat> . So when the HtmlFormEntryGenerator is called it generates the html for the obsGroup's separately and generates the field values independently having no relation to the dynamicRepeat or template.

10. In View mode there wont be any html generated.

11. In Edit mode, the html generated would be similar to that of the html generated in Enter mode.So all the points of 1-7 are repeated. The earlier obsGroup set's cannot be removed as of the dynamicRepeat remove  button as they are created as new obs specifically for the instance of the person(original form xml is untouched). So if you want to void or modify the existing obsGroups you just change the values as required(i.e. blank for voiding them and another concept name for modifying them).

Project's Video Tutorial (click on the following link to view the demo):


http://www.youtube.com/watch?v=Tya7bHIU_l0

Known Issues:


1. Dynamic Autocomplete Widget :

There are no known issue's in this widget.

2. Dynamic Repeat Tag :

 Presently there is no way we can identify the observation's that have been saved by this tag handler.So the View/Edit mode only works for observation groups(Enter mode will work for any tag in this i.e. not only it will duplicate the html to accept values but also it will save them in the database).

  • While rendering the View/Edit mode i need to replicate the xml in the <dynamicRepeat></dynamicRepeat> so to know the number of times to replicate the xml  session.getContext().getExistingObsInGroupsCount();  in HtmlFormEntryGenerator is being used. It gets the count of the existing obsGroups in the Context and this is used as the count as mentioned earlier.Even this is not perfect in the case where there are more obsGroups than the one's stored by dynamicRepeat tag handler the xml would be repeated even for them.

Possible solution: If there is dynamic Repeat table created in the database where we can store each dynamicRepeat as an entry and for that entry we can store the stored obs id's. We will know the count and we will be able to differentiate between one dynamicRepeat and other. Then dynamicRepeat would be working for any tag in it.