Bootstrap+Blazor essentials

xakpc

Pavel Osadchuk

Posted on October 13, 2020

Bootstrap+Blazor essentials

When you start a new Blazor project it will quickstart you with a default Bootstrap template. But when you spent several weeks or months on the project you starting to notice that there is a lot of duplicate HTML code that can be refactored and moved into components.

In this post, I want to give you some advice on how and which Bootstrap components you can wrap into .razor components to make your code simpler, easier to maintain, and expandable.

GitHub logo xakpc / Xakpc.BlazorBits.Bootstrap

Sample project for https://dev.to/xakpc/ article

All these components are alterations of what I use every day in my projects. Why not share the same? In real projects, there is usually some CSS template used that could alter the component significantly.

These examples use clean Bootstrap and can be the foundation that could be altered for your CSS template. That's why I usually just copy-paste them across projects and tune to fit the current CSS template, instead of moving them to separate NuGet package;

Bootstrap has more than 20 components, but for this article, I keep only the ones that used very often and make a great example for you where to go from hereby.

  • Button
  • Button Group
  • Card
  • Progress
  • Spinner
  • Toast
  • Modals

Next time - Form and Form Components in Blazor with MVVM

Button

The most basic component is a button. Because I use MVVM my button is based on the ICommand interface

When you add action to button's ICommand AddCommand = new Command<int>(DoAdd); take notice of the action

private void DoAdd(int p)
{
    Value += p;
    StateHasChanged(); // <--- not gonna work without it
}
Enter fullscreen mode Exit fullscreen mode

I found out that onaction will re-render only the component it belongs to. Even if actual action changes something from another (parent) component. But when you use ViewModel there is no need in StateHasChanged call here because the View should be subscribed to INotifyPropertyChanged anyway.

Button Group

Button group wraps several command buttons in a single neat component. I usually use it for data table editing
Alt Text

The component is rather simple, you can use icons instead of text for a better look, but the main thing that it nicely fits Blazor way of building tables.
You could pass commands and current item to ViewModel for processing. If you have a lot of tables in your app that saves tons of space also, you get consistency across tables and can easily adapt styles in the future.

<tbody>
    @foreach (var item in Items)
    {
        <tr>
            <td>@item</td>
            <td><RowButtonGroup 
                    EditCommand="EditItemCommand" 
                    DeleteCommand="DeleteItemCommand" 
                    Parameter="item"/></td>
        </tr>
    }
</tbody>
Enter fullscreen mode Exit fullscreen mode

Card

For me, the card is the primary component of any web app. This component wrap most of the basic HTML card into several optional RenderFragments. You can expand it further to add an optional card-image or list-group block.

I noticed that some folks missing the fact that you can use several RenderFragment inside your component. Well, you can and it allows you to use your components like that

<Card Title="Special title treatment">
    <Body>
        <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
        <a href="#" class="btn btn-primary">Go somewhere</a>
    </Body>
    <Footer>
        2 days ago
    </Footer>
</Card>
Enter fullscreen mode Exit fullscreen mode

Please note

[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; } 
    = new Dictionary<string, object>();
Enter fullscreen mode Exit fullscreen mode

This parameter is used on card to catch-all unmatched attributes that you want to assign to it <div class="card @Class" @attributes="InputAttributes">. It's usually a good practice to include such catch-all property to every custom component. Saves some time on unmatched exceptions later. But be aware where you put it, for some components, it might be wise to apply attributes to the child or not apply it at all.

Progress Bar

Small component but I want to show it here as an example of the power of Blazor components and proper MVVM usage.

Progress Bar with MVVM Example

Let's dig into the example of usage of ProgressBar with backing it ViewModel.

View

<h2 class="mt-3">Interactive (with VM)</h2>
<ProgressBar Progress="@DataContext.Progress" Class="mt-2 mb-2">@((DataContext.Progress/100f).ToString("P0"))</ProgressBar>
<CommandButton class="btn-primary" Command="@DataContext.Add">+</CommandButton>
<CommandButton class="btn-secondary" Command="@DataContext.Subtract">-</CommandButton>
Enter fullscreen mode Exit fullscreen mode

As you can see View is quite simple: we have progress which is transformed like that @((DataContext.Progress/100f).ToString("P0")) for display only and couple buttons with commands Add and Subtract.
These actions are calculated in ViewModel. To ensure that ViewModel created, initialized, and linked I use ContextComponentBase as a base class for all my pages.

Page code-behind looks like this

    public partial class Progresses
    {
        [Inject]
        protected new ProgressViewModel DataContext
        {
            get => base.DataContext as ProgressViewModel;
            set => base.DataContext = value;
        }
    }
Enter fullscreen mode Exit fullscreen mode

Ensure that .razor page itself is inherited from the proper class

@page "/progress"
@inherits Xakpc.BlazorBits.Bootstrap.ViewModels.ContextComponentBase;
Enter fullscreen mode Exit fullscreen mode

That's all we need for View, let's switch to ViewModel!

ViewModel

First, check out the previous code snippet. Our ViewModel is Injected into our View - so let's not forget to add it in Startup.cs

services.AddScoped<ProgressViewModel>();
Enter fullscreen mode Exit fullscreen mode

I recommend read several times about Scopes in Blazor. Once I lost a day trying to fix a problem caused by misunderstanding Blazor DI Scopes.
Finally a ViewModel itself

public class ProgressViewModel : ViewModelBase
{
    private int _progress;

    public ProgressViewModel()
    {
        Add = new Command<int>(i => Progress += 1, i => Progress < 100);
        Subtract = new Command<int>(i => Progress -= 1, i => Progress > 0);
    }

    public int Progress
    {
        get => _progress;
        set { _progress = value; OnPropertyChanged(nameof(Progress)); }
    }

    public override Task InitializeAsync()
    {
        Progress = 22;
        return base.InitializeAsync();
    }

    public ICommand Add { get; set; }
    public ICommand Subtract { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

I will not go deep into VM, only point to main things

  • InitializeAsync called once per scope on first page-load - that means once per connection for Blazor Server because we added it with AddScope;
  • Commands set Progress which fire OnPropertyChanged which fire StateHasChanged which fire page redraw;
  • Commands have CanExecute func to ensure that Progress in range 0-100;
  • CanExecute also used to disable buttons when needed;

Spinner

Spinner is another simple example of how easy to wrap into a Razor component.
Alt Text

Toasts

For toasts, I usually don't use a Bootstrap component but a nice Blazor port of very popular toastr.js

GitHub logo sotsera / sotsera.blazor.toaster

A Toastr.js port to Blazor in pure .Net.

Sotsera.Blazor.Toaster

A Blazor port of Toastr.js to Server and Webassembly Blazor Apps.

The transitions are implemented using System.Threading.Timer timers so the resource usage should be closely monitored when using the server-side hosting model.

Only for server-side projects

The static assets from Razor Component Libraries are available by default only in Development mode. They can be enabled on Production using the UseStaticWebAssets() method in the Program.cs file as in the following example:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStaticWebAssets();
            webBuilder.UseStartup<Startup>();
        });
Enter fullscreen mode Exit fullscreen mode

Sample

The client-side sample project has been published here.

Changes

__version 3.0.0

  • new configuration options for styling the toast title (ToastTitleClass) and message (ToastTitleClass) and an example for aligning their text to…

I should probably try Bootrstap toasts someday.

Modal

Bootstrap has a Modal component but it is highly dependant on JavaScript. In my work with Blazor, I prefer to minimize the amount of JS code overall, so I use Blazored.Modal - an amazing native component for Modals. You should check it out.

GitHub logo Blazored / Modal

A powerful and customizable modal implementation for Blazor applications.

Blazored Modal

A powerful and customizable modal implementation for Blazor applications.

Nuget version Nuget downloads Build & Test Main

Screenshot of Blazored Modal

Documentation

For information on getting Blazored Modal setup in your application, as well as the many customisation options, please checkout our docs.




And that's all for now. I am not very good at writing such long articles, so will be appreciated for any feedback.

And if you have any questions, feel free to mention me in Twitter

πŸ’– πŸ’ͺ πŸ™… 🚩
xakpc
Pavel Osadchuk

Posted on October 13, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Bootstrap+Blazor essentials
dotnet Bootstrap+Blazor essentials

October 13, 2020