ページを組み立てる
ページは Rust の関数で組み立てた El の値の木で、サーバーが HTML に描画します。
要素
html モジュールには div()、p()、button() のようによく使うタグごとの関数と、それ以外のタグに使う el("tag") があります。どれも El<S> を返します。S はページのシグナル構造体です。El のメソッドは要素そのものを返すので、木全体を 1 つの式で書けます。
use hypersig::Route;
use hypersig::html::{a, div, h1, p};
hypersig::routes! {
fn pages(Router<()>);
const ADA: Route = "/ada" => ada;
}
let card = div::<Profile>()
.id("ada")
.child(h1().text("Ada Lovelace"))
.child(
p().text("Wrote the first program. ")
.child(a().href(ADA.link()).text("Read more")),
);
assert_eq!(
card.render(),
r#"<div id="ada"><h1>Ada Lovelace</h1><p>Wrote the first program. <a href="/ada">Read more</a></p></div>"#,
);text は固定のテキストを、child は要素か文字列を末尾に追加します。href は Route からページへのリンクを張ります。これは ページとリンク で説明します。attr は普通の属性を設定します。id と class には専用のメソッドがあり、attr にこの 2 つや data-hs- で始まる名前を渡すとパニックします。data-hs- の属性は型付きのメソッドだけが書きます。
ページ
Page は木を包んで完全な HTML 文書にします。Page::new はタイトルとシグナルの初期値を受け取り、child は <body> に要素を追加し、render はハンドラが送る HTML を返します。文書は RUNTIME_PATH からランタイムを読み込みます。このパスは /_hypersig/ の下にあり、ファイルのハッシュを名前に含みます。RouterExt::hypersig_runtime がそのパスでファイルを配信します。
シグナルに追従する値
ブラウザで変わる値を要素に表示できます。束縛はそれぞれシグナルを使った式を受け取り、最初は初期値で描画されるので、HTML にはランタイムが計算するのと同じ値が入っています。
use hypersig::html::{button, p, span};
use hypersig::{Signals, if_else};
use serde::{Deserialize, Serialize};
#[derive(Signals, Serialize, Deserialize)]
struct Upload {
progress: i32,
busy: bool,
}
let progress = Upload::progress();
let busy = Upload::busy();
// Text that follows a signal.
let label = span::<Upload>().bind_text(progress.to_text().concat(" %"));
// An attribute that follows a signal, and one that is present while a flag is true.
let cancel = button::<Upload>()
.bind_attr("aria-label", if_else(busy, "Cancel the upload", "Nothing to cancel"))
.attr_when("disabled", !busy)
.text("Cancel");
// An element shown only while the upload runs.
let notice = p::<Upload>().show(busy).text("Uploading…");bind_textは要素のテキストを置き換えます。bind_attrは属性を文字列の式の値に合わせ続けます。attr_whenは、条件が成り立つ間だけdisabledのような真偽値の属性を付けます。showは条件が偽の間、要素を隠します。when(cond, el)は要素を取り除くので、要素が戻るたびにon_loadがもう一度実行され、入力欄は空から始まります。class_whenとcss_whenは、条件が成り立つ間だけクラスやスタイルを付けます。詳しくは スタイル で説明します。
このデモはテキスト欄を name に束縛し、空かどうかで 2 つの段落のどちらかを表示します。
実行結果
use hypersig::html::{div, p, span};
use hypersig::style::Style;
use hypersig::style::pattern::stack;
use hypersig::style::value::rem;
use hypersig::{Page, Signals};
use hypersig_ui::TextField;
use serde::{Deserialize, Serialize};
#[derive(Signals, Serialize, Deserialize)]
pub struct Greeting {
name: String,
}
pub fn page() -> Page<Greeting> {
let name = Greeting::name();
let typed = !name.trim().is_empty();
Page::new(
"あいさつ",
Greeting {
name: String::new(),
},
)
.child(
div()
.css(stack(rem(1)).max_width(rem(20)))
.child(TextField::new(
"名前",
name,
))
.child(p().show(!typed.clone()).text("上の欄に名前を入力してください。"))
.child(
p().show(typed)
.text("こんにちは、")
.child(span().bind_text(name.trim()))
.text("さん。"),
)
.child(
p().css(Style::new().font_size(rem(0.875)))
.show(name.char_count().gt(12))
.text("長い名前です。"),
),
)
}コンポーネント
コンポーネントは El<S> を返す Rust の関数です。S についてジェネリックにし、読むシグナルを引数で受け取れば、どのページでも使えます。
use hypersig::html::{El, input, label, span};
use hypersig::{Sig, Signals};
use serde::{Deserialize, Serialize};
/// A labeled text field bound to `value`, on any page.
fn field<S>(text: &str, value: Sig<S, String>) -> El<S> {
label()
.child(span().text(text))
.child(input().bind(value))
}
#[derive(Signals, Serialize, Deserialize)]
struct Signup {
name: String,
email: String,
}
let form = hypersig::html::form::<Signup>()
.child(field("Name", Signup::name()))
.child(field("Email", Signup::email()));hypersig_ui は shadcn/ui に倣った見た目のこうした関数を集めたクレートです。このサイトのデモはそのボタンとテキスト欄を使っています。