要素と装飾

BlazorCodeFirst は HTML を直に写します。Body の式に書いた要素の名前が、そのまま出力される要素の名前です。HTML の要素名とは別に独自の部品名を覚える必要はなく、実行時の UI ツリーもありません。

要素#

HTML の要素にはヘルパーがあります。名前はタグ名の先頭1文字だけを大文字にしたもので、FigCaption ではなく Figcaption、同じく ColgroupTextarea です。属性は要素に繋げ、子は角括弧に入れます。

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> の中だけに現れる要素。 htmlheadbodytitlebasemetalink
  • 生テキスト要素。scriptstylenoscript
  • レンダーツリーでは意味を持たない要素。templateslot
  • object。C# のキーワードと名前が重なります
  • 外来要素の svgmath。その配下すべて
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 要素の名前をすべて取り込みます。単純名の解決では、自分で宣言した名前が取り込まれた名前に勝ちます。LabelDataSummarySource という 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)。

.Attrstring?bool を取ります。bool は Blazor の条件付き属性です。true なら値の空な属性として出力します。HTML が disabledcheckedhidden を有効と読むのは、この形です。 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))

ハンドラー#

ActionFunc<Task> として書いたハンドラーは、何も受け取りません。イベントを読むには、ラムダの引数に型を書きます。すると .On が型付きのオーバーロードを選びます。

Input.Type("text").Attr("value", _name)
     .On("oninput", (ChangeEventArgs e) => _name = e.Value?.ToString() ?? "")

Razor と違って、引数の型はイベント名から推論されません。オーバーロードを選ぶのは、引数に書いた型です。ChangeEventArgsMicrosoft.AspNetCore.Components にあります。MouseEventArgsKeyboardEventArgsFocusEventArgsMicrosoft.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 を報告します。

装飾を書ける場所#

装飾は、単一の要素を対象にしなければなりません。IfForEachFragmentRaw、コンポーネントの結果に付けると BCF3008 を報告します。子の後ろに繋げて書いた場合(Div["text"].Class("card"))も同じです。角括弧は、もう View を作り終えています。

装飾は、このライブラリが宣言しているものである必要もあります。綴り違い(Div.Clas("card"))や、要素を取って要素を返す自作の拡張メソッドは BCF3026 を報告します。

次に読むもの#

An unhandled error has occurred. Reload 🗙