Twitterを見ていたらカナダのInsum Solutionsより、Elisabeth Ashleyさんが"How to Set Specific Cells of Oracle APEX Interactive Grids to Read Only"という記事を書きましたよ、とツイートがありました。そのツイートに対話グリッドを開発しているJohn Snydersより、check callbackを使った方が簡単では?と返信が付いていました。
I wonder if the check callback would be simpler. https://t.co/z0vvzYSXJz
— John Snyders (@J_Snyders) August 31, 2022
John Snydersが紹介しているCheckCalbackを使って、Insum Solutionsの記事にある実装を書き換えてみました。
記事とは条件を変えています。対話グリッド上で、上司のいない従業員の給与と手当を変更不可にします。
サンプル・データセットのEMP/DEPTに含まれる表EMPをソースとして、対話グリッドを作成します。その対話グリッドに静的IDとしてempを設定します。
対話グリッドの属性の詳細のJavaScript初期化コードとして、以下を記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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ファンクション本体として以下を記述します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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を選択します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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のアプリケーション作成の参考になれば幸いです。
完