Modules can extend the presentation layer through extension points. In an ideal world, modules would be able to modify the presentation layer with the same Aspect Oriented Programming (AOP) technique used for the API. However, the main presentation layer is currently via the webapp and html. There isn't currently a way to inject code in a general way into a jsp or html file.
Instead, Extension Points
must be placed throughout the webapp to provide "hooks". Modules then create Extension
s that hook into those points. A module can hook into any number of points any number of times. Each Extension must be defined in the config.xml file.
Extensions must extend the abstract class org.openmrs.module.web.Extension
. If the getOverrideContent(String)
returns a non-null, the returned value is inserted as the content for that extension.
For the webapp, the getMediaType()
should always return Extension.MEDIA_TYPE.html
or null. If generic display code is being returned, the media type can be null. The webapp will look for defined extension points that are either defined to be any (null) or html. In the future, when we have more than one type of presentation layer, we can have multiple MEDIA_TYPE options. (For now, the only one is html).
Example: Adding an Extension to a module (adding section to the admin screen)
Tags inserted into the config.xml file:
<extension> <point>org.openmrs.admin.list</point> <class>org.openmrs.module.htmlformentry.extension.html.AdminList</class> </extension>
The org.openmrs.admin.list
Extension Point is defined in the /openmrs/admin/index.jsp file. This is one of several unique extension points that require certain methods to be implemented. The admin.list point requires a getTitle() method and a getLinks() method as defined in org.openmrs.module.web.extension.AdministrationSectionExt
. (To ensure validity, your Extension can simply extend this abstract class. To create an api/jar/library that you can include in your module, use the package-web-src option in the core ant build script. A web-openmrs-api-*.jar file will be created in the /dist folder).
The FormEntryAdminExt class is similar to:
package org.openmrs.module.formentry.extension.html; import java.util.Map; import java.util.TreeMap; import org.openmrs.module.Extension; import org.openmrs.module.web.extension.AdministrationSectionExt; import org.openmrs.util.InsertedOrderComparator; public class FormEntryAdminExt extends AdministrationSectionExt { public Extension.MEDIA_TYPE getMediaType() { return Extension.MEDIA_TYPE.html; } public String getTitle() { return "formentry.title"; } public Map getLinks() { Map map = new TreeMap(new InsertedOrderComparator()); map.put("module/formentry/xsnUpload.form", "formentry.xsn.title"); map.put("module/formentry/formEntryQueue.list", "formentry.FormEntryQueue.manage"); map.put("module/formentry/formEntryInfo.htm", "formentry.info"); return map; } }
Example: Adding an Extension Point
At the very core, an Extension Point is simply a unique text string that defines a location into which information may be inserted. An Extension Point can be in either the core OpenMRS presentation layer or in a module's presentation layer.
The simplest Extension Point simply requires it's extensions to use the getOverrideContent(String) method:<openmrs:extensionPoint pointId="org.openmrs.login" />
(In the OpenMRS webapp, the openmrs:extensionPoint taglib defaults to type="html")
A more complicated Extension Point might want more than one method implemented for complicated information:
<openmrs:extensionPoint pointId="org.openmrs.admin.users.localHeader" type="html"> <c:forEach items="${extension.links}" var="link"> <li <c:if test="${fn:endsWith(pageContext.request.requestURI, link.key)}">class="active"</c:if> > <a href="${pageContext.request.contextPath}/${link.key}"><spring:message code="${link.value}"/></a> </li> </c:forEach> </openmrs:extensionPoint>
The extension points in the local headers of each admin section expect the getLinks() method to be implemented in the Extensions. This is done to ensure the proper layout on the admin screen. Of course, the Extension can be obstinate and use the getOverrideContent(String) method to put whatever it wants in there.
Getting New Extension Points into Core OpenMRS
If the page you want to add something to does not have an extensionPoint on it, the process is simple:
- Create a new ticket specifying the page the extPoint will be on
- Add the point to your local copy and test it out
- Create a patch and attach that patch diff to the ticket
- Wait for a developer to review your patch for consistency checks
- Apply the patch to trunk and the latest branches/1.x.x if desired
- Close the ticket and reference the changeset
Example: ticket:1979
Where are there extension points currently?
The most sure and up-to-date way to find extension points is to look on the page that you want to extend. Look through the jsp files in the source code.
Page |
Extension Point Id |
Parameters |
Works With |
Req Class |
Description |
---|---|---|---|---|---|
/patientDashboard |
org.openmrs.patientDashboard.afterLastEncounter |
patientId |
1.3+ |
|
Located in patient header after the "last encounter" text |
/WEB-INF/template/gutter (menu bar) |
org.openmrs.gutter.tools |
|
1.3+ |
|
Uses getRequiredPrivilege/getUrl/getLabel methods |
encounters/encounterDisplay |
org.openmrs.encounters.encounterListTop |
encounterId |
1.6.2+ |
|
At top of page. Uses getTitle and getPortletUrl |
admin/observations/obsForm |
org.openmrs.admin.observations.obsFormBottom |
obsId |
1.6.2+ |
|
At very bottom of page before footer. Uses getTitle and getPortletUrl |
admin/encounters/encounterForm |
org.openmrs.admin.encounters.encounterFormBeforeObs |
encounterId |
1.6.2+ |
|
Between encounter metadata and obs list. Uses getTitle and getPortletUrl |
admin/encounters/encounterForm |
org.openmrs.admin.encounters.encounterFormAddObsMenu |
|
1.3+ |
LinkProviderExtension |
|
admin/index |
org.openmrs.admin.list |
|
1.1+ |
|
Optionally extend: AdministrationSectionExt. Uses getRequiredPrivilege, getTitle, getLinks |
admin/index --> Maintenance section |
org.openmrs.admin.maintenance.localHeader |
|
1.3+ |
|
Uses getRequiredPrivilege, getLinks. Optionally extend: AdministrationSectionExt (and ignore getTitle) |
/WEB-INF/template/footerFull |
org.openmrs.footerFullBeforeStatusBar |
|
1.6.6+, 1.7.4+, 1.8.4+, 1.9.1+, 1.10.0+ |
|
Located at footerFull.jsp page right before the "status bar" (locale options strip, buildDate information, etc) |
/errorhandler |
org.openmrs.errorHandler |
|
1.7.4+, 1.8.4+, 1.9.1+, 1.10.0+ |
|
Located at errorhandler.jsp page after the "(The full error stack trace.." text |
(Add more here as you create/find them) |
|
|
|
|
|
What kinds of abstract classes for extensions are there and what are they for?
- AdministrationSectionExt
- Used on the /admin/index.jsp page and other included local_header.jsp pages
- BoxExt
- Used to add a box to the overview page. See how the extensionPoint is used in the overview portlet
- LinkExt
- LinkProviderExtension
- PatientDashboardTabExt
- PortletExt
- TableRowExt
FAQ
My portlet (loaded via e.g. PatientDashboardTabExt, BoxExt) uses a custom PortletController, but I cannot access my custom data added by the controller.
Try changing your module's processing order in moduleApplicationContext.xml (lower number means earlier processing, default is 99):
<bean id="basicmoduleUrlMapping"> <property name="order"><value>2</value></property> ... </bean>