Skip to content

必須と入力検証を付ける

必須、組み込みバリデータ、メッセージの差し替え。

必須は required: true、それ以外の検証は validators に並べる。

yaml
fields:
  - { field: code, label: コード, type: text, required: true,
      validators: [ { type: maxLength, value: 20 } ] }
  - { field: email, label: メール, type: text, required: true,
      validators: [ { type: email } ] }
  - { field: qty, label: 数量, type: number,
      validators: [ { type: min, value: 1 } ] }

validators はオブジェクトの配列

validators: [required, email] のように文字列を並べる書き方はできない(下の「よくある間違い」参照)。type でどの検証かを選び、残りのキーがその引数になる。

書きたいこと書き方
20文字まで{ type: maxLength, value: 20 }
1以上{ type: min, value: 1 }
形式を正規表現で縛る{ type: pattern, pattern: "^[A-Z]{2}\\d{4}$" }
メール・郵便番号{ type: email } / { type: postalCode }

メッセージを変える

message を足すと既定の日本語メッセージを上書きできる。業務の言葉で言いたいときに使う。

yaml
- { field: code, label: 顧客コード, type: text,
    validators: [ { type: pattern, pattern: "^C\\d{5}$",
                    message: 顧客コードは C で始まる6桁で入力してください } ] }

画面ごとではなくアプリ全体の文言を差し替えたい、あるいは日本語以外を出したいときは、定義ではなくメッセージ解決の仕組み(MessageResolver)を差し替える。同じ仕掛けが Dart / TypeScript / Java の3言語にあって、同じ名前・同じ挙動で動く。

空のときは動かない

required 以外の検証は、値が空なら通る。「任意だが入れるなら形式は守れ」が普通の要求なので、それに合わせてある。任意項目に pattern を書いても、空欄で弾かれることはない

明細の合計と突き合わせる

compare は「他の項目と比べる」検証だが、相手が明細(subTable)なら aggregateof を足すと行を畳んだ数と比べられる。「合計が明細の和と合っているか」はこれで書ける。

yaml
- { field: total, label: 合計, type: number,
    validators: [ { type: compare, operator: equals,
                    field: lines, aggregate: sum, of: amount } ] }

畳み方はダッシュボードのカードや計算項目(computed)と同じ集約なので、同じ書き方を すれば同じ数が出る。行を絞ってから比べるなら where を足す。

yaml
    validators: [ { type: compare, operator: equals,
                    field: lines, aggregate: sum, of: amount,
                    where: { field: cancelled, operator: notEquals, value: true } } ]

計算と検証で絞り方を揃えること。小計を「取消行を外した合計」で出しているのに検証が 全部の行を足すと、取消が1件でもあれば必ず食い違って、直せないエラーが出続ける。

足りないルールは足す

validatorstype も開いた文字列なので、独自ルール(社内のコード体系、他項目との突き合わせなど)は名前を決めて登録すれば書けるようになる。

同じ検証がバックエンドでも動く

画面で検証しても、API を直接叩かれれば意味がない。だから同じ定義をバックエンドでも読んで、同じ検証を実行できるようになっている(Java / TypeScript に同名の実装がある)。検証を二重に書き直す必要はない。

つまり画面側の検証は「早く気づかせるため」、バックエンド側の検証は「守るため」。どちらも同じ定義から動く。

ステップ入力での挙動

wizard では「次へ」を押したときにそのステップの項目だけが検証される。最後まで進んで1回保存するので、後のステップの必須項目が未入力でも前のステップは通る。

書けるキー

キー書く場所必須既定値有効なページ種別説明
requiredfieldboolean任意falsecrud dashboard detail form master report search wizard
validatorsfieldarrayvalidator任意crud dashboard detail form master report search wizard

この表は spec/reference.json から生成している(JSON Schema が正)。手元では npx hatake reference <キー名> で同じものが引ける。

近い例

例は丸ごと写して直すのが一番速い。以下は CI で検証済み(そのまま動く形)。

ファイル種別画面どういうときに使うか
customer_master.yamlcrud顧客マスタ検索して一覧に出して、その場で登録・修正・削除まで面倒を見る画面が欲しい
customer_form.yamlform顧客入力一覧を持たない単票の入力画面が欲しい(新規と編集を1枚で)
customer_wizard.yamlwizard顧客登録項目が多いので入力をステップに分けて、1ステップずつ検証したい

よくある間違い

validators に文字列を並べる(validators: [required, email]

なぜ駄目か validators の要素は { type, ...params } のオブジェクト。文字列だけ書いても検証は増えない(validators の中は自由な入れ物なので strict でも落ちない=気づけない)。

こう直す - { type: email } の形で書く。必須は required: true(項目直下)でも - { type: required } でもよい。

yaml
page:
  type: form
  id: customer_form
  title: 顧客入力
  repository: customerRepository
  form:
    sections:
      - fields:
          - field: mail
            label: メール
            required: true
            validators:
              - { type: email }
              - { type: maxLength, value: 120 }

条件によって必須にしたいので、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 <キー名>

実物を見る

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