Creating a MVC-based Module

Contents

Introduction

OpenMRS uses the Model-View-Controller (MVC) architecture to manage the interaction between the user and the system. According to this design pattern, the data (model) is separated from the user interface (view), so that these two aspects of the system can be dealt with independently. Changes to the data will not affect the user interface and vice versa. In MVC parlance, the business logic, which controls the data handling, is separated from the data presentation. The interaction between the model and view is managed by the controller.

In the following diagram the solid lines indicate a direct association, and the dashed line indicate an indirect association

Image:ModelViewControllerDiagram.png

According the MVC design pattern, the model contains the domain-specific representation of the information on which the application operates. In OpenMRS a database is used to store raw data and domain logic is used to manipulate and give meaning to the raw data. In the MVC design, the model encapsulates the data access layer. The view is the user-interface (UI) element, responsible for rendering the model into a form suitable for interaction. The controller processes and responds to events and user actions, which may involve changes to the model and/or the view.

JButton: A Java MVC Example

Java's Swing components are good examples of the MVC design. Consider for example a simple button that appears in an application. The button's state (whether it is clicked, whether it is enabled, etc.) is its model. Its appearance is its view and what it does (what actions it controls) is its controller.

A button's role is to appear on the visual interface waiting to be clicked. When it is clicked, its appearance changes in some way to allow the user to perceive the change. In the MVC both appearances must be represented in the view. When the button is clicked, the controller processes the click event and tells the model to change the button's internal state. In Swing, when the model's internal state changes, that generates an event that is picked up and communicated to the view, which causes a change in the visible representation.

One advantage of separating these three aspects is that it becomes possible to change any one of the three without having to change the others. For example, a user interface can be given a different look and feel by changing the view, without changing either the controller or the model.

Touchscreen Module: Creating an Extension on the Administration Page

An extension is a snippet of code that can be inserted into an OpenMRS interface at pre-determined points called extension points. In this example, we use the Touchscreen module as an example of using an existing extension point to add content to the OpenMRS Administration page.

Download and install the Touchscreen Module

Download and install the Touchscreen module by following the instructions given here. When this module is successfully loaded into OpenMRS, it will appear as a menu item on the Home>Administration Page. It will present a link labeled Find a patient that when clicked will bring the user to the Home>Find/Create Patient screen (http://localhost:8080/openmrs/findPatient.htm).

To understand this example, you should load and run the Touchscreen module. Go to the OpenMRS>Home>Administration page. You will see that the page contains a list of lists. Each of the lists is composed of a title and a collection of one or more hyperlinks. The Touchscreen module puts the title Touchscreen Interface Module and the hyperlink Find a patient on the Administration page. We will now see how it does this.

The View: Identifying an Existing Extension Point

The Touchscreen module adds content to an existing OpenMRS extension point on the Home>Administration page. Here is what the extension point looks like in the file openmrs/web/WEB-INF/view/admin/index.jsp:

<openmrs:extensionPoint pointId="org.openmrs.admin.list" type="html">
  <openmrs:hasPrivilege privilege="${extension.requiredPrivilege}">

    <div class="adminMenuList">
       <h4><spring:message code="${extension.title}"/></h4>
	 <ul id="menu">
	   <c:forEach items="${extension.links}" var="link">
	     <li><a href="${pageContext.request.contextPath}/${link.key}"><spring:message code="${link.value}"/></a></li>
	   </c:forEach>
	 </ul>
     </div>
 
  </openmrs:hasPrivilege>
</openmrs:extensionPoint>

The code shown here is mostly Java Server Pages Standard Tag Library (JSTL) code. The tags that begin with openmrs: are application-specific tags, whereas those that begin with c: are JSTL core tags. As indicated in the openmrs:ExtensionPoint tag, this is an HTML extension point and its ID is org.openmrs.admin.list. Note that it requires a certain privilege level (which we won't go into here). Contained within the <div> tag is the HTML code that will be generated for this page. Basically, there will be an unordered list of HTML links. Note that the code here controls the presentation of the information. This code represents the view component of the MVC design pattern. The code retrieves content from the model and displays it as HTML.

The MetaDATA

Rather than hard coding certain data, OpenMRS defines certain MVC data in metadata files. This allows an application to be extended and scaled within the need to rewrite the code. For example, this is how an interface can be internationalized and localized, by defining metadata that translates interface elements into different languages.

Linking a Controller to a View. One important piece of OpenMRS metadata links a view with its controller. In order to link Touchscreen Java code (controller) to an extension point, it is necessary to add a extension tag to the module's metadata/config.xml file:

<extension>
  <point>org.openmrs.admin.list</point>
  <class>@MODULE_PACKAGE@.extension.html.AdminList</class>
</extension>

This tag identifies the extension point (ID=org.openmrs.admin.list) and the Java class that will serve as its controller (extension.html.AdminList.java).

Defining Messages. Another form of MetaDATA used in OpenMRS are the many messages, menu titles, and other string data used in the interface. These are defined in various property files. For example, both the Touchscreen menu item title (Touchscreen Interface Module) and its hyperlink (Find a patient) are defined as follows in the metadata/messages.properties file:

touchscreen.title=Touchscreen Interface Module
touchscreen.findPatient.title=Find a patient

The OpenMRS/touchscreen/metadata directory also contains property files for the Spanish (messages_es.properties) and French (messages_fr.properties) translations of certain elements.

The Controller: An Extension Subclass

Looking again at the <extension> tag in metadata/config.xml, you can see that the <point> attribute in this tag matches the ID of the extension point on the Administration page. The <class> attribute provides the name of the Java class that will serve as the controller for this extension point. In this case, the class is openmrs/module/touchscreen/extension/html/AdminList.java and here is how it is coded:

 package org.openmrs.module.touchscreen.extension.html;

 import java.util.LinkedHashMap;
 import java.util.Map;
 import org.openmrs.module.Extension;
 import org.openmrs.module.web.extension.AdministrationSectionExt;

 public class AdminList extends AdministrationSectionExt {
	public Extension.MEDIA_TYPE getMediaType() {
		return Extension.MEDIA_TYPE.html;

	}
	public String getTitle() {
		return "touchscreen.title";
	}
	public Map<String, String> getLinks() {
		Map<String, String> map = new LinkedHashMap<String, String>();
		map.put("module/touchscreen/findPatient.form", "touchscreen.findPatient.title");
		return map;
	} 
 }

The Extension class and its subclasses provide methods that can be used to control the content that is delivered to the extension point. Note that the AdminList is a subclass of AdministrationSectionExt (which is a direct subclass of Extension. The AdministrationSectionExt part of the Extension hierarchy defines methods that are useful for adding content to the OpenMRS>Administration page. Other subclasses of Extension would be designed for adding content to other sections of OpenMRS.

The methods defined in AdminList are designed to insert a menu item and its corresponding hyperlinks on the OpenMRS>Administration page. The getMediaType() method identifies what type of media (HTML, Image, etc) will be placed at the extension point. In this case, we will insert HTML code at the extension point. The getTitle() method retrieves the Touchscreen menu item title (from the metadata/messages.properties file). The getLinks() method returns a Map of key/value pairs that can be used by the view to create a list of HTML hyperlink tags.

The Model: A Map of Hyperlink Data

In this case, the getLinks() method creates a Map of key/value pairs that can be used by the view to create a list of HTML hyperlink tags. The map(key,value) statement:

 map.put("module/touchscreen/configuration.form", "touchscreen.configuration.title");

creates a mapping from a URL (module/touchscreen/configuration.form) to the anchor string (touchscreen.configuration.title). This data structure constitutes the model component of the MVC architecture for this example. This is an example of a static model. It is created by the controller from MetaDATA and simply provides certain data that is displayed by the view. It is not changed by the user's or the program's actions.

The following JSP code found in the view retrieves the model data from the map and constructs an unordered HTML list of hyperlinks tags:

  <c:forEach items="${extension.links}" var="link">
     <li><a href="${pageContext.request.contextPath}/${link.key}"><spring:messagecode="${link.value}"/></a></li>
  </c:forEach>

Note the use of the JSP notation, ${link.key} and ${link.value}, to retrieve the data from the map.

The Spring MVC Framework: Using The Touchscreen Menu Item

As we have seen, the Touchscreen module contains a single hyperlink (Find a patient). If you play with the Touchscreen module, you will see that when you click on the hyperlink, it will bring up the page (module/touchscreen/findPatient.form), which presents a touch screen interface (an on-screen keypad, big buttons, etc.). How does this happen?

This particular part of the Touchscreen module is handled entirely by a Java application framework known as Spring. We will not go into detail about Spring, but here is a link to a tutorial if you want further details. The Spring framework allows you to define MVC components declaratively--that is, as data. The framework then handles much of the code generation. In this case, the action taken when you click on the Find a patient is defined as MetaDATA in metadata/moduleApplicationContext.xml. Here's the code:

 <beans>
    <bean id="touchscreenUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
      <property name="mappings">
        <props>
	  <prop key="module/touchscreen/findPatient.form">findPatientForm</prop>
	</props>
      </property>
    </bean>
	
 <!-- Controllers -->
		
    <bean id="findPatientForm" class="org.openmrs.module.touchscreen.web.controller.FindPatientFormController">
	<property name="commandName"><value>searchString</value></property>
	<property name="formView"><value>/module/touchscreen/findPatient</value></property>
	<property name="successView"><value>/findPatient.htm</value></property>
    </bean>
 </beans>

The first bean defined here creates a mapping between the URL and the name of the controller (findPatientFormController). The second bean maps the controller name to an actual Java class, org.openmrs.module.touchscreen.web.controller.FindPatientFormController.java. We will look at the code in this class below. The second bean also defines a number of properties. These define the actions that the controller will take depending on what the user does in the view (the interface). For example, the successView property defines what should be done when the user clicks on the "OK" or "Submit" button. In this case, the controller will transfer control to the findPatient.htm, which is the same page that one reaches from OpenMRS>Home>Find/Create Patient.

Here's what the FindPatientFormController class looks like (with some of the content hidden):

package org.openmrs.module.touchscreen.web.controller;

public class FindPatientFormController extends SimpleFormController {
		
    protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object obj, 
                                       BindException errors) throws Exception {
        String view = getFormView();		
	return new ModelAndView(new RedirectView(view));
    }

    protected Object formBackingObject(HttpServletRequest request) throws ServletException {
        return "";
    }   
}

This particular form is a SimpleFormController. The formBackingObject() method is invoked when a form is first displayed. It loads the form into the view. The onSubmit() method is invoked when the form's submit button is clicked. (Its operation are beyond the scope of this discussion.)