Create a Fragment

Now we are going to write our own fragment, listing all encounters in the database whose date is today. Writing a fragment follows the standard MVC pattern. A fragment has a FragmentController, which does business logic, and populates a FragmentModel. The framework then renders a FragmentView, which has access to the model variables. (A fragment is not actually required to have both a controller and a view, though it must have at least one. For example a decorator fragment is unlikely to need a controller. A fragment that does server-side logging would not need its own view.)

Fragment controllers follow specific naming conventions (a lesson learned from Ruby on Rails). The controller for the "encountersToday" fragment must be the controller() method of the class org.openmrs.module.yourmoduleid.fragment.controller.EncountersTodayFragmentController. (Basically take your fragment's name, capitalize the first letter, and put FragmentController after it.)

So let's start out by creating the class mentioned.

package org.openmrs.module.yourmoduleid.fragment.controller;

import java.util.Calendar;
import java.util.Date;

import org.openmrs.api.EncounterService;
import org.openmrs.ui.framework.annotation.SpringBean;
import org.openmrs.ui.framework.fragment.FragmentModel;

/**
 * Controller for a fragment that shows the encounters that have taken place today
 */
public class EncountersTodayFragmentController {

	public void controller(FragmentModel model, @SpringBean("encounterService") EncounterService service) {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date startOfDay = cal.getTime();

		cal.add(Calendar.DAY_OF_MONTH, 1);
		cal.add(Calendar.MILLISECOND, -1);
		Date endOfDay = cal.getTime();

		model.addAttribute("encounters", service.getEncounters(null, null, startOfDay, endOfDay, null, null, null, null, null, false));
	}

}

Controller methods support flexible parameter types, and type conversions, like in Spring MVC. (See Flexible Method Signatures for UI Framework Controller and Action Methods for complete documentation.) We also support the @SpringBean annotation that will cause the indicated Spring bean to be injected into the method call. (If you specify a value to the annotation, the bean is looked up by id, if not, then it's looked up by type.)

Like in Spring MVC, the fact that our controller method returns null means that we want to render the default view for this fragment. (Returning a non-null String would specify a different view name.) While a view could even live in a different module than the controller, and might use a non-groovy view provider, in most cases your fragment's view should be at fragmentName.gsp in the conventional location in the same module as its controller. So, create yourmodule/omod/src/main/webapp/fragments/encountersToday.gsp:

<table>
    <tr>
        <th>${ ui.message("Encounter.type") }</th>
        <th>${ ui.message("Encounter.datetime") }</th>
        <th>${ ui.message("Encounter.location") }</th>
        <th>${ ui.message("Encounter.provider") }</th>
    </tr>
    <% if (encounters) { %>
        <% encounters.each { %>
            <tr>
                <td>${ ui.format(it.encounterType) }</td>
                <td>${ ui.format(it.encounterDatetime) }</td>
                <td>${ ui.format(it.location) }</td>
                <td>${ ui.format(it.provider) }</td>
            </tr>
        <% } %>
    <% } else { %>
        <tr>
            <td colspan="4">${ ui.message("general.none") }</td>
        </tr>
    <% } %>
</table>

We've introduced two more ui methods

  1. ui.message is used to display a message from the messages.properties files:

    String message(String code, String... args)
    
  2. ui.format is used to generate a standard display for an object

Notice that in Groovy, an empty collection is false when treated as a boolean. So we can simply say "if (encounters) ...".

Also note that we're using the "each" method on the encounters collection, and passing it a closure to run on each element. We could also use a for loop, but the each/closure syntax is cleaner and more legible once you get used to it.

In order to see this fragment, we need to include it in our helloWorld page:

${ ui.includeFragment("uiframework", "helloUser") }
${ ui.includeFragment("yourmoduleid", "encountersToday") }

If you refresh your http://localhost:8080/openmrs/yourmoduleid/helloWorld.page, you will probably not see any encounters, because your development database is unlikely to have any encounters today. To show off another feature of the UI Framework, let's temporarily change the date that our fragment uses. Go ahead and do a sql query like "select max(encounter_datetime) from encounter" to find a date you have an encounter on. Now, change the code in EncountersTodayFragmentController to manually set a date, maybe by adding something like the following:

try {
    startOfDay = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse("2011-02-16 00:00:00.000");
    endOfDay = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse("2011-02-16 23:59:59.999");
} catch (Exception ex) { }

Once you've saved your changes, refresh http://localhost:8080/openmrs/yourmoduleid/helloWorld.page again, and notice that since you are in development mode, changes you make to fragment controllers are immediately recompiled without any action on your part.

If this feature doesn't seem to be working, make sure:

  • Make sure you've set up your run configuration to support development mode, as described here: Using the UI Framework in Your Module
  • in IntelliJ you actually have to do Build -> Compile on your class
  • in Eclipse, your project is set to buid automatically