UI Framework Reference Guide

Pages and Fragments

Controller classes

Unlike Spring controllers, UI Framework controllers are not components. To enable the development mode, controller classes are reloaded from the file system for each request, so they have no state and no static state across instances.

Because of the dynamic nature of controller classes one should be careful not to rely on the state of static variables within controller class. The instanceof keyword will also not function as expected when working with controller classes.

Controller method parameters

You may have zero or one of these annotations (and they are applied first):

  • @RequestParam: gets the argument value from an HttpServletRequest parameter
    • supports type conversion
  • @FragmentParam(only for Fragment controllers): gets the argument value from a FragmentConfiguration
    • supports type conversion
  • @SpringBean: gets the argument value from Spring's application context. Behaves like @Autowired; if you give an annotation value, that behaves like @Qualifier
  • @CookieValue: gets the argument value from a cookie in the HttpServletRequest
    • supports type conversion

If the above annotations did not produce a non-null value, and the argument has a @MethodParam annotation, then the method named in the annotation's value will be called to instantiate the argument.

If the @InjectBeans annotation is present, then we use Spring to set any properties on the argument marked with @Autowired. (If no previous annotation produced a non-null value for the argument, then the no-arg constructor on the argument class will be called.)

If the @BindParams annotation is present, then any request parameters that match properties on the argument class will be automatically type-converted and set on the argument object. (If no previous annotation produced a non-null value for the argument, then the no-arg constructor on the argument class will be called.)

If the @Validate annotation is present, then we fetch the preferred Validator (from the Spring application context) for the argument class, and use this to validate the argument value. Any validation errors will lead to a BindParamsValidationException being thrown, and the controller method will not be called.

A (contrived) example showing the most annotations you can use together
public class ExamplePageController {
    public void get(@RequestParam(value = "patientId", required = false) @MethodParam("ifNotSpecified") @InjectBeans @BindParams @Validate PatientWrapper wrapper) {
         // ...
    }

    public PatientWrapper ifNotSpecified() {
        return new PatientWrapper(new Patient());
    }
}

public class PatientWrapper {
    private Patient patient;
    @Autowired private VisitService visitService;

    public void setPatient(Patient patient) {
        // ...
    }

    public void setAge(Integer age) {
        // ...
    }

    public List<Visit> getVisits() {
        return visitService.getVisitsByPatient(patient, true, false);
    }
}

@Component
public class PatientToPatientWrapperConverter implements Converter<Patient, PatientWrapper>  {
    // ...
}

@Validator(PatientWrapper.class)
public class PatientWrapperValidator implements Validator  {
    // ...
}

Page request interceptors

These provide a mechanism for intercepting all page requests and performing some logic before the request is handled by the page controller. An interceptor is any component which extends PageRequestInterceptor.

Example of using an interceptor to check user authentication
@Component
public class RequireLoginPageRequestInterceptor implements PageRequestInterceptor {
	@Override
	public void beforeHandleRequest(PageContext context) {
		if (!Context.isAuthenticated()) {
			throw new APIAuthenticationException("Login is required");
		}
	}
} 

Formatting

Throughout the UI Framework, wherever you have access to a UiUtils object, you can format a wide variety of objects to a String, using UiUtils.format(Object).

This covers most OpenMRS objects, as well as Dates.

Date formatting

The default format for dates whose time component is 00:00:00.000 is "01.Feb.2003" and the default format for dates with a meaningful time component is "01.Feb.2003, 14:25:36".

Since version 2.5 you can change the format used by setting the global properties uiframework.formatter.dateFormat and uiframework.formatter.dateAndTimeFormat.

Changing formatter implementations

Since version 3.3 you can override the built-in formatting for different types at an installation-wide level by defining a Spring bean that describes how to format each class. Currently we support only Handlebars templates, but other templating technologies could be added.

Custom Handlebars Formatters

Define a spring bean for each class whose formatting you want to override. The implementation is handlebars.java, and we support two additional helpers:

  • {{format propertyName}} ... recursively applies formatting to the specified property
  • {{message 'code'}} ... localizes the given message code

Example:

add this to a moduleApplicationContext.xml
 <bean class="org.openmrs.ui.framework.formatter.HandlebarsFormatterFactory">
    <property name="forClass" value="org.openmrs.Encounter"/>
    <property name="template">
        <value>{{format encounterType}} {{message 'general.at'}} {{format location}} ({{message 'general.onDate'}} {{format encounterDatetime}})</value>
    </property>
</bean>

 

Localization

Messages

Throughout the UI Framework, wherever you have access to a UiUtils object you can use the message(String code, Object... args) method, which delegates to the Spring MessageSource, with the current locale.

Example
// in a GSP
${ ui.message("helloUser", loggedInUser.person.personName) }

// in a page controller
public String post(..., UiUtils ui) {
    ...
    return new SuccessResult(ui.message("yourmoduleid.action.successMessage"));
}

Localizing metadata (since version 2.5)

Since OpenMRS core does not have support for localizing metadata, the UI Framework provides a mechanism to do this. You can define certain specific localized messages that the UI Framework's formatter will use in preference to a metadata's name property.

Example
// in a module's messages.properties file
openmrs.property.MESSAGE_PROPERTY_ALLOW_KEYS_OUTSIDE_OF_MODULE_NAMESPACE = true
ui.i18n.PatientIdentifierType.name.e66645eb-03a8-4991-b4ce-e87318e37566 = Dossier Number

// in a GSP
${ ui.format(patient.patientIdentifier) }

// will output something like:
Dossier Number = ABC123
Versions of OpenMRS prior to 1.9.3 do not allow modules to define messages outside of their namespace, so to provide suitable messages you would have to use the Custom Messages module, or define a custom MessageSource.

As of version 3.0, you can use this mechanism to override the names of concepts. As concepts already have support for localization in the OpenMRS API, this use of UI Framework localization is only recommended for users of shared concept dictionaries who are not able to modify concept names in the database.

Including resources

How do I include a js or css file globally on every page?

You can define resources to be available on every pages of the application by adding a Spring bean that describes the resource to include.

For instance, to include the myStyle.css file, located in the directory mymodule/omod/src/main/webapp/resources/styles/ , simply edit the webModuleApplicationContext.xml file of your module and add:

Example of including CSS to the application
<!-- Includes the specified resources on every page in the module -->
<bean class="org.openmrs.ui.framework.page.GlobalResourceIncluder">
    <property name="resources">
        <list>
            <bean class="org.openmrs.ui.framework.resource.Resource">
                <property name="category" value="css" />
                <property name="providerName" value="${project.parent.artifactId}" />
                <property name="resourcePath" value="styles/myStyle.css" />
                <property name="priority" value="-100" />
            </bean>
        </list>
    </property>
</bean>

Notes:

  • The "resourcePath" property is implicitly prefixed by mymodule/omod/src/main/webapp/resources/ (because of the value we set for "providerName")
  • The "priority" property will define in which order resources are loaded (a high priority resource will be included before a low priority resource; for css, as in this example, files loaded later take precedence, so a lower priority will have greater effect)


See org.openmrs.ui.framework.page.GlobalResourceIncluder. (Added in 2.1)

See webModuleApplicationContext.xml in the referenceapplication module for a live example.

Styling

How do I use Compass and Sass to write CSS more easily?

See how PIH has incorporated this in the Mirebalais module. Look for "compass" in omod/pom.xml (TODO point this to a reference application usage).

Once you have set up the pom files as in (that module) then css files will be generated automatically as part of a maven build, during the "process-resources" phase. If you're going to be making iterative changes to your module's scss files, you can do:

mvn process-resources -Pwatch-sass

XSS Prevention

What is XSS?

XSS is a common web application vulnerability that allows an attacker to execute arbitrary javascript in the context of users who visit a vulnerable web page. XSS can lead to website defacement, compromised administrative accounts, and even client browser exploitation. For more information, check out OWASP’s XSS page: https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)

XSS Case Studies

Case Study #1: <script> tag injection

Consider the following code snippet:

 <div class="person-address">
     <% lines.each { %>
        ${ it }<br/>
     <% } %>
 </div>

This snippet comes from a previous version of formatAddress.gsp in the coreapps module. The XSS vulnerability here is triggered when one of the lines in person-address is something like: </script>alert('XSS');</script>. In this case, the gsp script will generate the following HTML:

<div class="person-address">
	person address line 1 <br/>
	person address line 2 <br/>
	<script>alert('XSS');</script>
</div>

When a client next visits a page that includes formatAddress.gsp, the attacker's javascript will execute in the client's browser. The javascript alert is only a placeholder. An attacker would be able to insert any kind of malicious javascript if he/she is able to edit a patient address.

Case Study #2: HTML attribute injection

Another common xss injection point is in HTML attributes. Consider the following code snippet:

<ul id="sessionLocation" class="select">
	<% locations.sort { ui.format(it) }.each { %>
    	<li id="${it.name}" tabindex="0"  value="${it.id}">${ui.encodeHtmlContent(ui.format(it))}</li>
	<% } %>
</ul>

This snippet comes from login.gsp in the referenceapplication module. Here, if the attacker attempts to inject <script> tags, the attack will fail because the user-supplied input is html-encoded. However, the attacker may still use 'onload' or 'onmouseover' html attributes to exploit the XSS vulnerability. For example, if an attacker was able to create a location with the name: " onload="alert('XSS'), then the gsp script would generate the following html:

<ul id="sessionLocation" class="select">
	<li id="location-1" tabindex="0" value="1"> location-1 </li>
	<li id="location-2" tabindex="0" value="2"> location-2 </li>
	<li id="" onload="alert('XSS')" tabindex="0" value="3"> " onload="alert('XSS') </li>
</ul>

When a client next visits the login page, the attacker-injected onload attribute will execute malicious javascript in the client's browser.

Case Study #3: Javascript injection

Sometimes XSS vulnerabilities exist when javascript is dynamically generated by a server-side gsp script. Consider the following code (clipped for succinctness):

<script>
...
...
var patientObj = {
	uuid:"${ it.uuid }",
	name:"${ it.personName ? ui.format(it.personName) : '' }",
	gender:"${ it.gender }",
    age:"${ it.age ?: '' }",
	birthdate:"${ it.birthdate ? dateFormatter.format(it.birthdate) : '' }",
	birthdateEstimated: ${ it.birthdateEstimated },
	identifier:"${ it.patientIdentifier ? it.patientIdentifier.identifier : '' }",
	widgetBirthdate:"${ it.birthdate ? searchWidgetDateFormatter.format(it.birthdate) : '' }"
}
...
...
</script>

This snippet comes from patientSearchWidget.gsp in the coreapps module. An attacker who is able to edit a patient's name, could inject the following string: " \n}\n alert('XSS'); \n/*. This would result in the gsp script generating the following javascript:

<script>
...
...
var patientObj = {
	uuid:"55555555-5555-5555-5555-555555555555",
	name:""
}
alert('XSS');
/*",
	gender:"M",
    age:"25",
	birthdate:"01-01-1980",
	birthdateEstimated: False,
	identifier:"id",
	widgetBirthdate:"01-01-1980"
}
...
...
</script>

On most browsers, the attacker-supplied alert('XSS') code would be executed when a client visits a page the incorporates the patientSearchWidget.

UI Framework Filtering Functions

The UI Framework includes a number of functions to filter untrusted user input and prevent XSS.

  • String encodeHtmlContent(String input) - This function allows untrusted data to be safely displayed in HTML. This is mainly achieved by converting < and > symbols to &#60; and &#62; respectively. This kind of filtering will prevent XSS similar to case study #1
  • String encodeHtmlAttribute(String input) - This function allows untrusted data to be safely displayed in HTML attributes. This is mainly achieved by converting " and ' symbols to &#34; and &#39; respectively. This kind of filtering will prevent XSS similar to case study #2
  • String encodeJavaScript(String input) - This function allows untrusted data to be safely displayed in dynamically generated JavaScript. This is mainly achieved by using javascript backslash-escaping. This kind of filtering will prevent XSS similar to case study #3

It is important to note that the three case studies presented above do not represent all classes of XSS vulnerabilities. XSS is a notoriously difficult bug to prevent. Front-end developers should be imaginative and thorough when testing for it.