Skip to content

条件で出し隠し・必須・読み取り専用を切り替える

他の項目の値や新規/編集の状態で、表示・入力可否・必須を切り替える。区画ごとの出し分けも。

他の項目の値によって項目を出し隠しするには visibleWhen、入力できる/できないを切り替えるには enabledWhen を書く。

yaml
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(逆)で包む。

yaml
- { 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 は使えない(下の「よくある間違い」参照)。範囲で判定したいときは allgtelte を並べる。

yaml
visibleWhen: { all: [ { field: age, operator: gte, value: 20 },
                      { field: age, operator: lte, value: 64 } ] }

逆に isEmpty / isNotEmpty は条件でしか使えない(検索欄には値を入れる場所が必要なので)。

判定の相手は「いま編集中のレコード」

条件が見るのは、その画面で入力中・表示中のレコードの値。他の画面の値やログイン情報は見られない。ロールで出し分けたいなら roles を使う(「権限で出し分ける」参照)。

隠れている項目は検証しない

visibleWhen で消えている項目は、required も他の検証も飛ばす。入力できない項目を必須にすると「直せないのに保存できない画面」になってしまうので、そちら側に倒してある。

なので 「出たときだけ必須」は素直に書ける。条件を2回書く必要はない。

yaml
- field: registryNo
  label: 法人番号
  required: true
  visibleWhen: { field: kind, operator: equals, value: corporate }

ただし、隠れている項目に値が残っていた場合、その値は保存される。検証を飛ばすだけで、値を消しには行かない(消したつもりのデータが残るより、勝手に消えるほうが事故が大きいので)。

見た目は変えずに、直せなくする

enabledWhen は灰色になる。「値は読ませたいが直させたくない」ときはこれだと目立ちすぎるので、readOnlyWhen を使う。見た目は普通の入力欄のまま、編集だけできなくなる。

yaml
# 個人には会員番号を直させない(でも読ませたい)
- { field: memberNo, label: 会員番号, readOnlyWhen: { field: kind, value: personal } }

enabledWhen: { not: ... } と書いても同じことはできるが、条件を反転させて読むぶん1枚挟まる。素直な向きで書けるようにしてある。

条件によって必須にする

「法人のときだけ登録番号が必須」のように、項目は出ているのに必須かどうかだけ変わる場合は requiredWhen

yaml
- { field: invoiceNo, label: 登録番号, requiredWhen: { field: kind, value: corp } }

必須の条件を validators の中に書こうとしても効かない。validators の要素はその項目の値しか見ないので、他の項目の値では分岐できない(そして余分なキーは黙って捨てられる)。

枠ごと出し分ける

項目が何個も同じ条件で出たり消えたりするなら、セクションに visibleWhen を書けば見出しごと消える。中の項目も検証されない。

yaml
sections:
  - title: 請求先
    visibleWhen: { field: kind, value: corp }
    fields:
      - { field: billingCode, label: 請求先コード, required: true }

同じ判定がバックエンドでも動く

条件の評価は Dart / TypeScript / Java の3言語に同名で用意されている。画面で隠した項目をサーバ側でも「無いもの」として扱えるので、判定ロジックを書き直さなくていい。

サーバ側の検証が見るのは visibleWhenrequiredWhen の2つ(enabledWhenreadOnlyWhen は見た目の話なので見ない)。{ mode: ... } を含む条件を使うなら、検証を呼ぶときにモードを渡すこと。渡さないと mode の判定が false になり、検証が緩む方に倒れる

新規のときだけ/編集のときだけ

「コードは登録時だけ入力できて、あとから変えさせない」は業務システムで必ず出る。これは他の項目の値では決まらない(フォームがいまどちらの状態かという話)なので、mode という専用のリーフで書く。

yaml
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になる(「新規のときだけ」は、新規と言えない場所では満たされない)。

書けるキー

キー書く場所必須既定値有効なページ種別説明
visibleWhenfieldobjectcondition任意crud dashboard detail form master report search wizardShow this field only when the condition matches the current record.
visibleWhensectionobjectcondition任意crud detail form masterShow this whole section only when the condition matches the current record. A hidden section's fields are not validated either.
enabledWhenactionobjectcondition任意crud dashboard detail form master report search wizardEnabled 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).
enabledWhenfieldobjectcondition任意crud dashboard detail form master report search wizardEnable 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.
allconditionarraycondition任意crud dashboard detail form master report search wizard
anyconditionarraycondition任意crud dashboard detail form master report search wizard
notconditionobjectcondition任意crud dashboard detail form master report search wizardA conditional expression evaluated against a record. Either a leaf {field, operator, value}, a leaf {mode: create|edit}, or a combinator {all|any: [..]} / {not: {..}}.
fieldcolumnstring必須crud dashboard detail form master report search wizard
fieldconditionstring任意crud dashboard detail form master report search wizard
fielddashboardItem.sortstring任意dashboard
fielddashboardValuestring任意dashboardField to reduce. Not needed by count.
fieldfieldstring必須crud dashboard detail form master report search wizard
fieldfield.computedstringlines ほか)任意crud dashboard detail form master report search wizardA 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.
fieldfilterstring必須crud dashboard master report searchBacking data key.
fieldreport.sortstring任意report
fieldreportGroupstring必須report
fieldreportTotalstring必須report
valueconditionany任意crud dashboard detail form master report search wizard
valuedashboardItemobjectdashboardValue任意dashboardReduction for a metric card. Omitted = count.
valueoption`stringnumberbooleannull`任意
valueoptionsSourcestring任意"code"crud dashboard detail form master report search wizardField of a row to store.
modeconditionstringcreate / edit任意crud dashboard detail form master report search wizardTrue 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).
requiredWhenfieldobjectcondition任意crud dashboard detail form master report search wizardRequired only when the condition matches the current record. Unlike visibleWhen / enabledWhen this is also checked server-side, by the same validator.
readOnlyWhenfieldobjectcondition任意crud dashboard detail form master report search wizardRead-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.yamlform顧客入力一覧を持たない単票の入力画面が欲しい(新規と編集を1枚で)
customer_wizard.yamlwizard顧客登録項目が多いので入力をステップに分けて、1ステップずつ検証したい
sales_dashboard.yamldashboard売上ダッシュボード件数・金額・グラフのカードを並べて、まず数字を見せたい

よくある間違い

「編集のとき」をキー項目の有無で判定する

なぜ駄目か { field: id, operator: isEmpty } は動くが、なぜ id を見ているのかが定義から読み取れない。キー項目名を変えたら黙って壊れるし、key を持たないページでは成り立たない。

こう直す { mode: create } / { mode: edit } と書く。フォームの状態そのものなので、キー項目に依存しない。

yaml
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 が必須。

yaml
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になる(=項目が出てこない)。

こう直す allgtelte を組み合わせる。

yaml
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 が同じ定義で行うので、サーバ側でも効く。

yaml
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 に同じ条件を書き足して二重管理になる。

こう直す 「出たら必須」は visibleWhenrequired: true でよい(条件は1回だけ)。requiredWhen は「出ているのに条件で必須が変わる」ときに使う。

yaml
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 <キー名>

実物を見る

デモアプリの「受注入力」がこれを使っている。 デモを開く