Background
Prior to OpenMRS version 1.8, the search widgets were written with dojo which involved writing a separate javascript file where you would have to extended the parent OpenmrsSearch for each Openmrs Object if it were to be searchable in the webapp. The widgets would fetch all hits in one ajax query which would take a while to return all the hits in case there are many making the widgets slow. In 1.8, this was changed by introducing a single and more generic search widget written with jquery, the widget is faster from a user's stand point in that it fetches just the exact number of results to display on the first page and then continue to query the server for the rest in the background while updating the table until all results are returned. The reason behind this is that it should take way less time to display the first N results to display on the first page rather waiting for all the matching hits to be returned in one call in case there are many. The objective is to increase the perceived speed from a user's point of view since they get back results for the first page in a nick of time even if it isn't all.
How to include a search widget for a domain object in a jsp
This can be relatively simple if the domain objects to search already have the required methods in the API. Currently the required methods have been added for Concepts, Encounters, Patients, Users, Locations, Providers, Drugs and ConceptReferenceTerms. Let's assume you wish to add an encounter search widget to your jsp, add the snippet below to your jsp (you can leave out the javascript and css files that you already have in the parent jsp).
Code Block |
---|
<%-- (OPTIONAL) This should be the target DWR service that processes the http requests for results in case you fetch them via DWR --%> <openmrs:htmlInclude file="/dwr/interface/DWREncounterService.js"/> <openmrs:htmlInclude file="/scripts/jquery/dataTables/css/dataTables_jui.css"/> <openmrs:htmlInclude file="/scripts/jquery/dataTables/js/jquery.dataTables.min.js"/> <openmrs:htmlInclude <%-- (OPTIONAL) Include this to apply css to improve the look and feel of the widget if the containing page doesn't include it --%> <openmrs:htmlInclude file="/scripts/jquery-ui/js/openmrsSearch.js" /> <script type="text/javascript"> var lastSearch; $j(document).ready(function() { new OpenmrsSearch("findEncounter", true, doEncounterSearch, doSelectionHandler, [ {fieldName:"personName", header:"Patient Name"}, {fieldName:"encounterType", header:"Encounter Type"}, {fieldName:"formName", header:"Encounter Form"}, {fieldName:"providerName", header:"Encounter Provider"}, {fieldName:"location", header:"Encounter LocationdataTables/css/dataTables_jui.css"/> <%-- This is required if the containing page doesn't include it --%> <openmrs:htmlInclude file="/scripts/jquery/dataTables/js/jquery.dataTables.min.js"/> <%-- REQUIRED --%> <openmrs:htmlInclude file="/scripts/jquery-ui/js/openmrsSearch.js" /> <script type="text/javascript"> var lastSearch; $j(document).ready(function() { new OpenmrsSearch("findEncounter", true, doEncounterSearch, doSelectionHandler, [ {fieldName:"personName", header:"Patient Name"}, {fieldName:"encounterDateStringencounterType", header:"Encounter DateType"}, ] {fieldName:"formName", header:"Encounter { searchLabel: '<spring:message code="Encounter.search" javaScriptEscape="true"/>', Form"}, {fieldName:"providerName", header:"Encounter Provider"}, {fieldName:"location", header:"Encounter Location"}, {fieldName:"encounterDateString", header:"Encounter Date"} ], searchPlaceholder:'<spring:message code="Encounter.search.placeholder" javaScriptEscape="true"/>'{ }); }); searchLabel: '<spring:message code="Encounter.search" javaScriptEscape="true"/>', //The action to take when the user selects an item from the hits in the widget function doSelectionHandler(index, data) { document.location = "encounter.form?encounterId=" + data.encounterId + "&phrase=" + lastSearch; } //Contains the logic that fetches the results from the server,, should return a map of the form <String, Object> function doEncounterSearch(text, resultHandler, getMatchCount, opts searchPlaceholder:'<spring:message code="Encounter.search.placeholder" javaScriptEscape="true"/>' }); }); //The action to take when the user selects an item from the hits in the widget function doSelectionHandler(index, data) { lastSearchdocument.location = text; DWREncounterService.findCountAndEncounters(text, opts.includeVoided, opts.start, opts.length, getMatchCount, resultHandler)"encounter.form?encounterId=" + data.encounterId + "&phrase=" + lastSearch; } < //script> |
The first four html includes are required because they contain the necessary scripts used by the widgets and the css file for improved styling via datatables' support for jquery-ui themes so that the widgets match the active theme on the page.
You call to the server needs to return a map of the results with the following key names:
...
Contains the logic that fetches the results from the server,, should return a map of the form <String, Object>
function doEncounterSearch(text, resultHandler, getMatchCount, opts) {
lastSearch = text;
DWREncounterService.findCountAndEncounters(text, opts.includeVoided, opts.start, opts.length, getMatchCount, resultHandler);
}
</script>
|
The first four html includes are required because they contain the necessary scripts used by the widgets and the css file for improved styling via datatables' support for jquery-ui themes so that the widgets match the active theme on the page.
You call to the server needs to return a map of the results with the following key names:
- count: Maps to the count of the total expected number of hits, this is only required for the first call, therefore in your code on the server, it is recommended to check when the start index is 0, you include the count
...
- notification: Maps to an informative or error message you wish to display for the user about the results in the resultsUI
- searchAgain: Maps to the phrase against which to search again, when this key is included with a value, the search widget machinery will discard the current results and re run the search against the new specified phrase, a use case for this is if there are no matches to what the user originally entered and you wish to send them back results based on a shorter phrase
...
If you don't wish to support paging in your widgets and want to have all results returned in one request just set the value of the length argument to null when calling your server side code in the search handler.
Widget
...
properties
- minLength: int (default: 1) The minimum number of characters required to trigger a search, this is ignored if 'doSearchWhenEmpty' is set to true
- searchLabel: string (default: omsgs.searchLabel) The text to be used as the label for the search textbox
- includeVoidedLabel: string (default: omsgs.includeVoided) The text to be used as the label for the 'includeVoided' checkbox
- showIncludeVoided: bool (default: false) - Specifies whether the 'includeVoided' checkbox and label should be displayed
- includeVerboseLabel: string (default: omsgs.searchLabelincludeVerbose) The text to be used as the label for the search textbox
- includeVoidedLabel: string (default: omsgs.includeVoided) The text to be used as the label for the 'includeVoided' checkbox
- showIncludeVoided: bool (default: false) - Specifies whether the 'includeVoided' checkbox and label should be displayed
- includeVerboseLabel: string (default: omsgs.includeVerbose) The text to be used as the label for the 'includeVerbose' checkbox
- showIncludeVerbose: bool (default: false) Specifies whether the 'includeVerbose' checkbox and label should be displayed
- searchHandler: function(text, resultHandler, options) (default:null) The function to be called to fetch search results from the server
- resultsHandler: function(results) (default:null) The function to be called
- selectionHandler: function(index, rowData)
- fieldsAndHeaders: Array of fieldNames and column header maps
- displayLength: int (default: 10)
- columnWidths: an array of column widths, the length of the array should be equal to the number of columns, should be of the same length as the number of columns
- columnRenderers: array of fnRender(s) for each column, should be of the same length as the number of columns, set a value of null for columns with no renderers
- columnVisibility: array of bVisible values for each column, true/false are the only possible values in the array and should be of the same length as the number of columns
- initialData: (default: null) The initial data to be displayed e.g if it is an encounter search, it should be an encounter list
- searchPhrase: string The phrase to be set in the search box so that a search is triggered on page load to display initial items
- doSearchWhenEmpty: string (default:false): If it is set to true, it lists all items initially and filters them with the given search phrase.
- verboseHandler: function to be called to return the text to display as verbose output'includeVerbose' checkbox
- showIncludeVerbose: bool (default: false) Specifies whether the 'includeVerbose' checkbox and label should be displayed
- searchHandler: function(text, resultHandler, options) (default:null) The function to be called to fetch search results from the server
- resultsHandler: function(results) (default:null) The function to be called
- selectionHandler: function(index, rowData)
- fieldsAndHeaders: Array of fieldNames and column header maps
- displayLength: int (default: 10)
- columnWidths: an array of column widths, the length of the array should be equal to the number of columns, should be of the same length as the number of columns
- columnRenderers: array of fnRender(s) for each column, should be of the same length as the number of columns, set a value of null for columns with no renderers
- columnVisibility: array of bVisible values for each column, true/false are the only possible values in the array and should be of the same length as the number of columns
- initialData: (default: null) The initial data to be displayed e.g if it is an encounter search, it should be an encounter list
- searchPhrase: string The phrase to be set in the search box so that a search is triggered on page load to display initial items
- doSearchWhenEmpty: string (default:false): If it is set to true, it lists all items initially and filters them with the given search phrase.
- verboseHandler: function to be called to return the text to display as verbose output
- attributes: Array of name and column headers for attributes columns to display in the list of results. For this to work, the returned items should have an 'attributes' property which should be a map of attribute names and their values. See PersonListItem.java and the patient index page for an example
- showSearchButton: bool (default: false) - Specifies whether the Search button for immediate search should be displayed
- lastSearchParams: Object with properties lastSearchText, includeVoided and includeVerbose, to preserve data with browser back button