2021年1月11日月曜日

Blazor向けUIコンポーネントMatBlazorでマテリアルデザイン

どうも、もりもりです。

前回は .NETでSPA(SinglePageApplication)を実現できるWebフレームワークの BlazorWebAssemblyでTodo画面を作成しました。 今回はMatBlazorを使って、そのTodo画面をマテリアルデザインにしてみました。

MatBlazor使ったらどうなるの?

こんな感じの画面が

こんな感じの画面になっちゃいます。

ソースコードは下記に置いてます。

前準備

まずは前準備としてMatBlazorのパッケージを追加し、使用できるように設定していきます。

◆ パッケージの追加

下記のコマンドでパッケージを追加します。念のためcsprojに追加されていることを確認しておきましょう。

$ dotnet add package MatBlazor
◆ _imports.razorを編集

下記を追加します。

_imports.razor

@using MatBlazor
◆ wwwroot/index.htmlを編集

下記を追加します。

index.html

<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
    <!-- 下記を追加 -->
    <script src="_content/MatBlazor/dist/matBlazor.js"></script>
</body>

これで準備は整いました。

Todoアイテム入力欄

ここではチェックボックス、入力欄、ボタンにMatBlazorを使用しています。

TodoMat.razor

@page "/todomat"

<style>
    .new-item-text-field > label.mat-text-field > span {
        margin-left: 30px;
    }
</style>

<h1>Todo</h1>

<div class="new-item-text-field" style="display: flex; position: relative; align-items: center;">
    <MatCheckbox Style="z-index: 1; position: absolute; left: 0;" 
                 @bind-Value="allChecked" Disabled="@(!TodoItemExists())"></MatCheckbox>
    <MatTextField @bind-Value="newItem" Label="What needs to be done?"
                  OnKeyDown="@Enter" OnInput="@OnInput"
                  Style="z-index: 0; width: 500px; padding-left: 50px;"></MatTextField>
    <MatButton OnClick="@AddItem" Outlined="true">add</MatButton>
</div>

MatBlazorのドキュメントに使用例がたくさん載ってるのでさらっと説明を。

◆ MatCheckbox

チェックボックスのコンポーネント。前回作成したTodo画面では入力欄のとこにチェックボックスを付けてなかったのですが追加しました。

Name Description
@bind-Value バインドする値。true / false
Disabled 無効にするかどうか。true / false

@bind-Valueには、プロパティallCheckedをバインド。 コメントの通りです。

Disabledには、メソッドTodoItemExists()の戻り値をバインド。

TodoMat.cs

// newItemのチェック状態
private bool allChecked
{
    get
    {
        // 未完了アイテムがない場合にチェックON、一つでもあればチェックOFF
        return !allTodos.Any(x => !x.IsDone);
    }
    set
    {
        // チェック状態に合わせてTodoアイテムすべてのチェック状態を変更
        allTodos.ForEach(x => x.IsDone = value);
    }
}
        
/// <summary>
/// Todoアイテムが存在するかどうか
/// </summary>
/// <returns></returns>
private bool TodoItemExists()
{
    return allTodos.Any();
}
◆ MatTextField

テキストフィールドのコンポーネント。

Name Description
@bind-Value バインドする値。入力した値を保持。
Label 入力項目名。今回はPlaceholder的な使い方してます。
HelperTextというのが別に用意されているので本来はこちらの方がいいのかな?
OnKeyDown キーダウンイベント。
OnInput 入力イベント。

ただ1個だけ面倒だったのがあります。

MatTextFieldのLabel「What needs to be done?」の文字の位置。 CheckBoxとTextFieldを重ねてるので入力内容はStyle属性で paddingを指定してやればズラせたのですが Labelの文字位置は用意されてる属性では触れそうになかったので Styleを強引にイジってます。(上記ソースの4-6行目) これが正解なのかどうかわかりませんがまたどっかで時間作って確認してみます。

◆ MatButton

ボタンのコンポーネント。

Name Description
OnClick クリックイベント。
Outlined ボタンの枠を表示するかどうか。

Todoアイテムリスト

リスト用コンポーネント。MatListとMatListItemを使用してます。今回はStyleのみ指定。

TodoMat.razor

<style>
    .completed-todo-item {
        text-decoration: line-through;
        color: lightgray !important;
    }
</style>
・・・
・・・
<MatList Style="width: 500px">
    @foreach (var item in todosForDisplay)
    {
        <MatListItem Style="padding: 0;">
            <MatCheckbox Style="z-index: 1;" @bind-Value="item.IsDone"></MatCheckbox>
            <MatStringField Style="z-index: 0; width: 100%; position: absolute; padding-left: 50px;" 
                            InputClass="@GetCompletedTodoItemCssClass(item.IsDone)" 
                            @bind-Value="item.Content"></MatStringField>
        </MatListItem>
    }
</MatList>

MatListItem内ではMatCheckboxとMatStringFieldを使用しています。 MatStringFieldはMatTextFieldと違ってString型専用のコンポーネントっぽいです。 MatTextFieldはintやDateTimeなどの型がバインドできます。

◆ MatStringField

String専用の入力用コンポーネント。

Name Description
InputClass input要素のCSSクラス。

Styleに「completed-todo-item」を用意し、InputClassに チェック状態によって異なるStyle名を返すメソッド「GetCompletedTodoItemCssClass()」をバインドしています。

TodoMat.cs

/// <summary>
/// Todo完了アイテムCSSクラス名を取得
/// </summary>
/// <param name="isDone">完了フラグ</param>
/// <returns>クラス名</returns>
private string GetCompletedTodoItemCssClass(bool isDone)
{
    return isDone ? "completed-todo-item" : "";
}

リスト下のボタン群

ボタン用のコンポーネント。

TodoMat.razor

@allTodos.Count(x => !x.IsDone) items left
<MatButton OnClick="@ShowAll" Outlined="true" Style="margin-left: 20px">All</MatButton>
<MatButton OnClick="@ShowActive" Outlined="true">Active</MatButton>
<MatButton OnClick="@ShowCompleted" Outlined="true" Style="margin-right: 20px">Completed</MatButton>
<MatButton OnClick="@ClearCompleted" Outlined="true" 
           Disabled="@(!CompletedTodoItemExists())">Clear completed</MatButton>

TodoMat.cs

/// <summary>
/// 完了済みのTodoアイテムが存在するかどうか
/// </summary>
/// <returns></returns>
private bool CompletedTodoItemExists()
{
    return allTodos.Any(x => x.IsDone);
}

さいごに

使ってみた感じ、CSSの知識がない人にとっては嬉しいとは思いますが Webの知識がある人からすると物足りなさそうな感じがします。 ちょっと細かいことに拘るとCSSの知識も必要になってきます。 ドキュメントはわかりやすかったのですんなり使えました。 他にもBlazor向けのコンポーネントで良さそうなのがあったらまた紹介したいと思います。

以上、もりもりでした。

2020年12月27日日曜日

LANケーブルを自作する

こんにちは、やっまむーです。

弊社は年末の大掃除の際に、机の下のコード類を整理しようとしています。
社内の個人PCはほとんどが有線LANで繋がっているのですが、そのために机の下を何本ものLANケーブルがはい回り、いくつものハブのポートが埋め尽くされています。
そして長年使われていることで、爪が欠けた物もいくつかあります。

前々から気になっていたので、この機会にいくつか修理をしようかと思います。
今回は予習のために自宅用の短いケーブルを作成します。

使う道具

  • LANケーブル
  • プラグ(RJ-45コネクタ)
  • 皮むき工具
  • かしめ工具
  • LANケーブルテスター

あとはハサミがあればOKです。
プラグはいくつか種類がありますが、できればロードバー(ケーブルを通すときのガイド)が付いたものの方が作業がやりやすいです。

LANケーブル自作の手順

まずは必要な長さを切り出し、両端の外皮を剥きます。

次に捻られている各ケーブルをほどいていきます。
今回使ったLANケーブルは中に強度をあげるための芯が入っていました。
これは不要なのでハサミで切ってしまいます。

今回作成するのはネットワーク用なので、両端が同じ並び順になるストレートケーブルにします。
各ケーブルの並びは標準的な「T568B」という規格にします。
ケーブルを「橙白・橙・緑白・青・青白・緑・茶白・茶」の順に並べます。

まっすぐ伸ばすために、ケーブルを複数本まとめて持って左右に振ったり、しごいたりしていき、最後にケーブルの先端を切りそろえます。

この順番を維持しながらコネクタに差し込み、かしめ工具で締め付ければ完成です。

反対側も同様の手順で作成し、疎通に問題がないかテスターで確認します。
両端で同じ番号のランプが点けば問題なしです。

コネクタにケーブルを通すのが最初は慣れませんが、ロードバー付きのものであれば初心者でも難しくありません。
LANケーブルが長すぎる場合など、ちょうどいい長さのケーブルが欲しい場合は自分で作ってみるのも面白いと思います。

では、本日はこのあたりでー。

2020年12月18日金曜日

Visual Studio Code で Draw.io を使ってみる!


こんにちは。よっしーです。

今日はお仕事のお話。


仕事でシステム設計をするとき、設計書作成にはいつも「Excel」を使っていました。

今まではそれでよかったのですが、ここ最近、仕事先は「脱Excel」の流れが来ていて、

ついに「Excel」での設計書作成が禁止となってしまいました。。。


UMLなどは、Excelで書いていたので、それに代わるツールをどうするか。

という話しになったのですが、最近では Visual Studio Code でいろいろ出来る。

ということがわかり、拡張機能として使える「Draw.io」を使ってみることにしました。




拡張機能から「Draw.io Integration」を選択してインストールします。

dio もしくは drawio という拡張子でファイルを作成すると認識してくれます。



使い勝手は「Visio」みたいな感じで、作図だけでいえば「Excel」よりも優秀な感じがします。



さらにこれのいいところは、SVG形式で出力できること。


SVGは画像ファイルですが、一般的な画像ファイルのように色情報の集合体(ビットマップ)ではなく、

ベクター情報(点の座標とそれらを結ぶ線など)で表現されていて、ファイル自体がテキストファイルになるので、

例えば画像内の文章をgrepなどで引っ掛けることが出来ます。

※ JPEG/PNGの資料からは文字列検索出来ませんが、SVGだと検索出来るので、
  資料を探し当てる際に便利です!!


SVGで出力し、マークダウン形式で設計書を作ってみると以下のようになります。


マークダウンなどもすべて Visual Studio Code上で作成、閲覧することが出来ます。


本当に Visual Studio Code でなんでもできちゃいますね。

UML図の作成についても使い勝手が良かったですし、慣れれば Excel よりも

生産性が出るような気がします。


皆さんも Visual Studio Code でいろいろやってみてはいかがでしょうか。

ではまた~。

2020年12月11日金曜日

リニューアルされたe-Govを使ってみた!

2020年11月下旬、行政手続きの電子申請サイト「e-Gov」がリニューアルされました。
その変更点は、


にあるドキュメントで確認できます。
その中で思わず笑ってしまったのが、下記の名称変更に関する記述です。

『e-Gov の誕生から現在に至るまで「電子政府の総合窓口(e-Gov)」を正式名称としていましたが、「名が体を表していない」との自己認識に基づき、現在の名称から「電子政府の総合窓口」を 取り除くこととしました。』

『「名が体を表していない」との自己認識』って・・・(笑)
でも、きちんと分析して、改善することはいいことだと思います。

今回はそんなリニューアルされたe-Govで賞与支払届の手続きをしました。

e-Gov電子申請アプリケーションを起動


Chromeでe-Gov電子申請サイトを開き、ログインボタンを押すと、「e-Gov電子申請アプリケーション起動」画面が表示されます。リニューアル前からこのアプリケーションは必要だったため、インストールしていたのですが、「e-Gov電子申請アプリケーション起動」を押しても起動しません。

リニューアルに伴い、新しくなったのだろうと思い、改めて「e-Gov電子申請アプリケーション」をダウンロードし、インストールすると、ログイン画面が表示されました。

ログインが必須


リニューアル前はログインしなくても利用できましたが、今回、ログインが必要となりました。

e-Govへのログインに使用できるアカウントは、

・e-Govアカウント
・GビズID
・Microsoftアカウント

の3つです。

賞与支払届を申請


ログイン後、手続検索で「賞与支払届」と入力して表示される検索結果の中から、「健康保険・厚生年金保険被保険者賞与支払届/70歳以上被用者賞与支払届(CSVファイル添付方式)(2020年12月以降手続き) 」を選択し、手続きを開始します。

「健康保険厚生年金保険CSV形式届書総括票」にデータを入力し、社会保険届書作成プログラムや各種ソフトで出力されるCSVファイル(SHFD0006.CSV)を添付書類として添付して申請します。
電子署名が必要な手続きですが、GビズIDを使用する場合は省略できます。
これでマイナンバーカードによる署名も必要ありません。

それにしても、統括票はなぜ必要なんでしょうね?
入力する事業者整理番号や事業者の住所などの項目はすべてCSVファイルに含まれています。
わざわざ入力するのが手間なので、将来的にはなくしてほしいものです。

またいつか、どこかで。

2020年12月4日金曜日

「こうべ避難ナビ」アップデートしました!

 10月に「こうべ避難ナビ」をリリースしました。

「こうべ避難ナビ(for iPhone/iPad)」バージョンアップしました!


リリース時、神戸市のホームページで公開されていた緊急避難場所データは、2018年、もしくは2019年のものでした。
先ごろ、各区のデータが2020年6月1日現在に統一されて公開されたので、データを更新し、アプリをアップデートしました。
ぜひ、ご利用ください。
Download on the App StoreGet it on Google Play

リリース用モジュールアップロードでエラー


アップデートするために、Google Play Consoleでリリース用のモジュールをアップロードしようとしたところ、

アップロード証明書で署名されていない APK をアップロードしました。
使用する証明書は同じである必要があります。
アップロード証明書のフィンガープリントは

SHA1: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX

ですが、アップロードした APK の署名に使用されている証明書のフィンガープリントは

SHA1: ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ:ZZ

です

と、エラーが発生しました。

そうなんです。
キーストアのパスワードが分からなくなり、新しく作成したキーストアを使用していたのです。

このアップロード鍵を紛失した場合の対応は、Play Console ヘルプに書かれています。

1.新しく作成したキーストアからアップロード鍵の証明書をエクスポートする。
2.1の証明書を付けて、サポートチームに鍵のリセットを依頼する。

依頼から20分ほどして、新しいアップロード鍵の登録完了メールが届きました。
ただし、すぐにはアップロードできず、メールに記載されている新しいアップロード鍵の利用可能日時以降にアップロードすることができました。

またいつか、どこかで。

2020年11月29日日曜日

cpprestsdkでHTTP通信に失敗

お久しぶりです。ひっくです。

C++でWebAPIと通信する機能を作成した際、環境要因で(?)
WebAPIからのレスポンスを受信できないという現象があり、
解決に手間取ったので、方法を備忘録として残しておきます。

前提条件


 ・C++でHTTP(S)通信を行うため、Microsoft提供のcpprestsdkを使用

 ・cpprestsdkのDLLはNuGetで取得(version:2.5.0)

 ・WindowsXP(Embedded)で発生(ただし1回目の通信は成功し、2回目以降で失敗)

発生した現象について



実装していたコードはざっくり記載するとこんな感じです。
http_client_config cfg;
http_client client(L"URL文字列", cfg)

auto request = client.request(method::Post, L"", postData.serialize(), L"application/json");
request.then([](http_response response)
{
    // 受信時の処理(ローカル変数に受信時のJSON文字列を保持)
}.wait();
このコード、Windows10(64bit)では問題なく動作しました。
しかし問題となっている環境(WindowsXP)では、何故か受信しないように見えるパターンが存在しました。

Wiresharkを入れて通信を監視してみましたが、クライアント - WebAPI間ではHTTPレスポンスも
受信できており、直前・直後のTCP通信も正常のため、何故??という状態になりました。

対処法



WebAPIからのレスポンス待機を行う部分について、別の書き方ができるか調べてみた結果
以下に変更するとうまくいきました。

変更前
request.then([](http_response response)
{
    // 受信時の処理(ローカル変数に受信時のJSON文字列を保持)
}.wait();

変更後
// ここでリクエスト送信+レスポンス待機
auto resp = request.get();
// 待機完了後、レスポンスのJSON文字列を取得
auto respJson = resp.extract_json().get();
get() 内部でレスポンス待機処理が記載されており、
問題が発生したコードと機能の実装は変わらないと言えます。

内部処理のため直接原因は分からないままでしたが、「http_client.request」が
タスクチェーンで「wait」「then」の組み合わせとなった場合に、問題の現象が発生するのかもしれません。

なお、別の書き方として以下の様な方法もありました。
while(!request.is_done())
{
    // 待機処理
}

こちらのコードも問題の現象は発生しなくなりますが、「request.is_done()」が完了済みと
判断する時点が分からないため、不要な通信が発生する可能性があるという点で、
使用は控えるべきかと思います。

今回はこのへんで。ではまた!

2020年11月24日火曜日

Blazor WebAssemblyをvscodeで触ってみた

どうも、もりもりです。

今月ついに.NET 5.0がリリースされましたね。 今回は.NETでSPA(SinglePageApplication)を実現できるWebフレームワークの BlazorWebAssemblyを触ってみました。 SPAとは、ページ遷移を行わない単一Webページで動作するWebアプリです。 一般的なWebアプリではJavaScriptを用いてSPAを実現しますが、 C#だけでできてしまうってC#erからしたら嬉しくないですか?

はじめに

Blazorには2種類のモデルがあり、サーバーサイドで動作するBlazor Serverと、 ブラウザ上で動作するBlazor WebAssemblyがあります。

Blazor Serverは、サーバー上の.NET Core環境で主な処理が行われ、サーバー側がブラウザのDOMを変更します。 Blazor WebAssemblyは、最初に.NETアセンブリとランタイムがブラウザにダウンロードされ ブラウザ上で.NETコードを実行し、別途プラグインなどは不要です。

vscodeでBlazor WebAssemblyプロジェクトを作成して、 さらにTodoMVC のようなTodoページを追加してみました。

プロジェクト作成

まずは任意の場所で下記コマンドを叩いてプロジェクトを作成します。

$ dotnet new blazorwasm -o BlazorWasmSample

プロジェクト構成

確認すると初めからテンプレート的な感じで3つほどページが用意されています。

BlazorWasmSample
  ├── App.razor
  ├── BlazorWasmSample.csprojs
  ├── Pages
  │   ├── Counter.razor
  │   ├── FetchData.razor
  │   └── Index.razor
  ├── Program.cs
  ├── Properties
  │   └── launchSettings.json
  ├── Shared
  │   ├── MainLayout.razor
  │   ├── MainLayout.razor.css
  │   ├── NavMenu.razor
  │   ├── NavMenu.razor.css
  │   └── SurveyPrompt.razor
  ├── _Imports.razor
  └── wwwroot
      ├── css
      │   ├── app.css
      │   ├── bootstrap
      │   │   ├── bootstrap.min.css
      │   │   └── bootstrap.min.css.map
      │   └── open-iconic
      │       ├── FONT-LICENSE
      │       ├── ICON-LICENSE
      │       ├── README.md
      │       └── font
      │           ├── css
      │           │   └── open-iconic-bootstrap.min.css
      │           └── fonts
      │               ├── open-iconic.eot
      │               ├── open-iconic.otf
      │               ├── open-iconic.svg
      │               ├── open-iconic.ttf
      │               └── open-iconic.woff
      ├── favicon.ico
      ├── index.html
      └── sample-data
          └── weather.json

ソースコードは下記に置いてます。

アプリ起動

ということで早速実行してみましょう。 BlazorWasmSampleまで移動して下記コマンドで実行します。

$ dotnet run

https://localhost:5001 にアクセスします。 シンプルで今風な感じの画面が表示されました。 Homeメニューには、おなじみの「Hello world」ですね。

Counterメニューをみてみましょう。 ボタンをポチポチするとカウントアップされていきます。

最後はFetch dataページです。 こちらはGrid的な表の画面です。 標準のtableなので編集やソートなどフィルタリングなどはできません。 このあたりはいい感じのパッケージがないかまた探してみます。

ソースを確認

Counterページのソースを確認してみましょう。

Counter.razor

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

これだけで書けてしまうんですね。 すごい。シンプル。

ボタンをクリックすると @code 内の変数 currentCount がインクリメントされ 画面上の @currentCount に反映されるようです。

Todoを追加してみる

◆ 追加 / 編集ファイル

では、TodoMVCのような ページを追加してみましょう。 追加、編集するファイルは下記の4つだけです。

BlazorWasmSample
  ├── Pages
  │   ├── [★] Todo.cs
  │   └── [★] Todo.razor
  ├── Shared
  │   ├── [★] NavMenu.razor
  ├── [★] TodoItem.cs
◆ TodoItemクラス追加

まずはTodoアイテムのクラスを追加します。

TodoItem.cs

public class TodoItem
{
    public string Content {get; set;}
    public bool IsDone {get; set;}
}
◆ Viewと処理を分けてファイルを追加

次に画面と処理ですが、コードが長くなってしまうのでrazorファイルにはViewのみとし、処理はpartialクラスにしてTodo.csと、ファイルを2つに分けて追加します。

Todo.razor

@page "/todo"

<h1>Todo</h1>

・・・

Todo.cs

namespace BlazorWasmSample.Pages
{
    public partial class Todo
    {
        ・・・
    }
}
◆ 入力したTodoアイテムを追加

Todoを入力してEnterでどんどん追加していくようにします。
※現状、Enterを2回押さないとリストに追加されないのでまた時間ある時にデバッグして調べてみます。。。

Todo.razor

<input style="width: 100%" placeholder="What needs to be done?" @bind="newItem" @onkeydown="@Enter" />

Todo.cs

// Todoアイテム全て
private List<TodoItem> allTodos = new List<TodoItem>();
// 追加するアイテム
private string newItem;
// 表示用のTodoアイテム
private List<TodoItem> todosForDisplay = new List<TodoItem>();
// 現在の表示状態
private State currentState;
// 表示状態
private enum State
{
    All,
    Active,
    Completed,
}

/// <summary>
/// アイテムを追加
/// </summary>
private void AddItem() {
    if (!string.IsNullOrWhiteSpace(newItem))
    {
        allTodos.Add(new TodoItem() { Content = newItem, });
        this.ShowTodo();
        newItem = string.Empty;
    }
}

/// <summary>
/// Todoアイテムを表示
/// </summary>
private void ShowTodo()
{
    switch (this.currentState)
    {
        case State.All:
            todosForDisplay = allTodos;
            break;
        case State.Active:
            todosForDisplay = allTodos.Where(x => !x.IsDone).ToList();
            break;
        case State.Completed:
            todosForDisplay = allTodos.Where(x => x.IsDone).ToList();
            break;
    }
}

/// <summary>
/// Enterキーイベント
/// </summary>
/// <param name="e">イベントデータ</param>
private void Enter(KeyboardEventArgs e)
{
    Console.WriteLine($"KeyboardEventArgs : Code[{e.Code}] Key[{e.Key}]");
    if (e.Key == "Enter")
    {
        this.AddItem();
    }
}
◆ Todoリスト表示

追加したアイテムは下記のように表示します。 TodoItem.IsDoneをCheckBoxに、TodoItem.ContentをTextBoxにバインドしてます。

Todo.razor

<ul>
    @foreach (var item in todosForDisplay)
    {
        <li>
            <input type="checkbox" @bind="item.IsDone" />
            <input @bind="item.Content"/>
        </li>
    }
</ul>
◆ Todoアイテムのチェック変更

追加したアイテムのCheckをONにすると取消線を入れ文字色をグレーに、OFFにすると黒に戻すようスタイルと処理を追加します。

Todo.razor

<ul>
    @foreach (var item in todosForDisplay)
    {
        <li>
            <input type="checkbox" @bind="item.IsDone" />
            <input @bind="item.Content" style="color: @GetTextStyles(item).foreColor; text-decoration: @GetTextStyles(item).decoration"/>
        </li>
    }
</ul>

色と装飾をTupleで返します。

Todo.cs

/// <summary>
/// Todoアイテムのテキストスタイルを取得
/// </summary>
/// <param name="item">Todoアイテム</param>
/// <returns>文字色と取消線を付けるかどうかのセット</returns>
private (string foreColor, string decoration) GetTextStyles(TodoItem item)
{
    return item.IsDone ? ("lightgray", "line-through") : ("black", "none");
}
◆ 未完了アイテムの残数表示

未完了のActiveなTodoItemのカウントを左下に表示します。View側に直接LINQでバインドしちゃいます。

Todo.razor

@allTodos.Count(x => !x.IsDone) items left
◆ 表示内容の切り替え

全て / 未完了 / 完了と表示を切り替えるボタンを追加し、メソッドをView側でバインドする。

Todo.razor

<button @onclick="ShowAll">All</button>
<button @onclick="ShowActive">Active</button>
<button @onclick="ShowCompleted">Completed</button>

Todo.cs

/// <summary>
/// Todoアイテム全て表示
/// </summary>
private void ShowAll() {
    currentState = State.All;
    this.ShowTodo();
}

/// <summary>
/// Todo未完了アイテム表示
/// </summary>
private void ShowActive() {
    currentState = State.Active;
    this.ShowTodo();
}

/// <summary>
/// Todo完了アイテム表示
/// </summary>
private void ShowCompleted() {
    currentState = State.Completed;
    this.ShowTodo();>
}
◆ 完了アイテムのクリア

完了アイテムが一つ以上ある場合はClear completedボタンを表示するようにし、 クリックで完了アイテムをメモリから削除する。

Todo.razor

<button @onclick="ClearCompleted" style="display: @GetDisplay()">Clear completed</button>

Todo.cs

/// <summary>
/// Todo完了アイテムをクリア
/// </summary>
private void ClearCompleted()
{
    var completedTodos = allTodos.Where(x => x.IsDone);
    allTodos = allTodos.Except(completedTodos).ToList();
    this.ShowTodo();
}

/// <summary>
/// ClearCompletedボタン表示スタイルを取得
/// </summary>
/// <returns>表示スタイル [inline-block / none]</returns>
private string GetDisplay()
{
    return allTodos.Any(x => x.IsDone) ? "inline-block" : "none";
}
◆ メニューへTodoページを追加

最後に、Todo.razorで指定した @page "/todo" へのリンクをメニューに追加します。

NavMenu.razor

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="todo">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Todo
            </NavLink>
        </li>
    </ul>
</div>

さいごに

記事の内容は長くなってしまいましたが、思ってたよりもスッキリとコードが書けるのでサクっと作れてしまいました。 ただブラウザにランタイムや.NETアセンブリをダウンロードして実行するので機能の多いアプリにしてしまうとダウンロードに時間がかかってしまうようです。 それでも手軽にSPAをC#で作れちゃうってなかなかイイですね。

今回追加したTodoはスタイルとか特にセットしてないので味気ない感じになってますが MatBlazorのようなマテリアルデザインのUIフレームワークもあり 面白そうなので近々使ってみようと思います。

以上、もりもりでした。