send と Ajax の送信準備・完了・失敗を読み解く
$p.send() は操作元とフォームから送信内容を準備し、$p.ajax() は確認・通信・応答反映を担当します。API を呼ぶ $p.apiExec() と違い、この経路は画面操作の命令配列を dispatch に渡します。
「ボタンを押しても送られない」「送信直後に成功と判断してしまう」「失敗したのにイベントが出ない」という問題を、呼び出し順で調べます。
send の引数と入口での中止
呼び出しは $p.send($control, formId, _async, clearMessage) です(入口)。
| 引数 | 役割 |
|---|---|
$control | ID・data-action・data-method・validate・data-confirm などを持つ操作元 |
formId | 明示すればそのフォーム。省略時は操作元の最も近い form |
_async | 省略時は後段で true。syncSend は false を渡す |
clearMessage | ajax のメッセージ消去へ渡す。false なら消去を省く |
最初に outsideDialog と no-send を調べ、該当すれば false を返します。outsideDialog は accesskey のある操作元について、表示中のダイアログがあり、その中にいないかを調べます。すべてのダイアログ外操作を禁止する判定ではありません(ダイアログ判定、中止条件)。
送信前に何が起こるか
| 順序 | 処理 | 実装上の境界 |
|---|---|---|
| 1 | 対象フォームを $p.store.$form に入れる | 明示した formId または closest form |
| 2 | before_setData | data はまだ null。false なら中止 |
| 3 | getData、after_setData | バッファを取得するだけ。全 DOM の収集ではない |
| 4 | URL 作成、メール認証パラメータ、選択タブ | data-action を小文字化してフォーム action の _action_ を置換。省略時は現在の URL |
| 5 | 非同期の既定値、ControlId の追加 | methodType が厳密に get でなければ追加 |
| 6 | validate クラスがあれば検証 | before_validate → formValidate・valid → after_validate。失敗時は中止 |
| 7 | get 以外なら setMustData | アクションに応じて追加の値を収集 |
| 8 | data-method が定義されていれば ajax | get なら data の引数を null、それ以外はバッファを渡す |
根拠は send 全体です。data-method を省略すると、後段の通信へ進みません。data-method="GET" は get との厳密な比較では一致しないので、ここでは小文字を使う前提で読みます。
setMustData は create・bulkupdate・その他で収集範囲が違います。更新操作で全項目を必ず送る関数ではありません(収集条件、送信バッファの詳細)。
図を読み込み中…
ajax に入っても、まだ通信開始ではない
$p.ajax(url, methodType, data, $control, _async, clearMessage) は、まず CAPTCHA の準備をします(前処理)。
- 不要な場合はデータをそのまま使う。
- reCAPTCHA v2・Turnstile でトークンがなければ、必要な操作を表示して false を返す。
- reCAPTCHA v3 でトークンがなければ、取得コールバックから
$p.ajaxを再呼び出しする。最初の呼び出しはその場で終了する。
必要性はメソッドが get 以外か、meta の captcha-type・captcha-site-key があるかで決まります。トークンは data や DOM から確認します(設定とトークン判定、再試行の準備)。CAPTCHA で止まった場合、通常の before_send や通信イベントまで到達しない経路があります。
準備ができたら、before_send の false、操作元の data-confirm による確認、loading、メッセージ消去、ajax_before_send、Token の追加を経て $.ajax へ進みます(送信準備)。
Token の Hidden 要素によるデータ追加とは別に、DOM Ready 時の $.ajaxSetup が同一オリジン向けリクエストへ meta の X-CSRF-TOKEN を付けます(ヘッダー設定)。
戻り値は通信の Promise ではない
$p.ajax は内部の jqXHR を返さず、ローカル変数 ret を返します。$p.send はこの戻り値を返す経路があります(ajax の返却、send の引き渡し)。
| 経路 | 戻り値の意味 |
|---|---|
| 通常の非同期通信 | 返却時点の ret は初期値0。後から失敗しても、受け取った0が変わるわけではない |
| 同期通信 | コールバックで更新した ret を返し得る。成功応答に alert-error の Message があれば -1、通信失敗も -1 |
| before_send・確認などで中止 | false |
| CAPTCHA のトークン取得待ち | 最初の呼び出しは undefined の経路がある |
| send の data-method なし | undefined |
await $p.send(...) としても、通常の非同期通信の完了を待つコードにはなりません。同期送信の 0 も、その応答内に alert-error がなかったという判定であり、任意の独自処理の成功保証とは区別します。
after_send は完了イベントではない
通常の非同期通信では、登録した done・fail・always より先に ajax_after_send と after_send を呼ぶ位置があります。同期通信では、通信コールバックが戻ってからこれらを呼びます(コールバックの登録と後段)。
成功時は次の順です。
ajax_before_donesetByJson(before_set → 命令実行 → 必要なら UI 再適用 → after_set)- 応答の Message を調べて ret を更新
ajax_after_doneajax_before_always→ ControlId の削除・loaded →ajax_after_always
after_set のイベント引数の ret は dispatch が undefined を渡します。応答内容による ret の判定も、この後です。画面反映後の結果判定を読む場合は ajax_after_done の引数を使います(dispatch の引数、結果判定)。
完了時点の結果を観察する例
以下はブラウザ側のスクリプトです。通常の編集画面へ配置し、既存の検証用レコードで更新を実行します。このコード自体は送信を開始せず、本体の通信完了イベントを記録します。
$p.events.ajax_after_done = function (args) {
console.log('応答反映後', {
control: args.$control ? args.$control.attr('id') : undefined,
method: args.methodType,
ret: args.ret
});
};
$p.events.ajax_after_always = function (args) {
console.log('通信の後始末後', {
ret: args.ret,
controlIdRemains: !!(args.data && 'ControlId' in args.data)
});
};正常な成功応答なら ret は0、alert-error の Message があれば -1です。通常の後始末後には ControlId が削除されています。既存の同名イベントへ追加する場合は 複数処理の登録を使います。CAPTCHA・事前確認などで通信開始前に中止した場合、この完了イベントは呼ばれません。
fail のイベントが出ない経路もある
fail では先に ret を -1にし、次の条件を処理します(失敗処理)。
| 条件 | 失敗時の扱い |
|---|---|
| abort | そこで return。ajax_before_fail・ajax_after_fail を呼ばない |
| status 0 またはレスポンスヘッダーなし | ServerConnectionError を出して return。同じく fail 前後イベントを呼ばない |
| 400・403 | try 内で alert 後に return。ajax_before_fail より前だが finally の ajax_after_fail は呼ぶ |
| 502・503・504 | ajax_before_fail 後に ServiceUnavailable。finally の ajax_after_fail も呼ぶ |
| その他 | サーバー応答のエラーメッセージを表示できなければ InternalServerError。finally で ajax_after_fail |
普通に fail が返った場合は、その後の always に進みます。ただし、done 内の dispatch や独自ハンドラで例外が出た場合は、通常の後始末が必ず走ると仮定せず、停止位置をデバッガーで確認してください。応答反映には命令ごとの例外捕捉がありません(dispatch の例外)。
multiUpload は通常の ajax と別の経路
multiUpload は FormData に Token を追加し、contentType: false・processData: false で送ります。アップロード進捗を statusBar へ渡せます(アップロード送信)。
成功時、callback を渡せばその関数が応答を受け取り、なければ応答文字列を JSON.parse して dispatch に渡します。通常の $p.ajax の CAPTCHA 前処理・before_send・ajax_* イベントを呼ぶコードではありません。always では ControlId の削除・loaded・statusBar の非表示を行い、jqXHR は statusBar の setAbort へ渡しますが、この関数自体は返しません(成功・後始末)。
clear と共通タイマーの境界
| 関数 | 実装と制約 |
|---|---|
getFormId($control) | closest form の ID。フォーム外なら undefined |
clear($control) | text・checkbox・select の DOM をクリアし、該当バッファを削除。textarea をクリアする分岐はない |
setFormChanged($control) | not-set-form-changed がなければ $p.formChanged = true |
addUrlParameter(url, key, value) | ? または & とキー・値を連結するだけ。URL エンコードする処理はない |
throttle(action, interval) | 全呼び出しで1つの lastTime を共有する |
debounce(action, interval) | 全呼び出しで1つの timer を共有し、前の予約を消す |
根拠は _form.js の前半と後半です。clear の「画面を空にしてバッファを削除」は、「空文字列を登録して保存する」と同じではありません。独自処理用のタイマーを分ける例は API クライアントにあります。
検証範囲と関連ページ
送信準備、同期・非同期の返却、成功・失敗イベントの順序と掲載コードは、本体 JavaScript を使い通信を模擬して確認しています。実サーバーへの更新、CAPTCHA サービス、アップロード UI の実機操作は未検証です。