選択肢を画面の値で動的に絞り込む
ColumnFilterExpressions は、編集中の画面の値からリンク項目の候補取得条件を作る設定です。条件を書くだけでは入力変更時の通信は起きません。自動ポストバックによるフィールド再描画や、検索ダイアログの候補取得が評価の契機になります。
設定方法と適用条件は公式の選択肢を他の項目の値で絞り込むを参照してください。ここでは式の展開、候補を作り直す経路、項目連携との併用を説明します。設定例はソースの評価規則を説明する構成例で、実機の画面操作は検証していません。
カテゴリの内部値で商品を絞る構成例
例では、参照先のカテゴリマスタをサイト ID 100、商品マスタを 200 とします。番号はダミーです。
| テーブル | 項目 | 内容 |
|---|---|---|
| 編集する記録テーブル | ClassA | カテゴリマスタへのリンク |
| 編集する記録テーブル | ClassB | 商品マスタへのリンク |
| 商品マスタ | ClassA | カテゴリマスタへのリンク |
編集テーブルの ClassB の選択肢一覧には、次の JSON を設定する構成にできます。
[
{
"SiteId": 200,
"View": {
"ColumnFilterExpressions": {
"ClassA": "[@ClassA]"
}
}
}
]左のキー ClassA は商品マスタ側の絞り込み対象、右の [@ClassA] は現在の編集画面の値です。この例では、カテゴリのレコード ID を条件に使います。[ClassA] は表示値、[@ClassA] は内部値を使う指定です(IncludedColumns、式の置き換え)。
| 記法 | 評価する内容 |
|---|---|
[ClassA] | 編集画面の ClassA の表示値 |
[@ClassA] | 編集画面の ClassA の内部値 |
=[@ClassA] | 内部値を展開し、先頭の = を除いてフィルタへ直接渡す |
= は SQL や JavaScript を実行する記号ではありません。型に応じたフィルタ変換を省く指定です(フィルタへの変換)。
通常のドロップダウンをカテゴリ変更直後に更新する構成では、ClassA の自動ポストバックを有効にします。「自動ポストバック時に返却する項目」を指定している場合は、候補を作り直す ClassB を含めます。式だけでは、ClassA の変更を監視しません(差し替え対象、返却対象の制限)。
カテゴリの表示名が同じでもレコード ID は別の値です。リンクの ID を保存している商品マスタ側の列へ、表示名を条件として渡さないよう、左右の値を揃えます。
候補を作り直す流れ
図を読み込み中…
根拠は フィールドの再生成、式の展開、フィールド応答、応答の反映です。
ColumnFilterExpressions の評価
画面の値を検索条件に変える
SiteSettings.ColumnFilterExpressionsLink() は、項目に対応する JSON 形式のリンクのうち View.ColumnFilterExpressions を持つものを取ります(SiteSettings.cs#L5156-L5163)。SetChoiceHashByFilterExpressions() は次の順に処理します(ResultUtilities.cs#L2826-L2908)。
- 辞書のキーから、絞り込む側(リンク先)の項目を決める
- 式に含まれる
[項目名]を、編集モデルの値に置き換える(計算はせず、文字列の置き換えだけ) - 置き換える値は、項目の出力の種類が表示値なら
ToDisplay()、それ以外はToValue()で作る View.SetColumnFilterHashByExpression()で型に合わせて検索条件に変えるColumn.SetChoiceHash()からLink.SetChoiceHash()を呼んで候補を取る
式の展開では、まず元の式の先頭が = かを raw に保存します。その後、参照する列の表記をいったん GUID に置き換え、GUID をモデルの値へ置き換えます。表示値と内部値を選ぶ箇所は次の部分です。
expression = expression.Replace(
guid,
includedColumn.OutputType == Column.OutputTypes.DisplayValue
? resultModel.ToDisplay(
context: context,
ss: ss,
column: includedColumn,
mine: resultModel.Mine(context: context))
: resultModel.ToValue(
context: context,
ss: ss,
column: includedColumn,
mine: resultModel.Mine(context: context)));OutputType が表示値なら ToDisplay、それ以外なら ToValue です。商品マスタのリンク列がカテゴリの ID を保持している構成では、カテゴリ名を返す [ClassA] ではなく、ID を返す [@ClassA] を使います。先に参照表記を GUID へ変えるため、後から代入する値を、次の参照表記として置き換える処理にはなっていません。
raw は展開前に決まります。参照した値自体が = で始まっても、それだけで直接フィルタへ渡す分岐に切り替わるわけではありません。直接渡す指定は、元の式の先頭に書きます。
SetColumnFilterHashByExpression() の変換は次のとおりです(View.cs#L3612-L3657)。
| 元の式・展開後の値 | ColumnFilterHash に入る条件 |
|---|---|
元の式が = で始まり、展開後が = だけ | リンク先テーブルの ID に -1(候補なし) |
元の式が = で始まり、展開後に値がある | 先頭の = を除いた文字列をそのままフィルタの値にする(SQL を実行する指定ではない) |
元の式が = で始まらず、展開後が空文字 | リンク先テーブルの ID に -1(候補なし) |
| それ以外 | リンク先の項目の型で変える。真偽値は True/False、日付は同じ日の範囲、選択肢のある項目と decimal は 1 要素の JSON 配列、ほかはそのまま |
式の先頭の = と、展開後の空文字は別の分岐です。raw の分岐を先に判定し、そのあとで空文字を扱います。
if (raw)
{
if (expression == "=")
{
ColumnFilterHash[Rds.IdColumn(ss.ReferenceType)] = "-1";
}
else
{
ColumnFilterHash[columnName] = expression.Substring(1);
}
}
else if (expression.IsNullOrEmpty())
{
ColumnFilterHash[Rds.IdColumn(ss.ReferenceType)] = "-1";
}例えば =[@ClassA] の内部値が空なら展開後は = になり、ID の条件に -1 が設定されます。[@ClassA] の展開結果が空の場合も、次の分岐で同じ ID 条件になります。値がある =1001 なら、先頭一文字を除いた 1001 が指定列のフィルタへ入ります。= は式を実行する機能ではなく、この変換の選択に使われています。
View.ColumnFilterHash に既にある条件は残り、同じキーは式の結果で上書きされます。ブラウザーで候補を隠すのではなく、サーバーで候補を取る条件を作る仕組みです。
評価される契機
| 契機 | 経路 |
|---|---|
| 画面を開いたときの項目の描画 | Field() → 式の評価 |
| 自動ポストバックでの項目の差し替え | FieldResponse() → Field() → 式の評価 |
| 選択肢の検索ダイアログ | SearchDropDownColumn() → 条件を満たすと式の評価 |
| 項目連携の候補取得 | 同じ SearchDropDownColumn() → 条件を満たすと式の評価 |
選択肢の検索ダイアログは、開くときにメインフォームの入力データをダイアログのフォームに写し、レコード ID を DropDownSearchReferenceId に入れます(dropdownsearch.js#L16-L62)。ItemUtilities.SetChoiceHashByFilterExpressions() は、そのレコード ID・新規かどうか・フォームの値で IssueModel か ResultModel を作り、テーブルの種類ごとの式の評価に渡します(ItemUtilities.cs#L375-L458)。自動ポストバックが無くても、検索ダイアログでは保存前の入力値で絞り込めます。
一方、式を書いただけでは、変更イベントの監視も自動ポストバックの有効化も行われません。 普通のドロップダウンを入力の直後に取り直したいなら、式が参照する項目で自動ポストバックを有効にするなど、別に契機を用意します。
自動ポストバックで候補を更新する
FieldResponse() は、差し替え対象の項目は ReplaceAll で項目の HTML ごと、それ以外は Val()(JSON では SetValue)で値だけを返します(ResultUtilities.cs#L2653-L2824)。差し替え対象は SiteSettings.ReplaceFieldColumns() が決め、ColumnFilterExpressions を持つリンク項目はここで無条件に加わります(SiteSettings.cs#L6177-L6201)。変更された項目が式に出てくるかどうかは見ていません。同じリストには、サーバースクリプトが差し替えを指示した項目、変更した項目の Lookups の転記先、UseSearch の項目、状況による制御の対象項目も入ります。
自動ポストバックの差し替え対象は、式を持つリンクの一覧から追加されます。
columns.AddRange(Links?
.Where(o => o.View?.ColumnFilterExpressions?.Any() == true)
.Select(o => o.ColumnName));この条件には、式の中に発火元の列が出てくるかを調べる処理がありません。ClassA の変更でも DescriptionA の変更でも、式を持つ ClassB は差し替え候補に入ります。ただし、差し替え候補であることと、返却対象として選ばれることは別の判定です。
差し替えの HTML を作る Field() が SetChoiceHashByFilterExpressions() を呼ぶので(ResultUtilities.cs#L1857-L1926)、自動ポストバックは項目の描き直しを通して式を評価し直します。
ただし、その前に GetEditorColumnNames(postbackColumn) で返す項目が絞られます。変更した項目の「自動ポストバック時に返す項目」(ColumnsReturnedWhenAutomaticPostback)が設定されていれば、そこに書かれた項目だけが返ります(SiteSettings.cs#L2719-L2742)。ここに式を持つリンク項目を入れていないと、差し替え対象のリストに入っていても、その項目は返らず選択肢も変わりません。
返却対象の設定があると、エディタ項目の一覧はその設定との共通部分に絞られます。
var postbackTargets = postbackColumn?.ColumnsReturnedWhenAutomaticPostback?.Split(',');
if (postbackTargets?.Any() == true)
{
columnNames = postbackTargets
.Where(columnName => columnNames.Contains(columnName))
.ToList();
}例えば式を持つ ClassB が差し替え候補でも、発火元の返却対象が NumA だけなら ClassB は返りません。式の値を直しても候補が変わらないときは、まず ReplaceAll の応答に ClassB が存在するかを確認すると、式の評価と返却対象の問題を分けられます。
項目連携の親条件との関係
SearchDropDownColumn() は、項目に JSON 形式のリンクがあると次の 2 つに分かれます(DropDowns.cs#L512-L594)。
項目連携と検索ダイアログが使う候補取得には、式を評価する経路と、親 ID を引数で渡す経路があります。分岐の入口は次の条件です。
if ((context.Forms.Bool("IsNew")
|| ss.SiteId != referenceId)
&& link.View?.ColumnFilterExpressions?.Any() == true)
{
ItemUtilities.SetChoiceHashByFilterExpressions(
context: context,
ss: ss,
column: column,
referenceId: referenceId,
searchText: searchText,
offset: offset,
search: true,
searchFormat: searchFormat);
}ColumnFilterExpressions があるだけでなく、新規か、サイト ID と referenceId が違うかを判定しています。式の経路の呼び出しには parentColumn・parentIds がありません。通常経路には両方があるため、二つの絞り込みが常に重なるものとして設定すると条件が抜けます。式の経路では、必要な親の条件も式に含めます。
通常経路の呼び出しには、子の参照先の対応列と親 ID の引数があります。
column.SetChoiceHash(
context: context,
ss: currentSs,
link: link,
searchText: searchText,
parentColumn: currentSs.GetColumn(
context: context,
columnName: parentClass),
parentIds: parentIds,
offset: offset,
search: true,
searchFormat: searchFormat);parentColumn は parentClass から取得し、parentIds を同時に渡します。式の経路とこの呼び出しを比較すると、親の値がどこで検索条件へ入るかを区別できます。
| 分岐の条件 | 呼び出し | 項目連携の親の条件 |
|---|---|---|
リンクに式があり、かつ IsNew か ss.SiteId != referenceId | ItemUtilities.SetChoiceHashByFilterExpressions() | 渡さない |
| それ以外 | Column.SetChoiceHash() | 親の項目(parentColumn)と parentIds を渡す |
referenceId はフォームの DropDownSearchReferenceId から読みます。この hidden 項目は選択肢の検索ダイアログのフォームにだけあり(HtmlDropDownSearches.cs#L29-L31)、編集画面の項目連携はメインフォームのデータを送るので、項目連携の要求では値が無く 0 として読まれます(Forms.cs#L35-L38)。したがって、編集画面の項目連携では、子のリンクに式があれば新規・編集を問わず式の側に入ります。 検索ダイアログでもレコード ID はサイト ID と違うので、同じく式の側に入ります。
通常の側の Link.Items() は、View の条件に加えて LinkHashRelatingColumnsSubQuery() による親 ID の絞り込みを足します(Link.cs#L636-L675)。式の側の SetChoiceHash() の呼び出しには親の引数が無いので、親 ID のサブクエリは付きません。親で絞りたいなら、式の中で同じ親の項目を参照する条件を書きます。
なお、式の側に入っても、候補取得の外側にある項目連携の処理(親が未選択のときの扱い、callbackRelatingColumn による連鎖)はそのまま残ります。
項目連携から式を評価するときのモデル
項目連携の要求から ItemUtilities.SetChoiceHashByFilterExpressions() に入ると、編集画面では IsNew が偽なので、レコード ID に上の referenceId(0)を渡して ResultModel(IssueModel)を作ります(ItemUtilities.cs#L375-L458)。保存済みのレコードを ID で読むのではなく、要求に含まれるフォームの値を元にモデルを作ることになります。式が参照する項目の値が期待どおりに入るかは、実機で確認していません。
絞り込み後の現在値と画面反映
動的絞り込みに専用の応答命令はありません。取得した候補は、フィールドを差し替える ReplaceAll や、項目連携の Html に含まれます。_dispatch.js はその HTML を反映し、全命令の実行後に UI と入力検証を再適用します(画面への反映)。
候補を絞ることと、現在の選択値を消すことは別です。通常のフィールド描画では、候補外の現在値でもリンク先のタイトルが取れれば候補に追加します。一方、項目連携の親変更後は同じ補完をしません。候補が変わっただけで現在値が必ずクリアされるとは判断できません(通常の候補補完、項目連携の選択値)。
商品の単価などを別の項目へ転記するには、絞り込みとは別に Lookups を設定します。転記条件は Lookup の転記と上書き制御を参照してください。