項目の取得と DOM イベントの接続を読み解く
項目名から取得する $p.getControl() と、DOM の change を受け取る本体のハンドラは、別の仕組みです。前者は対象を解決し、後者は現在の値を送信バッファへ登録します。値だけを .val() で変更した場合や、応答で要素を差し替えた場合は、この境界を確認します。
項目を取得できない3つの状態
getColumnName は #Columns の JSON から表示名または物理名が一致する最初の列を探し、getControl は ReferenceType_ColumnName、getField は末尾に Field を付けた ID を使います(_elements.js)。
| 状態 | getControl の結果 | 調べるもの |
|---|---|---|
| 項目名が Columns にない | undefined | 物理名・表示名・画面の列情報 |
| 項目名は解決できるが要素がない | 要素数0の jQuery オブジェクト | 表示対象、権限、画面の種類 |
| Columns の JSON がない・不正 | JSON 解析で例外 | その画面に項目のメタ情報があるか |
if ($control) だけでは、要素数0を除外できません。取得例は、タイトルがある通常の編集画面のブラウザコンソールで試せます。
(function () {
const $control = $p.getControl('Title');
if (!$control || $control.length !== 1) {
console.warn('タイトルのコントロールが1件に解決できません。');
return;
}
console.log({
id: $control.attr('id'),
form: $p.getFormId($control),
domValue: $control.val(),
raw: $control.attr('data-raw'),
bufferValue: $p.getData($control)[$control.attr('id')]
});
})();getData の呼び出しには、バッファの初期作成と $p.store.formId の更新があります。純粋に DOM の値だけを読む処理とは区別します(getData)。getValue は data-raw が存在すれば先に返すため、DOM の .val() とも結果が違うことがあります(値の取得、get・set の詳細)。
一覧の行とセルは項目コントロールとは別
| 関数 | 探す対象 | 注意点 |
|---|---|---|
getGridRow(id) | #Grid > tbody > tr の data-id | 同じ ID の履歴行も含み得る |
getGridColumnIndex(name) | ヘッダーの data-name に一致する列 | 見つからなければ -1 |
getGridCell(id, name, excludeHistory) | 行 ID とヘッダー位置から td を選ぶ | excludeHistory が真なら data-history のある行を除く |
getGridCell は列インデックスに1を足して nth-child にします。項目名が解決できても、現在の一覧ヘッダーにその列がなければセルは取得できません(一覧の解決)。
$p.on は document へイベントを委譲する
$p.on(events, name, func) は、登録時に getControl(name) の ID を読み、$(document).on(events, '#取得したID', func) として登録します(on の実装)。
- 登録時には、項目を解決できるメタ情報とコントロールの ID が必要。
- 登録後は、同じ ID の要素が差し替わっても document のハンドラが残る。
- 登録を繰り返すとハンドラが増える。名前空間を付けても、自動で前回分を消す処理はない。
- 関数は登録解除用の戻り値を返さない。解除には jQuery の
offを使う。
差し替えに対応し、登録の重複を防ぐ例
以下はブラウザ側のスクリプトです。新規・編集画面へ配置し、タイトル入力欄の通常の編集操作を対象にします。この例は input を受け取り、文字数を表示するだけなので、本体の change による送信や自動ポストバックを追加で発火させません。
$p.manualBindTitleLength = function () {
const $title = $p.getControl('Title');
if (!$title || $title.length !== 1) return;
const selector = '#' + $title.attr('id');
$(document).off('input.manualTitleLength', selector);
$p.on('input.manualTitleLength', 'Title', function () {
const $field = $p.getField('Title');
if (!$field || $field.length !== 1) return;
let $note = $field.find('.manual-title-length');
if ($note.length === 0) {
$note = $('<small/>').addClass('manual-title-length').appendTo($field);
}
$note.text(String($(this).val()).length + '文字');
});
};
$p.events.on_editor_load = function () {
$p.manualBindTitleLength();
};同じ登録関数を2回呼び、入力時に説明が1つだけ出ることを確認します。応答でタイトルフィールドが置き換わった後も、入力すると新しいフィールドに説明を作ります。コントロールの ID が変わる構成では再登録が必要です。
既存の on_editor_load がある場合は 複数処理の登録を使い、この代入で既存処理を上書きしないようにします。
本体の change 登録は class 属性の順序も見る
_controllevents.js の最初のハンドラは、[class^="control-"]:not(select[multiple]) を対象にします。これは class 属性の文字列が control- で始まるという条件です(登録と値収集)。
例えば class="control-textbox custom" と class="custom control-textbox" は、ここでは同じ判定になりません。独自に入力要素を作るとき、control-textbox クラスが含まれているだけで値収集が動くとは判断しないでください。送信時の setMustData が使う [class*="control-"] とは条件も違います(送信前の収集)。
このハンドラは、数値スピナーの空欄・文字・上下限を調整してから setData を呼びます。spin のハンドラは、DOM の値ではなく ui.value をバッファへ登録し、行のタイムスタンプも補います(スピナーの値登録)。
.val() と change の使い分け
| 操作 | この本体ハンドラへの影響 |
|---|---|
.val(value) | change を自動で発火しないため、このハンドラによる登録はしない |
.val(value).trigger('change') | 条件に一致すれば値登録する。自動ポストバックのハンドラにも届く |
$p.setData($control) | 値登録を直接行う。change は発火しない |
$p.set($control, value) | 本体関数で値を変えて登録。通常の単一選択では change も発火する |
根拠は イベント登録、set、setDataです。追加通信を避けたい場合、値の登録だけでよいのか、変更イベントも必要なのかを決めてから選びます。
自動ポストバックの対象条件・送信先・複数選択の別経路は 自動ポストバックの解説を参照してください。
検証範囲と関連ページ
掲載コードと委譲・重複解除・class 条件は、本体 JavaScript と DOM 環境で確認しています。実機の画面操作は未検証です。