対話モード・レポートの列にチェックボックスを配置し、その値をページを送信せずに更新するにはどうしたら?という相談がありました。APEX_ITEM.CHECKBOX2の使用を考えていて、その場合、ページを送信する必要があります。
ページを送信せずにデータベースに保存されているデータを更新するには、動的アクションを使えます。下のレイヤーで考えると、ページの送信はHTTPのPOSTリクエストの発行による更新、動的アクションはAjaxコールの発行による更新です。
ただし、動的アクションによる更新の場合、失われた更新の防止(Lost Write Protection)が行われません。
単に動的アクションで更新するだけであれば、それほど実装は難しくありません。ロスト・ライト・プロテクションについても実装してみようと思います。
以下の動作するアプリケーションを作成します。上の画面でチェックボックスをチェックしたり外したりします。すでに上の画面でデータを変更しているため、下の画面で同じ行のチェックボックスを操作しようとするとエラーが発生します。
エラーが発生したら、一旦、ページをリロードします。ページをリロードすると直前のデータベースの状態がレポートに反映されるため、チェックボックスの値を変更できます。下の画面でデータを更新すると、今度は上の画面でチェックボックスを操作しようするとエラーが発生します。
クイック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にデータを投入するために使用します。
以上でアプリケーションを作成します。
アプリケーションが作成されたら、ページ番号1のIRのページを開きます。
最初に対話モード・レポートのリージョンに静的IDを設定します。詳細の静的IDにstatementsと記述します。
列IS_APPROVEDには、データが文字のまま(YまたはN)表示されるようになっています。チェックボックスとして表示されるよう、以下のコードを列の書式のHTML式に記述します。チェックボックスの値が変更されるたびに、列ID、ROW_VERSIONとチェックボックスのステータスを含んだINPUT要素に紐づけた(CSSクラスdummychangeで紐づける)動的アクションが呼び出されます。
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
{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_APPROVEDのHTML式に記述したINPUT要素を指定します。
TRUEアクションとしてJavaScriptコードの実行を選択し、設定のコードに以下を記述します。Ajaxコールバックとして実装したUPDATE_CHECKBOXを呼び出します。引数x01に列ID、x02に列ROW_VERSION、x03にチェックボックスのステータスを渡し、Ajaxコールバックとして実装したプロセスUPDATE_CHECKBOXを呼び出します。
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
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コードとして以下を記述します。
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_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のアプリケーション作成の参考になれば幸いです。
完