Skip to content

確認ダイアログと成功後の動き

「本当に削除しますか」と「保存できたら一覧に戻る」を定義で書く。

「本当に削除しますか」と「保存できたらこう動く」は、定義に書く。Dart やプラグインで書くものではない。

yaml
actions:
  - id: delete
    type: delete
    label: 削除
    confirm:
      title: 顧客の削除
      message: この顧客を削除すると、受注履歴から辿れなくなります。よろしいですか?
      okLabel: 削除する
      danger: true
    onSuccess:
      message: 顧客を削除しました

削除は書かなくても確認される

delete は宣言しなくても必ず確認ダイアログが出る。confirm をわざわざ書くのは、次のどちらかのとき。

  • 一般的な文言ではなく、業務の言葉で聞きたいとき(上の例のように、消すと何が困るのかを書く)
  • danger: true で、取り返しがつかない操作だとハッキリ見せたいとき

逆に、確認が要らない操作にはそもそも書かない。

終わったあとの動きは onSuccess

保存や削除が成功したあと何をするか。メッセージを出すだけなら message、別の画面に戻したり送ったりするなら遷移先を書く。

yaml
onSuccess:
  message: 登録しました
  page: customer_master     # 一覧に戻る

これを画面側のコードで書くと、「保存後の挙動」が定義と実装に散って追えなくなる。定義に書いておけば、画面の振る舞いは定義を読むだけで全部分かる。

実行の前に聞くなら prompt

「却下の理由を書いてから却下」は業務でそのまま来る。confirm は「はい / いいえ」しか聞けないので、ここで止まるとアプリに手書きのダイアログが必要になる(このフレームワークが無くしたい物がそこで戻ってくる)。

yaml
prompt:
  title: 却下の理由
  okLabel: 却下する
  fields:
    - { field: reason, label: 理由, type: textarea, required: true }
    - { field: rejectedOn, label: 却下日, type: date }

聞くのは普通の項目なので、型・requiredvalidatorscomputednormalize がフォームと同じに効く。書いていなければ実行されず、ダイアログは開いたまま(閉じてしまうと書き直す場所が無くなる)。値は保存と同じ正規化を通ってからハンドラに届く。

dart
'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 …)。事実だが業務の言葉ではないし、同じ失敗が画面ごとに違う意味を持つ(「在庫が足りません」「締め済みなので直せません」)。

yaml
onError:
  message: 受注が残っているので削除できません({error})

onError に遷移先は書けない。 onSuccess は書けるのに無いのは意図的で、失敗した画面から離れると、何が起きたか読めなくなり、直すべき行も視界から消える。

{error} は失敗の理由。ほかに {count} / {failed} / {total}(件数)と {failedKeys}(失敗した行のキー)、{skipped}(送っていない件数)が書けるが、埋まるのは一括(scope: selection)のときだけ。埋まらない差し込みは文字のまま出てしまうので、npx hatake validate が押す前に言う(placeholder-not-filled)。

差し込みは閉じた集合{orderNo} のように項目名を書いても埋まらず、そのまま文字で出る(レコードの値は文言に渡っていない)。開いた形なのは遷移のパラメータ($row.<項目名>)だけで、そこと混同しやすい。書ける全部と「いつ埋まるか」は引ける。

bash
npx hatake reference --placeholders

onError書かなかったときは、失敗の理由がそのまま画面に出る(業務の言葉ではない)。書き忘れても動くので、npx hatake explain が「失敗したら理由がそのまま出る」と読み返しに出す。

一括は「一部だけ失敗」が普通

5件のうち1件だけ出荷済みで承認できなかった、は失敗でも成功でもない。ハンドラが件数を返すと、何と言うかは定義が決める。

dart
'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)],
  ));
},
yaml
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 はダイアログの見出し。省けば既定の文言が出るので、変える必要があるところだけ書く。

書けるキー

キー書く場所必須既定値有効なページ種別説明
confirmactionobjectconfirm任意crud dashboard detail form master report search wizardAsk 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).
okLabelactionPromptstring任意crud dashboard detail form master report search wizardConfirming button. Falls back to confirm.okLabel, then the label.
okLabelconfirmstring任意crud dashboard detail form master report search wizardLabel of the button that runs the action.
cancelLabelactionPromptstring任意crud dashboard detail form master report search wizardCancelling button. Falls back to confirm.cancelLabel.
cancelLabelconfirmstring任意crud dashboard detail form master report search wizardLabel of the button that does nothing.
dangerconfirmboolean任意falsecrud dashboard detail form master report search wizardStyle the confirming button as destructive. A delete action is destructive by default.
messageactionErrorstring必須crud dashboard detail form master report search wizardShown instead of the raw failure (a snackbar / toast).
messageactionSuccessstring任意crud dashboard detail form master report search wizardShown briefly to the user (a snackbar / toast).
messageconfirmstring必須crud dashboard detail form master report search wizardThe question itself. May carry {count} (the number of rows picked).
messagevalidatorstring任意crud dashboard detail form master report search wizardOverride message shown on failure.
onSuccessactionobjectactionSuccess任意crud dashboard detail form master report search wizardWhat happens once the action succeeded. Nothing here runs when it fails.
onErroractionobjectactionError任意crud dashboard detail form master report search wizardWhat 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.
promptactionobjectactionPrompt任意crud dashboard detail form master report search wizardAsked 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.yamlcrud顧客マスタ検索して一覧に出して、その場で登録・修正・削除まで面倒を見る画面が欲しい
roles_app.yamlapp人事管理見せる相手を役割で変えたい(画面ごと隠す・列や項目だけ隠す・押せる人を絞る)

よくある間違い

確認ダイアログや「保存できたら戻る」を Dart / プラグインで書く

なぜ駄目か 「削除前に確認する」「終わったら一覧に戻る」はその操作の業務ルールなので、画面ごとに実装すると画面ごとにズレる。delete は宣言が無くても必ず確認する。

こう直す アクションに confirm / onSuccess を書く。onSuccess成功したときだけ動くので、失敗時に「保存しました」と出る事故も防げる。

yaml
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 に遷移先は無い=失敗した画面から離れない。

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

実物を見る

デモアプリの「顧客マスタ」がこれを使っている。 デモを開く