$p.get 系・$p.set 系のスクリプト関数
$p.getValue() や $p.set() などの関数は名前が似ていますが、内部で呼んでいる処理と、最終的に触る値の場所がまったく違います。 画面(DOM)と送信バッファ($p.data)は別物で、$p.set() は両方、$p.setValue() は DOM だけ、$p.setData() はバッファだけを更新します。また $p.getValue() は data-raw 属性を最優先で返すため、数値項目では画面で入力しただけの値を取得できません。
このページでは、各関数の実装と入れ子構造、使い分けをまとめます。
$p オブジェクトの構成
$p はプリザンターがグローバルに用意しているオブジェクトで、定義は 5 つの入れ物を持っているだけです。各スクリプトファイルが後からここに関数を追加していきます。
window.$p = {
data: {},
events: {},
ex: {},
modal: {},
store: {}
};get 系・set 系の関数に関係するのは次の 2 つです。
| プロパティ | 役割 |
|---|---|
$p.data | サーバへ送信する値をためておくバッファ。フォーム ID ごとに $p.data[formId][コントロールid] = 値 の形で保持する |
$p.store | 処理中の一時的な状態(現在のフォームなど)を置く場所 |
関数の 2 つのグループ
$p の get 系・set 系関数は、引数の型で 2 つのグループに分かれます。
| グループ | 引数 | 代表的な関数 | 想定利用者 |
|---|---|---|---|
| 項目名グループ | 項目の表示名またはカラム名(文字列) | $p.getColumnName() / $p.getControl() / $p.getField() / $p.getValue() | スクリプトを書く利用者 |
| コントロールグループ | jQuery オブジェクト($control) | $p.set() / $p.setValue() / $p.setData() / $p.getData() | プリザンター本体の内部処理 |
利用者が書くスクリプトでは、項目名グループで対象のコントロールを取得し、それをコントロールグループの関数に渡します。
// 項目名グループで取得 → コントロールグループに渡す
$p.set($p.getControl('ClassA'), '選択肢1');項目名グループ
$p.getControl() と $p.getColumnName()
$p.getControl() は、項目名から画面上のコントロールを探し出す関数で、すべての起点になります。
$p.getColumnName = function (name) {
var data = JSON.parse($('#Columns').val()).filter(function (column) {
return column.LabelText === name || column.ColumnName === name;
});
return data.length > 0 ? data[0].ColumnName : undefined;
};
$p.getControl = function (name) {
var columnName = $p.getColumnName(name);
return columnName !== undefined
? $('#' + $('#ReferenceType').val() + '_' + columnName)
: undefined;
};2 つの hidden 項目(Columns と ReferenceType)から値を読んで、コントロールの id を組み立てています。
図を読み込み中…
- 引数には表示名とカラム名のどちらを渡してもよい。
LabelText === name || column.ColumnName === nameで両方を照合しています。項目名を「数値A」から「金額」に変えた場合、$p.getControl('金額')でも$p.getControl('NumA')でも取得できます。 - コントロールの id は「テーブル種別 +
_+ カラム名」。 期限付きテーブルならIssues_NumA、記録テーブルならResults_NumAになります。 - 見つからないと
undefinedが返る。$p.getControl('存在しない項目').val()と書くとundefinedに対する.val()でエラーになるため、戻り値のチェックが必要です。
INFO
$p.getColumnName() は呼ばれるたびに hidden 項目の JSON を JSON.parse します。ループの中で $p.getControl() を何度も呼ぶ場合は、結果を変数に持っておくと無駄な解析を避けられます。
$p.getField()
$p.getField() は同じ仕組みで、末尾に Field を付けた id の要素を返します。コントロールそのものではなく、ラベルを含む項目の外枠を取得する関数です。項目全体を非表示にしたいときなどに使います。
$p.getValue() — data-raw を最優先で返す
$p.getValue() は $p.getControl() の上に乗っている関数で、コントロールから値を取り出します。
$p.getValue = function (name) {
let $control = $p.getControl(name);
if ($control === undefined || $control.length === 0) {
return undefined;
}
let element = $control[0];
//data-raw属性があればそれを優先的に返却
let dataRaw = element.getAttribute('data-raw');
if (dataRaw !== null) {
return dataRaw;
}
//Input要素はvalue,またはchecked、Selsect要素は$control.val() それ以外はtextContentを返す
switch (element.tagName) {
case 'INPUT':
return element.type === 'checkbox' ? element.checked : element.value;
case 'SELECT':
return $control.val();
case 'TEXTAREA':
return element.value;
default:
return element.textContent;
}
};図を読み込み中…
この関数の性格を決めているのは、data-raw 属性があればそれを最優先で返すという分岐です。
data-raw が付く場所
data-raw は、サーバが HTML を描画するときに付与する属性です。数値項目のテキストボックスでは次のように付与されます。
case ControlTypes.TextBoxNumeric:
return hb.FieldTextBox(
textType: HtmlTypes.TextTypes.Normal,
fieldId: controlId + "Field",
controlId: controlId,
// ~中略~
attributes: new Dictionary<string, string>()
{
["data-raw"] = rawValue?.ToString()
?? (column.Nullable == true ? "" : "0")
},スピナー(上下ボタン付きの数値入力)でも同様です。
.Add("data-raw",
value != null
? value.ToString()
: string.Empty)data-raw の目的は、表示は書式化されるので、書式なしの生値を別に持っておくことです。数値項目は「1,000 円」のように桁区切りや単位が付いた形で表示されることがあり、表示文字列はそのまま計算に使えません。読み取り専用の項目(SPAN で描画される)でも、textContent は表示テキストなので生値が別途必要になります。
data-raw は画面操作では更新されない
クライアント側のスクリプトには、data-raw を書き換える処理が存在しません。読んでいるのは $p.getValue() の 1 か所だけです。 そのため data-raw はサーバが HTML を描画した時点の値のまま固定され、更新されるのは保存などでサーバから HTML が返ってきて再描画されたときだけです。
図を読み込み中…
この挙動が問題になるのは、たとえばプロセス機能のボタンから発火したスクリプトで、保存前の数値項目を読みたいケースです。
| 項目の種類 | data-raw | $p.getValue() が返す値 |
|---|---|---|
数値項目(NumA など) | 付く | 描画時点の値(保存前の入力は反映されない) |
分類項目(ClassA など) | 付かない | 画面の現在値 |
| タイトル・説明項目 | 付かない | 画面の現在値 |
読み取り専用項目(SPAN) | 付く | 描画時点の生値 |
WARNING
保存前の数値項目の入力値を読みたい場合は、$p.getValue() ではなく $p.getControl('NumA').val() を使います。戻り値は文字列なので、計算に使うときは Number() などで数値化してください。
var $control = $p.getControl('NumA');
var num = $control !== undefined ? Number($control.val()) : 0;コントロールグループ
$p.getData() — 送信バッファを取り出す
$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];
};$p.getFormId() は、渡されたコントロールから一番近い form を遡って id を取る関数です。
$p.getFormId = function ($control) {
return $control.closest('form').attr('id');
};図を読み込み中…
戻り値はオブジェクトへの参照なので、受け取った側が data.Xxx = 1 と書けば $p.data 本体が書き換わります。本体のコードでもこの性質を使って、直接プロパティを足している箇所が多くあります。
$p.getData($('.main-form')) と書けば、いま送信対象になっている値の一覧をまとめて確認できるため、デバッグに便利です。
$p.setData() — 画面の値を送信バッファへ写す
$p.setData() は、渡されたコントロールの現在値を読み取って $p.data に格納する関数です。画面は書き換えません。 「画面 → バッファ」の一方通行です。
$p.setData = function ($control, data) {
var controlId = $control.attr('id');
if (!$control.hasClass('not-send')) {
if (data === undefined) {
data = $p.getData($control);
}
$p.setGridTimestamp($control, data);
switch ($control.prop('type')) {
case 'checkbox':
data[controlId] = $control.prop('checked');
break;
// ~中略~
default:
switch ($control.prop('tagName')) {
case 'SPAN':
data[controlId] =
$control.attr('data-value') !== undefined
? $control.attr('data-value')
: $control.text();
break;
// ~中略~
default:
data[controlId] = $control.val();
break;
}
break;
}
}
};図を読み込み中…
$p.setData() は data-raw を見ていません。 $control.val() を読むので、画面で入力した直後の値がそのままバッファに入ります。数値項目を画面で変更してから保存すると変更後の値が保存されるのはこのためです。
$p.setData() はコントロールの change イベントから自動的に呼ばれています。
$(document).on('change', '[class^="control-"]:not(select[multiple])', function (e) {
var $control = $(this);
// ~中略(スピナーの範囲補正)~
$p.setData($control);
e.preventDefault();
});INFO
スクリプトから jQuery で直接 val() を書き換えると change イベントが発火しないため、$p.data にも入りません。値を書き換えたのに保存されない、という現象の典型的な原因がこれです。
$p.set() — DOM とバッファの両方を更新する
$p.set() は、ここまでの関数を束ねた、利用者が使うべき書き込み関数です。
$p.set = function ($control, val) {
if ($control.length === 1) {
switch ($control.prop('type')) {
case 'checkbox':
$control.prop('checked', val);
break;
case 'textarea':
$control.val(val);
// ~中略(マークダウンのビューア切り替え)~
break;
default:
switch ($control.prop('tagName')) {
case 'SELECT':
// ~中略(検索付きドロップダウンなら Ajax で候補を取得)~
if ($control.attr('multiple')) {
$p.selectMultiSelect($control, val);
} else {
$control.val(val);
$control.change();
}
break;
default:
// ~中略(ラジオ・アンカーの個別処理)~
$control.val(val);
break;
}
break;
}
$p.setData($control);
}
};$p.setData() → $p.getData() → $p.getFormId() と 3 段の入れ子になっています。
図を読み込み中…
- DOM とバッファの両方を更新するので、1 回の呼び出しで「画面にも見えるし、保存もされる」状態になります。
$control.length === 1が条件なので、コントロールが見つからなかったときは何もせず終了します。エラーにならない代わりに気付きにくい点に注意してください。- 選択肢の項目では
change()を発火させるため、その項目に紐づく他の処理(自動ポストバックや連動絞り込み)も動きます。
$p.setValue() — DOM だけを更新する
$p.setValue() は名前が似ていますが、$p.set() とは別物です。
$p.setValue = function ($control, value) {
switch ($control.prop('type')) {
case 'checkbox':
$control.prop('checked', value);
break;
case 'radio':
$control.val([value]);
break;
// ~中略~
default:
switch ($control.prop('tagName')) {
case 'SELECT':
// ~中略~
case 'SPAN':
$control.html(value);
break;
case 'TIME':
$control.html(value);
$control.attr('datetime', value);
break;
default:
// ~中略~
$control.val(value);
break;
}
}
};末尾に $p.setData() の呼び出しがありません。DOM を書き換えるだけで、送信バッファには触りません。
この関数はサーバからのレスポンスを画面に反映するために使われています。サーバースクリプトなどで context.AddResponse() を組み立てたときの、SetValue メソッドの受け口がここです。
case 'SetValue':
$p.setValue($(target), value);
$p.hideField(target, options);
break;図を読み込み中…
サーバ側の C# では、この 2 つは別のメソッドとして用意されています。
public ResponseCollection Set(
string target,
object value,
bool _using = true)
{
return _using
? Add(
method: "Set",
target: target,
value: value)
: this;
}ソース(ResponseCollection.cs L222-L233)
public ResponseCollection Val(
string target,
object value,
string options = null,
bool _using = true)
{
return _using
? Add(
method: "SetValue",
target: target,
value: value,
options: options)
: this;
}ソース(ResponseCollection.cs L384-L397)
C# の Val() が JavaScript の SetValue に対応している点に注意してください。
サーバへ送信されるまでの流れ
ボタンなどをクリックしたときに走る $p.send() の中で、バッファがそのまま送信データになります。
図を読み込み中…
$p.setMustData() により、新規作成時は画面のすべてのコントロールが送信対象になります。一方、更新時はバッファに入っている項目しか送られません。スクリプトから値を変えたのに更新されない場合は、$p.set() を使わずに val() だけで書き換えていないか確認してください。
全体像と使い分け
図を読み込み中…
関数ごとの守備範囲
| 関数 | 引数 | DOM を更新 | $p.data を更新 | 主な用途 |
|---|---|---|---|---|
$p.getColumnName(name) | 項目名 | — | — | 表示名からカラム名を解決する |
$p.getControl(name) | 項目名 | — | — | コントロールを取得する |
$p.getField(name) | 項目名 | — | — | 項目の外枠を取得する |
$p.getValue(name) | 項目名 | — | — | 生値(data-raw 優先)を読む |
$p.getData($control) | jQuery | — | — | 送信バッファを参照する |
$p.set($control, v) | jQuery | する | する | 値を書き換える(通常はこれ) |
$p.setValue($control, v) | jQuery | する | しない | 表示だけ差し替える |
$p.setData($control) | jQuery | しない | する | 画面の値をバッファへ写す |
やりたいこと別の選び方
| やりたいこと | 使う関数 |
|---|---|
| 保存済みの生値を読みたい | $p.getValue(name) |
| 画面で入力中の値を読みたい | $p.getControl(name).val() |
| 値を変更して保存もしたい | $p.set($p.getControl(name), value) |
| 表示だけ変えて保存はしたくない | $p.setValue($p.getControl(name), value) |
val() で直接書き換えた値を保存対象にしたい | $p.setData($p.getControl(name)) |
スクリプトが思ったとおりに動かないときは、「いま触っているのは DOM なのかバッファなのか」を切り分けると原因にたどり着きやすくなります。
関連ページ
スクリプトで使えるシステム変数 —
$p.getControl()が読んでいるReferenceTypeなどの Hidden 要素context.AddResponse の Method と引数 —
Set/SetValueを含むレスポンスの Method