要素と装飾
BlazorCodeFirst は HTML を直に写します。Body の式に書いた要素の名前が、そのまま出力される要素の名前です。HTML の要素名とは別に独自の部品名を覚える必要はなく、実行時の UI ツリーもありません。
要素#
HTML の要素にはヘルパーがあります。名前はタグ名の先頭1文字だけを大文字にしたもので、FigCaption
ではなく Figcaption、同じく Colgroup と Textarea です。属性は要素に繋げ、子は角括弧に入れます。
protected override View Body => Figure[ Img.Src("/diagram.png").Alt("Architecture"), Figcaption["The compilation pipeline"]];
<figure> <img src="/diagram.png" alt="Architecture"> <figcaption>The compilation pipeline</figcaption> </figure>
HTML Living Standard が適合と認める要素には、すべて専用のヘルパーがあります。
Element は、ヘルパーの無い要素を受け持ちます。対象は2種類です。ひとつはカスタム要素と Web
Components で、そのタグ名にヘルパーはありません。もうひとつは、ヘルパーが用意されていない数少ない標準要素です。
- 文書そのものと
<head>の中だけに現れる要素。html、head、body、title、base、meta、link - 生テキスト要素。
script、style、noscript - レンダーツリーでは意味を持たない要素。
template、slot object。C# のキーワードと名前が重なります- 外来要素の
svgとmath。その配下すべて
private const string Widget = "my-widget"; Element(Widget).Attr("value", "42") // カスタム要素 Element("svg")[Element("circle")] // 外来要素 Element(_kind + "-widget") // BCF3009: 定数ではない Element("my widget") // BCF3009: タグ名の形ではない
タグは、タグ名の形をしたコンパイル時定数である必要があります。そうでなければ BCF3009 を報告します。
子に置けるもの#
文字列をそのまま書くと、テキストノードになります。そのため Text() のような構文は用意していません。子ノードの無い要素は角括弧ごと省きます。
protected override View Body => Div[ "plain text, then ", A.Href("/docs")["a link"], Br, Img.Src("/logo.png").Alt("Logo")];
<div>plain text, then <a href="/docs">a link</a><br><img src="/logo.png" alt="Logo"></div>
Blazor の RenderFragment は、ほかの子と同じように子の並びへ置けます。
[Parameter] public RenderFragment? ChildContent { get; set; } protected override View Body => Div["before", ChildContent];
ジェネレーターは、子のひとつひとつを独立した式として読めなければ、シーケンス番号を振れません。子をコレクション式で入れ子に書くのは受け付けます。C# がそれを同じ呼び出しへ展開するからです。
Div[["a", "b"]] // Div["a", "b"] と同じ。書くならこちら
ジェネレーターが読み取れない子の並びを渡すと、BCF1003 を報告します。繰り返しには ForEach を使ってください。
空要素は子を取らない#
HTML 標準の空要素は13個あり、どれも閉じタグを持ちません。そのため子を書くと BCF3016 を報告します。
Img.Src("/logo.png")["Logo"] // BCF3016 Element("img")["Logo"] // BCF3016。同じ規則 Img.Src("/logo.png").Alt("Logo") // こう書く
空要素は装飾で設定して、内容はその隣に置いてください。この API が HTML について検査するのは、ここまでです。この線引きは意図したものです。Table[Div["x"]] もハイドレーションの後で表示が変わりますが受け付けますし、要素が定義していない属性(Div.Href("/x"))も同じです。方針の全体は
DESIGN.md §4.1 にあります。
名前が衝突する場合#
using static BlazorCodeFirst.Html; は、適合する HTML 要素の名前をすべて取り込みます。単純名の解決では、自分で宣言した名前が取り込まれた名前に勝ちます。Label、Data、Summary、Source
という Blazor のパラメーター名はありふれているので、この衝突は実際に起きます。
[Parameter] public string Data { get; set; } Div[Data["Heading"]] // BCF3027 Div[Html.Data["Heading"]] // こう書く
自分の型、名前空間、メソッドも、同じように要素ヘルパーの名前を奪います。どれも BCF3027 で、奪ったものが何かを示します。
装飾#
装飾は、それが属する要素に、子より前に繋げて書きます。HTML が属性をタグの中に書くのと同じ並びです。装飾はラッパーのノードを作らず、所有する要素の属性へ畳み込まれます。class は畳み込まれるので、.Class を2回以上繋げると値は1つの属性にまとまります。
protected override View Body => Button .Class("btn") .Class("btn-primary") .Title("Save the current document")["Save"];
<button class="btn btn-primary" title="Save the current document">Save</button>
使える装飾は .Class、.Role、.OnClick、汎用の手段である .Attr(name, value) と
.On(eventName, handler)、そして142個の標準由来ショートカットです。HTML Living Standard の属性名が
C# の識別子にそのまま綴れるものへ、1つずつ対応します。前段で使った .Href、.Src、.Alt、
.Type、.Title もその一部です。全部の一覧は要素リファレンスにあり、string? を取るか Blazor の条件付き bool を取るかで分けています。
.On には on を含んだ属性名をそのまま渡します(.On("onmouseenter", …))。接頭辞をこちらで補うことはなく、on の無い名前は BCF3019 です。.Attr や .On に渡す名前は、空でないコンパイル時定数である必要があります(BCF3011)。
.Attr は string? か bool を取ります。bool は Blazor の条件付き属性です。true なら値の空な属性として出力します。HTML が disabled、checked、hidden を有効と読むのは、この形です。
false なら属性ごと出しません。いつも付く属性は、HTML と同じように値なしで書いてください。
bool は、付いたり付かなかったりする場合のためにあります。
Input.Type("checkbox").Attr("checked") // <input type="checkbox" checked> Button.Attr("disabled", _submitting)["Save"] // 条件付き
文字列の null も属性を出しません。そのため、値が付くこともあれば付かないこともある属性のために、要素を分岐で囲む必要はありません。
Span.Attr("title", _hasTip ? _tip : null)["Hover me"]
null と "" は別の値です。フレームでも、プリレンダリングされた HTML でも、再レンダリングでも変わりません。"" は title="" になり、null では title そのものが出ません。再レンダリングで値が null になったとき、Blazor は要素を差し替えるのではなく、すでに DOM にある要素から属性を取り除きます。値を1つ取る装飾は、どれもこの意味で null を受け付けます。
object のオーバーロードは、あえて用意していません。ほかの型の値は、レンダリングの時点で文字列になります。そのとき使われるのは書式化を実行するスレッドのカルチャーで、コンポーネントが動いたときのカルチャーではありません。どのカルチャーを選んだかがコードに現れるよう、自分で文字列にしてください。
Div.Attr("tabindex", index.ToString(CultureInfo.InvariantCulture))
ハンドラー#
Action や Func<Task> として書いたハンドラーは、何も受け取りません。イベントを読むには、ラムダの引数に型を書きます。すると .On が型付きのオーバーロードを選びます。
Input.Type("text").Attr("value", _name) .On("oninput", (ChangeEventArgs e) => _name = e.Value?.ToString() ?? "")
Razor と違って、引数の型はイベント名から推論されません。オーバーロードを選ぶのは、引数に書いた型です。ChangeEventArgs は Microsoft.AspNetCore.Components にあります。MouseEventArgs、
KeyboardEventArgs、FocusEventArgs は Microsoft.AspNetCore.Components.Web です。
.Web のほうも、Blazor のアプリがすでに参照している名前空間です。
型は推論されませんが、検査はされます。そのイベントが渡さない型を書くと
BCF3028 を報告します。判断のもとは、Razor が使うのと同じ
[EventHandler] のメタデータです。渡される型の基底クラスは受け付けます。ハンドラーが実際に受け取れるのがそれだからです。
Button.On("onclick", (MouseEventArgs e) => Zoom(e.ClientX, e.ClientY))["Zoom"] // 渡される型 Button.On("onclick", (EventArgs e) => Save())["Save"] // その基底。これでよい Button.On("onclick", (KeyboardEventArgs e) => Save())["Save"] // BCF3028
[EventHandler] の登録が無いイベントには、照合する対応表がありません。そのため登録していないカスタムイベントは検査しません。登録は Blazor の標準の仕組みで、自分のプロジェクトでの登録も読みます。
[EventHandler("onrate", typeof(RatingEventArgs))] public static class AppEventHandlers;
属性を出してイベントを受け取る、この対をひとつの装飾で書くのが
.Bindです。
class のチャネル#
class のチャネルは値をテキストとして繋ぐので、class は文字列しか取らない唯一の名前です。
.Attr("class", flag) は BCF3023 を報告します。
.Attr("class") も同じです。値を書かない形は、属性があることを表すだけで、繋ぐ文字列を持ちません。条件付きのクラスは文字列で書き、消したい項には null を渡してください。
Div.Class("card").Class(_selected ? "is-selected" : "")
null の項は連結から外れるので、class の装飾を1つだけ持つ要素は、その項が null のとき属性ごと消えます。連結する項がもう1つあるときは、区切りだけが残ります。たとえば
Div.Class("card").Class(_selected ? "is-selected" : null) を考えます。_selected が false のあいだ、出力は class="card " です。ブラウザーはこれを card 1つのクラスとして読みます。
ほかの属性とイベントは、どれも1回しかバインドできません。同じ要素に2度書くと
BCF3010 を報告します。style もそのひとつで、.Attr("style", …) と書きます。.Bind("class", …) は、この名前を書く3つ目の方法で、畳み込まれない唯一の方法です。そのため .Class と併せて書いた要素は BCF3024 を報告します。
装飾を書ける場所#
装飾は、単一の要素を対象にしなければなりません。If、ForEach、Fragment、Raw、コンポーネントの結果に付けると BCF3008 を報告します。子の後ろに繋げて書いた場合(Div["text"].Class("card"))も同じです。角括弧は、もう View を作り終えています。
装飾は、このライブラリが宣言しているものである必要もあります。綴り違い(Div.Clas("card"))や、要素を取って要素を返す自作の拡張メソッドは BCF3026 を報告します。