MudBlazor を使ってみた

# はじめに

自分はウェブアプリを開発するのに、Onsen UI＋Vue.js でアプリ開発してきましたが、.NET Blazor を使い始めました。

[.NET Blazor を始めてみた - Qiita](https://qiita.com/tinymouse/items/01b1bc4a74429ddc0b17)

Blazor でアプリ開発するにあたって、UI ライブラリをどうするか悩みました。
幾つか試してみましたが、MudBlazor がよさそうです。

参考：[Blazor で使える Material Design なコンポーネント 6 選 - Qiita](https://qiita.com/jsakamoto/items/da7f978e1281fabb6093)

# MudBlazor を使ってみた

## MudBlazor を使う

以下のページの説明に従って導入します。

[Installation - MudBlazor](https://mudblazor.com/getting-started/installation#prerequisites)

1. Nuget で「MudBlazor」パッケージをインストールする
2. ```_Imports.razor``` ファイルに ```@using MudBlazor``` を追加する
3. ```Pages/_Host.cshtml``` または ```wwwroot/index.html``` に CSS ファイルの参照を追加する
4. 上記のファイルに JS ファイルの参照を追加する
5. 上記のファイルから不要な CSS ファイルの参照を削除する
6. ```Program.cs``` に ```using MudBlazor.Services;``` ```builder.Services.AddMudServices();``` を追加する
7. 必要に応じて ```MainLayout.razor``` に ```<MudThemeProvider />``` ```<MudDialogProvider />``` ```<MudSnackbarProvider />``` を追加する

## ページを定義する

ヘッダとスクロール領域があるページを定義します。

```html:Index.razor
<MudLayout>
    <MudAppBar>
        <MudText>TryMudBlazor</MudText>
    </MudAppBar>
    <MudMainContent>

    </MudMainContent>
</MudLayout>
```

## データを準備する

まず、データを準備します。
C# のコードで用意します。

```csharp:Index.razor
@code {
	public class Contact
	{
		public string firstName { get; set; } = string.Empty;
		public string lastName { get; set; } = string.Empty;
		public bool hasPhone { get; set; } = false;
		public string phoneNumber { get; set; } = string.Empty;
	}
    public List<Contact> contacts = new()
    {
        new Contact { firstName = "Alice", lastName = "Arten", hasPhone = true, phoneNumber = "555-0184" },
        new Contact { firstName = "Bob", lastName = "Brigham", hasPhone = true, phoneNumber = "555-0163" },
        new Contact { firstName = "Charlie", lastName = "Campbell", hasPhone = true, phoneNumber = "555-0129" },
    };
}
```

## データをレンダリングする

データが表示されるリストを配置します。

```@foreach``` を使ってみます。

```html:Index.razor
    <MudMainContent>
        <MudList Clickable="true">
            @foreach (var contact in contacts)
            {
                <MudListItem>
                    <MudStack Row="true" AlignItems="AlignItems.Center">
                        <div>@(contact.firstName) @(contact.lastName)</div>
                    </MudStack>
                </MudListItem>
            }
        </MudList>
```

MudBlazor ライブラリは、データをリスト表示するコンポーネントが用意されていました。

```html:Index.razor
    <MudMainContent>
        <MudTable Items="@contacts" Hover="true">
            <RowTemplate>
                <MudTd>
                    <MudStack Row="true" AlignItems="AlignItems.Center">
                        <div>@(context.firstName) @(context.lastName)</div>
                    </MudStack>
                </MudTd>
            </RowTemplate>
        </MudTable>
```

```@foreach``` で実装すると、明細ごとのデータを扱う変数を ```var contact``` と宣言しましたが、```MudTable``` で実装すると、予め用意された ```context``` で参照します。

## ユーザ入力をハンドリングする

リスト表示に「編集」と「削除」ボタンを追加します。
```@onclick``` または ```OnClick``` ディレクティブで実行したいメソッドを指定します。

```html:Index.razor
                    <MudStack Row="true" AlignItems="AlignItems.Center">
                        <div>@(contact.firstName) @(contact.lastName)</div>
                        <MudSpacer />
                        <div>
                            <MudIconButton Icon="@Icons.Material.Filled.Edit" OnClick="() => ShowDetail(contact)" Edge="Edge.End" />
                            <MudIconButton Icon="@Icons.Material.Filled.Delete" OnClick="() => DeleteItem(contact)" Edge="Edge.End" />
                        </div>
                    </MudStack>
```

実行するメソッドを設定します。

```csharp:Index.razor
@code {
    private void DeleteItem(Contact contact)
    {
        Console.WriteLine("DeleteItem()");
    }
    private void ShowDetail(Contact contact)
    {
        Console.WriteLine("ShowDetail()");
    }
```

## データを削除できるようにする

```csharp:Index.razor
    private void DeleteItem(Contact contact)
    {
        contacts.Remove(contact);
    }
```

コードでデータを削除すると、画面に表示された一覧も変更されます。

## 詳細ページを表示する

ページ遷移やダイアログボックスで実装するのも試しましたが、MudBlazor に用意されている「ドロワー」コンポーネントを使うことにします。

```html:Index.razor
    <MudDrawer @bind-Open="OpenedDetail" Anchor="Anchor.End" Variant="@DrawerVariant.Temporary">
        <MudDrawerHeader>
            <MudText>Detail</MudText>
        </MudDrawerHeader>
        <MudContainer>
            <MudForm>
                <MudTextField T="string" Label="First Name" />
                <MudTextField T="string" Label="Last Name" />
            </MudForm>
        </MudContainer>
    </MudDrawer>
```
```csharp:Index.razor
@code {
    private bool OpenedDetail = false;

    private void ShowDetail(Contact contact)
    {
        OpenedDetail = true;
```

表示する／しないの制御は、```@bind-Open``` に紐づけられた ```OpenDetail``` を変更します。
ページ遷移のように見せたいので、ボックスを画面右端に表示する ```Anchor="Anchor.End" Variant="@DrawerVariant.Temporary"``` を使いたいと思います。
このとき、```<MudDrawer />``` を追記する場所に注意が必要です。```<MudLayout>``` の外側に書くと、表示画面が横スクロールできて、ドロワー領域が見えてしまいます。

```html:Index.razor
<MudLayout>
    中略
    <MudDrawer @bind-Open="OpenedDetail" Anchor="Anchor.End" Variant="@DrawerVariant.Temporary">
</MudLayout>
```
## 詳細ページに内容を表示する準備する

```ShowDetail()``` で取得した ```contact``` は、このメソッド内でしか参照できないので、```selected``` 変数を用意して ```contact``` を代入して、```Index.razor``` 全体で参照できるようにします。

```csharp:Index.razor
    private Contact selected = new();

    private void ShowDetail(Contact contact)
    {
        selected = contact;
```

## 双方向データバインディングする

```@bind-Value``` を使います。

```html:Index.razor
                <MudTextField T="string" Label="First Name" @bind-Value="@(selected.firstName)" />
                <MudTextField T="string" Label="Last Name" @bind-Value="@(selected.lastName)" />
```

```selected``` の内容が画面に表示されるだけでなく、
画面の要素の内容で ```selected``` の内容が更新されるようになります。

```selected``` はポインタです。値がコピーされているわけではありません。
詳細画面の要素の内容を変えると、```contacts``` の内容も変わっています。

Blazor の機能で、双方向データバインディングが簡単にできます。

## データを追加できるようにする

ツールバーに「＋」ボタンを追加して、データを追加できるようにします。

```html:Index.razor
    <MudAppBar>
        中略
        <MudSpacer />
        <div>
            <MudIconButton Icon="@Icons.Material.Outlined.Add" OnClick="() => AddItem()" Color="Color.Inherit" Edge="Edge.End" />
        </div>
    </MudAppBar>
```
```csharp:Index.razor
@code {
    private void AddItem()
    {
        Console.WriteLine("AddItem()");
        Contact contact = new();
        contacts.Add(contact);
        ShowDetail(contact);
    }
```