一覧画面のカスタマイズ
一覧画面(およびサイト一覧)を見やすく・使いやすくするカスタマイズをまとめます。いずれも本体を改修せず、サーバースクリプト・スクリプト・拡張機能(拡張スクリプト、拡張スタイル、拡張フィールド、拡張 SQL)だけで実装できます。
| カスタマイズ | 使う機能 | 実行タイミング |
|---|---|---|
| ヘッダのフィルタ状態を表示する | サーバースクリプト | 画面表示の前 |
| ソート状態のアイコンを見やすくする | サーバースクリプト(+ 拡張 HTML) | 画面表示の前 |
| ソートの順番を表示する | サーバースクリプト | 画面表示の前 |
| 選択件数カウンターを表示する | 拡張スクリプト | ― |
| 一覧を一定間隔で自動更新する | 拡張フィールド + 拡張スクリプト | ― |
| 数値項目にデータバーを表示する | サーバースクリプト | 行表示の前 |
| セルに斜線を引く | スタイル / 拡張スタイル + サーバースクリプト | 行表示の前 |
| サイト一覧にテーブルのロック状態を表示する | 拡張 SQL + 拡張スクリプト | ― |
ヘッダのフィルタ状態を表示する
フィルタの「一覧のヘッダメニューでフィルタを使用する」を ON にすると、ヘッダから直接フィルタをかけられます。ただし、フィルタ欄に表示されていない項目にフィルタをかけると、その項目にフィルタがかかっているかどうかが画面から分かりません。フィルタがかかっている項目のヘッダに filter_alt アイコンを表示して、これを解消します。
![]()
仕組み
- ヘッダ要素(
th)の HTML は、ヘッダメニューでのフィルタが ON でも OFF でも同じで、フィルタ状態を示す属性はありません。 - サーバースクリプトの
viewオブジェクトはビューの設定だけでなく、現在のビュー情報の取得にも使えます。view.Filtersにはフィルタがかかっている項目が入ります。
context.Log($ps.JSON.stringify(view.Filters));
// ClassA にだけフィルタをかけた場合の出力
// {"ClassA":"[\"3528414\"]"}- アイコンはプリザンターに組み込まれている Google Icons(
material-symbols-outlined)を使い、th[data-name="項目名"] divの先頭に追加します。
設定
サーバースクリプト(または拡張サーバースクリプト)に、条件「画面表示の前」で次のコードを設定します。
if (['index', 'gridrows', 'newongrid', 'copyrow'].includes(context.Action))
{
for (const [key, value] of Object.entries(view.Filters)) {
if (value && value !== '[]') {
context.AddResponse('Prepend', `th[data-name="${key}"] div:not(:has(.view-filter-icon))`,
'<span class="view-filter-icon material-symbols-outlined" style="font-size: 1.5em;">filter_alt</span>');
}
}
}context.Actionをindex(画面遷移)・gridrows(一覧の表の書き換え)・newongrid/copyrow(一覧画面編集での行追加・行コピー)に限定しています。- フィルタをクリアしたとき、選択肢項目や日付項目には空であることを示す空配列
[]が渡されるため、これを除外しています。 :not(:has(.view-filter-icon))で、アイコンが二重に追加されないようにしています。
ソート状態のアイコンを見やすくする
ヘッダをクリックしてソートしたときに表示される昇順・降順のアイコンは小さく見にくいので、大きなアイコンに差し替えます。
仕組み
- ソート中の項目では、ヘッダのタイトルの隣に
ui-icon ui-icon-triangle-1-n(昇順)/ui-icon-triangle-1-s(降順)のspanが入ります。 data-order-type属性には「次にクリックしたときのソート順」が入っているため、要素から現在の状態を判断するのは困難です。アイコンのクラスで判定する方法も、将来のデザイン変更で変わる可能性があるため避けます。- 代わりに、サーバースクリプトの
view.Sortersで現在のソート状態を取得します。
context.Log($ps.JSON.stringify(view.Sorters));
// TitleBody を昇順、ClassA を降順にした場合の出力
// {"TitleBody":"asc","ClassA":"desc"}設定(Google Icons を使う場合)
サーバースクリプト(または拡張サーバースクリプト)に、条件「画面表示の前」で次のコードを設定します。昇順に stat_3、降順に stat_minus_3 を使います。
if (['index', 'gridrows', 'newongrid', 'copyrow'].includes(context.Action)) {
for (const [key, value] of Object.entries(view.Sorters)) {
switch (value) {
case "asc":
context.AddResponse('ReplaceAll', `th[data-name="${key}"] div .ui-icon`,
'<span class="material-symbols-outlined">stat_3</span>');
break;
case "desc":
context.AddResponse('ReplaceAll', `th[data-name="${key}"] div .ui-icon`,
'<span class="material-symbols-outlined">stat_minus_3</span>');
break;
}
}
}context.Action の限定はヘッダのフィルタ状態を表示すると同じ理由です。
![]()
設定(Line Awesome を使う場合)
より直感的なアイコンとして、ライセンスの緩い Line Awesome の sort-alpha-down-solid(昇順)/ sort-alpha-down-alt-solid(降順)を使うこともできます。CDN を使うとインターネット接続が必要になるため、リソースをプリザンターに組み込みます。
Line Awesome の公式サイトから ZIP ファイルをダウンロードし、プリザンターのインストールディレクトリの
wwwroot/line-awesome/を作成して、解凍したsvg・css・fontsディレクトリを中身ごと配置します。text[プリザンターのインストールディレクトリ] └ wwwroot └ line-awesome ← 作成する ├ svg ← 解凍したディレクトリ(全ファイル) ├ css ← 同上 └ fonts ← 同上拡張 HTML を用意します。ファイル名の言語サフィックス(2 レターコード)を省略すると、全言語に適用されます。
html<link rel="stylesheet" href="/line-awesome/css/line-awesome.min.css" />サーバースクリプトに、条件「画面表示の前」で次のコードを設定します。
jsif (['index', 'gridrows', 'newongrid', 'copyrow'].includes(context.Action)) { for (const [key, value] of Object.entries(view.Sorters)) { switch (value) { case "asc": context.AddResponse('ReplaceAll', `th[data-name="${key}"] div .ui-icon`, '<span class="las la-sort-alpha-down" style="font-size: 1.5em;"></span>'); break; case "desc": context.AddResponse('ReplaceAll', `th[data-name="${key}"] div .ui-icon`, '<span class="las la-sort-alpha-down-alt" style="font-size: 1.5em;"></span>'); break; } } }
ソートの順番を表示する
複数の項目でソートした場合、ソートはヘッダをクリックした順に適用されますが、その順番は画面に表示されません。ソートアイコンの横に Google Icons の filter_1 〜 filter_9、10 番目以降は filter_9_plus を表示します。

サーバースクリプトに、条件「画面表示の前」で次のコードを設定します。ソートアイコンには前項の Line Awesome を使っています。
if (['index', 'gridrows', 'newongrid', 'copyrow'].includes(context.Action)) {
//環境によっては GetType などのメソッドも列挙されるため、値が asc / desc のものだけに絞る
const sorters = Object.entries(view.Sorters)
.filter(([key, value]) => value === 'asc' || value === 'desc');
for (const [index, [key, value]] of sorters.entries()) {
var number = index + 1;
var icons = "";
switch (value) {
case "asc":
icons +=
`<span class="las la-sort-alpha-down" style="font-size: 1.5em;"></span>`;
break;
case "desc":
icons +=
`<span class="las la-sort-alpha-down-alt" style="font-size: 1.5em;"></span>`;
break;
}
if (9 < number) {
icons +=
`<span class="material-symbols-outlined" style="font-size: 1.5em;">filter_9_plus</span>`;
} else {
icons +=
`<span class="material-symbols-outlined" style="font-size: 1.5em;">filter_${number}</span>`;
}
context.AddResponse('ReplaceAll', `th[data-name="${key}"] div .ui-icon`,
icons);
}
}WARNING
環境によっては、view.Sorters に対して Object.entries を使うと先頭に GetType・ToString・Equals・GetHashCode の 4 つが格納されることがあります。view.Sorters の実体は .NET の ExpandoObject で(ServerScriptModelView.cs#L20)、メソッドが列挙されるかどうかはスクリプトエンジン側の扱いによります。「先頭 4 件を読み飛ばす」処理にすると、メソッドが列挙されない環境では最初の 4 項目のソートが表示されません。上のコードでは値が asc / desc のものだけに絞り込み、どちらの環境でも動くようにしています。
選択件数カウンターを表示する
一覧画面でチェックボックスを選択すると、「○件選択中」というメッセージを画面上部のメッセージ領域にリアルタイムに表示します。拡張スクリプトだけで実装でき、拡張スタイルは不要です。

INFO
バージョン 1.5.1.0 以降を対象にしています。
仕組み
| 要素 | HTML | 参照 |
|---|---|---|
| ヘッダの全選択チェックボックス | input#GridCheckAll | HtmlGrids.cs#L103 |
| 各行のチェックボックス | input.grid-check(data-id にレコード ID) | HtmlGrids.cs#L412 |
組み込みスクリプト(gridevents.js)でのイベントの発生は次のとおりです。
| 操作 | 発生するイベント | 備考 |
|---|---|---|
| 個別チェック | .grid-check の change | チェックボックスを直接クリックした場合 |
| 行クリック | .grid-check の change | セルをクリックするとチェックが切り替わり change が発火する |
| 全選択チェック | #GridCheckAll の click | .grid-check の change は 発火しない |
WARNING
全選択チェックボックスをクリックした場合、各行の .grid-check は prop('checked', ...) で直接状態が設定されるため、change イベントは発火しません。
設定
拡張スクリプトとして次のファイルを配置します。
(function () {
/**
* チェック済みの件数を数えてメッセージの表示を更新する。
*/
function updateCounter() {
var count = document.querySelectorAll('.grid-check:checked').length;
// 前回表示したカウンターのメッセージだけを消す
$('#Message .selection-counter').remove();
if (count > 0) {
$p.clearMessage();
$p.setMessage('#Message', JSON.stringify({
Css: 'alert-information',
Text: count + '件選択中'
}));
// 追加したメッセージに目印のクラスを付ける
$('#Message > div').last().addClass('selection-counter');
}
}
// 個別チェックボックスの変更
$(document).on('change', '.grid-check', updateCounter);
// 全選択チェックボックスのクリック
// (組み込みの click ハンドラで .grid-check の状態が更新された後にカウントするため遅延)
$(document).on('click', '#GridCheckAll', function () {
setTimeout(updateCounter, 0);
});
// Ajax によるグリッド再描画(ソート・フィルタ・ページ移動など)
// (DOM の更新完了後にカウントするため遅延)
$(document).ajaxComplete(function () {
setTimeout(updateCounter, 0);
});
// 初期表示
updateCounter();
})();ポイントと注意点
$p.setMessageは第 1 引数に表示先のセレクタ、第 2 引数にCssとTextを含む JSON 文字列を渡します。Cssに指定できるクラスは次のとおりです。CSS クラス 用途 色 alert-success成功通知 緑 alert-information情報メッセージ 青 alert-warning警告 黄 alert-errorエラー 赤 全選択は
#GridCheckAllのclickを監視し、setTimeout(updateCounter, 0)で組み込みのハンドラが状態を更新した後にカウントします。ソート・フィルタ・ページ移動などの Ajax 再描画ではチェック状態がリセットされるため、
ajaxCompleteでも更新しています。選択中の ID を返す
$p.selectedIds()は内部で同期的な Ajax 通信を行うため(grid.js#L55)、チェックのたびに呼ぶとサーバーに不要な負荷がかかります。ここでは DOM のチェックボックスを直接数えています。選択が 0 件のときは、カウンターが表示したメッセージ(目印の
selection-counterクラスを付けたもの)だけを消します。$p.clearMessage()はclassにmessageを含む要素をすべて空にするため(message.js#L50-L52)、0 件のときにも呼ぶと、ajaxCompleteのたびに一括削除などの結果メッセージまで消えてしまいます。
WARNING
1 件以上選択している間は $p.clearMessage() でメッセージ領域を空にしてから表示するため、保存直後の「更新しました」などの組み込みメッセージも、チェックボックス操作時に置き換わります。
一覧を一定間隔で自動更新する
一覧画面を一定間隔で、画面全体の再遷移ではなく Ajax で更新します。拡張フィールドで「自動更新の ON/OFF」と「更新間隔(秒)」をフィルタ領域に持たせ、拡張スクリプトでタイマー実行します。
- 更新は
$p.send()で一覧のGridRowsを再取得します。プリザンター標準の Ajax 経路を通るため、既存のローディング表示がそのまま使えます。 - 更新中はメッセージ領域に「一覧を更新中...」を表示します。
拡張フィールドの準備
App_Data/Parameters/ExtendedFields/ に次の 2 ファイルを配置します(名前は例です)。"FieldType": "Filter" を指定すると、一覧画面のフィルタ領域(#ViewFilters)に表示されます。フィルタ領域のコントロールの ID は ViewFilters__ + 項目名(例: ViewFilters__CheckRefreshTimer)になります(HtmlViewFilters.cs#L369)。
{
"Name": "CheckRefreshTimer",
"FieldType": "Filter",
"TypeName": "bit",
"LabelText": "自動更新ON/OFF"
}{
"Name": "NumRefreshSpan",
"FieldType": "Filter",
"TypeName": "nvarchar",
"LabelText": "更新間隔(秒)"
}
INFO
更新間隔は TypeName を nvarchar(文字列)にしてテキストボックスで入力させます。フィルタ領域では数値型(decimal など)の項目が範囲選択のドロップダウンになり(HtmlViewFilters.cs#L432-L453)、秒数を直接入力できないためです。また、フィルタ領域の入力欄にはビューに保存された値が表示され DefaultInput は使われないため、未入力のときはスクリプトの DEFAULT_SEC(60 秒)を使います。
設定ファイルを追加・変更したら、特権ユーザー(Security.json の PrivilegedUsers に登録したユーザー)でログインした状態で次の URL にアクセスして反映します(ベースパスの有無は環境によります)。
- ベースパスなし:
https://{ドメイン}/admins/reloadparameters - ベースパスあり:
https://{ドメイン}/{ベースパス}/admins/reloadparameters
WARNING
未ログイン状態や特権ユーザー以外のユーザーでアクセスしても反映されません(ParametersInitializer.cs#L9)。テナント管理者の権限だけでは反映されません。
拡張フィールドはフィルタ領域の先頭に追加されます。既存の項目の後ろに置きたい場合は、拡張フィールドの After に項目名を指定します(SiteSettings.cs#L2665-L2686)。
拡張スクリプト
App_Data/Parameters/ExtendedScripts/ に次のファイルを配置します。
(function () {
'use strict';
// ===== 設定 =====
var MIN_SEC = 10;
var MAX_SEC = 3600;
var DEFAULT_SEC = 60;
var CHECK_SELECTOR = '#ViewFilters__CheckRefreshTimer'; // 自動更新ON/OFF(拡張フィールド)
var INTERVAL_SELECTOR = '#ViewFilters__NumRefreshSpan'; // 更新間隔秒(拡張フィールド)
var MESSAGE_TARGET = '#Message';
var GRID_TRIGGER_SELECTOR = '[data-action="GridRows"][data-method="post"]';
// 一覧画面以外では何もしない
if (!$('#Grid').length) return;
var timerId = null;
var busy = false;
// 既存 Ajax と干渉しないための簡易排他
$(document)
.off('.autoReloadBusy')
.on('ajaxStart.autoReloadBusy', function () {
busy = true;
})
.on('ajaxStop.autoReloadBusy', function () {
busy = false;
});
function toInt(value, fallback) {
var n = parseInt(value, 10);
return Number.isFinite(n) ? n : fallback;
}
function getEnabled() {
var $check = $(CHECK_SELECTOR);
if (!$check.length) return false;
return $check.prop('checked') || $check.val() === '1' || $check.val() === 'true';
}
function getIntervalSec() {
var $interval = $(INTERVAL_SELECTOR);
var sec = toInt($interval.val(), DEFAULT_SEC);
if (sec < MIN_SEC) sec = MIN_SEC;
if (sec > MAX_SEC) sec = MAX_SEC;
return sec;
}
function setInfo(text) {
if (!$p || typeof $p.setMessage !== 'function') return;
$p.clearMessage();
$p.setMessage(
MESSAGE_TARGET,
JSON.stringify({
Css: 'alert-information',
Text: text
})
);
}
function reloadGridByAjax() {
if (busy) return;
if (!getEnabled()) return;
// タブが非表示の間は不要な負荷を避けるため更新しない
if (document.hidden) return;
var $trigger = $(GRID_TRIGGER_SELECTOR).first();
if (!$trigger.length) {
setInfo('自動更新対象(GridRows)が見つからないため更新をスキップしました');
return;
}
setInfo('一覧を更新中...');
// プリザンター標準の Ajax 送信を使う
// 既存のローディング表示がそのまま表示される
$p.send($trigger);
}
function restartTimer() {
if (timerId) {
clearInterval(timerId);
timerId = null;
}
if (!getEnabled()) return;
var intervalMs = getIntervalSec() * 1000;
timerId = setInterval(reloadGridByAjax, intervalMs);
}
// 初期化
restartTimer();
// 設定変更時にタイマーを再起動
$(document)
.off('change.autoReload', CHECK_SELECTOR)
.off('change.autoReload', INTERVAL_SELECTOR)
.on('change.autoReload', CHECK_SELECTOR + ',' + INTERVAL_SELECTOR, function () {
restartTimer();
});
// タブ復帰時に取りこぼし防止
$(document)
.off('visibilitychange.autoReload')
.on('visibilitychange.autoReload', function () {
if (!document.hidden) {
reloadGridByAjax();
}
});
})();設定のポイント
ExtendedScriptsフォルダに置いた.jsファイルは、条件なしですべての画面で読み込まれます(Initializer.cs#L725-L756)。そのためスクリプトの冒頭で、#Gridが無い画面では何もせずに終了しています。特定のテーブルに限定したい場合は、拡張機能テーブル(Extensions)にExtensionTypeをScriptとして登録し、ExtensionSettingsにSiteIdListなどの条件、Bodyにスクリプトを入れます(ExtensionInitializer.cs#L83-L95)。CheckRefreshTimerが OFF のときはタイマーを停止します。- 過負荷を防ぐため、間隔は 10 秒未満にしません(スクリプトでも 10〜3600 秒に丸めています)。まずは 30〜60 秒程度から始め、利用者数や件数に合わせて調整します。
- タブが非表示の間は更新せず、タブに戻ったときに 1 回更新します。
- 拡張フィールドの物理名が異なる場合は、
CHECK_SELECTOR/INTERVAL_SELECTORのViewFilters__の後ろを読み替えます。フィルタ領域のコントロールの ID にはViewFilters__が付くため、#CheckRefreshTimerのように項目名だけを ID に書くと動きません。
WARNING
「自動更新対象(GridRows)が見つからない」と表示される場合は、次を確認してください。
- 対象画面が一覧画面か(
#Gridがある画面か) GridRowsの送信トリガー要素がカスタマイズで変更されていないかCHECK_SELECTOR/INTERVAL_SELECTORが実環境の物理名に合っているか
数値項目にデータバーを表示する
数値項目のセルに、Excel のデータバーのような横棒グラフを表示します。RawText を使ってセルに SVG を直接書き出します。
サーバースクリプトに、条件「行表示の前」で次のコードを設定します(NumD に百分率の値が入っている例です)。
columns.NumD.RawText = `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="33">
<text x="0" y="13">${model.NumD}%</text>
<rect x="0" y="20" width="${100 - model.NumD}" height="10" fill="none" stroke="#000"></rect>
<rect x="${100 - model.NumD}" y="20" width="${model.NumD}" height="10" fill="#f00" stroke="#000"></rect>
</svg>`;
四角形を左右に 2 つ描画することで、百分率であることが分かりやすくなります。同じ方法で、円グラフや簡単な折れ線グラフなども描画できます。
セルに斜線を引く
サーバースクリプトや自動ポストバックで編集画面の項目を非表示にしていても、一覧画面ではセルがそのまま表示されるため、「未入力」なのか「入力不要(非表示)」なのかを区別できません。入力不要のセルに Excel のような斜線を表示します。
スタイルの用意
次の CSS を用意します。
.cell-slash {
background-image: linear-gradient(to top right, transparent calc(50% - 1px), rgb(34, 34, 34), transparent calc(50% + 1px));
}置き場所は、対象のセルがリンクなどで他のサイトにも表示されるかどうかで決めます。
| 置き場所 | 特徴 |
|---|---|
| サイトのスタイル | 他のサイトからは呼び出せない。リンク先のサイトにも表示する場合は、リンク先のサイトのスタイルにも同じものを用意する |
| 拡張スタイル | プリザンター全体で使い回せる。ただし Pleasanter.net 環境などではセキュリティの観点から使えない |
このクラスを項目のセル CSS に設定すると、斜線が表示されます。
入れ子の要素を消す
説明項目や添付ファイル項目などは表示の都合でセル内にタグが入れ子になっており、その要素に背景色が設定されていると斜線が隠れてしまいます。サーバースクリプトで RawText を半角スペース 1 文字にして入れ子の要素を消し、ExtendedCellCss でクラスを付けます。
//条件は行表示の前
columns.DescriptionA.RawText = ' ';
columns.DescriptionA.ExtendedCellCss = 'cell-slash';RawText は空文字('')だと未設定と同じ扱いになり、通常のセルがそのまま出力されます(IssueUtilities.cs#L795)。そのため、空文字ではなく半角スペースを設定しています。

サイト一覧にテーブルのロック状態を表示する
テーブルのロック中のテーブルは、開くと画面上部に赤帯で「読取専用です。」と表示されますが、サイト一覧(フォルダビュー)ではどれがロック中か分かりません。拡張 SQL と拡張スクリプトを組み合わせて、サイト一覧でロック中のテーブルのパネルを赤く表示します。
ロック判定の仕組み
- テーブルの管理 > エディタで「テーブルのロックを許可」をオンにすると、管理メニューに「テーブルをロック」が表示されます。ロック・ロック解除は、ロックを実行したユーザーまたは特権管理者だけが操作できます。
- ロック情報は
SitesテーブルのLockedTime列(ロック日時)とLockedUser列(ロックしたユーザー ID)に保持されます。 - 本体のロック判定は
LockedTime IS NOT NULLではなく、SiteSettings.LockedTable()で「ロック日時が有効な範囲内で、かつロックユーザーが匿名ユーザーでない」ことを見ています(SiteSettings.cs)。
public bool LockedTable()
{
return LockedTableTime?.Value.InRange() == true
&& LockedTableUser?.Anonymous() == false;
}- ロック解除時、
LockedTimeは現在時刻に更新されたまま残り、LockedUserだけがnullになります。 LockedUserは現在テナントのユーザーキャッシュからUserに解決され、存在しないユーザー ID は匿名ユーザー扱いになります。ユーザーキャッシュは無効ユーザーも含むため、Users.Disabledでの除外は不要です。- サイト一覧のパネルを作る
SiteUtilities.Menu()は、SitesからSiteId・Title・ReferenceType・SiteSettingsだけを取り、LockedTime・LockedUserは取っていません(SiteUtilities.cs)。画面の HTML にロック状態が含まれないので、ここでは拡張 SQL で別に取得します。本体側でパネルにlockedクラスを付ける改修案は サイトメニューにテーブルのロック状態を出す(本体改修) にあります。
このため、SQL では次の 3 点を条件にして、本体の判定に近づけます。
LockedTimeが本体の有効日時範囲(1900-01-01〜2100-01-01)内にあることUsersテーブルに該当ユーザーが存在することLockedUserが匿名ユーザー ID(2)でないこと(旧環境ではUsersにAnonymousユーザーが残っていることがあるため)
WARNING
この SQL は標準設定を前提にした実用上の近似で、本体の LockedTable() と完全に一致する保証はありません。日時範囲は標準の General.json の既定値、匿名ユーザー ID 2 は標準実装に基づく値です。これらを変更している環境では条件を見直してください。完全一致が必要な場合は、サーバー側のカスタマイズで LockedTable() 相当の処理を実行する方法を検討してください。
処理の流れ
図を読み込み中…
拡張 SQL
App_Data/Parameters/ExtendedSqls/ に定義ファイルと SQL ファイルを配置します。ブラウザから /api/extended/sql 経由で呼び出すため、"Api": true が必要です。
{
"Name": "GetLockedSites",
"Api": true,
"CommandText": "-- Write an arbitrary SQL statement."
}SELECT
S.[SiteId]
FROM
[Sites] AS S
WHERE
S.[TenantId] = @_T
AND S.[ParentId] = @SiteId
AND S.[LockedTime] >= '1900-01-01'
AND S.[LockedTime] <= '2100-01-01'
AND S.[LockedUser] <> 2
AND EXISTS (
SELECT 1
FROM [Users] AS U
WHERE U.[TenantId] = S.[TenantId]
AND U.[UserId] = S.[LockedUser]
)SELECT
S."SiteId"
FROM
"Sites" AS S
WHERE
S."TenantId" = @ipT
AND S."ParentId" = @SiteId
AND S."LockedTime" >= TIMESTAMP '1900-01-01 00:00:00'
AND S."LockedTime" <= TIMESTAMP '2100-01-01 00:00:00'
AND S."LockedUser" <> 2
AND EXISTS (
SELECT 1
FROM "Users" AS U
WHERE U."TenantId" = S."TenantId"
AND U."UserId" = S."LockedUser"
)SELECT
S.`SiteId`
FROM
`Sites` AS S
WHERE
S.`TenantId` = @ipT
AND S.`ParentId` = @SiteId
AND S.`LockedTime` >= '1900-01-01 00:00:00'
AND S.`LockedTime` <= '2100-01-01 00:00:00'
AND S.`LockedUser` <> 2
AND EXISTS (
SELECT 1
FROM `Users` AS U
WHERE U.`TenantId` = S.`TenantId`
AND U.`UserId` = S.`LockedUser`
)SQL ファイル名はいずれも GetLockedSites.json.sql です。@_T(PostgreSQL・MySQL では @ipT)は拡張 SQL の組み込みパラメータで現在のテナント ID が入り、@SiteId は拡張スクリプトから渡すパラメータ(現在開いているサイトの ID)です。これで表示中の親サイト配下の子サイトだけに絞ります。
PostgreSQL・MySQL では @_T ではなく @ipT
組み込みパラメータの接頭辞は DBMS で変わり、SQL Server は @_、PostgreSQL・MySQL は @ip です(Parameter.json の SqlParameterPrefix が空の既定のとき。Parameter.cs、SqlIo.cs)。PostgreSQL で @_T と書くと column "_t" does not exist のエラーになり、結果が返りません。
INFO
絞り込めるのは「現在開いているサイト配下の子サイト」までです。画面上で検索やフィルタをかけた結果と完全に一致させたい場合は、クライアント側で表示中の ID を取得して別途絞り込む必要があります。
拡張スクリプト
App_Data/Parameters/ExtendedScripts/ に次のファイルを配置します。拡張スクリプトは JSON 定義ファイルなしで .js ファイル 1 つで動作します。
$(function () {
if ($('.nav-site').length === 0) return;
$p.apiExec(
$('#ApplicationPath').val() + 'api/extended/sql',
{
data: {
Name: 'GetLockedSites',
Params: { SiteId: $p.siteId() }
},
done: function (res) {
if (!res || !res.Response || !res.Response.Data) return;
var tables = res.Response.Data;
var tableKey = Object.keys(tables)[0];
if (!tableKey) return;
var rows = tables[tableKey];
if (!rows || rows.length === 0) return;
for (var i = 0; i < rows.length; i++) {
var siteId = rows[i].SiteId || rows[i].siteId;
if (!siteId) continue;
$('.nav-site[data-value="' + siteId + '"]')
.css({
'background': '#ffd0d0',
'box-shadow': 'inset 0 0 0 3px #e53935'
});
}
}
}
);
});- 拡張スクリプトはすべての画面で読み込まれるため、
.nav-siteが無い画面では即座に終了し、不要な API 呼び出しを防ぎます。 - エンドポイントは
$('#ApplicationPath').val()で取得したベースパスにapi/extended/sqlを連結します。認証トークンは$p.apiExecが自動で付与します。 $p.siteId()は現在開いているサイトの ID を整数で返し、SQL の@SiteIdとして渡ります。- レスポンスは次の構造で、
Response.Dataの最初のキー(Table)が結果セットです。カラム名は SQL のSELECTで指定した名前のままです。
{
"StatusCode": 200,
"Response": {
"Data": {
"Table": [
{ "SiteId": 123 },
{ "SiteId": 456 }
]
}
}
}配置ファイルまとめ
| ファイル | 配置先 |
|---|---|
GetLockedSites.json | App_Data/Parameters/ExtendedSqls/ |
GetLockedSites.json.sql | App_Data/Parameters/ExtendedSqls/ |
LockedSiteIndicator.js | App_Data/Parameters/ExtendedScripts/ |
ファイルを配置したら、プリザンターを再起動するか、一覧を一定間隔で自動更新すると同じ /admins/reloadparameters で拡張機能を読み込みます(拡張 SQL・拡張スクリプトもこの URL で再読み込みされます。Initializer.cs#L124-L126)。
