Skip to content

ボタンと処理を定義する

画面上部のアクション、組み込みの処理、独自処理の差し込み。

画面のボタンは actions に並べる。type がその挙動を決める。

yaml
actions:
  - { id: create, type: create, label: 新規登録 }
  - { id: export, type: export, label: CSV出力, config: { filename: 顧客一覧, bom: true } }
  - { id: sync,   type: plugin, plugin: syncCustomers, label: 外部連携 }

6つの type で足りるか、足りないか

type何をするか自分のコードが必要か
create editフォームを開く不要
delete確認してから削除する不要
navigate別の画面へ移る不要
export画面の列と行から CSV を組む出力先の登録だけ
plugin登録した処理を呼ぶ必要(処理の中身)

つまり業務固有の処理は全部 plugin に落ちる。承認する、外部システムに送る、帳票を作る、といったものはここ。

plugin の中身は Framework の外に書く

plugin: syncCustomers は「syncCustomers という名前で登録された処理を呼ぶ」という指定でしかない。処理そのものは自分のコードで書いて、アプリ起動時に名前で登録する。Framework は業務ロジックを持たないので、ここが自分のコードとの境界になる。

定義側から渡せるのは config。同じ処理を画面ごとに少し変えたいときはここに差を書く。

CSV 出力は「画面の見えているもの」が出る

type: export は、その画面の列定義と検索結果から CSV を組む。だからロールで見えない列は CSV にも出ない。一覧の export は表示中のページではなく検索結果全体(limit まで)を出す。

configfilename / delimiter / newline / bom / rawformat を通さない生値)などを指定できる。Excel で開く前提なら bom: true

ファイルを実際に書くのは利用者側。Framework はファイルシステムもダウンロードも知らないので、出力先を登録しておく必要がある。登録していなければ失敗する(onSuccess も動かない)。

押しても何も起きないボタンは、押す前に言う

type はどれも「その画面が持っているもの」に効く。持っていない画面に置くと、定義は通り、ボタンも出て、押すまで気づけない(押した人には壊れているように見える)。この枠組みで一番まずい転び方なので、npx hatake validate が押す前に言う。

書き方押すとどうなるか規則
createcrud / master 以外に何も起きない(開く一覧が無い)create-action-unusable
export を表の無い画面に何も出ない(CSV にする行が無い)export-without-rows
printreport の無い画面に何も出ない(刷る紙が無い)print-without-report
pluginplugin: を書き忘れ何も起きない(呼ぶ相手が無い)plugin-without-name
navigate の行き先がその画面自身同じ画面がもう1枚開くだけnavigate-to-self
edit / delete を行の外に何も起きない(行の操作なので)row-declaration-unused

判定は1画面ぶんの情報だけで決まる(外の登録も、他の画面も見ない)。だから CI に置ける。

いま押せるかどうかを、定義で言う

「出荷済は却下できない」を、押してから断るのではなくボタンの活性で言う。条件の書き方は visibleWhen と同じ。

yaml
table:
  rowActions: [openEntry]
actions:
  - id: openEntry
    type: navigate
    label: 明細編集
    page: order_entry
    params: { id: "$row.orderNo" }
    enabledWhen: { field: status, operator: notEquals, value: 出荷済 }

判定する相手は置き場所で決まる。行アクションはその行、一括(scope: selection)は選んだ行全部、入力する画面(form / wizard)のボタンはいま入力されている値、読むだけの画面(detail)のボタンはいま開いているレコード。一覧の上のボタンには判定する相手が無いので、書いても効かない(validate が言う)。

何で出し分けるか使うもの
誰が使えるか(見えるかどうか)roles
いまの状態で押せるか(出たまま灰色)enabledWhen
1回で動かせる件数maxRows

押せないボタンは消えない。灰色のまま出て、何の状態で決まるのかが添えられる(文言は書かなくてよい=条件から作る)。消してしまうと、その操作が在ること自体が分からなくなる。

一括は「選んだ行が全部満たすときだけ」押せる。合わない行が混ざっている間はボタンに件数が出る(「一括承認(3 件:1 件は条件に合いません)」)=選び直せば押せることが、押す前に読める。

入力する画面では、保存を挟まずに変わる

「下書きに直したら送信できる」は、直した時点で押せるようになる。ボタンが見ているのは、項目の出し分け(visibleWhen)や計算(computed)が見ているものと同じ record(いま入力されている値)だからで、保存を挟む必要はない。

yaml
    enabledWhen: { field: status, operator: equals, value: 下書き }

計算した項目でも書ける(enabledWhen: { field: 合計, operator: gt, value: 0 }=金額が入るまで送信させない)。新規入力のときだけ押せるボタンは { mode: create }

保存しないと押せないままだと、押した人は「直したのに壊れている」と読む。画面に出ている値で判定するのがこの機能の意味です。読むだけの画面(detail)は入力が無いので、開いているレコードで判定する。

押せない理由に出る項目名は、その画面の見出しで言う(「いまは押せません(状態 によります)」)=定義に書いた label をそのまま使う。

params は navigate のためのもの

params は遷移先に渡す値。$row.<項目> / $record.<項目> で現在の行やレコードの値を埋められる。詳しくは「画面から画面へ遷移する」に書いた。

確認と成功後の動き

confirmonSuccess はどの type にも書ける。ただし create / edit はフォームを開くだけで保存の成否がその時点では分からないので、onSuccess は動かない。詳しくは「確認ダイアログと成功後の動き」に書いた。

書けるキー

キー書く場所必須既定値有効なページ種別説明
actionscrudPagearrayaction任意crudPage-level actions (buttons).
actionsdashboardPagearrayaction任意dashboard
actionsdetailPagearrayaction任意detail
actionsformPagearrayaction任意form
actionsmasterPagearrayaction任意master
actionsreportPagearrayaction任意report
actionssearchPagearrayaction任意searchPage-level actions; also referenced by table.rowActions.
actionswizardPagearrayaction任意wizard
paramsactionobjectaction.params任意crud dashboard detail form master report search wizardRoute params for a navigate action; $row.id / $record.id template against the current row/record.
paramsactionSuccessobjectactionSuccess.params任意crud dashboard detail form master report search wizardRoute params for page; $row.id / $record.id template against the current row/record.
pluginactionstring任意crud dashboard detail form master report search wizardRegistered action plugin key (used when type is 'plugin').

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

近い例

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

ファイル種別画面どういうときに使うか
customer_master.yamlcrud顧客マスタ検索して一覧に出して、その場で登録・修正・削除まで面倒を見る画面が欲しい
dept_master.yamlmaster部門マスタコードと名前だけの小さなマスタを、最小の定義でメンテしたい
order_entry.yamlform受注入力ヘッダと明細行を1画面で入力して、まとめて保存したい
sales_report.yamlreport売上明細表一覧の印刷版が欲しい。顧客ごとに小計を出して、CSV も落としたい、紙にも刷りたい
sales_app.yamlapp販売管理複数の画面をメニューで束ねて、1つのアプリにしたい。会社の色にもしたい

よくある間違い

scope: selectiontype: delete を書いて「一括削除」にする

なぜ駄目か 選んだ行にまとめて実行できるのは type: plugin だけなので、押しても実行されない。用意していないのは、取り消せない操作は事故が件数ぶん大きくなるから。1件ずつなら「押し間違えた」で済むが、全選択のあとの1回は戻せない。

こう直す 消すのは1件ずつ(table.rowActionsdelete)。どうしてもまとめて消すなら type: plugin にして、消す条件と件数の上限をアプリ側で持つ(そこで確認と記録も残せる)。

yaml
page:
  type: search
  id: order_search
  title: 受注照会
  repository: orderRepository
  key: orderNo
  table:
    rowActions: [delete]
    columns:
      - { field: orderNo, label: 受注番号 }
  actions:
    - { id: remove, type: delete, label: 削除, roles: [admin] }
    - id: approveSelected
      type: plugin
      plugin: approveOrders
      label: 一括承認
      scope: selection
      roles: [manager]
      confirm: { message: 選んだ受注を承認します }

一覧の画面に type: print の印刷ボタンを置く

なぜ駄目か 紙の形(用紙・1枚の行数・グループ・小計)を決めているのは report なので、report の無い画面には刷るものが無い。定義は通り、ボタンも出るが、押すと「このページでは刷れません」と言われる(押すまで分からない)。

こう直す 印刷は帳票(type: report)に置く。一覧をそのまま持ち出したいなら type: export(CSV)で、こちらはどの画面でも動く。

yaml
page:
  type: report
  id: sales_report
  title: 売上明細表
  repository: orderRepository
  table:
    columns:
      - { field: amount, label: 金額, type: number }
  report:
    paper: { size: A4 }
    rowsPerPage: 30
  actions:
    - { id: printPdf, type: print, label: 印刷 }

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

実物を見る

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