$p.data とフォームの送信バッファ
$p.data は、ブラウザ側でサーバーへ送る値を保持する、フォーム ID ごとのオブジェクトです。画面の値は DOM、保存済みの値はサーバー側にあり、$p.data の内容と常に一致するわけではありません。
このページでは、値が入るタイミング、送信時の追加、応答による削除を追います。関数ごとの詳細は別ページで扱い、ここでは「いまバッファに何があるか」を中心に説明します。内容は本体ソースで確認しており、実機での画面操作は未検証です。
フォーム ID の下に送信する値が入る
本体は $p の初期定義に空の data オブジェクトを用意します。$p.getData($control) は、対象の最も近い form の ID を取得し、その ID のオブジェクトがなければ作って返します。DOM の全項目を読み取る処理はありません(初期定義、getData、フォーム ID の取得)。
getData が返すものをコードで確認すると、DOM を走査する処理ではなく、フォーム ID で辞書を取得する処理だと分かります。
$p.getData = function ($control) {
$p.store.formId = $p.getFormId($control);
if (!($p.store.formId in $p.data)) {
$p.data[$p.store.formId] = {};
}
return $p.data[$p.store.formId];
};最後の return はオブジェクトのコピーを作っていません。例えば戻り値に data.Results_NumA = "100" を代入すると、同じフォームの $p.data も変わります。一方、この関数内には .val() を読む処理がないため、入力欄へ値を入れただけではここに取り込まれません。フォーム ID の設定とバッファの初期作成があるので、取得にも状態の変化があります。
次は、結果テーブルの編集画面で入力値などが格納された場合の模式例です。常にこのキーが存在するという意味ではありません。
// 構造の説明用。$p.data への代入コードではありません。
const example = {
MainForm: {
Results_ClassA: '1001',
Results_DescriptionA: '入力したメモ',
Results_CheckA: false,
ControlId: 'Results_ClassA'
},
DialogEditorForm: {
Results_DescriptionA: 'ダイアログで入力したメモ'
}
};通常の値のキーは ClassA などの列名ではなく、Results_ClassA などのコントロール ID です。ラジオボタンでは name を使う経路もあります。ControlId など、入力項目以外の送信制御情報も同じオブジェクトに加わります(setData、send)。
自動ポストバックは、ダイアログ内の項目なら DialogEditorForm、それ以外なら MainForm を取得します。同名の項目でもフォームごとのバッファを区別して調べる必要があります(自動ポストバック)。
DOM・送信バッファ・保存済みの値は別々に見る
| 場所 | 保持しているもの | 調べる対象 |
|---|---|---|
| DOM | 現在の画面上の入力値・表示値 | 対象の input・select など |
$p.data[formId] | 本体の処理が登録した送信用の値や制御情報 | フォームごとのオブジェクト |
| サーバー側 | 送信値を反映して処理するモデル | リクエストと処理結果 |
$p.getData を呼ぶだけでは DOM から値を取り込みません。また、送信処理はアクションに応じて追加の値を集めます。したがって、確認時点のバッファだけを見て「画面にある値はすべて送られる」「キーがない項目は空で保存される」とは判断できません(getData、送信直前の収集)。
送信前の収集範囲は、画面が新規かどうかではなく、渡された action の条件で切り替わります。
$p.setMustData = function ($form, action) {
if (action !== undefined && action.toLowerCase() === 'create') {
// 新規作成時はコントロールの値を全て送信する
// 読み取り専用(SPAN)でCssClassにalways-sendの設定がないものを除く
$form.find('[class*="control-"]:not(span:not(.always-send))').each(function () {
$p.setData($(this));
});
} else if (action !== undefined && action.toLowerCase() === 'bulkupdate') {
$form.find('[class*="control-"]').each(function () {
$p.setData($(this));
});新規作成の広い収集には、未定義ではない action が create である必要があります。自動ポストバックの setMustData($form) はこの引数を渡さないので、新規画面でもこの分岐には入りません。確認時点のバッファ、必須送信で追加した値、実際のリクエストの三つを分けて見る理由がここにあります。
図を読み込み中…
値が登録されるタイミングと型
通常の単一選択などのコントロールでは、委譲された change ハンドラが $p.setData を呼びます。複数選択は専用処理で格納します。単に入力要素へ .val(...) で値を代入する操作には、この change ハンドラを呼ぶ処理が含まれません(変更イベント、複数選択)。
$p.setData は、対象の値を種類に応じて取り込みます。not-send クラスの対象は、この関数による格納を行いません(setData)。
| コントロール | バッファへ格納する値 |
|---|---|
| 通常の入力、単一選択 | .val() の値 |
| チェックボックス | checked の真偽値 |
| ラジオボタン | チェックされている場合に name をキーとして値を格納 |
読み取り専用の SPAN | data-value があればその値、なければテキスト |
| 複数選択 | 選択値の配列を JSON 化した文字列 |
複数選択の値は、JavaScript の配列がそのまま入るわけではありません。チェックされた候補の値を集め、JSON.stringify した文字列を格納します。また、グリッド内の項目では行のタイムスタンプも追加する処理があります(複数選択、グリッドのタイムスタンプ)。
似た操作でも更新する場所が違う
| 操作 | DOM | 送信バッファ |
|---|---|---|
$p.getData($control) | 値を読み取らない | 対象フォームのオブジェクトを取得・必要なら作成 |
$p.setData($control) | 値を取り込む対象 | 取り込んだ値を格納 |
$p.set($control, value) | 値を設定 | 設定後に setData を呼ぶ |
$p.setValue($control, value) | 表示値を設定 | setData を呼ばない |
$p.clearData(id, data) | 値を変更しない | キーを削除 |
$p.set は通常の単一選択で .change() も呼びます。$p.setValue はその経路では .change() を呼びません。バッファを更新したいからといって、選択項目へ $p.set を実行すると、設定によって項目連携や自動ポストバックも動き得ます(set、setValue)。
getData の戻り値はコピーではない
$p.getData は $p.data[formId] そのものを返します。取得した data のプロパティを変更すると、同じフォームのバッファも変更されます(getData)。
調査時には、フォーム内のコントロールを起点にして、DOM の値とバッファの値を並べます。次の例は通常の単一選択項目 ClassA を想定しています。
const $control = $p.getControl('ClassA');
if ($control && $control.length === 1 && $control.closest('form').length === 1) {
const data = $p.getData($control);
const id = $control.attr('id');
console.log('フォーム', $control.closest('form').attr('id'));
console.log('DOM の値', $control.val());
console.log('バッファにキーがある', Object.prototype.hasOwnProperty.call(data, id));
console.log('バッファの値', data[id]);
console.log('確認時点のバッファ', JSON.stringify(data));
}このコードは送信しません。ただし、getData 自体には未作成のフォーム用バッファを作り、$p.store.formId を設定する作用があります。純粋な読み取りだけの関数ではありません(getData)。
送信時に値を追加する
通常の $p.send は getData で取得したオブジェクトに、GET 以外で発火元の ControlId を追加します。続いて setMustData を呼び、Ajax に渡します(send)。
setMustData が収集する範囲は、アクションで変わります(setMustData)。
| アクション | 追加で収集する対象 |
|---|---|
create | control- をクラスに含む項目。ただし always-send のない SPAN は除く |
bulkupdate | control- をクラスに含む項目 |
| その他・引数なし | always-send または data-always-send="1" の対象。テーブル項目 ID で data-readonly="1" の対象は除く |
いずれも値の取り込みには setData を使うため、not-send の条件は適用されます。
項目の自動ポストバックは $p.send を経由せず、フォームのバッファを取得して setMustData($form) を呼びます。引数のアクションを渡さないため、新規作成画面でもこの呼び出しが create の全項目収集に切り替わるわけではありません。ControlId と ReplaceFieldColumns を追加し、$p.ajax に渡します(自動ポストバック)。
変更されていない項目も必ずバッファにある、という前提は置けません。 一方、必須送信の対象は未変更でも取り込まれるため、「変更項目だけの辞書」とも言い切れません。
応答を受けるとバッファも変わる
_dispatch.js は JSON 応答を配列の順に実行します。画面を更新する命令に加え、送信データを操作する命令があります(応答の実行)。
| 応答の Method | バッファに対する処理 |
|---|---|
SetData | 対象 DOM の現在値を setData で格納 |
SetFormData | 応答処理に渡された data の Target キーへ Value を代入 |
ClearFormData | 応答処理に渡された data から、指定したキーを clearData で削除 |
SetValue | 表示値を設定。setData は呼ばない |
ReplaceAll | HTML を差し替え。この命令自体は setData を呼ばない |
SetData は DOM の所属フォームを調べて格納する一方、SetFormData と ClearFormData は応答処理に渡された data を操作します。その data が getData で取得したフォームのオブジェクトなら、フォームのバッファにも変更が反映されます。独自のオブジェクトを渡した Ajax まで、常に $p.data.MainForm を変更するわけではありません(各命令、clearData)。
clearData は、まず送信オブジェクトのキーとして削除できるかを調べ、見つからなければ DOM のセレクタから ID を解決します。
} else {
if (target in data) {
Delete(target);
} else if ($(target).length !== 0) {
Delete($(target).attr('id'));
}この二つの経路の先で実行する Delete は delete data[key] です。値を空文字にする操作ではありません。Lookup の ContainsKey 判定では、空文字のキーは「ある」、削除されたキーは「ない」と扱われます。同じ空欄の表示でも、後続の転記条件に違いが出ます。
同じ応答の命令でも、値を取得する場所が違います。
case 'SetData':
$p.setData($(target));
break;
case 'SetFormData':
data[target] = value;
break;SetData は Target を DOM のセレクタとして扱い、その時点の画面の値を読み直します。SetFormData は Target を送信オブジェクトのキーとして扱い、応答の値を直接代入します。#Results_NumA と Results_NumA を同じ意味で使うと、意図したキーが更新されません。
Ajax の共通完了処理は ControlId を削除しますが、ここで全キーを無条件に消す処理はありません。項目の値などが残るかは、応答の ClearFormData や個別の処理にも依存します。「送ったら自動で空になるバッファ」と考えると、次の送信内容を読み違えます(Ajax の完了処理)。
空文字とキーの削除は違う
| バッファの状態 | 意味 |
|---|---|
キーがあり値が '' | 空文字が送信用の値として登録されている |
| キーがない | そのキーは現在のバッファに登録されていない |
clearData は delete data[key] でキーを削除します。DOM を空にする処理はありません。引数なしで削除する経路には control-selectable のキーを残す条件があり、startsWith や ignoreView による削除範囲の指定もあります(clearData)。
この差は Lookup の判定にも現れます。Lookup はフォーム内に転記先のキーがあるかを調べ、OverwriteForm・発火元・新規レコードの既定値を使って転記可否を決めます。空文字を扱う Overwrite 側の条件も別にあります。表示が同じ空欄でも、キーの有無を無視すると上書き制御を理解できません(Lookup の判定)。
自動ポストバックの Lookup 応答は、OverwriteForm=true の対象について ClearFormData を返す処理を持ちます。転記先の画面表示と、以前の入力が残った送信バッファを別々に処理するためです(Lookup のバッファ削除命令)。
送信内容を調べる順序
- 対象コントロールの所属フォームと ID を確認します。
- DOM の現在値、バッファのキーの有無、バッファの値を比較します。
- Network のリクエストで、実際に送った値を確認します。送信直前に追加される値も含めて見ます。
- JSON 応答の
SetData・SetFormData・ClearFormDataと、画面を更新する命令を確認します。 - 応答反映後の DOM とバッファを再度比較します。
$p.data への直接代入では、DOM の反映や変更イベントは実行されません。画面の値を変更する目的なら適切な設定関数を使い、送信内容だけを変更する場合も、その後の必須送信や応答処理による上書き・削除まで確認します。