view.OnSelectingWhere とフィルター
サーバースクリプトから一覧の絞り込みを動的に制御する方法をまとめます。
- 「ビュー処理時」の
view.OnSelectingWhereで、拡張 SQL を名前で指定して WHERE 句に追加できます。SQL にはプレースホルダー・カラムプレースホルダー・@パラメータ名(拡張フィールドのSqlParam)で値を渡せます。 - 数千件のコードで IN 絞り込みをするときは、カンマ区切り文字列 1 つにまとめて渡し、SQL 側で
STRING_SPLITで展開すると SQL Server のパラメータ数上限を回避できます。 - 拡張 SQL の本文の先頭に
ANDを書くと、一覧が「この項目は並べ替えることができません」のエラーになります。 - 「画面表示の前」(
BeforeOpeningPage)でSetFormDataを積むと、一覧のソート・フィルター操作時のgridrowsリクエストにフィルターを引き継げます。
SQL 例の読み方
DBMS 名のない SQL は、3 DBMS で共通の構文を使った説明用の抜粋です。プリザンターは MySQL 接続でも ansi_quotes を設定するため、識別子の二重引用符が使えます。DB のコンソールで直接実行するときの引用符・パラメータの扱いは DBMS ごとの SQL の書き方 を参照してください。... を含む例は実行用 SQL ではありません。
view.OnSelectingWhere の基本
一覧表示時に View.SetColumnsWhere() が呼び出されると、内部で「ビュー処理時」サーバースクリプトが実行されます。スクリプトで view.OnSelectingWhere = "name" を設定すると、その名前に一致する拡張 SQL が WHERE 句に追加されます。公式の説明は公式マニュアルにあります。
view.OnSelectingWhere = "MyFilter";拡張 SQL の JSON では "SpecifyByName": true を設定します。これを設定しないと、"OnSelectingWhere": true のすべての拡張 SQL が名前に関係なく適用されます。
{
"Name": "MyFilter",
"SpecifyByName": true,
"SiteIdList": [123],
"OnSelectingWhere": true
}"Issues"."ClassA" = '001'SQL の先頭に AND は書かない
OnSelectingWhere の SQL は、プリザンターが組み立てた WHERE 句の条件の 1 つとして追加され、各条件は and で連結されます。確認したソースでは、拡張 SQL の本文がそのまま条件として追加され(Rds.cs)、条件同士の連結で and が自動で付きます(SqlWhereCollection.cs)。SQL の先頭に AND を書くと ... and AND ... となって構文エラーになるため、先頭に AND や OR は書きません。公式マニュアルの FAQ のサンプルもこの書き方です。先頭に AND を書いたときに一覧で出るエラーは「この項目は並べ替えることができません」になるときを参照してください。
パラメータを使う
view.OnSelectingWhere の SQL に値を渡す方法は次の 4 つです。
| 機能 | 設定箇所 | 内容 |
|---|---|---|
文字列プレースホルダー( / / ) | SQL の CommandText | 実行時に自動で文字列置換される |
| カラムプレースホルダー | サーバースクリプトの view.AddColumnPlaceholder(key, col) | SQL 内の をカラム参照に置換する(テーブル名も自動解決) |
@パラメータ名 | 拡張フィールドの JSON(SqlParam)+ サーバースクリプト | view.Filters.パラメータ名 の値を SQL パラメータとして渡す |
OnSelectingWhereParams | 拡張 SQL の JSON | 指定したカラムのフィルターが有効なときだけ SQL を適用する |
文字列プレースホルダー
CommandText には、実行時に自動置換される組み込みプレースホルダーを使えます。
| プレースホルダー | 置換後の値 |
|---|---|
| 現在のサイト ID(数値) |
| 現在のコンテキスト ID(一覧画面ではサイト ID と同じ) |
| タイムスタンプ(yyyy/M/d H:m:s.fff 形式) |
これらは SQL パラメータではなく、単純な文字列置換です。整数値や固定の文字列を SQL に埋め込むのに使います。
{
"Name": "SiteFilter",
"SpecifyByName": true,
"OnSelectingWhere": true
}"Issues"."ClassA" IN (
SELECT [Value]
FROM [SiteMaster]
WHERE [SiteId] = {{SiteId}}
AND [UserId] IN (@_U)
)"Issues"."ClassA" IN (
SELECT "Value"
FROM "SiteMaster"
WHERE "SiteId" = {{SiteId}}
AND "UserId" IN (@ipU)
)`Issues`.`ClassA` IN (
SELECT `Value`
FROM `SiteMaster`
WHERE `SiteId` = {{SiteId}}
AND `UserId` IN (@ipU)
)SQL ファイル名はいずれも App_Data/Parameters/ExtendedSqls/SiteFilter.json.sql です。
サイト ID をハードコードせず を使うと、同じ拡張 SQL の設定を複数のサイトで再利用できます。
@_U(PostgreSQL・MySQL では @ipU)はログインユーザーのユーザー ID で、拡張 SQL に自動で渡されます。接頭辞は DBMS で変わり、Parameter.json の SqlParameterPrefix が空の既定なら SQL Server は @_、PostgreSQL・MySQL は @ip です(Parameter.cs、SqlIo.cs)。MySQL の例は識別子をバッククォートで囲んでいますが、プリザンターは MySQL で SQL を実行する前に sql_mode へ ansi_quotes を設定するため、プリザンターが組み立てる部分( の展開結果など)と同じく二重引用符でも書けます(MySqlCommandText.cs、SqlIo.cs)。
カラムプレースホルダー:view.AddColumnPlaceholder()
view.AddColumnPlaceholder(key, columnName) を使うと、SQL 内のプレースホルダー がカラム参照("テーブル名"."カラム名" 形式)に置換されます。
view.OnSelectingWhere = "DivisionFilter";
view.AddColumnPlaceholder("DivisionCol", "ClassA");{{DivisionCol}} IN (
SELECT [ClassA]
FROM [Users]
WHERE [UserId] IN (@_U)
){{DivisionCol}} IN (
SELECT "ClassA"
FROM "Users"
WHERE "UserId" IN (@ipU)
){{DivisionCol}} IN (
SELECT `ClassA`
FROM `Users`
WHERE `UserId` IN (@ipU)
)SQL ファイル名はいずれも App_Data/Parameters/ExtendedSqls/DivisionFilter.json.sql です。
は期限付きテーブル(Issues)なら "Issues"."ClassA"、記録テーブル(Results)なら "Results"."ClassA" に展開されます。テーブル名を意識せずに同じ SQL 設定を使い回せます。
サイトによって参照したいカラムが異なる場合(あるサイトでは ClassA、別のサイトでは ClassB に部署コードが入っているなど)は、拡張 SQL は共通のまま、サーバースクリプトでカラム名だけを差し替えられます。
// サイトID によって参照カラムを切り替える
if (context.SiteId === 100) {
view.OnSelectingWhere = "DivisionFilter";
view.AddColumnPlaceholder("DivisionCol", "ClassA");
} else if (context.SiteId === 200) {
view.OnSelectingWhere = "DivisionFilter";
view.AddColumnPlaceholder("DivisionCol", "ClassB");
}{
"Name": "DivisionFilter",
"SpecifyByName": true,
"OnSelectingWhere": true
}SiteIdList を指定しなくても、"SpecifyByName": true にしておけばサーバースクリプトから明示的に指定した場合にだけ適用されるため、意図しないサイトへの適用を防げます。
view.AddColumnPlaceholder() を呼ぶと、プレースホルダーの情報がビューオブジェクトに格納されます。WHERE 句の構築時に、カラム名がサイト設定(SiteSettings)を使って解決され、テーブル修飾付きのカラム参照に変換されます。
WARNING
指定したカラム名がサイト設定に存在しない場合、プレースホルダーは展開されません。
@パラメータ名で値を渡す:拡張フィールドの SqlParam
文字列置換とは別に、@パラメータ名 形式の正規の SQL パラメータを使う方法もあります。拡張フィールド(ExtendedField)の SqlParam: true を使うと、サーバースクリプトで設定した任意の値を SQL パラメータとして渡せます。
View.Param() が呼び出されると、App_Data/Parameters/ExtendedFields/ に配置した拡張フィールドのうち FieldType: "Filter" かつ SqlParam: true のものが、フィルターハッシュ(ColumnFilterHash)の値から SQL パラメータとして生成されます。この Param() は一覧取得時に View.Where() と同じクエリに渡されるため、OnSelectingWhere の SQL 内で @パラメータ名 を使えます。
図を読み込み中…
App_Data/Parameters/ExtendedFields/にSqlParam: trueのフィールド定義を配置します。json{ "Name": "MyParam", "FieldType": "Filter", "SqlParam": true }サーバースクリプト(ビュー処理時)で
view.Filters.MyParamに値を設定します。フィルターハッシュに"MyParam" → "001"が追加され、拡張フィールドのNameと一致するため@MyParam = "001"という SQL パラメータが生成されます。jsview.OnSelectingWhere = "MyParamFilter"; view.Filters.MyParam = "001";拡張 SQL で
@MyParamを使います。json{ "Name": "MyParamFilter", "SpecifyByName": true, "OnSelectingWhere": true }sql"Issues"."ClassA" = @MyParam
展開方法の比較
などは文字列置換のため、SQL インジェクションのリスクが理論上あります(整数値なら問題になりません)。@パラメータ名 はデータベースドライバーが安全にバインドするため、文字列値を渡す場合はこちらを検討してください。
| 機能 | 展開方法 | 型 | インジェクション対策 |
|---|---|---|---|
| 文字列置換 | 文字列 | 整数値のみ安全 |
| 文字列置換(カラム参照) | カラム参照 | カラム参照のみ |
@パラメータ名 | SQL パラメータバインド | 任意 | 安全 |
OnSelectingWhereParams で条件付きで適用する
拡張 SQL の JSON で OnSelectingWhereParams を設定すると、ビューのフィルターに指定したカラムが含まれているときだけ SQL が適用されます。フィルターがない状態では SQL は無視されます。
{
"OnSelectingWhere": true,
"OnSelectingWhereParams": ["ClassA"]
}"Issues"."ClassA" IN (
SELECT [ClassA]
FROM [Users]
WHERE [UserId] IN (@_U)
)"Issues"."ClassA" IN (
SELECT "ClassA"
FROM "Users"
WHERE "UserId" IN (@ipU)
)`Issues`.`ClassA` IN (
SELECT `ClassA`
FROM `Users`
WHERE `UserId` IN (@ipU)
)SQL ファイル名はいずれも App_Data/Parameters/ExtendedSqls/ConditionalFilter.json.sql です。
複数のカラムを指定した場合は、すべてのカラムがフィルターに含まれている必要があります(AND 条件)。
"OnSelectingWhereParams": ["ClassA", "ClassB"]たとえば「部署フィルターが有効なときだけ、ログインユーザーと同じ部署のレコードに絞る」(フィルター未設定なら全件表示)といった動作を実現できます。
組み合わせの例
これらの機能は組み合わせて使えます。
view.OnSelectingWhere = "AdvancedFilter";
view.AddColumnPlaceholder("TargetCol", "ClassA");{
"Name": "AdvancedFilter",
"SpecifyByName": true,
"OnSelectingWhere": true,
"OnSelectingWhereParams": ["ClassA"]
}{{TargetCol}} IN (
SELECT [Value]
FROM [MasterTable]
WHERE [SiteId] = {{SiteId}}
AND [UserId] IN (@_U)
){{TargetCol}} IN (
SELECT "Value"
FROM "MasterTable"
WHERE "SiteId" = {{SiteId}}
AND "UserId" IN (@ipU)
){{TargetCol}} IN (
SELECT `Value`
FROM `MasterTable`
WHERE `SiteId` = {{SiteId}}
AND `UserId` IN (@ipU)
)SQL ファイル名はいずれも App_Data/Parameters/ExtendedSqls/AdvancedFilter.json.sql です。
| パラメータ | 展開後の値 | 役割 |
|---|---|---|
| "Issues"."ClassA" | サーバースクリプトで指定したカラム参照 |
| 現在のサイト ID | マスタテーブルの絞り込み |
OnSelectingWhereParams: ["ClassA"] | — | ClassA フィルター有効時のみ SQL を適用 |
多数の IN 条件を渡す
別のテーブルや API から取得した数千件のコードリストに一致するレコードだけを表示したい場合、コードを @param1, @param2, ... と個別のパラメータに分解すると、SQL Server のパラメータ数制限に引っかかります。
SQL Server のパラメータ数制限
SQL Server では、1 クエリに含められるパラメータ数の上限は 2,100 個です(sp_executesql の制限)。2,000〜3,000 件のコードを IN (@p1, @p2, @p3, ...) の形式で渡すと、上限を超えて次のエラーになります。
The incoming request has too many parameters. The server supports a maximum of 2100 parameters.プリザンター内部の WHERE 句には他にもパラメータが含まれているため、2,100 を少し下回る件数でも問題になります。
カンマ区切り文字列 + STRING_SPLIT
コードを "A,B,C,D,F,..." のようなカンマ区切り文字列 1 つとして SQL パラメータに渡し、SQL 側で STRING_SPLIT 関数を使って展開します。3,000 件を含む場合でも、使うパラメータは 1 個で済みます。
図を読み込み中…
| 方法 | パラメータ数 | 3,000 件での問題 |
|---|---|---|
@p1, @p2, ... で個別に渡す | 件数分 | 上限 2,100 を超えてエラー |
| カンマ区切り文字列で渡す | 1 | 問題なし |
手順 1:拡張フィールドの設定
App_Data/Parameters/ExtendedFields/ に次の JSON を配置します。"FieldType": "Filter" と "SqlParam": true の組み合わせにより、view.Filters.FilterCodes に設定した値が @FilterCodes という SQL パラメータとして渡されます。
{
"Name": "FilterCodes",
"FieldType": "Filter",
"SqlParam": true
}手順 2:サーバースクリプト(ビュー処理時)
コードリストをカンマ区切り文字列に組み立て、view.Filters.FilterCodes に設定します。コードリストが空のときは OnSelectingWhere を設定しない(全件表示のまま)ようにしています。
// 別テーブル(マスタサイト)からコードリストを取得する例
// ここではサイトID 123 の Items を対象に ClassA を収集する
var masterSiteId = 123;
var masterItems = items.Get(masterSiteId);
var codeList = [];
for (var i = 0; i < masterItems.length; i++) {
var code = masterItems[i].ClassA;
if (code) {
codeList.push(code);
}
}
if (codeList.length > 0) {
view.OnSelectingWhere = "ClassAInFilter";
view.Filters.FilterCodes = codeList.join(",");
}手順 3:拡張 SQL の設定
{
"Name": "ClassAInFilter",
"SpecifyByName": true,
"OnSelectingWhere": true
}SQL ファイル名は App_Data/Parameters/ExtendedSqls/ClassAInFilter.json.sql です。
"Issues"."ClassA" IN (
SELECT [value]
FROM STRING_SPLIT(@FilterCodes, ',')
)"Issues"."ClassA" IN (
SELECT unnest(string_to_array(@FilterCodes, ','))
)FIND_IN_SET(`Issues`.`ClassA`, @FilterCodes) > 0SQL Server は STRING_SPLIT、PostgreSQL は string_to_array と unnest でカンマ区切りの文字列を行に展開し、IN の条件に使います。MySQL は FIND_IN_SET でリスト内の値と照合します。いずれも ClassA がリスト内のコードと一致するレコードを取得します。
注意事項
STRING_SPLITは SQL Server 2016(互換性レベル 130)以降が必要です。それ以前のバージョンでは使えません。文字列長の上限(SQL Server):
nvarchar(max)のパラメータの最大長は 2GB(約 10 億文字)です。コードが 3,000 件で 1 件あたり平均 10 文字なら約 33,000 文字なので、実用上は問題ありません。ただし想定外に長い文字列が渡される可能性に備え、サーバースクリプト側で件数の上限チェックを入れておくと安心です。js// 件数が多すぎる場合の保護 if (codeList.length > 5000) { // 必要に応じてエラーを通知するか、先頭の一定件数に絞るなどの処理を入れる codeList = codeList.slice(0, 5000); }カンマを含むコード:コード自体にカンマが含まれていると区切りが崩れます。カンマ入りのコードが想定される場合は、パイプ(
|)など別の区切り文字への変更や、JSON 形式での渡し方を検討してください。MySQL のFIND_IN_SETはカンマ区切り専用なので、区切り文字だけの変更では対応できません。JSON.stringifyで渡さない:STRING_SPLIT(@FilterCodes, ',')に渡す値はjoin(",")で作ります。JSON.stringify(["A","B"])は["A","B"]という文字列になるため、カンマで分けると["A"と"B"]になり、角かっこと引用符が値に残ります。
一覧が「この項目は並べ替えることができません」になるとき
拡張 SQL(OnSelectingWhere)と拡張フィールド(FieldType: "Filter"・SqlParam: true)を組み合わせ、ビュー処理時のサーバースクリプトで view.OnSelectingWhere を設定したところ、一覧が表示されずに次のエラーが記録されることがあります。
Implem.Libraries.Exceptions.CanNotGridSortException: この項目は並べ替えることができません
---> Microsoft.Data.SqlClient.SqlException (0x80131904):
キーワード 'AND' 付近に不適切な構文があります。
キーワード 'and' 付近に不適切な構文があります。
FETCH ステートメントのオプション next の使用法が無効です。原因は拡張 SQL の本文の先頭に書いた AND です。次のような .json.sql で起こります。
AND "Issues"."ClassA" IN (
SELECT [value]
FROM STRING_SPLIT(@FilterCodes, ',')
)先頭の AND を消して条件式だけにすれば直ります(正しい書き方は上の「手順 3:拡張 SQL の設定」)。並べ替えの操作をしていなくても「並べ替えることができません」と出るため、原因が分かりにくいエラーです。
SQL の組み立て
確認したソースでは、WHERE 句は次のように組み立てられます。
OnSelectingWhereExtendedSqls()が、拡張 SQL の本文(などのプレースホルダーを置き換えただけのもの)を raw の条件として WHERE の条件の集まりに加えます(Rds.cs)。置き換えは文字列の置換だけで、先頭のANDやORは取り除きません(ExtendedSql.cs)。- raw の条件は、SQL にするときに本文がそのまま返されます(SqlWhere.cs)。
SqlWhereCollection.Sql()が、先頭に"where "(Clauseの既定値)を付け、各条件を" and "でつなぎます(SqlWhereCollection.cs、L94-L106)。
そのため、先頭に AND を書いた拡張 SQL からは次の SQL ができます。
| 拡張 SQL の位置 | できる SQL | 問題 |
|---|---|---|
| WHERE の条件がほかに無い | where AND "Issues"."ClassA" IN (...) | where AND が構文エラー |
| ほかの条件の後ろ | where <ほかの条件> and AND "Issues"."ClassA" IN (...) | and AND が構文エラー |
一覧の取得では、データ取得の SQL と件数取得の SQL が同じ WHERE 句を使って 1 回で実行されるため(GridData.cs)、構文エラーが両方の SQL で報告されます。3 行目の FETCH のエラーは、構文エラーの WHERE 句の後ろに続くページングの部分(OFFSET ... FETCH NEXT)で報告されるものです。
エラーの表示が「並べ替え」になる理由
GridData.Get() は、一覧の SQL の実行で出た DbException のうち、タイムアウト以外のものをすべて CanNotGridSortException に変え、メッセージ CanNotGridSort(「この項目は並べ替えることができません」)を出します。このときセッションのビューを空のビュー(new View())で上書きします(GridData.cs)。原因がソートでなくても、拡張 SQL の構文エラーやパラメータの未定義など、SQL のエラーはこのメッセージになります。原因を調べるときは、ログの内側の例外(SqlException のメッセージ)を見ます。
件数取得の SQL と @パラメータ名
GridData.Get() は、データ取得の Rds.Select() には view.Param() の SQL パラメータを渡しますが、件数取得の Rds.SelectCount() には渡していません(GridData.cs)。それでも 1.5.8.1 では、件数取得の SQL から @FilterCodes を参照できます。
- 2 つの SQL は 1 つの SqlCommand にまとめて実行され、パラメータもそのコマンドに追加されます(SqlIo.cs、SqlStatement.cs)。
- 拡張フィールドの SQL パラメータは
NoCount = trueで作られるため、名前に文の番号が付かず@FilterCodesのまま追加されます(View.cs、SqlStatement.cs)。
したがって、このエラーの原因は先頭の AND で、件数取得の SQL にパラメータを渡していないことではありません。
view.Filters.OnSelectingWhere でも適用される
拡張 SQL が WHERE 句に加わる経路は 2 つあります(View.cs)。
| 経路 | 拡張 SQL の名前の取り方 |
|---|---|
SetColumnsWhere() の最後 | ビューの OnSelectingWhere(サーバースクリプトの view.OnSelectingWhere。ServerScriptUtilities.cs) |
| フィルターハッシュの走査 | キーが OnSelectingWhere のフィルターの値(View.cs、L2373-L2385) |
サーバースクリプトの view.Filters の値はフィルターハッシュ(ColumnFilterHash)に入るため(ServerScriptUtilities.cs)、view.Filters.OnSelectingWhere = "名前" と書いても、その名前の拡張 SQL が適用されます。どちらの経路でも本文は同じ OnSelectingWhereExtendedSqls() で加わるので、先頭の AND の扱いは変わりません。
BeforeOpeningPage から gridrows にフィルターを渡す
一覧画面でソートやフィルターを変更すると、ブラウザは action=gridrows の AJAX リクエストを発行します。このリクエストの中身はプリザンターが自動生成するフォームデータで、外から自由にキーを追加できません。
「別のページから URL パラメーター付きで遷移してきたときに、特定の絞り込みを事前にセットしておきたい」といった場合は、BeforeOpeningPage(画面表示の前)サーバースクリプトで SetFormData を積み、ブラウザのフォームデータに ViewFilters__カラム名 を注入します。
仕組み
図を読み込み中…
ポイントは ③ で $p.data を書き換えている点です。$p.data["MainForm"] はプリザンターが AJAX リクエスト時に POST ボディとして送るフォームデータの実体なので、ここに ViewFilters__ClassA を入れておくと、次の gridrows 送信時に自動的に含まれます($p.data については $p.get 系・$p.set 系のスクリプト関数 を参照)。
サンプルコード
一覧画面に ?Dept=sales を付けてアクセスしたときに、ClassA が「sales」のレコードだけを表示するフィルターを自動適用する例です。
// URL クエリストリングからパラメーターを取得
const dept = context.QueryStrings.Data("Dept");
if (dept) {
// $p.data["MainForm"]["ViewFilters__ClassA"] に値をセット
// 次回以降の gridrows POST ボディに含まれるようになる
context.AddResponse(
"SetFormData",
"ViewFilters__ClassA",
JSON.stringify([dept])
);
}JSON.stringify([dept]) は '["sales"]' という文字列になります。ドロップダウンのフィルターは値を JSON 配列形式で扱うため、この形式に揃えます。
DescriptionA のような自由記述テキストのフィルターには、配列ではなく文字列をそのまま渡します。
const keyword = context.QueryStrings.Data("Keyword");
if (keyword) {
context.AddResponse(
"SetFormData",
"ViewFilters__DescriptionA",
keyword
);
}ResponseSet を使う方法
context.ResponseSet を使うと、DOM の hidden 入力値も同時に更新できます。
const dept = context.QueryStrings.Data("Dept");
if (dept) {
// '#ViewFilters__ClassA' という CSS セレクターで hidden 要素を特定する
context.ResponseSet(
"#ViewFilters__ClassA",
JSON.stringify([dept])
);
}ResponseSet は内部で AddResponse("Set", ...) を呼びます。JavaScript 側では $p.set が実行され、対象要素の .val() を更新したうえで $p.setData によって $p.data を更新します。
| 方法 | $p.data の更新 | DOM の更新 | DOM 要素が必要か |
|---|---|---|---|
AddResponse("SetFormData", ...) | される | されない | 不要 |
ResponseSet("#ViewFilters__...", ...) | される | される | 必要 |
ビューのフィルター設定にそのカラムが含まれていれば #ViewFilters__ClassA という hidden 入力が DOM に存在するため、ResponseSet も使えます。フィルター設定に含まれていないカラムを書き込む場合や、確実に動かしたい場合は AddResponse("SetFormData", ...) が安全です。
WARNING
ResponseSet で SELECT 要素(ドロップダウンのフィルター)を対象にした場合、auto-postback クラスが付いていると即座に gridrows リクエストが発動することがあります。
処理の詳細
BeforeOpeningPage が context.ResponseCollection にデータを積むと、HtmlScripts.OnEditorLoad がそれを ServerScriptResponseCollection という hidden フィールドに JSON で書き出します。
private static HtmlBuilder OnEditorLoad(this HtmlBuilder hb, Context context)
{
if (context?.ResponseCollection?.Any() == true)
{
hb
.Hidden(
controlId: "ServerScriptResponseCollection",
value: context.ResponseCollection.ToJson())
.Script(
script: OnDomReadyScript("$p.setByJson(undefined, undefined, $p.getData($('#MainForm')), $('#MainForm'), undefined, JSON.parse($('#ServerScriptResponseCollection').val()));"),
nonce: context.Nonce);
}
...
}OnDomReadyScript は、埋め込むスクリプトを DOMContentLoaded まで遅延させるラッパーです。
private static string OnDomReadyScript(string script)
{
return $"(function() {{ var run = () => {{ {script} }}; if (document.readyState !== 'loading') {{ run(); }} else {{ document.addEventListener('DOMContentLoaded', run); }} }})();";
}これは非 Ajax のページ描画時(初回アクセス時)に限り実行されます。$p.setByJson が呼ばれると、各 JSON 要素が $p.setByJsonElement で処理されます。
case 'SetFormData':
data[target] = value; // data = $p.getData($('#MainForm'))
break;data は $p.getData($('#MainForm')) の戻り値で、$p.data["MainForm"] への参照です。
gridrows を受け取ったサーバー側では Views.GetBySession が呼ばれます。
view = GetView(context, ss, useUsersView); // セッションからビューを復元
view.SetByForm(context, ss); // POST フォームデータを適用
SetSession(context, ss, view, setSession); // セッションに保存view.SetByForm 内で ViewFilters__ClassA を検出すると AddColumnFilterHash("ClassA", value) が呼ばれてフィルターハッシュに値が追加され、その状態がセッションに保存されます。以降の gridrows はセッションから ClassA フィルターを復元します。
注意事項
- 初回表示にはフィルターが反映されません。
SetFormDataは$p.dataをセットするだけで、最初の HTML 描画(GetGridDataの呼び出し時)はこの値を参照しません。フィルターが有効になるのは、ユーザーがソートやフィルターを操作した最初のgridrows以降です。 - 初回から反映したい場合は、
api/sessions/Setでページ遷移前にセッションに値を保存し、WhenViewProcessingタイミングの拡張 SQL で参照する方法が有効です(セッション間のデータ共有 を参照)。 - フィルターはセッションに残ります。 一度保存されると、ページを開き直しても残り続けます。URL パラメーター付きでアクセスしたときだけに限定したい場合は、
BeforeOpeningPageでパラメーターが存在しないときに明示的にフィルターを消去する処理も合わせて実装してください。
INFO
gridrows のリクエストでは context.QueryStrings が空になります。URL で渡した値を gridrows 以降のサーバースクリプトで使う方法は context.Condition と context.Action の「context.QueryStrings」を参照してください。