Way back I wrote the post Blazor Components. This post demonstrated how to create simple components within Blazor. I didn’t progress massively, using Blazor back when that post was written, but I’ve been getting back into Blazor recently.
Let’s look at a powerful feature of Blazor (a little bit like WPF lookless controls) in that we can create Blazor templates using code behind with entry points (placeholders) for the UI to interact with.
One of the obvious uses of such a template might be for lists or grids etc. as these might allow us to add a header and/or footer along with rows which have the same look and feel but with different data.
Let’s look at a very simple example, a ListView template which allows us to output a list of rows where the list (at a top level) can be styled by the code using it and each row and be styled as well. Ofcourse you could style things quite easily if you know the CSS class etc. but here I mean styled as in you can change the UI itself, hence let’s begin by looking at how we could change a ListView to use ordered lists or unordered lists.
Note: I’m going to reuse the weather forecast data supplied when creating a Blazor WebAssembly application in Visual Studio
The component
First off, let’s create a component named ListView, I’m creating it within a folder named Components. The ListView.razor look like this
@typeparam TItem @if (ListTemplate is not null) { @ListTemplate( @:@{foreach (var item in Items) { @ItemTemplate(item) } } ) }
We’re declaring a typeparam named TItem as this adds generic type parameter support which you’ll see being used in the code behind file. We can add a constraint to this type if required, but for this example we’re not too concerned about what type TItem takes. In the example above we’re assuming the user will supply the ListTemplate otherwise nothing is displayed, ofcourse we could create a default output if we wished or display a message for the developer.
The code behind, the ListView.razor.cs looks like this
using Microsoft.AspNetCore.Components; namespace BlazorTemplates.Components; public partial class ListView<TItem> { [Parameter] public RenderFragment<RenderFragment>? ListTemplate { get; set; } [Parameter] public RenderFragment<TItem> ItemTemplate { get; set; } = default!; [Parameter] public IReadOnlyList<TItem> Items { get; set; } = default!; }
What’s happening here is that we’re declaring a ListTemplate which is a RenderFragment, which is simple put, a piece of UI content, the generic type is also a RenderFragment – so ListTemplate is UI content of UI content essentially. This will act as our “outer” UI, so for example, if were using the ListView for ordered list items, then this would contain the <ol></ot> section of the UI.
The ItemTemplate is again a RenderFragment, hence UI content supplied by the calling code, but this type it takes a generic TItem type, this refers to the type of the list items themselves, i.e. for the WeatherForeast sample code, this is a WeatherForecast class/type. Hence the ItemTemplate will be passed an item from the supplied Items list (within our template) and allows the calling code to render each item.
The Items property is what’s used by the calling code to supply the list of data to our template.
Calling the component
If we look again at the .razor code you’ll see that really all we’re doing it calling the ListTemplate and passing through a segment of code using Wig-Pig syntax which creates a RanderFragment. This then gets used to call through to the ItemTemplate and this template is passed each item from the list of Items so that we can render each row, let’s see the code in use
<ListView Items="_forecasts"> <ListTemplate Context="rows"> <ul>@rows</ul> </ListTemplate> <ItemTemplate Context="forecast"> <li>@forecast.Summary</li> </ItemTemplate> </ListView>
Note: _forecasts is the same as the Weather page in the default Blazor WebAssembly template, hence an array of WeatherForeast items
In the example above we’re renaming the context for use within each template, we could have just used the following if we preferred
<ListView Items="_forecasts"> <ListTemplate> <ul>@context</ul> </ListTemplate> <ItemTemplate> <li>@context.Summary</li> </ItemTemplate> </ListView>
As can be seen (which ever version you use) then ListTemplate is creating the “outer” element, then passing the context into the RenderFragement we supplied within the ListView.razor file, which itself then loops through each item with the Items list (which was defined on the ListView Items property in the above code.
The template then calls the ItemTemplate for each item and the template for this is supplied in the ItemTemplate fragment in the above code. In this case we using li and displaying the summary for each weather forecast.
Using the template if different scenarios
Now using the template to display an unordered list is great, and yes it’s obvious how we can easily change the ol to an ul but I’m sure this looks like a lot of effort for something very easy in Blazor anyway. So let’s look at extending this into something a little more useful, but before we do that, here’s the example of using this template with ordered lists, unordered lists and tables
<ListView Items="_forecasts"> <ListTemplate> <ul>@context</ul> </ListTemplate> <ItemTemplate> <li>@context.Summary</li> </ItemTemplate> </ListView> <ListView Items="_forecasts"> <ListTemplate> <ol>@context</ol> </ListTemplate> <ItemTemplate> <li>@context.Summary</li> </ItemTemplate> </ListView> <ListView Items="_forecasts"> <ListTemplate> <table>@context</table> </ListTemplate> <ItemTemplate> <tr>@context.Summary</tr> </ItemTemplate> </ListView>
How about we extend our template to allow for a header and a footer, hence allowing us to use more of the table functionality but also have the same functionality now for the ordered and unordered lists.
We can simply add the following to our ListView.razor.cs class
[Parameter] public RenderFragment? HeaderTemplate { get; set; } [Parameter] public RenderFragment? FooterTemplate { get; set; }
and within the ListView.razor file just add HeaderTemplate and FooterTemplate like this
@typeparam TItem @if (ListTemplate is not null) { @HeaderTemplate @ListTemplate( @:@{foreach (var item in Items) { @ItemTemplate(item) } } ) @FooterTemplate }
All that’s left to do is add HeaderTemplate code to our calling code, so for example the table based version would look like this (I’ve added more table like element/attribute parts to this code)
<ListView Items="_forecasts"> <HeaderTemplate> <thead><tr><th scope="col">Summary</th></tr></thead> </HeaderTemplate> <FooterTemplate> <tfoot><tr><th scope="row">Footer</th></tr></tfoot> </FooterTemplate> <ListTemplate> <table> <caption>This is a table</caption> <tbody>@context</tbody> </table> </ListTemplate> <ItemTemplate> <tr scope="row">@context.Summary</tr> </ItemTemplate> </ListView>
Now we can also add a header and footer to our ordered and unordered lists in the same way, i.e.
<ListView Items="_forecasts"> <HeaderTemplate> <div>Summary</div> </HeaderTemplate> <FooterTemplate> <div>Footer</div> </FooterTemplate> <ListTemplate> <ul>@context</ul> </ListTemplate> <ItemTemplate> <li>@context.Summary</li> </ItemTemplate> </ListView>
Note: ofcourse this is not very pretty, so I’ll leave it to the reader to create some nice CSS for the header and footer divs
That’s pretty much it – templates can be useful if you’re going to abstract some UI pattern in a reusable way.
Code for this post is available on GitHub.