条件で出し隠し・必須・読み取り専用を切り替える
他の項目の値や新規/編集の状態で、表示・入力可否・必須を切り替える。区画ごとの出し分けも。
他の項目の値によって項目を出し隠しするには visibleWhen、入力できる/できないを切り替えるには enabledWhen を書く。
fields:
- { field: kind, label: 区分, type: select, required: true,
options: [ { value: corporate, label: 法人 }, { value: personal, label: 個人 } ] }
# 法人のときだけ聞く
- { field: registryNo, label: 法人番号, type: text,
visibleWhen: { field: kind, operator: equals, value: corporate } }隠すか、灰色にするか
| 使いどころ | |
|---|---|
visibleWhen | その条件ではそもそも存在しない項目。法人にしか無い法人番号 |
enabledWhen | 項目は常にあるが、いまは入れられないもの。出荷済になったら数量を触らせない |
迷ったら「利用者がその項目の存在を知っておくべきか」で切る。知らなくていいなら隠す、知っておいて欲しいなら灰色にする。
条件は入れ子にできる
1つの条件は { field, operator, value }。複数を組み合わせるときは all(全部満たす)/any(どれか満たす)/not(逆)で包む。
- { field: memo, label: 備考, type: textarea,
enabledWhen: { any: [ { field: kind, operator: equals, value: vip },
{ field: age, operator: gte, value: 65 } ] } }all / any の中にさらに all / any を書けるので、複雑な条件も表現できる。ただし読めなくなるので、3段以上入れ子になったらその項目を別のセクションや別の画面に分けたほうがいいというサインだと思ったほうがいい。
検索条件とは演算子が違う
条件で使えるのは equals notEquals gt gte lt lte contains in isEmpty isNotEmpty。
between は使えない(下の「よくある間違い」参照)。範囲で判定したいときは all で gte と lte を並べる。
visibleWhen: { all: [ { field: age, operator: gte, value: 20 },
{ field: age, operator: lte, value: 64 } ] }逆に isEmpty / isNotEmpty は条件でしか使えない(検索欄には値を入れる場所が必要なので)。
判定の相手は「いま編集中のレコード」
条件が見るのは、その画面で入力中・表示中のレコードの値。他の画面の値やログイン情報は見られない。ロールで出し分けたいなら roles を使う(「権限で出し分ける」参照)。
隠れている項目は検証しない
visibleWhen で消えている項目は、required も他の検証も飛ばす。入力できない項目を必須にすると「直せないのに保存できない画面」になってしまうので、そちら側に倒してある。
なので 「出たときだけ必須」は素直に書ける。条件を2回書く必要はない。
- field: registryNo
label: 法人番号
required: true
visibleWhen: { field: kind, operator: equals, value: corporate }ただし、隠れている項目に値が残っていた場合、その値は保存される。検証を飛ばすだけで、値を消しには行かない(消したつもりのデータが残るより、勝手に消えるほうが事故が大きいので)。
見た目は変えずに、直せなくする
enabledWhen は灰色になる。「値は読ませたいが直させたくない」ときはこれだと目立ちすぎるので、readOnlyWhen を使う。見た目は普通の入力欄のまま、編集だけできなくなる。
# 個人には会員番号を直させない(でも読ませたい)
- { field: memberNo, label: 会員番号, readOnlyWhen: { field: kind, value: personal } }enabledWhen: { not: ... } と書いても同じことはできるが、条件を反転させて読むぶん1枚挟まる。素直な向きで書けるようにしてある。
条件によって必須にする
「法人のときだけ登録番号が必須」のように、項目は出ているのに必須かどうかだけ変わる場合は requiredWhen。
- { field: invoiceNo, label: 登録番号, requiredWhen: { field: kind, value: corp } }必須の条件を validators の中に書こうとしても効かない。validators の要素はその項目の値しか見ないので、他の項目の値では分岐できない(そして余分なキーは黙って捨てられる)。
枠ごと出し分ける
項目が何個も同じ条件で出たり消えたりするなら、セクションに visibleWhen を書けば見出しごと消える。中の項目も検証されない。
sections:
- title: 請求先
visibleWhen: { field: kind, value: corp }
fields:
- { field: billingCode, label: 請求先コード, required: true }同じ判定がバックエンドでも動く
条件の評価は Dart / TypeScript / Java の3言語に同名で用意されている。画面で隠した項目をサーバ側でも「無いもの」として扱えるので、判定ロジックを書き直さなくていい。
サーバ側の検証が見るのは visibleWhen と requiredWhen の2つ(enabledWhen と readOnlyWhen は見た目の話なので見ない)。{ mode: ... } を含む条件を使うなら、検証を呼ぶときにモードを渡すこと。渡さないと mode の判定が false になり、検証が緩む方に倒れる。
新規のときだけ/編集のときだけ
「コードは登録時だけ入力できて、あとから変えさせない」は業務システムで必ず出る。これは他の項目の値では決まらない(フォームがいまどちらの状態かという話)なので、mode という専用のリーフで書く。
fields:
# 新規のときだけ入力できる
- { field: code, label: コード, enabledWhen: { mode: create } }
# 編集のときだけ出す(新規の時点では存在しない項目)
- { field: updatedBy, label: 更新者, readOnly: true, visibleWhen: { mode: edit } }{ field: id, operator: isEmpty } のようにキー項目の有無で判定しても動くが、なぜ id を見ているのかが定義から読み取れない。キー項目名を変えた瞬間に黙って壊れるので、mode と書く。
明細(subTable)の行では、行を追加するときが create、既にある行を開いたときが edit。読み取り専用の詳細画面のようにそもそもモードが無い場所では falseになる(「新規のときだけ」は、新規と言えない場所では満たされない)。
書けるキー
| キー | 書く場所 | 型 | 必須 | 既定値 | 有効なページ種別 | 説明 |
|---|---|---|---|---|---|---|
visibleWhen | field | object → condition | 任意 | — | crud dashboard detail form master report search wizard | Show this field only when the condition matches the current record. |
visibleWhen | section | object → condition | 任意 | — | crud detail form master | Show this whole section only when the condition matches the current record. A hidden section's fields are not validated either. |
enabledWhen | action | object → condition | 任意 | — | crud dashboard detail form master report search wizard | Enabled only while this condition matches. The record judged is the row for a row action, every checked row for scope: selection (all of them must match), and the current record for a page that has one. A page with no record to judge leaves the button enabled (validate says so). |
enabledWhen | field | object → condition | 任意 | — | crud dashboard detail form master report search wizard | Enable this field only when the condition matches the current record. A disabled field is greyed out; use readOnlyWhen when the value should stay plainly readable. |
all | condition | array → condition | 任意 | — | crud dashboard detail form master report search wizard | — |
any | condition | array → condition | 任意 | — | crud dashboard detail form master report search wizard | — |
not | condition | object → condition | 任意 | — | crud dashboard detail form master report search wizard | A conditional expression evaluated against a record. Either a leaf {field, operator, value}, a leaf {mode: create|edit}, or a combinator {all|any: [..]} / {not: {..}}. |
field | column | string | 必須 | — | crud dashboard detail form master report search wizard | — |
field | condition | string | 任意 | — | crud dashboard detail form master report search wizard | — |
field | dashboardItem.sort | string | 任意 | — | dashboard | — |
field | dashboardValue | string | 任意 | — | dashboard | Field to reduce. Not needed by count. |
field | field | string | 必須 | — | crud dashboard detail form master report search wizard | — |
field | field.computed | string (lines ほか) | 任意 | — | crud dashboard detail form master report search wizard | A subTable field of this form whose rows are folded (row-folding mode). The rows must be saved with the parent record: a subTable with source is paged, so its rows are not here to fold. |
field | filter | string | 必須 | — | crud dashboard master report search | Backing data key. |
field | report.sort | string | 任意 | — | report | — |
field | reportGroup | string | 必須 | — | report | — |
field | reportTotal | string | 必須 | — | report | — |
value | condition | any | 任意 | — | crud dashboard detail form master report search wizard | — |
value | dashboardItem | object → dashboardValue | 任意 | — | dashboard | Reduction for a metric card. Omitted = count. |
value | option | `string | number | boolean | null` | 任意 |
value | optionsSource | string | 任意 | "code" | crud dashboard detail form master report search wizard | Field of a row to store. |
mode | condition | string (create / edit) | 任意 | — | crud dashboard detail form master report search wizard | True while the form is in this mode. Use it for "only when creating" / "only when editing" instead of inspecting the key field. False wherever the mode is unknown (a read-only detail page has none). |
requiredWhen | field | object → condition | 任意 | — | crud dashboard detail form master report search wizard | Required only when the condition matches the current record. Unlike visibleWhen / enabledWhen this is also checked server-side, by the same validator. |
readOnlyWhen | field | object → condition | 任意 | — | crud dashboard detail form master report search wizard | Read-only while the condition matches: the value stays readable, only editing is blocked. Compare enabledWhen, which greys the input out. |
この表は spec/reference.json から生成している(JSON Schema が正)。手元では npx hatake reference <キー名> で同じものが引ける。
近い例
例は丸ごと写して直すのが一番速い。以下は CI で検証済み(そのまま動く形)。
| ファイル | 種別 | 画面 | どういうときに使うか |
|---|---|---|---|
customer_form.yaml | form | 顧客入力 | 一覧を持たない単票の入力画面が欲しい(新規と編集を1枚で) |
customer_wizard.yaml | wizard | 顧客登録 | 項目が多いので入力をステップに分けて、1ステップずつ検証したい |
sales_dashboard.yaml | dashboard | 売上ダッシュボード | 件数・金額・グラフのカードを並べて、まず数字を見せたい |
よくある間違い
「編集のとき」をキー項目の有無で判定する
なぜ駄目か { field: id, operator: isEmpty } は動くが、なぜ id を見ているのかが定義から読み取れない。キー項目名を変えたら黙って壊れるし、key を持たないページでは成り立たない。
こう直す { mode: create } / { mode: edit } と書く。フォームの状態そのものなので、キー項目に依存しない。
page:
type: crud
id: customer_master
title: 顧客マスタ
repository: customerRepository
table:
columns: [{ field: code, label: コード }]
form:
sections:
- fields:
- { field: code, label: コード, enabledWhen: { mode: create } }
- { field: updatedBy, label: 更新者, readOnly: true,
visibleWhen: { mode: edit } }合計を出したいのに value を省く
なぜ駄目か value を省いた metric カードは 件数(count)。金額を足したいのに件数が出る、という間違いは画面を見ても気づきにくい。
こう直す value: { aggregate: sum, field: amount } のように、畳み込み方と対象項目を書く。count 以外は field が必須。
page:
type: dashboard
id: sales_dashboard
title: 売上ダッシュボード
repository: orderRepository
items:
- { id: orderCount, title: 受注件数 }
- id: total
title: 受注金額
value: { aggregate: sum, field: amount }
format: currency条件(visibleWhen / enabledWhen)で between を使う
なぜ駄目か between は検索条件専用の演算子。条件式が知っているのは equals notEquals gt gte lt lte contains in isEmpty isNotEmpty だけで、知らない演算子は黙って falseになる(=項目が出てこない)。
こう直す all で gte と lte を組み合わせる。
page:
type: form
id: customer_form
title: 顧客入力
repository: customerRepository
form:
sections:
- fields:
- { field: age, label: 年齢, type: number }
- field: note
label: 備考
type: textarea
visibleWhen:
all:
- { field: age, operator: gte, value: 20 }
- { field: age, operator: lte, value: 65 }条件によって必須にしたいので、validators に条件を書こうとする(- { type: required, when: ... })
なぜ駄目か validators の要素はその項目の値だけを見る規則で、他の項目は見えない。when のような余分なキーは黙って無視されるので、いつでも必須になる(validators は自由な入れ物なので strict でも落ちない=気づけない)。
こう直す 項目直下の requiredWhen に条件を書く。判定は3言語の FormValidator が同じ定義で行うので、サーバ側でも効く。
page:
type: form
id: customer_form
title: 顧客入力
repository: customerRepository
form:
sections:
- fields:
- { field: kind, label: 区分, type: select,
options: [{ value: personal, label: 個人 }, { value: corp, label: 法人 }] }
- field: invoiceNo
label: 登録番号
requiredWhen: { field: kind, value: corp }条件で隠す項目に required: true を残したまま、必須が効き続けると思っている(あるいは効かないように条件を二重に書く)
なぜ駄目か 隠れている項目は検証しない(required も他のバリデータも飛ぶ)。入力できない項目を必須にすると、直せないのに保存できない画面になるため。この規則を知らないと、requiredWhen に同じ条件を書き足して二重管理になる。
こう直す 「出たら必須」は visibleWhen + required: true でよい(条件は1回だけ)。requiredWhen は「出ているのに条件で必須が変わる」ときに使う。
page:
type: form
id: customer_form
title: 顧客入力
repository: customerRepository
form:
sections:
- fields:
- { field: kind, label: 区分, type: select,
options: [{ value: personal, label: 個人 }, { value: corp, label: 法人 }] }
- field: corpName
label: 法人名
required: true
visibleWhen: { field: kind, value: corp }spec/pitfalls.json から生成。各項目は CI で検証済み(間違いは本当に落ち、正しい方は本当に通る)。手元では npx hatake pitfalls <キー名>。
実物を見る
デモアプリの「受注入力」がこれを使っている。 デモを開く