自動ポストバックと画面への応答反映
自動ポストバックでは、編集中の値をサーバーへ送り、返された画面操作の指示をブラウザで実行します。応答を受け取る _dispatch.js は、JSON 配列の Method を読み、値の設定・HTML の差し替え・関数呼び出しなどへ振り分けます。
入力値の設定、フィールド全体の差し替え、次回送信用のデータ更新は、それぞれ別の操作です。 この違いが、ポストバック後にカスタマイズが消えたり、選択項目の変更から別の通信が発生したりする理由を調べる手掛かりになります。
公式マニュアルとの関係
設定方法と用途は、公式の自動ポストバックを参照してください。公式では、計算式・ルックアップ・サーバスクリプトなどの結果を編集画面へ反映する機能として説明されています。
ここでは、項目の詳細設定で有効にする自動ポストバックを中心に、送信から応答反映までの実装を扱います。処理を決める部分を短く引用し、確認したコミットの該当行へリンクします。
処理全体の流れ
図を読み込み中…
送信は _controllevents.js、応答の反映は _ajax.js と _dispatch.js が担当します。
項目の変更から送信まで
control-auto-postback と auto-postback は送信経路が違う
項目の AutoPostBack が有効で、描画時に無効化されていなければ、入力コントロールに control-auto-postback クラスが付きます(HtmlFields.cs)。
| クラス・操作 | 送信経路 |
|---|---|
control-auto-postback の通常の項目 | change から $p.controlAutoPostBack |
select[multiple] で search.applied が付いた項目 | 専用の change ハンドラから $p.controlAutoPostBack |
| 通常の複数選択 UI | プラグインの操作から $p.changeMultiSelect |
| 数値スピナー | spin 時にも $p.controlAutoPostBack |
auto-postback のテキスト入力 | keyup でデータを格納し、Enter 時に 500 ms のデバウンスを経て $p.send |
auto-postback の通常の変更・日付入力 | 対応する change ハンドラから $p.send |
根拠は _controllevents.js のイベント登録、複数選択、スピナーです。複数選択では no-postback による抑止もあります。
項目の自動ポストバックを、すべて Enter 後の 500 ms 待機で送信されるものとして扱わないでください。 その待機は auto-postback のテキスト入力の処理です。
送信するのはフォーム単位のバッファ
一般のコントロールの change ハンドラは、まず $p.setData で入力値を送信バッファへ格納します。$p.getData が返すのは、フォーム ID ごとの $p.data のオブジェクトです(_controllevents.js のデータ格納、_data.js)。
$p.controlAutoPostBack は次の処理を行います。
$p.disableAutPostbackが真、または URL にverパラメータがあれば終了します。- 通常は
MainFormを使い、ダイアログ編集ではDialogEditorFormとダイアログのレコード ID に切り替えます。 BaseUrl・レコード ID・画面のアクションから URL を作り、control-auto-postback=1と選択中のTabIndexを付けます。- フォームの送信バッファを取得し、
$p.setMustDataで常時送信する項目を補います。 - 発火元の
ControlIdと画面のReplaceFieldColumnsを格納し、$p.ajaxへ POST を依頼します。
この経路では $p.ajax の _async 引数に false を渡します。$p.send を経由しないため、同関数内の before_setData・after_setData や validate クラスによる検証処理も通りません。before_send など $p.ajax 側の処理は通ります(送信本体、$p.send)。
送信直前のコードには、データの取得、必須項目の追加、発火元の記録が並びます。
var data = $p.getData($form);
$p.setMustData($form);
data.ControlId = $control.attr('id');
data.ReplaceFieldColumns = $('#ReplaceFieldColumns').val();
return $p.ajax(
url,
'post',
data,
$control,
false,
!$control.hasClass('not-set-form-changed')
);getData の戻り値へ ControlId と ReplaceFieldColumns を追加し、その同じオブジェクトを $p.ajax に渡しています。新しい送信用コピーを作る処理ではありません。setMustData にはアクションを渡さず、Ajax の _async 引数には false を渡します。$p.send の経路と比べるときは、この二つの呼び出し条件まで確認します。
この呼び出しの $p.setMustData にはアクションを渡していません。そのため、毎回すべての入力を読み直す処理ではなく、既存バッファに .always-send・data-always-send="1" の対象を補う経路です。DOM の .val() だけを変更すると、バッファへ入らないことがあります(_data.js)。
サーバーは値と HTML を使い分けて返す
記録テーブルでは EditorResponse が control-auto-postback を判定して EditorFields へ進みます。この経路は編集画面の応答を生成する処理です。自動ポストバックの通信が完了しただけで、更新ボタンによる保存が完了したと判断しないでください(EditorResponse)。
EditorFields は編集時の検証後に画面表示前のサーバスクリプトを適用し、FieldResponse、注意書き、関連項目の初期化などの応答を作ります。コマンドボタンの差し替えは SwitchCommandButtonsAutoPostBack が有効な場合です(EditorFields)。
FieldResponse では、各返却項目を次のように扱います。
| 条件 | 生成する応答 |
|---|---|
ReplaceFieldColumns に含まれる項目 | ReplaceAll でフィールド全体を差し替え |
| 通常の値を返す項目 | .Val(...) で値を設定 |
| 添付項目 | 専用の HTML を生成して ReplaceAll |
根拠は FieldResponseです。差し替え対象には、サーバスクリプトの列制御、ルックアップ先、選択肢のフィルタ式、検索機能、状況による制御などから決まる項目が含まれます(ReplaceFieldColumns)。
「自動ポストバック時に返却する項目」を指定すると、発火元の列設定に従ってエディタ項目の返却対象を絞ります。未指定ならエディタの項目群が対象です。これは返却対象の選別であり、送信する項目の指定ではありません(GetEditorColumnNames)。
_dispatch.js がしていること
応答は画面操作の命令配列
$p.ajax は JSON 応答を $p.setByJson に渡します。$p.setByJson は配列を先頭から走査し、各要素を $p.setByJsonElement で実行します。各要素の主なフィールドは Method・Target・Value で、Options があれば JSON 文字列として解析します(_dispatch.js)。
JSON 応答は、命令の種類ごとにまとめ直されるのではなく、配列の順で実行されます。
if (json) {
$.each(json, function () {
$p.setByJsonElement(this, data, $control);
});
}例えば Html → Invoke → ClearFormData なら、Invoke が呼ぶ関数は、HTML の更新後、バッファ削除前の状態を見ます。この関数が change を発火すれば、後続の命令が終わる前に変更イベントの処理が始まります。応答を読むときは命令名の一覧だけでなく、配列内の順序を追う必要があります。
次は形式を説明するための応答例です。実際の応答を採取したものではありません。
[
{ "Method": "SetValue", "Target": "#Results_NumA", "Value": "1200" },
{ "Method": "SetFormData", "Target": "Results_NumA", "Value": "1200" },
{ "Method": "Focus", "Target": "", "Value": null }
]この例では、画面の NumA に値を設定し、送信データにも値を格納してから、発火元の項目へフォーカスを戻します。SetValue の対象はセレクタですが、SetFormData の対象は data のキーです。空の Target を持つ Focus は data.ControlId を使います(SetFormData、Focus と SetValue)。
表示値を返す命令と、送信データを消す命令も分離されています。
case 'SetValue':
$p.setValue($(target), value);
$p.hideField(target, options);
break;
case 'ClearFormData':
$p.clearData(target, data, value);
break;SetValue は対象要素へ setValue を実行し、ClearFormData は応答処理に渡された data を clearData へ渡します。転記先の画面表示が変わっても、以前の手入力を表すキーがバッファに残るかは別の命令で決まります。Lookup の応答を調べるときに、この両方を見る必要があります。
Method ごとの主な処理
Method | 実行する処理 |
|---|---|
Html | 対象要素の内側を .html(value) で置換 |
ReplaceAll | 対象要素自体を、返された HTML で置換 |
Append / Prepend / After / Before | HTML を追加 |
Set | $p.set でコントロールの値と送信バッファを更新 |
SetValue | $p.setValue で表示値を設定し、$p.hideField へ Options を渡す |
SetData | 対象の現在値を $p.setData で送信バッファに格納 |
SetFormData | data[Target] = Value で送信データに直接格納 |
ClearFormData | $p.clearData で指定の送信データを削除 |
SetMemory | $p[Target] = Value でブラウザ側の状態を更新 |
Attr / Css / Disabled | 属性・スタイル・無効状態を変更 |
Focus | 対象へフォーカスを移す |
Invoke | $p[Target](Value) を呼び出す |
Trigger / Events | DOM イベント・プリザンターのイベントを実行 |
Message / Href | メッセージを表示・画面を遷移 |
これらは自動ポストバック専用ではなく、$p.setByJson へ渡された応答に共通する処理です(振り分け処理の全体)。
.Val(...) の応答は SetValue
サーバー側の ResponseCollection.Val(...) は、JSON の Method に SetValue を指定します。C# のメソッド名と、ブラウザで振り分ける命令名は同じとは限りません(ResponseCollection.Val)。
SetValue は $p.setValue を使います。同関数は表示値を設定しますが、$p.setData を呼びません。通常の単一選択でも .change() を呼びません。一方、Set が使う $p.set は $p.setData を呼び、単一選択では .change() も実行します。自動ポストバックが有効な単一選択項目に Set が届くと、変更イベントを通じて追加のポストバックが起こり得ます($p.setValue、$p.set)。
したがって、画面の値が変わったことだけで $p.data も同じ値になったとは判断できません。応答の SetData・SetFormData・ClearFormData と、次回送信時のデータ格納を合わせて確認します。
HTML の変更後に UI と入力検証を再適用する
全命令の実行後、配列に Html・ReplaceAll・Append・Prepend・After・Before のいずれかがあれば、$p.apply() と $p.applyValidator() を呼びます。SetValue だけでは、この条件に入りません(再適用の判定)。
$p.apply() には、タブ、アイコン付きボタン、複数選択などへ jQuery UI を適用する処理があります。applied クラスで適用済み要素を除外する処理もあります(jqueryui.js)。
ReplaceAll はフィールドの DOM 自体を置き換えます。そのため、古い要素に追加した装飾や、その要素に直接登録したイベントハンドラは、新しい要素へ引き継がれません。本体の通常の change ハンドラは document に登録するイベント委譲なので、差し替え後も対象のクラスに一致すれば処理されます(ReplaceAll、イベントの委譲)。
HTML の更新にも二つの操作があります。
case 'Html':
$(target).html(value);
break;
case 'ReplaceAll':
$(value).replaceAll(target);
break;Html は対象要素の内側を置き換えます。ReplaceAll は対象要素そのものを、返された HTML の要素へ置き換えます。古いフィールドの要素に直接付けたカスタマイズを再適用する場合は、応答前に保持した要素ではなく、差し替え後の DOM を取り直す必要があります。本体の UI 再適用と独自処理の再適用は別に考えます。
応答反映のイベント順
通信成功時の主な順序は次のとおりです。before_set と after_set は、$p.setByJson に URL が渡された場合に呼ばれます。
図を読み込み中…
根拠は _ajax.js と _dispatch.jsです。before_set の戻り値で反映を中止する分岐はありません。送信を止める before_send の false とは扱いが異なります(_event.js)。
挙動を調べるときの確認点
ブラウザの開発者ツールで、次の順に確認すると送信と反映を切り分けられます。
- Network の送信先 —
control-auto-postback=1があるか確認します。$p.sendを通る別経路と区別できます。 - 送信データ — 発火元の
ControlId、入力値、ReplaceFieldColumnsを確認します。未送信の値は応答反映より前の問題です。 - JSON 応答 — 対象項目への
SetValueとReplaceAll、バッファを扱う命令の有無と順序を確認します。 - DOM の差し替え — カスタマイズ対象が置き換えられたか確認します。再適用する装飾は
after_setなどの反映後のタイミングを検討します。 - 追加の通信 —
SetやTriggerから変更イベントが起きていないか確認します。
返却する項目を絞った場合は、計算結果や列制御の対象が返却対象に含まれるかも確認してください。サーバー側で作った結果と、ブラウザへ返す項目の範囲は別です。