コンポーネントの作成
次に、カレンダー機能を実現するコンポーネントを作成しましょう。コンポーネントといっても、特に難しいわけではありません。Blazorのコンポーネントは、入力フォームなどのUI要素を持つRazorページとして作成します。
ソリューションエクスプローラーのSharedフォルダを選択して、右クリックし、[追加]ー[Razorコンポーネント]を選びます。「新しい項目の追加」というダイアログが表示されますので、「Calendar.razor」という名前にして、追加ボタンをクリックします。
すると、h3タグと空の@codeディレクティブだけのファイルが作成されます。今回、h3タグは不要ですので削除し、代わりに次のような、カレンダーをテーブルタグで作成するHTMLコードを追加しましょう。なお、SelectDay、NextMonthは、それぞれ後で追加するプロパティとメソッドです。
<div class="calendar">
<table>
<caption>
<button class="calendar-day"
@onclick="@(() => NextMonth(-1))">◀</button>
@SelectDay.ToString("yyyy年MM月dd日")
<button class="calendar-day"
@onclick="@(() => NextMonth(1))">▶</button>
</caption>
<thead>
<tr>
<th class="calendar-day">日</th>
<th class="calendar-day">月</th>
<th class="calendar-day">火</th>
<th class="calendar-day">水</th>
<th class="calendar-day">木</th>
<th class="calendar-day">金</th>
<th class="calendar-day">土</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
@code {}
前回説明したように、@onclickディレクティブを使うと、クリック時のイベントハンドラを指定することができます。つまりここでは「button要素をクリックするとNextMonthメソッドが呼ばれる」という意味となります。
なお、イベントハンドラに引数を渡す場合は、次のようなラムダ式の構文で記述する必要があります。
() => NextMonth(1)]
単に、@NextMonth(1)のように書きたくなりますが、コンパイルエラーになってしまうので注意しましょう。
1か月分の日付の作成
今回のカレンダーは、該当の1か月を表示するものです。1か月分の日付を表示する方法は、いろいろ考えられそうですが、今回は、週単位のDateTime型の配列を作成する方法にしてみました。Calendar.razorに、次のようなコードを追加します。
~略~
@code {
// 本日の設定
public DateTime Today { get; } = DateTime.Now;
// 1か月分の週毎のDateTime配列を受け取る
public IEnumerable<DateTime[]> Weeks;
// 選択されている日
public DateTime SelectDay { get; set; }
// 1か月分の週毎のDateTime配列を返す
public IEnumerable<DateTime[]> GetWeeks()
{
// 月初
DateTime day = new DateTime(SelectDay.Year, SelectDay.Month, 1);
DateTime end = day.AddMonths(1).AddDays(-1); // 月末(翌月1日の前日)
var week = new DateTime[7];
while (day.Month == SelectDay.Month) // 該当の月の間
{
week[(int)day.DayOfWeek] = day; // 1週分の配列に設定する②
// 土曜日または月末のとき ③
if (day.DayOfWeek == DayOfWeek.Saturday || day.Day == end.Day)
{
yield return week;
Array.Clear(week, 0, week.Length);
}
day = day.AddDays(1); // 翌日にする
}
}
protected override void OnInitialized()
{
SelectDay = Today; // 今日を設定する
Weeks = GetWeeks(); // 当月の週ごとのDateTime配列を取得する
}
}
GetWeeksメソッドは、該当月の1日から月末までを、週単位にDateTime型の配列で返すものです。
DateTimeクラスのDayOfWeekプロパティは、週を示す列挙型定数で、日曜が0、順に土曜が6の数値として取得可能です。コードの②では、そのDayOfWeekプロパティの値をそのまま利用して、配列の添字として指定しています。
カレンダー表示の1週間は、土曜日または月末で終わりなので、その場合は、yield returnで配列を返すようにしています(③)。
OnInitializedメソッドは、コンポーネントが初期化されるときに呼び出されるメソッドです。ここでは、本日の日付の設定と、Weeksプロパティに当月分のDateTime配列を取得するコードを追加しています。
日付の設定
C#のコード部分ができましたので、次に1か月の日付を示すWeeksプロパティを、HTMLに反映させましょう。さきほどのカレンダテーブルのtbodyタグを、次のコードに変更します。
~略~
<tbody>
@foreach (var days in Weeks)
{
<tr>
@foreach (var day in days)
{
<td class="calendar-day">
@if (day != default(DateTime))
{
// css用文字列の作成①
var css = "calendar-day "
+ day switch
{
_ when (day.Date == Today.Date) => "today",
{ DayOfWeek: DayOfWeek.Saturday } => "saturday",
{ DayOfWeek: DayOfWeek.Sunday } => "sunday",
_ => ""
};
<button class="@css"
@onclick="@(() => DayClick(day))">@day.Day</button>
}
</td>
}
</tr>
}
</tbody>
~略~
@foreach、@ifディレクティブは、名前から想像できるとおり、C#の同じ名前のステートメントと同様の機能となります。タグの後の()に、C#のコードが記述できます。ここでは、@foreachディレクティブを二重に使って、Weeksプロパティから、週単位で日ごとのDateTimeオブジェクトを取り出しています。各日付の表示は、button要素で作成しています。
コード①では、button要素のclass属性に設定するクラス名を、cssという変数に設定しています。このクラス名は、日付(button)のデザインを「本日」「土曜日」「日曜日」で変更するためのものです。「本日」や曜日の判定、クラス名の文字列作成のところは、素直にIF文で書いても問題ありませんが、ここではC# 8.0のswitch式を利用してみました。
また、button要素には、イベントハンドラ「DayClick」を追加しています。DayClickの引数は、クリックされた日を示すDateTimeオブジェクトとなります。
【note】switch式
C# 8.0のswitch式では、従来のswitch構文のcaseやbreakキーワードの代わりに、「=>」という記号を使用します。さらに判定する変数は、switchキーワードの前に記述します。またswitch式での条件判定では、パターンマッチングと呼ばれる機能を使い、柔軟に記述できるようになっています。
今回のコードのswitch式にある、「_(アンダースコア) 」は、「判定する変数がすべての場合に一致する」という意味です。whenキーワードは、フィルター的に条件を追加したい場合に使います。
{ DayOfWeek: DayOfWeek.Saturday } は、プロパティパターンと呼ばれるもので、プロパティと判定したい値を「:(コロン)」でつなぎます。つまり、変数「day」のプロパティDayOfWeekが、DayOfWeek.Saturdayと一致する場合、という意味になります。
最後の、「_ => ""」は、不要のようにも思えますが、switch式では、すべての条件を網羅する必要があるため追加しています。switch構文でのdefaultラベルと同様の意味となります。
コンポーネントのイベント連携
次に、カレンダーの表示を、「前月」「来月」に変更する処理を追加しましょう。captionタグ内にあるボタンのクリックで変更できるようにします。すでにbutton要素にはハンドラを指定していますので、ここではイベントハンドラとして呼び出されるNextMonthメソッドを追加します。
それから、各日付をクリックしたときの処理も追加しておきます。イベントハンドラであるDayClickメソッドを、@codeディレクティブ内に追加します。
~略~
private void NextMonth(int n) => SelectDay = SelectDay.AddMonths(n);
[Parameter]
public EventCallback<DateTime> OnClick { get; set; }
// 日付がクリックされた時に呼ばれる
private void DayClick(DateTime d)
{
SelectDay = d; // クリックされた日を設定する
OnClick.InvokeAsync(d); // イベントを通知する①
}
~略~
NextMonthメソッドは、DateTimeクラスのAddMonthsメソッドを呼び出すだけです。AddMonthsメソッドの引数を負にすれば、過去の日付に設定することができます。
DayClickメソッドでは、選択された日の設定と共に、イベントを通知するコードを追加しています(①)。これは、コンポーネントで発生したイベントを、そのコンポーネントを呼び出している側にも通知する処理です。
このようにイベントを連携するには、EventCallback型のプロパティを追加して、InvokeAsyncメソッドを呼び出します。するとこのプロパティを介して、呼び出し側のイベントハンドラが実行されます。なお、このプロパティはコンポーネント外から操作するパラメータとなりますので、[Parameter]を付加して宣言しておきます。
これでカレンダーコンポーネントの完成です。
コンポーネントの表示
カレンダーコンポーネントが完成しましたので、index.razorの編集に戻りましょう。カレンダーコンポーネントの表示は、次のように簡単です。
<Calendar OnClick="ClickHandler" />
~略~
@code {
void ClickHandler(DateTime dt){ }
}
~略~
カレンダーコンポーネントがクリックされると、ハンドラであるClickHandlerメソッドが呼び出されます。ClickHandlerメソッドについては、次回解説します。
カレンダー用のCSSの設定がない場合は、デフォルトのボタン表示となります。
最後に
日記の本文を、データベースから読み込む処理/保存する処理は、次回解説することにします。
