確認ダイアログと成功後の動き
「本当に削除しますか」と「保存できたら一覧に戻る」を定義で書く。
「本当に削除しますか」と「保存できたらこう動く」は、定義に書く。Dart やプラグインで書くものではない。
actions:
- id: delete
type: delete
label: 削除
confirm:
title: 顧客の削除
message: この顧客を削除すると、受注履歴から辿れなくなります。よろしいですか?
okLabel: 削除する
danger: true
onSuccess:
message: 顧客を削除しました削除は書かなくても確認される
delete は宣言しなくても必ず確認ダイアログが出る。confirm をわざわざ書くのは、次のどちらかのとき。
- 一般的な文言ではなく、業務の言葉で聞きたいとき(上の例のように、消すと何が困るのかを書く)
danger: trueで、取り返しがつかない操作だとハッキリ見せたいとき
逆に、確認が要らない操作にはそもそも書かない。
終わったあとの動きは onSuccess
保存や削除が成功したあと何をするか。メッセージを出すだけなら message、別の画面に戻したり送ったりするなら遷移先を書く。
onSuccess:
message: 登録しました
page: customer_master # 一覧に戻るこれを画面側のコードで書くと、「保存後の挙動」が定義と実装に散って追えなくなる。定義に書いておけば、画面の振る舞いは定義を読むだけで全部分かる。
実行の前に聞くなら prompt
「却下の理由を書いてから却下」は業務でそのまま来る。confirm は「はい / いいえ」しか聞けないので、ここで止まるとアプリに手書きのダイアログが必要になる(このフレームワークが無くしたい物がそこで戻ってくる)。
prompt:
title: 却下の理由
okLabel: 却下する
fields:
- { field: reason, label: 理由, type: textarea, required: true }
- { field: rejectedOn, label: 却下日, type: date }聞くのは普通の項目なので、型・required・validators・computed・normalize がフォームと同じに効く。書いていなければ実行されず、ダイアログは開いたまま(閉じてしまうと書き直す場所が無くなる)。値は保存と同じ正規化を通ってからハンドラに届く。
'rejectOrders': (ctx) async {
await api.reject(ctx.records, reason: ctx.input['reason']);
},確認ダイアログは増えない。 聞くことがあるなら、その OK が確認そのもの。confirm に書いた文言・ボタン名・danger はこのダイアログが引き取る(2枚続けて出すのは、読まずに押す練習をさせるだけ)。
一括(scope: selection)でも聞くのは1回で、選んだ行に同じ理由が付く。行ごとに聞かれたら誰も使わない。
受け取れるのは type: plugin だけ。ほかの型は聞いた値の行き先が無いので、npx hatake validate が警告する。
失敗したときの文言は onError
書かなければ、失敗の理由がそのまま出る(RepositoryHttpException: … 500 …)。事実だが業務の言葉ではないし、同じ失敗が画面ごとに違う意味を持つ(「在庫が足りません」「締め済みなので直せません」)。
onError:
message: 受注が残っているので削除できません({error})onError に遷移先は書けない。 onSuccess は書けるのに無いのは意図的で、失敗した画面から離れると、何が起きたか読めなくなり、直すべき行も視界から消える。
{error} は失敗の理由。ほかに {count} / {failed} / {total}(件数)と {failedKeys}(失敗した行のキー)、{skipped}(送っていない件数)が書けるが、埋まるのは一括(scope: selection)のときだけ。埋まらない差し込みは文字のまま出てしまうので、npx hatake validate が押す前に言う(placeholder-not-filled)。
差し込みは閉じた集合。{orderNo} のように項目名を書いても埋まらず、そのまま文字で出る(レコードの値は文言に渡っていない)。開いた形なのは遷移のパラメータ($row.<項目名>)だけで、そこと混同しやすい。書ける全部と「いつ埋まるか」は引ける。
npx hatake reference --placeholdersonError を書かなかったときは、失敗の理由がそのまま画面に出る(業務の言葉ではない)。書き忘れても動くので、npx hatake explain が「失敗したら理由がそのまま出る」と読み返しに出す。
一括は「一部だけ失敗」が普通
5件のうち1件だけ出荷済みで承認できなかった、は失敗でも成功でもない。ハンドラが件数を返すと、何と言うかは定義が決める。
'approveOrders': (ctx) async {
final rejected = await api.approve(ctx.records); // 呼ぶのは1回
// 行を**名指しで**報告する(件数だけでも動く)。
ctx.report(ActionOutcome.rejected(
succeeded: ctx.records.length - rejected.length,
rows: [for (final one in rejected) FailedRow(one.orderNo, reason: one.why)],
));
},onSuccess: { message: '{count} 件を承認しました' }
onError: { message: '{count} 件を承認しました({failed} 件は出荷済み: {failedKeys})' }1件でも失敗が残っていれば onSuccess は動かない。 画面を移してしまうと、直すべき行が視界から消えるため。何も報告せずに戻ったら成功扱いで、一括なら渡した行数がそのまま {count} に入る。
「3件失敗しました」で終わらせない
件数だけ言われると、現場は全部やり直すしかない。行を名指しで報告すると3つ付いてくる。
- 文言の
{failedKeys}が埋まる(SO-2, SO-7) - 失敗の通知から**「どの行か」**を開ける(キーと理由を1件ずつ。理由は行ごとに違ってよい)
- そこからその行だけを選び直せる(もう一度押す相手を、人が選び直さなくていい)
名指ししなければ件数だけの報告として扱われ、{failedKeys} は文字のまま出る(「行が分かっていない」と読める)。failed より少なく名指ししてもよく、そのときは「1 件だけが分かっています」と出る=分かっていない分を無かったことにしない。
文言だけ差し替えたいとき
okLabel / cancelLabel はボタンの文字。title はダイアログの見出し。省けば既定の文言が出るので、変える必要があるところだけ書く。
書けるキー
| キー | 書く場所 | 型 | 必須 | 既定値 | 有効なページ種別 | 説明 |
|---|---|---|---|---|---|---|
confirm | action | object → confirm | 任意 | — | crud dashboard detail form master report search wizard | Ask before running the action. A delete action asks even without this; declaring it replaces the wording. title and message may carry {count}, the number of rows picked — filled only on a scope: selection action, since nothing else knows a count before it runs. Nothing has happened yet, so {failed} / {total} / {error} do not fill here (validate says so). |
okLabel | actionPrompt | string | 任意 | — | crud dashboard detail form master report search wizard | Confirming button. Falls back to confirm.okLabel, then the label. |
okLabel | confirm | string | 任意 | — | crud dashboard detail form master report search wizard | Label of the button that runs the action. |
cancelLabel | actionPrompt | string | 任意 | — | crud dashboard detail form master report search wizard | Cancelling button. Falls back to confirm.cancelLabel. |
cancelLabel | confirm | string | 任意 | — | crud dashboard detail form master report search wizard | Label of the button that does nothing. |
danger | confirm | boolean | 任意 | false | crud dashboard detail form master report search wizard | Style the confirming button as destructive. A delete action is destructive by default. |
message | actionError | string | 必須 | — | crud dashboard detail form master report search wizard | Shown instead of the raw failure (a snackbar / toast). |
message | actionSuccess | string | 任意 | — | crud dashboard detail form master report search wizard | Shown briefly to the user (a snackbar / toast). |
message | confirm | string | 必須 | — | crud dashboard detail form master report search wizard | The question itself. May carry {count} (the number of rows picked). |
message | validator | string | 任意 | — | crud dashboard detail form master report search wizard | Override message shown on failure. |
onSuccess | action | object → actionSuccess | 任意 | — | crud dashboard detail form master report search wizard | What happens once the action succeeded. Nothing here runs when it fails. |
onError | action | object → actionError | 任意 | — | crud dashboard detail form master report search wizard | What the user is told when the action failed. Placeholders: {error} the reason as reported, {failed} / {count} / {total} the row counts of a scope: selection action. |
prompt | action | object → actionPrompt | 任意 | — | crud dashboard detail form master report search wizard | Asked before the action runs: a small form whose values reach the handler as input. It replaces the confirmation dialog rather than adding a second one — the same OK button confirms. title may carry {count} on a scope: selection action. Style the OK button as destructive with confirm: { danger: true } (the prompt has no danger of its own). |
この表は spec/reference.json から生成している(JSON Schema が正)。手元では npx hatake reference <キー名> で同じものが引ける。
近い例
例は丸ごと写して直すのが一番速い。以下は CI で検証済み(そのまま動く形)。
| ファイル | 種別 | 画面 | どういうときに使うか |
|---|---|---|---|
customer_master.yaml | crud | 顧客マスタ | 検索して一覧に出して、その場で登録・修正・削除まで面倒を見る画面が欲しい |
roles_app.yaml | app | 人事管理 | 見せる相手を役割で変えたい(画面ごと隠す・列や項目だけ隠す・押せる人を絞る) |
よくある間違い
確認ダイアログや「保存できたら戻る」を Dart / プラグインで書く
なぜ駄目か 「削除前に確認する」「終わったら一覧に戻る」はその操作の業務ルールなので、画面ごとに実装すると画面ごとにズレる。delete は宣言が無くても必ず確認する。
こう直す アクションに confirm / onSuccess を書く。onSuccess は成功したときだけ動くので、失敗時に「保存しました」と出る事故も防げる。
page:
type: crud
id: customer_master
title: 顧客マスタ
repository: customerRepository
table:
rowActions: [edit, delete]
columns: [{ field: code, label: コード }]
actions:
- id: delete
type: delete
label: 削除
confirm:
message: 受注履歴から辿れなくなります。よろしいですか?
okLabel: 削除する
danger: true
onSuccess:
message: 顧客を削除しました失敗したときの文言を、プラグインのハンドラの中で ScaffoldMessenger に出す
なぜ駄目か 同じ失敗が画面ごとに違う意味を持つ(「在庫が足りません」/「締め済みなので直せません」)ので、文言はその画面の業務の話。ハンドラの中に書くと、画面ごとに文言が散り、定義を読んでも何と言うのか分からない。一括の「5件中1件失敗」も、件数だけ報告すれば文言は定義側で組める。
こう直す アクションに onError: { message: … } を書く({error} で理由も出せる)。ハンドラは投げるか、件数を ctx.report で返すかだけにする。onError に遷移先は無い=失敗した画面から離れない。
page:
type: search
id: order_search
title: 受注照会
repository: orderRepository
key: orderNo
table:
columns: [{ field: orderNo, label: 受注番号 }]
actions:
- id: approveSelected
type: plugin
plugin: approveOrders
label: 一括承認
scope: selection
confirm: { message: 選んだ受注を承認します }
onSuccess: { message: '{count} 件を承認しました' }
onError: { message: '{count} 件を承認しました({failed} 件は出荷済み)' }spec/pitfalls.json から生成。各項目は CI で検証済み(間違いは本当に落ち、正しい方は本当に通る)。手元では npx hatake pitfalls <キー名>。
実物を見る
デモアプリの「顧客マスタ」がこれを使っている。 デモを開く