2022年9月1日木曜日

対話グリッドの特定のセルを読み取り専用にする

Twitterを見ていたらカナダのInsum Solutionsより、Elisabeth Ashleyさんが"How to Set Specific Cells of Oracle APEX Interactive Grids to Read Only"という記事を書きましたよ、とツイートがありました。そのツイートに対話グリッドを開発しているJohn Snydersより、check callbackを使った方が簡単では?と返信が付いていました。

John Snydersが紹介しているCheckCalbackを使って、Insum Solutionsの記事にある実装を書き換えてみました。

記事とは条件を変えています。対話グリッド上で、上司のいない従業員の給与と手当を変更不可にします。

サンプル・データセットEMP/DEPTに含まれる表EMPをソースとして、対話グリッドを作成します。その対話グリッドに静的IDとしてempを設定します。

対話グリッドの属性詳細JavaScript初期化コードとして、以下を記述します。

function(options) {
options.defaultModelOptions = {
check: (result, operation, record, addAction, recordsToAdd) => {
// 編集可能なときのみcheckを行う。
if (!result) {
return result;
}
// 編集モード(削除ではない)のときのみcheckを行う。
if (operation !== "canEdit") {
return result;
}
// 編集対象のレコードが存在するときのみcheckを行う。
if (record === undefined || record === null) {
return result;
};
// model、id、meta, fieldsを準備する。
let grid = apex.region("emp").call("getViews","grid");
let model = grid.model;
let id = model.getRecordId(record),
meta = model.getRecordMetadata(id);
let fields = meta.fields;
// RecordFieldMetadataを初期化する。
// https://docs.oracle.com/en/database/oracle/apex/22.1/aexjs/model.html#.RecordFieldMetadata
if (!fields) {
fields = meta.fields = {};
}
// 編集可/不可を設定するセルSAL、COMMのメタデータが無ければ初期化する。
if (!fields["SAL"]) {
fields["SAL"] = {};
}
if (!fields["COMM"]) {
fields["COMM"] = {};
}
// 上司の値を取得し、上司が設定されていなければ列SAL、COMMともに編集不可にする。
let mgr = record[model.getFieldKey("MGR")];
if (mgr.d == "") {
fields["SAL"].ck = 1;
fields["COMM"].ck = 1;
}
else
{
fields["SAL"].ck = "";
fields["COMM"].ck = "";
}
// それ以外は編集可能なのでtrueを返す。
return true;
}
}
return options;
}

設定は以上です。

動作を確認してみます。


Managerが設定されている行は、Salary、Commissionともに編集できます。Managerが設定されていないKINGのHiredは変更できますが、SalaryとCommissionは変更できません。ただし、一時的にManagerに値を設定するとSalaryとCommissionに値を入力でき、データベースに保存することもできます。これは、読取専用にしているのはあくまで対話グリッド上のセルであって、サーバー側には制限がかかっていないためです。

新規業の場合は、Managerに値がなくてもSalaryとCommissionを設定できます。

おおむね想定どおりの実装ができています。

対話グリッド上で一時的にManagerを変更しSalaryやCommissionを変更するという抜け道を禁止するには、対話グリッドでManagerの変更を禁止するという方法もありますが、サーバーに検証を作成する方が確実です

検証を作成し、名前を給与の変更禁止とします。検証編集可能リージョンとして対話グリッドのリージョンを選択します。タイプとしてファンクション本体(ブールを返す)を選択し、PL/SQLファンクション本体として以下を記述します。

declare
l_sal0 emp.sal%type;
l_sal1 emp.sal%type;
begin
if :APEX$ROW_STATUS = 'U' and :MGR is null then
-- 変更前のデータの取得
select sal into l_sal0 from emp where empno = :EMPNO;
-- 変更後のデータの取得
select to_number(:SAL, format_mask) into l_sal1 from APEX_APPL_PAGE_IG_COLUMNS
where application_id = :APP_ID and page_id = :APP_PAGE_ID and name = 'SAL';
-- 前後を比較して変更がないときにtrueを返す。
if
(l_sal0 is not null and l_sal1 is not null and l_sal0 = l_sal1) or (l_sal0 is null and l_sal1 is null)
then
return true;
end if;
return false;
end if;
return true;
end;
エラーエラー・メッセージは「上司のいない従業員の給与は変更できません。」とします。関連付けられた列としてSALを選択します。


同様に検証として手当の変更禁止を作成します。

declare
l_comm0 emp.sal%type;
l_comm1 emp.sal%type;
begin
if :APEX$ROW_STATUS = 'U' and :MGR is null then
-- 変更前のデータの取得
select comm into l_comm0 from emp where empno = :EMPNO;
-- 変更後のデータの取得
select to_number(:COMM, format_mask) into l_comm1 from APEX_APPL_PAGE_IG_COLUMNS
where application_id = :APP_ID and page_id = :APP_PAGE_ID and name = 'COMM';
-- 前後を比較して変更がないときにtrueを返す。
if
(l_comm0 is not null and l_comm1 is not null and l_comm0 = l_comm1) or (l_comm0 is null and l_comm1 is null)
then
return true;
end if;
return false;
end if;
return true;
end;
エラーエラー・メッセージ関連付けられた列は、給与、SALから手当COMMに変更します。


以上でManagerが設定されていない従業員の給与と手当を更新しようとすると、サーバー側でエラーが発生するようになりました。


以上でアプリケーションは完成です。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/grid-check-callback.zip

Oracle APEX JavaScript APIリファレンスのmodelインターフェスが、主に関連する部分です。
https://docs.oracle.com/en/database/oracle/apex/22.1/aexjs/model.html#.CheckCallback
https://docs.oracle.com/en/database/oracle/apex/22.1/aexjs/model.html#check
https://docs.oracle.com/en/database/oracle/apex/22.1/aexjs/model.html#.RecordFieldMetadata

Oracle APEXのアプリケーション作成の参考になれば幸いです。