Skip to content

選択肢を連動させる

親の項目で子の選択肢を絞る(都道府県→市区町村)。定義に書くか、Repository から引く。検索条件でも同じ。

都道府県を選んだら、市区町村の選択肢がその県のものだけになる。業務システムでは定番の動きで、これまでは自分でコードを書くしかなかった。定義で書けるようにしたのがこれ。

親の項目を optionsFrom で指して、選択肢それぞれに「どの親の値のときに出るか」を when で書く。

yaml
- { field: prefecture, label: 都道府県, type: select,
    options: [ { value: tokyo, label: 東京都 }, { value: osaka, label: 大阪府 } ] }
- field: city
  label: 市区町村
  type: select
  optionsFrom: prefecture
  options:
    - { value: shibuya, label: 渋谷区, when: tokyo }
    - { value: kita,    label: 北区,   when: osaka }
    - { value: other,   label: その他 }

when を書いていない選択肢(上の「その他」)は常に出る。「未選択」「不明」「その他」のような、親に関係なく要るものに使う。

親を選ぶまで、子は空

親が未入力のあいだ、when 付きの選択肢は出ない。全部出しておいて後で絞るのではなく、選べる状態になってから出す

親を選び直したときに、子に入っていた値が新しい選択肢に無ければその値は捨てる。東京都・渋谷区と入れたあとで大阪府に変えたら、市区町村は空に戻る。放っておくと「大阪府なのに渋谷区」で保存できてしまうので、消えて選び直してもらう方を選んだ。ここは知らないと驚くところなので、先に書いておく。

選択肢がデータのときは Repository から引く

市区町村を全部定義に書くわけにはいかない。選択肢そのものがマスタにあるなら optionsSource で引く。

yaml
- field: city
  label: 市区町村
  type: select
  optionsFrom: prefecture
  optionsSource:
    repository: cityRepository   # 自分で登録した Repository
    value: code                  # 行のどの項目を値にするか
    label: name                  # 行のどの項目を表示するか
    parentKey: prefecture        # 親の値を、この名前で絞り込み条件として渡す

引き先は一覧画面と同じ Repository。フレームワークは HTTP も SQL も知らないので、{ prefecture: "osaka" } という条件で search を呼ぶところまでしかしない。どう絞るかは実装した人の領分。

親が未入力のあいだは引きにも行かない(全件返ってきても連動の意味がないので)。親を変えれば引き直し、同じ親のままなら1回しか引かない。

どちらで書くか

定義に書く(whenRepository から引く(optionsSource
向いている区分・種別・ステータスのような固定の分類マスタにあるもの(取引先、品目、市区町村)
増えたとき定義を直してリリースデータを足すだけ
オフラインでも動くか動くRepository 次第

両方書いた場合は引いてきた方が勝つ。書いた options が黙って無視される形になるので、hatake validate が警告する。

検索条件でも同じ

検索欄(search.filters)でも同じキーが同じ意味で使える。「絞ってから探す」は入力より先に欲しがられるやつ。

yaml
search:
  filters:
    - { field: category, label: カテゴリ, type: select, operator: equals,
        options: [ { value: food, label: 食品 }, { value: drink, label: 飲料 } ] }
    - field: subCategory
      label: 細目
      type: select
      operator: equals
      optionsFrom: category
      options:
        - { value: vegetable, label: 野菜,     when: food }
        - { value: juice,     label: ジュース, when: drink }

判定は入力項目とまったく同じものを使っている(違うのは「いまの値」がレコードか検索欄かだけ)。親を変えて選べなくなった条件は捨てられるので、絞った先に無い条件で検索してしまうことがない

範囲指定(operator: between)の条件は値を2つ持つので、親にはできない。

書けるキー

キー書く場所必須既定値有効なページ種別説明
optionsFromfieldstring任意crud dashboard detail form master report search wizardParent field name. The options shown are those whose when matches the parent's current value (plus any option without a when). Changing the parent clears a child value that is no longer offered.
optionsFromfilterstring任意crud dashboard master report searchParent filter name in the same search area. The options shown are those whose when matches the parent's current value (plus any option without a when). Changing the parent clears a child value that is no longer offered.
whenoption`stringnumberboolean`任意
optionsSourcefieldobjectoptionsSource任意crud dashboard detail form master report search wizardFetch a field's options from a repository instead of listing them. The framework knows no HTTP or SQL: it asks the repository the application registered, passing the parent value as a filter when parentKey is set.
optionsSourcefilterobjectoptionsSource任意crud dashboard master report searchFetch a field's options from a repository instead of listing them. The framework knows no HTTP or SQL: it asks the repository the application registered, passing the parent value as a filter when parentKey is set.
optionsfieldarrayoption任意crud dashboard detail form master report search wizard
optionsfilterarrayoption任意crud dashboard master report searchOptions for select-style filters.

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

近い例

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

ファイル種別画面どういうときに使うか
product_search.yamlsearch商品照会検索して一覧を見るだけ(登録も更新もさせない)画面が欲しい
customer_form.yamlform顧客入力一覧を持たない単票の入力画面が欲しい(新規と編集を1枚で)

よくある間違い

選択肢の連動を、子の項目を親の値ごとに並べて visibleWhen で出し分けて作る

なぜ駄目か 親の値が3つなら項目が3つになり、保存される項目名も3つに分かれる(cityTokyo / cityOsaka …)。組み合わせが増えると破綻し、データの形まで歪む。

こう直す 子は1項目のままにして、optionsFrom で親を指し、各選択肢に when を書く。選択肢がデータなら optionsSource で Repository から引く。

yaml
page:
  type: form
  id: customer_form
  title: 顧客入力
  repository: customerRepository
  form:
    sections:
      - fields:
          - { field: prefecture, label: 都道府県, type: select,
              options: [{ value: tokyo, label: 東京都 }, { value: osaka, label: 大阪府 }] }
          - field: city
            label: 市区町村
            type: select
            optionsFrom: prefecture
            options:
              - { value: shibuya, label: 渋谷区, when: tokyo }
              - { value: kita,    label: 北区,   when: osaka }

spec/pitfalls.json から生成。各項目は CI で検証済み(間違いは本当に落ち、正しい方は本当に通る)。手元では npx hatake pitfalls <キー名>

実物を見る

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