前回の続きでサーバースクリプトでの一覧画面のフィルターをやっていきます。
※おことわり
2024年1月時点の情報です。プリザンターのバージョンは 1.3.50.2 です。Google Chrome でやっています。
javascript,html,cssともに初心者です。調べながら、やってみながら、きっとこうすればいいんだ!という感じで書いていますので、間違っている場合、効率的な書き方ではない可能性が大いにあります。間違ってるよ!とか、こうしたほうがいいよ!ということがありましたら、コメント等で教えていただけると大変ありがたいです。
1.はじめに
おさらい。(前回の「はじめに」のコピペ)
公式マニュアルより
概要
引用元:https://pleasanter.org/manual/server-script-view-filters
「viewオブジェクト」の「Filters」です。「サーバスクリプト」で「一覧画面」や「エディタ」に表示する「レコード」を「フィルタ」することで、ユーザに閲覧させるレコードを制限することができます。「レコードのアクセス制御」と異なり「レコード」1件1件にアクセス権を設定する必要がありません。「JSONデータレイアウト:View」が使用できます。
詳細は公式マニュアルをご参照ください。
開発者向け機能:サーバスクリプト:view.Filters
以上を踏まえ、サーバースクリプトでフィルタをかけるときは以下の点に注意してやるのがよいのかなと思いました。
・サーバースクリプトの条件は「ビュー処理時」を使用する
・サーバースクリプトで強制的にフィルタをかけている項目はユーザー手作業でのフィルタが効かなくなるので、サイト設計の際に一覧のフィルタから当該項目を外したほうが良い場合がある
・管理者自身も強制的にかけたフィルタが適用されてしまうため、全部見れるユーザー(または組織・グループ)を考慮したコードにする必要がある
2.ステータス完了しか見えない。ただし特定グループにはすべて見える
(1)やりたいこと
①状況=900完了のレコードしか見えない
②グループID1「管理チーム」のユーザーには①のフィルタは適用されずすべて見える
(2)グループ
グループ「管理チーム」
グループIDは1です。
(3)サーバースクリプト
以下のコードをサーバースクリプトに入力します。条件は「ビュー処理時」です。
//ログインユーザーのIDを変数myUserIdに代入
const myUserId = context.UserId;
//グループID1のグループオブジェクトを変数acceptGroupに代入
const acceptGroup = groups.Get(1);
//ログインユーザーIDが1ではない、かつacceptGroupに含まれない場合、フィルターを実行
if ( myUserId !== 1 && !acceptGroup.ContainsUser(myUserId)) {
view.Filters.Status = '["900"]';
}
(4)結果
「管理チーム」グループに所属する田中花子さんがログインしている場合、フィルターは実行されずすべてのレコードが表示されます。
「管理チーム」グループに所属しない森田健介さんがログインしている場合、フィルターが実行され状況が「完了」のレコードのみ表示されます。
(5)コードの解説
const myUserId = context.UserId;
ログインユーザーのIDを変数 myUserId に代入します。
const acceptGroup = groups.Get(1);
グループID1のグループオブジェクトを取得し、変数 acceptGroup に代入します。
if ( myUserId !== 1 && !acceptGroup.ContainsUser(myUserId)) {・・・}
myUserId が 1 ではない、かつ、myUserId が acceptGroup に含まれない場合、{}内の処理を実行します。
田中さんはmyUserId が acceptGroup に含まれるため{}内の処理は実行されません。
森田さん(ユーザーID22)は「myUserId が 1 ではない」「 acceptGroup に含まれない」の両方を満たすため{}内の処理が実行されます。
view.Filters.Status = ‘[“900”]’;
「状況」が900(完了)のレコードを絞り込み表示します。
groupのメソッドなどについては、公式マニュアル、またはサーバースクリプト~groupの回を参考にしてください。
3.自部署かつ日付が1年前以降のレコードに絞り込み(AND条件、日付条件)
(1)やりたいこと
①分類A「部署」が自分の部署である
②日付A「申請日」の日付が1年前以降
上記①②の両方を満たすレコードを表示する
③ただし、ユーザーIDが3の場合、上記のフィルタを適用せず、すべてのレコードが表示される。
これはAND条件です。さてさてさてどうするのかというと view.Filters を二つ設定すればよいみたいです。
(2)エディタ
分類A
・表示名:部署
・選択肢:[[Depts]]
日付A
・表示名:申請日
・書式:年月日
(3)日付でフィルタするには
まず、ウォーミングアップ。日付でフィルタするにはサーバースクリプトのコードをどのように書くのかをやってみましょう。
①日付Aが今日のレコードで絞り込み
view.Filters.DateA = ‘[“Today”]’;
②日付Aが今月のレコードで絞り込み
view.Filters.DateA = ‘[“ThisMonth”]’;
③日付Aが 2023/10/1 以降
view.Filters.DateA = ‘[“2023/10/01,”]’;
④日付Aが 2023/10/1 ~ 2023/10/31 の間
view.Filters.DateA = ‘[“2023/10/01, 2023/10/31 23:59:59.997”]’;
では今日から1年前以降は・・・?
1)今日の日付を取得
2)(1)から1年前の日付を取得
3)(2)の1年前の日付の文字列を作成してフィルタの条件にセット
の手順で行けそうですね。
前に作成した 日付文字列作成のスクリプトを利用して1年前の日付を返すFunctionを作成しました。
let vStrDate = fnGetOneYearAgoDate(new Date());
function fnGetOneYearAgoDate(date) {
let y = date.getFullYear()-1;
let mm = '00' + (date.getMonth() + 1);
let dd = '00' + (date.getDate() + 1);
return y + '/' + mm.slice(-2) + '/' +dd.slice(-2) + " 00:00:00";
}
今日が2024年1月7日だとします。
このコードを実行すると vStrDate には 文字列で 「2023/01/08 00:00:00」がはいります。
コードの解説をします。
function fnGetOneYearAgoDate(date) { ・・・}
fnGetOneYearAgoDateという名前の関数です。date(日付)を引数に取ります。
引数のdateに 2024/1/7 が入ったものとして後の説明をします。
let y = date.getFullYear()-1;
date.getFullYear() で、引数の日付の「年」を取得します。そこから1を引いた数を変数 y に代入します。2024年から1を引くので 2023 が y に入ります。
let mm = ’00’ + (date.getMonth() + 1);
date.getMonth() + 1 で、引数の日付の「月」を取り出します。月は「0」始まりで1月が0になるため1を足します。
頭に 00 をくっつけます。1月の場合「001」となります(後で頭の余分な0をカットする処理が入ります)。その 001 を変数 mm に代入します。
let dd = ’00’ + (date.getDate() + 1);
date.getDate() で引数の日付の日にち部分(7日)を取り出します。1年間としたいので、2023/1/8としたいため、日にちに1を足しています。頭に 00 をくっつけて、008 とします。(後で頭の余分な0をカットする処理が入ります)その 008 を変数 dd に代入します。
return y + ‘/’ + mm.slice(-2) + ‘/’ +dd.slice(-2) + ” 00:00:00″;
return で作成した文字列を返します。
y + ‘/’ で 「2023/」
mm.slice(-2) + ‘/’ で 「01/」。mm.slice(-2) で「001」の後ろから2文字だけを取り出しています。
dd.slice(-2) + ” 00:00:00″ で「08 00:00:00」。dd.slice(-2) で「008」の後ろから2文字だけを取り出しています。
全部くっつけて 2023/01/08 00:00:00 となります。
この文字列を return で返します。
let vStrDate = fnGetOneYearAgoDate(new Date());
関数を実行しているコードです。
fnGetOneYearAgoDate(new Date()) で帰ってきた「2023/01/08 00:00:00」が変数 vStrDate に入ります。
上記には記載ありませんが、この変数に入った文字列を使用して view.Filters をかける際は
view.Filters.DateA = ‘[“‘ + vStrDate + ‘,“]‘;
とします。「以降」としたいので、vStrDate の後ろのカンマが必要です。忘れずに。
(4)サーバースクリプト
本題に戻ります(3)の日付のフィルタ方法を踏まえて、以下のコードを作成し、サーバースクリプトに入力します。条件は「ビュー処理時」です。
//一年前の日付を取得
const vStrDate = fnGetOneYearAgoDate(new Date()) ;
context.Log(vStrDate);
//ユーザーID3以外、フィルターを適用
if ( context.UserId !== 3 ) {
view.Filters.DateA = '["' + vStrDate + ',"]';
view.Filters.ClassA = '["' + context.DeptId + '"]';
}
//一年前の日付文字列を作成する関数の定義
function fnGetOneYearAgoDate(date) {
let y = date.getFullYear()-1;
let mm = '00' + (date.getMonth() + 1);
let dd = '00' + (date.getDate() + 1);
return y + '/' + mm.slice(-2) + '/' +dd.slice(-2) + " 00:00:00";
}
(5)結果
ユーザーID3の田中さんはフィルタ制御なく、すべてが表示されます。
総務部の山田さんは、総務部の一年前以降のデータのみ表示されます。
(6)コードの解説
日付部分は(3)でやりましたので、フィルタの部分のみ。
view.Filters.DateA = ‘[“‘ + vStrDate + ‘,”]’;
view.Filters.ClassA = ‘[“‘ + context.DeptId + ‘”]’;
複数項目でAND(かつ)で絞り込みしたい場合は、上記のように並列で条件を設定すればよいのですね。
4.担当者または管理者が自分のIDのレコードを表示(OR条件)
3でAND条件をやりましたが、ORはどうするのでしょう。
たとえば鈴木花子さんがログインしたとき、担当者または管理者が鈴木さんのデータを表示したい、というケースです。
(1)やりたいこと
①「担当者」にログインユーザーのIDが設定されている
②「管理者」にログインユーザーのIDが設定されている
上記①または②の条件を満たすレコードを表示
③ただし、ユーザーID3の場合、フィルタを適用しない
(2)エディタ
担当者(Owner)
・選択肢:[[Users]]
管理者(Manager)
・選択肢:[[Users]]
(3)サーバースクリプト
以下のコードをサーバースクリプトに入力します。条件は「ビュー処理時」とします。
if ( context.UserId !== 3 ) {
// 画面からのフィルタ操作を無効化
view.Filters.Owner = '';
view.Filters.Manager = '';
// OR条件の設定
let data = {};
data.Owner = context.UserId;
data.Manager = context.UserId;
view.Filters.or_MyFilterName = JSON.stringify(data);
}
(4)結果
鈴木花子さんがログインした場合の画面。意図したとおりにいってますね。
(5)コードの解説
すみません。公式マニュアルの例をコピペしたので、意味が分からないため解説は割愛します。。。
画面からのフィルタ操作を無効化の部分はあってもなくても、動きは同じで「担当者」「管理者」ともサーバースクリプトのフィルタが効いているときは、手動フィルタは無効でした。なので、なぜ画面からのフィルタを無効にしているのか、理由がわかりませんでした。
OR条件はdataオブジェクトを作成し、json文字列に変換して投げているようですね。
「担当者」「管理者」項目ではなく分類項目などの場合、[]でくくることが必要になります。
たとえば、部署(分類A)が自分の部署または状況が完了(900)のデータを絞り込みしたい場合は以下のように書きます。
let data = {};
data.ClassA = '["' + context.DeptId + '"]';
data.Status = '["900"]';
view.Filters.or_MyFilterName = JSON.stringify(data);
以下のように書いても同じ結果が得られました。
let data = {
ClassA : '["' + context.DeptId + '"]',
Status : '["900"]'
}
view.Filters.or_MyFilterName = JSON.stringify(data);
5.同一グループ内の部署が見える
(1)やりたいこと
総務経理グループ:総務部(ID2)、経理部(ID5)
営業推進グループ:営業部(ID1)、マーケティング部(ID4)
その他:開発部(ID3) ※その他は上記グループに属さない部
「総務経理グループ」「営業推進グループ」があります。同一グループ内の部署は相互参照できます。
(総務部(経理部)の人は総務部と経理部のレコードが見える。営業部(マーケティング部)の人は営業部とマーケティング部のレコードが見える。)その他グループに属さない部は自分の部のレコードのみ見える。
というのをやりたい。
(2)サーバースクリプト(直指定)
context.Log(context.DeptId);
if ( context.UserId !== 1 ) {
switch (context.DeptId) {
case 1:
case 4:
view.Filters.ClassA = '["1","4"]';
break;
case 2:
case 5:
view.Filters.ClassA = '["2","5"]';
break;
default:
view.Filters.ClassA = '["' + context.DeptId + '"]';
break;
}
}
(3)コードの解説
直接部署IDをコードに書いた場合の例です。
switch (context.DeptId) {・・・}
switch文で分岐しています。
【JavaScript】switch文で条件分岐!基本の書き方やif文との使い分け
case 1:
case 4:
view.Filters.ClassA = ‘[“1″,”4”]’;
break;
ログインユーザーの部署IDが 1(営業部) または 4(マーケティング部)の場合、分類A(部署)を1と4で絞り込みします。 このケースに該当した場合、break で switch を抜けます。
case 2:
case 5:
view.Filters.ClassA = ‘[“2″,”5”]’;
break;
ログインユーザーの部署IDが 2(総務部) または 5(経理部)の場合、分類A(部署)を2と5で絞り込みします。 このケースに該当した場合、break で switch を抜けます。
default:
view.Filters.ClassA = ‘[“‘ + context.DeptId + ‘”]’;
break;
上記ケースで break で抜けなかった場合、default部分を実行します。
分類Aをログインユーザーの部署でフィルタします。
(4)グループを使った場合
グループを使った場合をやってみます。
以下の二つのグループを作成しています。
総務経理グループ(ID2):総務部(組織ID2)、経理部(組織ID5)
営業推進グループ(ID3):営業部(組織ID1)、マーケティング部(組織ID4)
(5)サーバースクリプト(グループを使用)
①ログインユーザーの部署IDを変数 myId に代入
②対象のグループIDを配列 trgGroupIds に代入
③配列 trgGroupIdsのグループIDを一つずつ処理
④グループIDからグループオブジェクトを取得→vGroup
⑤vGroup に自部署(myId) が含まれているか
⑥含まれている場合、vGroup に含まれている部署IDをすべてstrに連結していく
⑦連結した部署IDが入っている str でフィルタ
⑧str が空の場合、自部署のIDでフィルタ
で、コードを書いたら長くなってしまって、きっともっといい方法があるんだろうなあと思いつつ、今日のところはこれで良しとする。
//部署IDをmyId に代入
const myId = context.DeptId;
//対象のグループIDを配列trgGroupIds に代入
const trgGroupIds = [2,3];
//絞り込み部署IDを入れる変数strを用意
let str ="";
//ユーザーIDが3の場合はフィルタを行わない
if ( context.UserId !== 3 ) {
//trgGroupIdsをループ
for ( let i = 0;i < trgGroupIds.length;i++ ) {
//vGroupにグループ情報をセット
let vGroup = groups.Get(trgGroupIds[i]);
//myId(自部署)がグループに含まれているか
if (vGroup.ContainsDept(myId)) {
//含まれている場合、グループの部署IDをstrに入れていく
for (let member of vGroup.GetMembers()){
if ( member.DeptId ) {
str = str + "," + member.DeptId;
}
}
break;
}
}
context.Log(`結果${str}`);
if ( str !== "" ) {
view.Filters.ClassA ='[' + str.slice(1) + ']';
} else {
view.Filters.ClassA ='["' + myId + '"]';
}
}
6.最後に
二回にわたって view.Filters をやりました。長くなってしまいました。
今回学んだことは
・サーバースクリプト view.Filters での複数条件の指定方法(AND,OR)
・グループを使用した条件の切り分け
・日付条件の指定方法
等です。
この部署だけに見せたい、などのニーズは結構ありますよね。
アクセス権の設定でも同じようなことができるように思うので、後日やってみたいと思います。
お読みいただきありがとうございました。
もっといい方法がある!間違ってるよ!などの場合はコメント等で教えていただけるとありがたいです!
7.参考文献・記事
公式マニュアルより
開発者向け機能:サーバスクリプト:view.Filters
【プリザンター】 第123回)サーバスクリプトでビューのフィルタリング設定
【JavaScript】switch文で条件分岐!基本の書き方やif文との使い分け
JavaScriptのfor文におけるbreakについて現役エンジニアが解説【初心者向け】
■ユーザー組織あたりをあれこれやってみたシリーズ
【pleasanter】ユーザー、組織、グループあたりをあれこれやってみた①
【pleasanter】ユーザー、組織、グループあたりをあれこれやってみた②~プルダウンリストのフィルター、ソート
【pleasanter】ユーザー、組織あたりをあれこれやってみた③~ルックアップ
【pleasanter】スクリプト~ボタンでユーザー、組織を入力~ユーザー組織あたりをあれこれやってみたシリーズ④
【pleasanter】スクリプト~ユーザーにより項目の編集可否を切り替える~ユーザー組織あたりをあれこれやってみたシリーズ⑤
【pleasanter】スクリプト~セクションの表示・非表示~ユーザー組織あたりをあれこれやってみたシリーズ⑥
【pleasanter】スクリプト~タブの表示・非表示~ユーザー組織あたりをあれこれやってみたシリーズ⑦
【pleasanter】サーバースクリプト~context,user,dept~ユーザー組織あたりをあれこれやってみたシリーズ⑧
【pleasanter】サーバースクリプト~group~ユーザー組織あたりをあれこれやってみたシリーズ⑨
【pleasanter】サーバースクリプト~プルダウンリスト作成~ユーザー組織あたりをあれこれやってみたシリーズ⑩
【pleasanter】サーバースクリプト~一覧のフィルター~ユーザー組織あたりをあれこれやってみたシリーズ⑪
【pleasanter】サーバースクリプト~一覧のフィルター2~ユーザー組織あたりをあれこれやってみたシリーズ⑫
コメント