| Comments

I was finally getting around to updating a little internal app I had that showed some various data that some groups use to triage bugs.  As you can imagine it is a classic “table of stuff” type dataset with various titles, numbers, IDs, etc. as visible columns.  I had built it using Blazor server and wanted to update it a bit.  In doing some of the updates I came across a preferred visual I liked for the grid view and applied the CASE methodology to implement that.  Oh you don’t know what CASE methodology is?  Copy Always, Steal Everything.  In this case the culprit was Immo on my team.  I know right? I couldn’t believe it either that he had something I wanted to take from a UI standpoint.  I digress…

In the end I wanted to provide a rendered table UI quickly and provide a global filter:

Picture of a filtered data table

Styling the table

I copied what I needed and realized I could be using the Bootstrap styles/tables in my use case.  Immo was using just <divs> but I own this t-shirt, so I went with <table> and plus, I like that Bootsrap had a nice example for me.  Off I went and changed my iteration loop. to a nice beautiful striped table.  Here’s what it looked like in the styling initially:

<table class="table table-striped">
    <thead class="thead-light">
        <tr>
            <th scope="col">Date</th>
            <th scope="col">Temp. (C)</th>
            <th scope="col">Temp. (F)</th>
            <th scope="col">Summary</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var forecast in forecasts)
        {
            <tr>
                <td>@forecast.Date.ToShortDateString()</td>
                <td>@forecast.TemperatureC</td>
                <td>@forecast.TemperatureF</td>
                <td>@forecast.Summary</td>
            </tr>
        }
    </tbody>
</table>

Adding a filter

Now I wanted to add some filtering capabilities more globally.  Awesome “boostrap filtering” searching I went and landed on this simple tutorial.  Wow! a few lines of JavaScript, sweet, done.  Or so I thought.  As someone who hasn’t done a lot of SPA web app development I was quickly hit with the reality that once you choose a SPA framework (like Angular, React, Vue, Blazor) that you are essentially buying in to the whole philosophy and that for the most part jQuery-style DOM manipulations will no longer be at your fingertips as easily.  Sigh, off to some teammates I went to complain and look for their sympathy.  Narrator: they had no sympathy.

After another quick chat with Immo who had implementing the same thing he smacked me around and said in the most polite German accent “Why don’t you just use C# idiot?”  Okay, I added the idiot part, but I felt like he was typing it and then deleted that part before hitting send.  Knowing that Blazor renders everything and then re-renders when things change I just had to implement some checking logic in the foreach loop.  First I needed to add the filter input field:

<div class="form-group">
    <input class="form-control" type="text" placeholder="Filter..." 
           @bind="Filter" 
           @bind:event="oninput">
</div>
<table class="table table-striped">
...
</table>

Observe that I added an @bind and @bind:event attributes that enable me to wire these up to properties and client-side events.  So I’m telling it to bind the input to my ‘Field’ property and do this on ‘oninput’ (basically when the keys are typed in the input box).  Now off to implement the property.  I’m doing this simply in the @code block of the page itself:

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }

    public string Filter { get; set; }
}

And then I needed to implement the logic for filtering.  I’m doing a global filter so that I can control whatever fields I want searched/filtered.  I basically have the IsVisible function called each iteration and deciding if it should be rendered.  For this sample I’m looking at if the summary contains the filter text or if the celsius or farenheit temperatures start with the digits being entered.  I actually have access to the item model so I could even filter off of something not visible if I wanted (which would be weird for your users, so you probably shouldn’t do that).  Here’s what I implemented:

public bool IsVisible(WeatherForecast forecast)
{
    if (string.IsNullOrEmpty(Filter))
        return true;

    if (forecast.Summary.Contains(Filter, StringComparison.OrdinalIgnoreCase))
        return true;

    if (forecast.TemperatureC.ToString().StartsWith(Filter) || forecast.TemperatureF.ToString().StartsWith(Filter))
        return true;

    return false;
}

Implementing the filter

Once I had the parameter, input event, and the logic, now I just needed to implement that in my loop.  A simple change to the foreach loop does the trick:

<table class="table table-striped">
    <thead class="thead-light">
        <tr>
            <th scope="col">Date</th>
            <th scope="col">Temp. (C)</th>
            <th scope="col">Temp. (F)</th>
            <th scope="col">Summary</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var forecast in forecasts)
        {
            if (!IsVisible(forecast))
                continue;
            <tr>
                <td>@forecast.Date.ToShortDateString()</td>
                <td>@forecast.TemperatureC</td>
                <td>@forecast.TemperatureF</td>
                <td>@forecast.Summary</td>
            </tr>
        }
    </tbody>
</table>

Now when I type it automatically filters the view based on input.  Like a thing of beauty.  Here it is in action:

Animation of table being filtered

Pretty awesome.  While I’ve used the default template here to show this example, this technique can of course be applied to your logic.  I’ve put this in a repo to look at more detailed (this is running .NET 5-rc2 bits) at timheuer/BlazorFilteringWithBootstrap.

More advanced filtering

This was a simple use case and worked fine for me.  But there are more advanced use-cases, better user experiences to provide more logic to the filter (i.e., define your own contains versus equals, etc.) and that’s where 3rd party components come in.  There are a lot that provide built-in grids that have this capability.  Here are just a few:

Just to name a few popular ones.  These are all great components authored by proven vendors in the .NET component space.  These are way richer than simple filtering and provide a plethora of capabilities on top of grid-based rendering of large sets of data.  I recommend if you have those needs you check them out.

I’m enjoying my own journey writing Blazor apps and hope you found this dumb little sample useful.  If not, that’s cool.  I mainly am bookmarking here for my own use later when I forget and need to search…maybe I’ll find it back on my own site.

Hope this helps!

Please enjoy some of these other recent posts...

Comments