2023年5月18日木曜日

対話モード・レポートの列を動的アクションで更新する

対話モード・レポートの列にチェックボックスを配置し、その値をページを送信せずに更新するにはどうしたら?という相談がありました。APEX_ITEM.CHECKBOX2の使用を考えていて、その場合、ページを送信する必要があります。

ページを送信せずにデータベースに保存されているデータを更新するには、動的アクションを使えます。下のレイヤーで考えると、ページの送信はHTTPのPOSTリクエストの発行による更新、動的アクションはAjaxコールの発行による更新です。

ただし、動的アクションによる更新の場合、失われた更新の防止(Lost Write Protection)が行われません。

単に動的アクションで更新するだけであれば、それほど実装は難しくありません。ロスト・ライト・プロテクションについても実装してみようと思います。

以下の動作するアプリケーションを作成します。上の画面でチェックボックスをチェックしたり外したりします。すでに上の画面でデータを変更しているため、下の画面で同じ行のチェックボックスを操作しようとするとエラーが発生します。

エラーが発生したら、一旦、ページをリロードします。ページをリロードすると直前のデータベースの状態がレポートに反映されるため、チェックボックスの値を変更できます。下の画面でデータを更新すると、今度は上の画面でチェックボックスを操作しようするとエラーが発生します。


アプリケーションで使用する表CBX_STATEMENTSを作成します。

クイックSQLの以下のモデルを使用します。
# prefix: cbx
# rowversion: true
# auditcols: true
statements
    message     vc80 /nn
    is_approved vc1  /nn /check Y,N /default N
ロスト・ライト・プロテクションの実装に、行バージョンを使用します。そのためクイックSQLのモデルにrowversion: trueを指定します。行バージョンの代わりにチェックサムを使うこともできますが、異なる実装になります。(APEXはロスト・ライト・プロテクションにチェックサムを使っています)保護対象をすべての列ではなく、一部の列に限定する場合はチェックサムによる実装が必要です。

クイックSQLでは表CBX_STATEMENTSの作成までを行い、アプリケーションの作成はしません。


表が作成されたら、アプリケーション作成ウィザードを起動します。

アプリケーションの名前Checkbox on IRとします。デフォルトで作成されているホーム・ページを削除し、代わりに対話モード・レポートのページIR対話グリッドのページIGを追加します。


対話モード・レポートのページ定義です。表またはビューとしてCBX_STATEMENTSを指定します。動的アクションによるデータの更新は、このページに実装します。


対話グリッドのページ定義です。対話グリッドは表CBX_STATEMENTSにデータを投入するために使用します。


以上でアプリケーションを作成します。

アプリケーションが作成されたら、ページ番号のIRのページを開きます。

最初に対話モード・レポートのリージョンに静的IDを設定します。詳細静的IDstatementsと記述します。


IS_APPROVEDには、データが文字のまま(YまたはN)表示されるようになっています。チェックボックスとして表示されるよう、以下のコードを列の書式HTML式に記述します。チェックボックスの値が変更されるたびに、列IDROW_VERSIONチェックボックスのステータスを含んだINPUT要素に紐づけた(CSSクラスdummychangeで紐づける)動的アクションが呼び出されます。

{case IS_APPROVED/}
{when Y/}
<input type="checkbox" data-id="#ID#" data-version="#ROW_VERSION#" class="dummychange" checked>
{otherwise/}
<input type="checkbox" data-id="#ID#" data-version="#ROW_VERSION#" class="dummychange" >
{endcase/}


動的アクション・ビューを開き、動的アクションを作成します。

識別名前チェックボックスの値変更とします。

実行イベント有効範囲動的とし、静的コンテナ(jQueryセレクタ)には#statementsを指定します。これは対話モード・レポートのリージョンです。タイプ即時です。

タイミングイベント変更選択タイプとしてjQueryセレクタを選択します。jQueryセレクタとして.dummychangeを指定し、列IS_APPROVEDHTML式に記述したINPUT要素を指定します。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定コードに以下を記述します。Ajaxコールバックとして実装したUPDATE_CHECKBOXを呼び出します。引数x01に列IDx02に列ROW_VERSIONx03チェックボックスのステータスを渡し、Ajaxコールバックとして実装したプロセスUPDATE_CHECKBOXを呼び出します。

let el = this.triggeringElement;
el.disabled = true; // 更新中はクリックを禁止する。
const result = apex.server.process(
"UPDATE_CHECKBOX",
{
x01: el.dataset.id,
x02: el.dataset.version,
x03: el.checked
}
);
result.done( ( response ) => {
if ( response.success ) {
/* 継続して更新できるよう、data-versionを更新する */
el.setAttribute("data-version", response.row_version);
console.log("successfully updated ", response, el );
el.disabled = false; // クリック可能に戻す。
}
else
{
el.checked = response.is_approved === 'Y' ? true : false;
// el.disabled = true; // 更新に失敗したらクリック不可を維持する。
apex.message.clearErrors();
apex.message.showErrors([
{
type: "error",
location: "page",
message: "checkbox has already changed by " + response.updated_by,
unsafe: false
}
]);
console.log("update failed ", response, el );
};
});


プロセス・ビューを開き、AjaxコールバックとしてプロセスUPDATE_CHECKBOXを作成します。ソースPL/SQLコードとして以下を記述します。

declare
l_id cbx_statements.id%type;
l_rv cbx_statements.row_version%type;
r cbx_statements%rowtype;
l_response varchar2(32767);
l_yn varchar2(1) := 'N';
begin
l_id := to_number(apex_application.g_x01);
select * into r from cbx_statements where id = l_id for update nowait;
if r.row_version > to_number(apex_application.g_x02) then
/* ROW_VERSIONが進んでいるということは、すでに他の人が更新している */
l_response := json_object(
key 'success' value false,
key 'is_approved' value r.is_approved,
key 'row_version' value r.row_version,
key 'updated_by' value r.updated_by,
key 'updated' value r.updated
);
else
/* 更新する */
if apex_application.g_x03 = 'true' then
l_yn := 'Y';
end if;
update cbx_statements set is_approved = l_yn where id = l_id
returning row_version into l_rv;
l_response := json_object(
key 'success' value true,
key 'is_approved' value l_yn,
key 'row_version' value l_rv
);
end if;
htp.p(l_response);
end;


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

アプリケーションを実行すると、記事の先頭のGIF動画のように動作します。

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

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