日記データのデータベースからの読み込み
JavaScriptの用意ができましたので、次に、カレンダーの日付をクリックしたときの処理を追加します。日付をクリックしたら、データベースから該当の日付の日記を読み込むようにします。
BlazorでJavaScriptのコードと連携するには、JSRuntimeというオブジェクトを利用します。index.razorの冒頭に、@injectディレクティブで、JSRuntimeオブジェクトを定義します(IJSRuntime型はインターフェースです)。そして、ClickHandlerメソッドを次のように変更します。
@page "/"
@inject IJSRuntime Js
<Calendar OnClick="ClickHandler" />
~略~
@code {
// 日記データクラス
public class Diary
{
public DateTime SelectDay { get; set; }
public string Text { get; set; }
}
private async Task ClickHandler(DateTime dt)
{
var v = await Js.InvokeAsync<Diary>("getDiary", dt);
TextValue = v?.Text ?? "日記がありません";
}
}
~略~
日付がクリックされると、該当の日付を引数にしてClickHandlerメソッドが呼び出されます。ClickHandlerメソッドでは、JSRuntimeのInvokeAsyncメソッドを使って、JavaScriptで記述したgetDiaryメソッドを呼び出します。なお、getDiaryメソッドには戻り値があるので、ジェネリックの型引数に、戻り値として受け取りたい型を指定します。ここでは、日記データクラスのDiaryとしています。
メソッドの前にawait、最後にはAsyncキーワードがついていることからわかるように、このJavaScriptのメソッド呼び出しは、非同期での実行になります。そのため、JavaScript側でのメソッドも、値をそのまま返すものだけでなく、Promiseオブジェクトを返すものも利用できます。Promiseオブジェクトとは、非同期処理の最終的な結果を表すオブジェクトで、非同期処理をあつかいやすくするために導入されたものです。IndexedDBは、基本的に非同期での処理なので、Dexie.jsも、多くはPromiseオブジェクトを返すメソッドとなっています。
JavaScriptのコードとの連携では、オブジェクトはJSON文字列にシリアライズされてやりとりされます。基本的には、C#で定義したデータ型をそのままJavaScriptのコードであつかえるようになります。ここでは、DateTime型のオブジェクトも、そのままJavaScriptのメソッドの引数に渡しています。
Diaryクラスは、日記のデータクラスです。先ほどのDexie.jsのstoresメソッドで指定した、SelectDayという名前のプロパティが必要となります。それ以外は任意のプロパティが定義できます。ここでは日記の本文を、Textプロパティとしました。
getDiaryメソッドでDiary型のオブジェクトが返されない、または日記本文がない場合は、「日記がありません」という文字列を、TextValueに設定します。
日記データのデータベースへの保存
次に、日記データをデータベースに保存する処理を追加します。日記を保存するボタンを追加して、ボタンが押されたときに、該当の日付をキーとして、入力した日記本文(TextValueプロパティ)をデータベースに書き込みます。
<Calendar @ref="cal" OnClick="ClickHandler" />
<button class="btn btn-primary mt-4"
@onclick="@(() => WriteDiary())" >日記を保存する</button>
~略~
@code {
private Calendar cal; // カレンダーコンポーネントの参照用
// 日記を保存する
private async Task WriteDiary()
{
var key = await Js.InvokeAsync<string>("putDiary",
new Diary{SelectDay = cal.SelectDay, Text = TextValue});
}
index.razorでは、Calendarコンポーネントを挿入するタグを変更します。@refディレクティブを追加して、親ページからコンポーネントを参照できるようにします。またコード部分にも、Calendarコンポーネントを参照する変数を追加します。これで、現在選択されている日付(cal.SelectDay)を、親ページで取得できるようになります。
日記の保存は、JavaScriptのputDiaryメソッドを呼び出します。引数には、日記データクラスのオブジェクトを作成して指定します。ここでは、オブジェクト初期化子を使って、プロパティに値を割り当てています。
カレンダー表示の機能を追加
ここまでで日記の機能は、ひととおり実装できました。せっかくなので、もう少し機能を追加してみましょう。
日記を保存している日を判定する
日記が存在する日を、土日とは別の背景色にして、カレンダー上でわかるようにします。この機能を実現するには、カレンダーを表示する前に、日記が存在する日をリストアップする必要があります。
まずは、日記が存在する日を保存するために、カレンダーコンポーネントに、DateTime型の配列のExistsDatesプロパティを追加します。
~略~
@code {
// 日記が存在する日の保存用
public DateTime[] ExistsDates { get; set; }
~略~
そして、ExistsDatesプロパティに設定するためのメソッドを、index.razorに追加します。
@code {
// 日記がある日をExistsDatesプロパティに設定する
private async Task CheckDiary(DateTime dt)
{
// 月初
DateTime s = new DateTime(dt.Year, dt.Month, 1);
// 翌月月初
DateTime e = s.AddMonths(1);
// 指定月に存在する日記の日付を取得する①
cal.ExistsDates = await Js.InvokeAsync<DateTime[]>("getKeys", s, e);
}
~略~
}
日記が存在する日を取得するには、先ほどのJavaScriptのコードで追加したgetKeysメソッドを利用します。該当月の月初と翌月月初を指定して、その範囲に存在する日記の日付を取得します(①)。そして、DateTime型の配列として受け取った日付を、カレンダーコンポーネントのExistsDatesプロパティに設定します。
Blazorページのライフサイクル
追加したCheckDiaryメソッドは、カレンダーを表示する前に呼び出す必要があります。この実行タイミングを把握するには、Blazorページのライフサイクルを利用します。
Blazorページで生成されるクラスは、ComponentBaseクラスの派生クラスとなります。ComponentBaseクラスには、ページが表示される際のイベントで呼び出される仮想メソッドが定義されています。この仮想メソッドをオーバーライドすれば、ページが表示される特定のタイミングで実行する処理を追加することができます。
ComponentBaseクラスに定義されている主な仮想メソッドは次のとおりです。メソッドの最後にAsyncがついているのは非同期処理用のメソッドです。
| メソッド名 | 呼び出されるタイミング |
|---|---|
| OnInitialized | コンポーネントが読み込まれたとき |
| OnInitializedAsync | 〃 |
| OnParametersSet | 親コンポーネントのパラメータを受け取ったとき |
| OnParametersSetAsync | 〃 |
| OnAfterRender | DOM要素が組み立てられた後 |
| OnAfterRenderAsync | 〃 |
index.razorに、CheckDiaryメソッドを呼び出すコードを追加します。
@code {
~略~
// DOMが組み立てられた後に呼び出される
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// 初回のみ実行する①
if (firstRender) {
await CheckDiary(cal.SelectDay);
StateHasChanged();
}
}
private async Task WriteDiary()
{
~略~
await CheckDiary(cal.SelectDay); // 書き込み後に表示する②
}
}
CheckDiaryメソッドを呼び出すのは、いずれのタイミングの仮想メソッドでも良さそうに思えますが、OnAfterRenderおよびOnAfterRenderAsyncメソッド以外では、実行時に例外が発生してしまいます。CheckDiaryメソッド内で、JavaScriptのコードを呼び出しているのが原因のようです。また、OnAfterRenderメソッドでは、同期処理用のため、例外が発生しないものの、初期画面で反映されないなど、うまく動作しません。
なお、OnAfterRenderおよびOnAfterRenderAsyncメソッドでは、bool型の引数があります。この引数は、初回の呼び出しの際にtrueになり、2回目以降はfalseとなります。CheckDiaryメソッドは、最初の一度だけ実行すれば問題ないので、引数がtrueのときだけ実行しています(①)。またStateHasChangedメソッドも実行していますが、これは、ページを最初に表示した際にも、日記が存在する日を表示させるためです。
WriteDiaryメソッドに追加したのは、日記を書き込んだ直後に、その日の表示を変更させるためです(②)。
なお、カレンダーの月をボタンで変更した場合、CheckDiaryメソッドが呼び出されないので、日付のボタンがクリックされたときと同様に、イベントを通知する処理を追加します。そして、そのイベントハンドラに、CheckDiaryメソッドを指定します。コードの説明は割愛しますが、サンプルファイルをダウンロードして確認してみてください。
カレンダーコンポーネントの変更
カレンダーコンポーネントは、次のようにコードを変更します。
~略~
// css用文字列の作成
var css = "calendar-day "
+ day switch
{
_ when (day.Date == Today.Date) => "today",
_ when (ExistsDates?.Contains(day) ?? false) => "exists",
{DayOfWeek: DayOfWeek.Saturday} => "saturday",
{DayOfWeek: DayOfWeek.Sunday} => "sunday",
_ => ""
};
~略~
@code {
public DateTime[] ExistsDates { get; set; }
~略~
}
日にちのCSSクラスの文字列を組み立てるコードには、追加したExistsDatesプロパティに、該当の日が含まれるかどうかを判定する処理を追加します。すでに日記が存在する日には、existsという名のクラス名を付加します。
なお、カレンダー表示用のCSSは、wwwrootフォルダのcss\app.cssファイルに追加します。スタイルについての説明は割愛しますので、サンプルファイルをビルドして実際に確認してみてください。
最後に
2回にわたって日記アプリを作成しました。ごくシンプルな仕様だったものの、Blazorならではの処理も多かったのではないでしょうか。読者のみなさんの参考になれば幸いです。
次回からは、ASP.NET Core Hostedを使ったアプリを作成する予定です。
