Simple Form UI

 

The UI Commons module includes an HTML+JS+CSS framework for a "Simple Form UI" patterned after the question-per-screen approach originally pioneered by Baobab Health, but built for keyboard-friendly entry following PIH's experience in Lacolline, rather than touch screens with an on-screen keyboard.

The model consists of Sections (element: <section>) which contain one or more Questions (element: <fieldset>) which contain one or more Fields (container: <p> | element: <input>). The form author has the ability to use this structure as they see fit. However, the common approach is to display one concept or concept set per screen in the UI. 

Including the Framework

In a regular page, you include the framework as follows:

<% // include JS like this (I don't know if the exact order is important)
    ui.includeJavascript("uicommons", "navigator/validators.js", Integer.MAX_VALUE - 19)
    ui.includeJavascript("uicommons", "navigator/navigator.js", Integer.MAX_VALUE - 20)
    ui.includeJavascript("uicommons", "navigator/navigatorHandlers.js", Integer.MAX_VALUE - 21)
    ui.includeJavascript("uicommons", "navigator/navigatorModels.js", Integer.MAX_VALUE - 21)
    ui.includeJavascript("uicommons", "navigator/navigatorTemplates.js", Integer.MAX_VALUE - 21)
    ui.includeJavascript("uicommons", "navigator/exitHandlers.js", Integer.MAX_VALUE - 22)
%>

The snippet above uses the UI Framework to include these javascript files, but you could include them directly in a static html page as well.

You may also use this UI in an HTML Form. See the section below. Note that you do not need the snippet above when building an HTML Form in the 2.x Reference Application.

General HTML Structure

To use the framework you must write semantic HTML, with the following structure:

Semantic HTML Structure for Simple Form UI
<form>
	<section>
		<span class="title"> Section title (should be short; this is removed from the DOM and displayed in the navigation sidebar) </span>
 
		<fieldset> <!-- Each fieldset is a question within a section -->
			<legend> Question title (should be short; this is removed from the DOM and displayed in the navigation sidebar) </legend>
			<p> <!-- Each P is a field within a question -->
				<label> Field title (this will be displayed as content; it does not appear in the navigation sidebar) </label>
				<input type="text" name="will-be-submitted"/>
			</p>
		</fieldset>
		<!-- more fieldsets = more questions in the section -->
	</section>
	<!-- more sections -->
</form>

Navigation

To Do

Confirmation

At the end of the form is a dynamically-generated confirmation dialog that lets you review all values entered in the form before you submit it.

Sections

Sections are the basic building block for any form. Each section has multiple questions which are tagged as <fieldset>. Sections are set in bold font in the navigation menu. Additionally, questions are nested (read: indented) within the section in the navigation menu. The questions from the section that is actively being completed are displayed in the navigation menu. Section titles remain in the navigation menu, but questions (<fieldset>) are hidden until the user begins to work on that section.

Questions

A question is represented by a <fieldset>, which is its element. Each question needs a <legend> tag to display the question title in the navigation sidebar. Make sure this is short because long phrases may cause display issues when questions are completed and the green check appears. 

Display in Confirmation

Each question is displayed as a row on the confirmation page. By default, its display value is the concatenation of display values of all its fields, separated by spaces.

To override the separator, put a "field-separator" attribute on the fieldset element. Example: <fieldset field-separator=", ">

You may completely override the display with a Handlebars template. Example: <fieldset display-template="{{field.[0]}}, {{field.[1]}} {{#unless field.[1]}}?{{/unless}} {{message 'htmlformentry.general.days'}}">

These handlebars templates have access to all field's display values in an array called "field", and the underlying JS FieldModel objects in an array called "fields". We provide a few custom helpers:

  • "message" ... delegates to the emr.message function
  • "nvl" ... uses a replacement value if the first value is null or empty (e.g. {{ nvl field.[6] '-' }}) (since UI Commons 1.4)

Fields

A field is identified by it's container <p> with a <label> and a single <input>, <select>, or <textarea> element. (Note that only the first of multiple <input> tags will be displayed, so separate each field with it's own <p> container.)

The <label> tag is displayed above the <input>. In common cases, the <label> is the worded question you want to ask of the user.

See the section below for how to use the <obs/> element within HTML Forms.

Validation

You apply validation to a field by putting a css class on the element, its container, or the fieldset for the section. Available validations:

  • date

  • integer

  • number

  • numeric-range (expects "min" and/or "max" attributes on the field element)

  • regex (expects a "regex" attribute on the field element)

  • required

Note that validation is applied when you exit a field/question (or try to skip past it). Therefore any fields in hidden sections or questions will not be validated. (E.g. it is safe to put "required" validation on a field that is only required when its section is shown.)

Non-Standard Display/Value

Normally a field's value is taken from the value of the element. To have it read its value from another element (e.g. a hidden input), put a css selector in a "data-value-from" attribute on the element or container.

Normally a field's display value (shown in the final confirmation dialog) is taken from the element. To override this (e.g. if you're building a fancy AngularJS widget) put a "data-display-value" attribute on the element or container.

Example - Overriding value and display
<p id="fancy-widget" data-display-value="{{ formattedDisplayValue() }}">
	<input type="text" data-value-from="#actual-value" typeahead="..."/>
</p>
<input type="hidden" id="actual-value" ng-value="valueToSubmit()"/>

 

Simple Form UI in an HTML Form

You can use the Simple Form UI framework in an HTML Form.

You need to build your form matching the General HTML Structure given above, but because of the way HTML Form Entry transforms certain tags (e.g. section) you need to do a few special things.

For a full example of a form, see the vitals form from the reference application.

Linking to the form

The htmlformentryui module has pages "htmlform/enterHtmlFormWithSimpleUi" and "htmlform/editHtmlFormWithSimpleUi" which include all the JavaScript and CSS for the simple UI. (This normal forms are "...WithStandardUi".)

So you would link to a form something like:

.../htmlformentryui/htmlform/enterHtmlFormWithSimpleUi.page?patientId={{patient.uuid}}&visitId={{visit.uuid}}&definitionUiResource=referenceapplication:htmlforms/vitals.xml 

Encounter datetime

The date/time pickers in HTML Form Entry work badly with this UI, so you probably only want to use this mechanism for real-time forms, and do <encounterDate default="now" widget="hidden"/>.

(This PIH form has a terribly hacky example of a custom date/time selector widget, that needs to be refactored into something reusable at some point.)

Section tags

HTML Form Entry was written before HTML5, and defined its own behavior for the "section" tag. To make things work right you need to do the following:

<section sectionTag="section" headerStyle="title" headerCode="message.code.for.title.text">

Question labels

In normal usage, the <obs> tag in HTML Form Entry creates both the label and widget for a field. However this doesn't work with the expected semantic HTML of the simple form UI. Instead, you need to do something like this (note how velocity lookup functions are to get the question labels, though you may hardcode text, or use the <uimessage> tag if you prefer):

<fieldset>
    <legend><lookup expression="fn.getConcept('CIEL:5090').name"/></legend>
            
    <p class="left">
        <label><lookup expression="fn.getConcept('CIEL:5090').description"/></label>
        <obs conceptId="CIEL:5090" showUnits="uicommons.units.centimeters" unitsCssClass="append-to-value"/>
    </p>
</fieldset>

The <legend> tag will be removed from the DOM and its text will be used in the navigation sidebar.

This usage of the <lookup> tag gets us a concept's preferred name. You could also hardcode instead of doing this, or use the <uimessage> tag.

Dynamic content

The Simple Form UI lets you dynamically show/hide sections, questions, and fields. The HTML Form Entry UI module wraps this functionality in some convenient functions:

  • htmlForm.simpleUi.showSection('id-on-section-tag')
  • htmlForm.simpleUi.hideSection('id-on-section-tag')
  • htmlForm.simpleUi.showQuestion('id-on-fieldset-tag')
  • htmlForm.simpleUi.hideQuestion('id-on-fieldset-tag')
  • htmlForm.simpleUi.showField('id-on-p-tag')
  • htmlForm.simpleUi.hideField('id-on-p-tag')

Note that the show/hideField functions take the id of the <p> tag wrapping a field, rather than the input itself. This is to accommodate the frequent use of the <obs> tag in HTML Forms (because it does not give you precise control over the html of the input elements it generates). Additionally, this allows you to hide both the <label> and <obs> with one command because they are placed inside the <p> container.

Here is a fully-worked-out example of only displaying a non-coded field if you choose the other option from a coded field:

<obsgroup groupingConceptId="PIH:9722">
    <p>
        <label>Form signed by</label>
        <obs id="form-signed-role" conceptId="PIH:TITLE WHO COMPLETED FORM" answerConceptIds="PIH:DOCTOR,PIH:NURSE,PIH:OTHER" size="3">
            <controls>
                <when value="PIH:OTHER"
                    thenJavaScript="htmlForm.simpleUi.showField('form-signed-role-other')"
                    elseJavaScript="htmlForm.simpleUi.hideField('form-signed-role-other')"/>
            </controls>
        </obs>
    </p>
    <p id="form-signed-role-other" class="left">
        <label>Specify:</label>
        <obs conceptId="PIH:GENERAL FREE TEXT"/>
    </p>
</obsgroup>

Note: You can add multiple show/hide functions within the same thenJavaScript line. Just separate the functions by a semicolon ';'

Here's an example:

<when value="1056"
	thenJavaScript="htmlForm.simpleUi.showSection('marriage-details'); htmlForm.simpleUi.hideField('ever-married')"/>

Hiding Fields when the form is loaded

You can use the same JavaScript functions to hide fields when the page loads and display them when a particular value is selected by adding a jQuery function to the top of your page

Function to Hide Fields on form load
<script type="text/javascript">
jQuery(function() { 
	htmlForm.simpleUi.hideQuestion('ENTER_FIELDSET_ID');
	htmlForm.simpleUi.hideQuestion('ENTER_FIELDSET_ID');
	});
</script>

The examples page shows a fully worked out example showing two questions that are hidden when the page loads and then are displayed if the user selects "No" or "Refused to Answer"

Choosing how to structure your concepts to easily hide a field or question

You can only dynamically hide one field (<p>) at a time. If you have multiple fields that need to be hidden, it's best to place these fields in a <fieldset> and use the htmlForm.simpleUi.showQuestion('id-on-fieldset-tag') function. For example, we often have multiple observations when the user selects "other" from a coded concept dropdown menu. We then ask the user to specify the "Other" option in a text box. This occasion would only require hiding one field (<p>). Let's take it a step further and say you want to ask a yes/no coded concept that shows another coded concept with an "Other" coded response that asks the user to specify what they mean when selecting "Other" from the second dropdown observation. In this case, you would have to place the second coded concept and "Specify Other" concept within a <fieldset> and use the showQuestion function. Then, you can use the showField function on the second coded concept to dynamically display the "Specify Other" text field.

The examples page shows a complex example that uses the showQuestion function to display a simulated select-multiple question set:

Troubleshooting

No block level elements inside paragraphs

It is invalid HTML to put a block-level element (e.g. <h1>, <h2>, <div>) inside a <p> element. If you do this, browsers typically handle it by automatically closing the <p> element. Thus if you do something like <p><h3>Label</h3><input .../></p> the input ends up outside the <p> container, and the JavaScript code will error. A key symptom is the lack of keyboard navigation to a question. Look for poorly structured fields if you can't navigate to a field by using the tab key on the keyboard.