Overview
Many FHIR include complex nesting. For example the Item component of Questionnaire can either be a single item or an array of more items, which can themselves also be arrays of more items, with even more arrays below. Arrays all the way down! A previous post (FHIR + Blazor + Recursion = Rendering a Questionnaire ) showed a method to render objects with complex nesting: using Dynamic Fragments. This article shows an alternative method: using self-referential components.
Why use components?
Components will allow easier reuse than Dynamic Fragment rendered pages. Imagine a case where we want to both create new and allow updates of complex nested children. If we use components, we can easily adapt a component for both EDIT and CREATE. More reuse means less code!
How it’ll work
We’ll create a parent component. The parent component will render the child component.
Parent Component;
<span>@Parent.Title</span>
<p>@Parent.Description</p>
@{
int childNum=1;
}
@foreach(var child in Parent.children){
Child #@childNum
ChildComponent child=child />
childNum++;
}
AND HERE’s THE MAGIC:
Child Component:
<span>@Child.Title</span>
@foreach(var child in Child.children){
<ChildComponent child=child />
}
The child component will render additional child components.
FHIR Specific Example
Check out the code below from (FHIRBlaze)
Parent Component (QuestionnaireComponent)
<EditForm Model=Questionnaire OnValidSubmit=Submit>
<label class="col-sm-2 form-label">Title:</label>
<InputText @bind-Value=Questionnaire.Title />
@foreach(var item in Questionnaire.Item)
{
<div class="border border-primary rounded-left rounded-right p-2">
<div class="row">
<div class="col-sm-12">@GetHeader(item)</div>
</div>
<div class="row">
<div class="col-sm-11">
<ItemDisplay ItemComponent=item/>
</div>
<div class="col-sm-1">
<button type="button" class="btn btn-primary" @onclick="()=>RemoveItem(item)">
<span class="oi oi-trash" />
</button>
</div>
</div>
</div>
}
<div>
<ItemTypeComponent ItemSelected="AddItem" />
</div>
<br/>
<button type="submit">Submit</button>
</EditForm>
Note the ItemDisplay component.
Child Component ( ItemDisplay )
<div class="card-body">
<label class="sr-only" >@GetTitleText(ItemComponent)</label>
<input type='text' required class='form-control' id='question' placeholder=@GetTitleText(ItemComponent) @bind-value='ItemComponent.Text' >
<label class="sr-only">LinkId:</label>
<input type='text' required class='form-control' placeholder='linkId' @bind-value='ItemComponent.LinkId'>
@switch (ItemComponent.Type)
{
case Questionnaire.QuestionnaireItemType.Group:
foreach(var groupitem in ItemComponent.Item)
{
<div class="border border-primary rounded-left rounded-right p-2">
<div class="row">
<div class="col-sm">@GetHeader(groupitem)</div>
</div>
<div class="row">
<div class="col-sm-11">
<ItemDisplay ItemComponent=groupitem/>
</div>
<div class="col-sm">
<button type="button" class="btn btn-primary" @onclick="()=>ItemComponent.Item.Remove(groupitem)">
<span class="oi oi-trash" />
</button>
</div>
</div>
</div>
}
break;
case Questionnaire.QuestionnaireItemType.Choice:
int ansnum= 1;
@foreach (var opt in ItemComponent.AnswerOption)
{
<div class="row">
<form class="form-inline">
<div class="col-sm-1">#@ansnum</div>
<div class="col-sm-10"><AnswerCoding Coding=(Coding)opt.Value /></div>
<div class="col-sm-1"><button type="button" class="btn btn-primary" @onclick="()=>ItemComponent.AnswerOption.Remove(opt)"><span class="oi oi-trash" /></button></div>
</form>
</div>
ansnum++;
}
<button type="button" @onclick=AddAnswer >Add Choice</button>
break;
default:
break;
}
@if (ItemComponent.Type.Equals(Questionnaire.QuestionnaireItemType.Group))
{
<div>
<ItemTypeComponent ItemSelected="AddItem" />
</div>
}
</div>
Line 58 is the key line. This component renders itself!
Caveats
With this you should be able to quickly render nested Item after nested Item. But there are some caveats you should know.
#1: Memory Consumption
As you render each nesting- all those objects are loaded into memory. If a FHIR has an unknown number of children- each of which could have its own children, you could potentially consume large amounts of memory. This is a particular problem because you could be rendering on a phone.
Suggested Mitigation: Consider rendering to a max depth and a max number of children. Render “see more” type links to allow the user to see remaining children
#2: Display Clipping
The standard approach is to indent children. But if you have 5 levels of nesting and the app is rendered on mobile than your 5th level children may show up in a single character column. If you render 100 levels of nested children, your final level may not render at all.
Suggested Mitigation: Consider an alternative to displaying all children. For example- consider using collapsible sections to show children.
#3: Labeling Problems
If you’re allowing Edit of a component with complex nesting it may be difficult for a user to remember which level of children, they are in. For example, imagine the following are rendered as cards: Parent 1: Child 1: Child 2: Child 3: Child 4: Child 5.
Suggested Mitigation: Consider using borders and labeling to help users determine which child is currently selected