This article is contributed. See the original author and article here.
Searching a FHIR resource is simple. But what if you want to create a highly reusable search form? In this article we walk through a highly reusable search control.
FHIR Search Scenario
Let’s say we have a list of patients. Our app displays patients in a table (first 3 rows displayed below):
ID | Name | Date of Birth |
0001 | Joe Patient | 01/13/81 |
0002 | Jane Patient | 02/13/80 |
If we have 1000 patients, we might want to allow users to search by ID and Name.
First Attempt
In the past we were tempted to create custom Blazor forms like:
Then on submit of this form we could assemble a search criteria.
public async Task<IList<Patient>> SearchPatient(string givenName, string familyName, string identifier )
{
Bundle bundle;
if (!string.IsNullOrEmpty(identifier))
{
bundle = await _fhirClient.SearchByIdAsync<Patient>(identifier);
if (bundle != null)
return bundle.Entry.Select(p => (Patient)p.Resource).ToList();
}
if (!string.IsNullOrEmpty(familyName))
{
bundle = await _fhirClient.SearchAsync<Patient>(criteria: new[] { $"family:contains={familyName}" });
if (bundle != null)
return bundle.Entry.Select(p => (Patient)p.Resource).ToList();
}
return await GetPatientsAsync();
}
Great! But there’s trouble ahead as soon as we start adding more search criteria.
A Growing Form
Now let’s say we want to support searching by birth date. Our first thought might be to add another field and end up with a form like this.
Our form is quickly growing. And so is our submit code.
public async Task<IList<Patient>> SearchPatient(string givenName, string familyName, string identifier, string birthdate )
{
Bundle bundle;
if (!string.IsNullOrEmpty(identifier))
{
bundle = await _fhirClient.SearchByIdAsync<Patient>(identifier);
if (bundle != null)
return bundle.Entry.Select(p => (Patient)p.Resource).ToList();
}
if (!string.IsNullOrEmpty(familyName))
{
bundle = await _fhirClient.SearchAsync<Patient>(criteria: new[] { $"family:contains={familyName}dateofbirth:contains{birthdate}" });
if (bundle != null)
return bundle.Entry.Select(p => (Patient)p.Resource).ToList();
}
return await GetPatientsAsync();
}
Now what happens if we want to add search by Telephone, address, or by anything in the Patient object? Our form is going to get longer and longer. Pretty soon it’s going to be gigantic! Even worse we can’t reuse this component for searching other FHIR resources. For example, our search for Questionnaires will look 100% different! After all Questionnaires don’t have Family Names, or Birthdates.
A better approach
Instead, we started to use a much simpler form:
The code for this component is similarly simple:
private async Task<IList<Patient>> SearchPatient(IDictionary<string, string> searchParameters)
{
var searchResults = new List<Patient>();
IList<string> filterStrings = new List<string>();
foreach (var parameter in searchParameters)
{
if (!string.IsNullOrEmpty(parameter.Value))
{
filterStrings.Add($"{parameter.Key}:contains={parameter.Value}");
}
}
Bundle bundle = await _fhirService.SearchAsync<Patient>(criteria: filterStrings.ToArray<string>());
if (bundle != null)
{
searchResults = bundle.Entry.Select(p => (Patient)p.Resource).ToList();
}
return searchResults;
}
But the magic is that each Resource that uses the component uses a method like this to build search criteria:
So now using that simple component our users can search for id like this:
Id:0001
Search for Birthdate like this:
birthdate:01/26/82
Or do a combined search:
Id:0001 birthdate:01/26/82
And for Questionnaires we can use the same component and search for a Questionnaire with complex search terms like:
Name:health description:nutrition
See it in Action
View our implementation of this component in FHIR Blaze- our sample FHIR + Blazor app.
Caveats
Ux: Though we have a simplified form- it’s no longer clear what available search fields. How is your user supposed to know that “family:” is how one searches for last name? To resolve this, we recommend visual hints or auto complete. For example, below the text box you could include simple text explaining the most likely search terms as well as linking to a document with all the terms defined.
Performance: Since our search routine uses “contains” our search uses much more Rus than if we included an exact search term. Consider including keyword to allow exact searches.
Spaces: Our search routine does a simple split. This means we can’t search for anything with a space (ex: a family name of “De Luis”. Consider modifying our sample code to more intelligently split search terms.
Cancelling search: Our code assumes submitting the search with no terms is to cancel the search. This isn’t apparent and might be confusing.
Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.
Recent Comments