項目連携の候補取得とイベントの連鎖
項目連携は、親の選択 ID に従って子の候補を取得し、その子の変更イベントで次の段へつなぐ機能です。項目の自動ポストバックを有効にしなくても、独自の候補取得要求を送ります。
設定方法は公式の項目連携を参照してください。ここでは参照先のリンク構造、送信データ、_dispatch.js に返す命令とイベントの連鎖を説明します。確認は本体ソースによるもので、実機の画面操作は検証していません。
カテゴリから商品の候補を選ぶ構成例
例では、参照先のカテゴリマスタをサイト ID 100、商品マスタを 200 とします。番号はダミーです。
| テーブル | 項目 | 内容 |
|---|---|---|
| 編集する記録テーブル | ClassA | カテゴリマスタへのリンク |
| 編集する記録テーブル | ClassB | 商品マスタへのリンク |
| 商品マスタ | ClassA | カテゴリマスタへのリンク |
項目連携に、編集テーブルの ClassA → ClassB の順を指定します。商品マスタ側にもカテゴリマスタへのリンクがあるため、その列を親との対応に使えます。現在の画面の二つの項目を並べるだけでなく、子の参照先から親の参照先へのリンクが必要です(親との対応列を探す処理)。
親の対応列を探すときに比較しているのは、列名ではなく参照先サイトです。子の商品マスタの Links から、カテゴリマスタの parentSiteId と一致するリンクを選んでいます。編集テーブルの列名をそろえるだけでは、この条件を満たせません。
return Destinations
.Values
.Where(dest => dest.SiteId == childSiteId)
.Select(dest => dest.Links
.Where(o => o.SiteId > 0)
.Where(l => l.SiteId == parentSiteId)例えば編集側の親が ClassA、商品マスタ側のカテゴリ列が ClassC でも、同じカテゴリマスタへリンクしていれば対応列の候補になります。複数の候補がある場合の並び順の判定は、この絞り込みの後です。
子の参照先に同じ親サイトへのリンクが複数あるときは、エディタ項目の並び順で先頭の列が選ばれます。「同名の ClassA が必ず使われる」という判定ではありません。
項目連携
親子関係はリンクの構造から決まる
項目連携は、画面の項目同士の値の比べ方を自由に書く機能ではありません。参照先テーブル同士のリンクの構造から親の条件を導く機能です。
SetRelatingColumnsLinkedClass() は、設定された項目の並びを隣り合う 2 つずつ処理します(A → B → C なら A→B と B→C)。GetParentLinkedClass() は、子の項目の参照先テーブルの中から、親の項目の参照先テーブルにリンクしている項目を探し、その項目名を ColumnsLinkedClass に入れます。候補が複数あるときは、子の参照先テーブルの編集画面での項目の順で先頭のものを使います(SiteSettings.cs#L6079-L6134)。
送信
relatingcolumns.js は hidden 項目 TriggerRelatingColumns_Editor の設定を読み、子が SELECT のときに親の change イベントを登録します(relatingcolumns.js#L49-L113)。
送信データでは、画面の子項目と、商品マスタ側で親を参照する列を別のキーに入れます。次の部分を見ると、ClassA という名前だけで親子を結び付けていないことが分かります。
var formData = $p.getData($trigger.closest('form'));
formData['RelatingDropDownControlId'] = tablename + '_' + chld;
formData['RelatingDropDownSelected'] = JSON.stringify(childIds);
formData['RelatingDropDownParentClass'] = linkedClass;
formData['RelatingDropDownParentDataId'] = JSON.stringify(parentIds);
formData['IsInitDisplay'] = isInitDisplay;RelatingDropDownControlId は候補を入れ替える画面の子項目、RelatingDropDownParentClass は子の参照先の対応列です。親 ID と子の選択済み ID は配列を JSON 化して渡します。IsInitDisplay は初期候補取得と親変更後の取得を区別し、サーバー側の選択済み ID の補完にも使われます。
- 画面を開いたときに
IsInitDisplay=trueで 1 回候補を取る - 親が変わると、500 ミリ秒のデバウンスのあと
IsInitDisplay=falseで候補を取る - 親の選択 ID、子の選択済み ID、子の参照先にある親へのリンク項目名をフォームのデータに入れ、
$p.send()でRelatingDropDownに POST する
| 送信する項目 | 内容 |
|---|---|
RelatingDropDownControlId | 更新する子のコントロール |
RelatingDropDownSelected | 子の選択済み ID |
RelatingDropDownParentClass | 子の参照先にある親へのリンク項目名 |
RelatingDropDownParentDataId | 親の選択 ID のリスト |
IsInitDisplay | 画面を開いたときの取得かどうか |
同時に子の要素へ parent-data-class・parent-data-id・selected-options 属性を付けます。この親の情報は、選択肢の検索ダイアログを開くときにも使われます(dropdownsearch.js#L58-L59)。
この送信に AutoPostBack は要りません。 項目連携は $p.controlAutoPostBack() とは別に送ります。
サーバーの処理と次の段への連鎖
DropDowns.RelatingDropDown() は SearchDropDownColumn() で候補を取り、Html(子の option の差し替え)→ Invoke("callbackRelatingColumn") → ClearFormData の順で命令を返します(DropDowns.cs#L351-L443)。
選択済み ID の補完は、初期表示かどうかの条件の内側にあります。
if (isInitDisplay)
{
selectedValue.Deserialize<string[]>().ForEach(o =>
{
if (!column.ChoiceHash?.ContainsKey(o) == true)
{
column.AddToChoiceHash(
context: context,
value: o);
}
});初期表示では、候補の辞書にない選択済み ID を AddToChoiceHash で補います。親を変更した要求では isInitDisplay が偽なので、この補完を通りません。初期表示で見えていた商品が親変更後も残るかを考えるとき、この二つの要求を区別する必要があります。
$p.callbackRelatingColumn() は子の複数選択の表示を更新し、always-send クラスを付け、子の change を明示的に発火します(relatingcolumns.js#L114-L124)。これで B→C のような次の段の項目連携が動きます。
候補を書き換えるだけでは次の段は動きません。応答から呼ばれるコールバックが、子の change を明示的に発火します。
$p.RefreshMultiSelectRelatingColum($target);
$target.addClass('always-send');
$target.addClass('not-set-form-changed');
$target.trigger('change');
$target.removeClass('not-set-form-changed');always-send は子の値を後続の送信対象に加えます。not-set-form-changed は change の発火中だけ付与し、その後に外します。ここには次の項目連携や自動ポストバックを無効にする分岐はありません。したがって、連鎖を調べるときは「親の通信が一回終わった」だけでなく、この change の発火先も追います。
応答は配列の順に実行する
図を読み込み中…
_dispatch.js は Html、Invoke、ClearFormData を応答の配列の順に実行します。Invoke が子のコールバックを呼ぶのは配列の途中で、UI と入力検証の再適用は全命令の実行後です。ClearFormData は画面の値ではなく送信バッファを削除します(命令配列の実行、バッファの削除命令)。
自動ポストバックとの併用
自動ポストバックで項目を差し替えると、項目連携が付けた属性も付け直しが要ります。そのため応答の最後の initRelatingColumnEditorNoSend() が noSend=true で項目連携を初期化し直し、属性だけを付けて、そこからの RelatingDropDown の送信は止めます(relatingcolumns.js#L12-L15)。
これは通信を 1 回にまとめる仕組みではありません。
- 同じ項目が項目連携の親で、かつ自動ポストバックも有効なら、2 つの
changeハンドラーがそれぞれ送信する - 項目連携が終わったあとの子の
changeは、子の自動ポストバックも起こし得る callbackRelatingColumnが付けるnot-set-form-changedは、変更状態の扱いを変えるためのもので、送信は止めない$p.disableAutPostbackは両方の処理にチェックがあるが、普段の併用を自動で調整するものではない(使い方は 公式マニュアルに載っていない $p の関数 を参照)
項目連携は 500 ミリ秒のデバウンスを持ち、両方の送信は同期(_async=false)で行われます。ただし、スクリプトのイベントなども挟まるため、実際の通信の回数と順序は実機で確かめてください。
選択済みの値の扱い
候補を絞ることと、今の値を消すかどうかは別の処理です。
| 経路・条件 | 選択済みの値 |
|---|---|
| 項目連携(画面を開いたとき) | 候補に無い選択済みの値を AddToChoiceHash() で候補に足す |
| 項目連携(親を変えたあと) | 候補に足さず、addSelectedValue=false で描く |
項目連携で親 ID のリストが空(parentIds?.Any() != true) | 選択値を null にする。候補も作らない(ビューのフィルタの項目連携では候補は作る) |
| 通常のリンク項目の描画(自動ポストバックの差し替えを含む) | HtmlFields.EditChoices() が、候補に無い選択済みの値を、リンク先のタイトルが取れれば候補に足す |
項目連携の扱いは DropDowns.cs#L351-L443、通常の項目は HtmlFields.cs#L267-L303 です。「親 ID のリストが空」は parentIds?.Any() != true の判定のことで、画面で空欄に見える状態がすべてこれに当たるとは限りません。通常の項目の補完はリンク先のタイトルが取れることが条件なので、式で候補が変わっても、今の値が必ず消えるわけではありません。
動的な絞り込み式との併用
子のリンクに ColumnFilterExpressions がある場合は、候補取得が式の評価へ分岐し、項目連携の親 ID を検索条件へ渡さない経路があります。親条件が自動で AND されるとは判断せず、式側にも必要な条件を書きます(候補取得の分岐)。詳しい条件は 選択肢を画面の値で動的に絞り込むを参照してください。