Background
Prior to OpenMRS version 1.8, the search widgets were written with dojo which involved writing a separate javascript file that extended the parent openmrsSearch for each Openmrs Object if it were to be searchable on w web page, the dojo widgets would fetch all hits in one ajax query which would take sometime 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 expected to be faster from a user's stand point in that it fetches just the exact amount 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.
How to include a search widget for a domain object in a jsp
This can be relatively trivial if the domain object already has the required methods for the search widgets to work as expected. Let's assume you wish to add an encounter search to you jsp, below is what you need to do.
<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 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 Location}, {fieldName:"encounterDateString", header:"Encounter Date} ], { searchLabel: '<spring:message code="Encounter.search" javaScriptEscape="true"/>', 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) { document.location = "encounter.form?encounterId=" + data.encounterId + "&phrase=" + lastSearch; } //Contains the logic that fetches the results from the server 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 styling.
Next thing is to initialize the widget and there 2 ways to do it, the one used above is one of them which uses a helper function that takes in 4 arguments. Below is the other way:
$j(document).ready(function() { $j("#elementId").openmrsSearch({ searchLabel:'<spring:message code="General.search"/>', searchPlaceholder: '<spring:message code="Encounter.search.placeholder" javaScriptEscape="true"/>', searchHandler: doSearchHandler, selectionHandler: doSelectionHandler, fieldsAndHeaders: [ {fieldName:"personName", header:"Patient Name}, {fieldName:"encounterType", header:"Encounter Type}, {fieldName:"formName", header:"Encounter Form}, {fieldName:"providerName", header:"Encounter Provider}, {fieldName:"location", header:"Encounter Location}, {fieldName:"encounterDateString", header:"Encounter Date} ] }); });
The arguments passed to the widgets are to used to customize the widget properties, in the example above, 'searchLabel' specifies the label that appears to the left of the search input box, 'searchPlaceHolder' specifies the text to to display inside the input box as a place holder, 'searchHandler' specifies the javascript function to be called to fetch the hits from the server, and 'fieldsAndHeaders' specifies the properties of the returned objects to display along with their respective column headers See below for the full list of the widget properties. 'SelectionHandler' specifies the function to be called when the user clicks on an item from the widget, in our example above, we open the encounter form and the encounter to edit becomes the selected one.
'SearchHandler' specifies the function that will get called by the script to fetch results from the server, the function gets called at least once once the search is triggered, the first call is expected to return a map with 2 key-value pairs i.e the expected total count of results and results to display on the first page. Typically, you should have methods on the server that get called by this function via ajax and it is important to make them behave as expected. The keys in the returned map are 'count' and 'objectList' and their values should be the total count of expected results and the results to display on the first page respectively. The implementation of the server side logic should call the API methods that return the count and the appropriate search method that supports paging for the given domain object, in this case they would be EncounterService.getCountOfEncounters(String, boolean) and EncounterService.getEncounters(String, Integer, Integer, boolean). In the DAO layer, it's highly recommended that these methods use the same criteria object and the difference should be that the one that gets the count sets a row count projection while the other should return the actual hits, implying that the total number of hits should always match the value returned by the get Count method otherwise the scripts in the widgets will fail with the assumption that there is either more hits to fetch or some will get left out. For an example implementation of the methods in the DAO layer you can look at HibernateEncounterDAO.getCountOfEncounters(String, boolean) and HibernateEncounterDAO.getEncounters(String, Integer, Integer, boolean).
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.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