2023年9月27日水曜日

動的アクションから開いたモーダル・ダイアログのページのクローズ・イベントを取る方法

動的アクションよりモーダル・ダイアログのページ(以下フォームと記します)を開いた場合、イベントのダイアログのクローズが取れない(TRUEアクションが実行されない)との相談がありました。

原因と対処方法について紹介します。

最初に検証に使用する簡単なアプリケーションを作成します。

アプリケーション作成ウィザードを起動します。アプリケーションの名前Dialog Close Eventとします。

ホーム・ページは削除し、サンプル・データセットのEMP/DEPTに含まれる表EMPをソースとした、対話モード・レポートとフォームのページを追加します。


対話モード・レポートのページ名EMPとし、表またはビューとしてEMPを選択します。フォームを含めるチェックをいれ、ページの追加を行います。


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

ページ・デザイナで、ページ番号の表EMPの対話モード・レポートのページを開きます。


動的アクションでフォームを開く実装を追加します。

フォームを開くURLを保持するページ・アイテムとしてP1_URLを作成します。

一般的な実装ではタイプとして非表示を選択しますが、今回は検証なのでテキスト・フィールドを選択します。


フォームを開く際の引数となる従業員番号を保持するページ・アイテムとしてP1_EMPNOを作成します。タイプテキスト・フィールドです。


フォームを開くボタンとしてOPENを作成します。動作アクションとして動的アクションで定義を選択します。


ボタンOPENに動的アクションを作成します。

識別名前onClick OPENとします。タイミングイベントはデフォルトのクリックです。


最初のTRUEアクションで、フォームを開くためのURLを生成します。

アクションとしてサーバー側のコードを実行を選択し、設定PL/SQLコードとして以下を記述します。
:P1_URL := apex_page.get_url(p_page => 2, p_items => 'P2_EMPNO', p_values => :P1_EMPNO);
追記: 以下のようにapex_page.get_urlの引数p_triggering_elementとして対話モード・レポートの静的IDを指定すると、JavaScriptによるワークアラウンドは不要でした。
:P1_URL := apex_page.get_url(p_page => 2, p_items => 'P2_EMPNO', p_values => :P1_EMPNO, p_triggering_element => 'emp');

送信するアイテムとしてP1_EMPNOを指定し、戻すアイテムとしてP1_URLを指定します。

ボタンのアクションとしてこのアプリケーションのページにリダイレクトを選択してフォームを開く場合は、ページが表示された時点でのページ・アイテムの値が引数となります。ページ表示後に変更したページ・アイテムの値を引数とすることはできないため、このような場合は、動的アクションとして実装します。

実行結果を待機はかならずオンにします。初期化時に実行する必要は無いので、これはオフにします。


ページ・アイテムP1_URLとして設定されたページを開くTRUEアクションを作成します。

アクションとしてJavaScriptコードの実行を選択し、設定コードとして以下を記述します。
apex.navigation.redirect(apex.items.P1_URL.value);

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

Empnoとして7788を入力し、ボタンOPENをクリックします。


指定した従業員番号でフォームが開きます。動的アクションは適切に実装されていることがわかります。

Commissionなどを変更し、変更の適用をクリックします。


変更の適用をクリックするとフォームが閉じますが、対話モード・レポートがリフレッシュされないため、変更した値がレポートに反映されません。


このようになる理由は、ダイアログのクローズに登録されている動的アクションとファンクションapex.navigation.dialog(apex.theme42.dialog)呼び出し時のイベントのターゲットの不一致にあります。

ページ作成ウィザードで対話モード・レポートとフォームのページを作成すると、レポートの編集 - ダイアログのクローズという名前で、レポートをリフレッシュする動的アクションが作成されます。

この動的アクションのタイミング選択タイプとしてリージョンリージョンとして対話モード・レポートのリージョンであるEmployeesが選択されています。


ページ・アイテムP1_URLに実際にフォームを開くURLが設定されています。こちらを確認すると、以下のようになっています。
javascript:apex.theme42.dialog('\u002Fords\u002Fr\u002Fapexdev\u002Fdialog-close-event\u002Femployee?p2_empno=7788\u0026session=107992989464019\u0026cs=3mbMpJtQdUFyCb1YAXSH6hLtMuzCcsW93-WP8d5oMfp9J5S4Dw5nDFMEHLvOPgfUVORO3IxljtqC_NTpKInotEw\u0026dialogCs=kwCn_71eCWwNlEi-AzXtGDzAVWhhenYNbvXNDRUKCKLRDluOCNLUMotdGi_ZlKcPv5qzmDil0lWfd4yV41Z1bA',{title:'Employee',w:'720',mxw:'960',modal:true,dialog:null,dlgCls:'t-Drawer-page--standard '+''},' js-dialog-class-t-Drawer--pullOutEnd',this)
JavaScript APIのapex.theme42.dialogを呼び出していますが、APIリファレンスのapex.navigation.dialogを同等なので、以下のリファレンスを参照します。

https://docs.oracle.com/en/database/oracle/apex/23.1/aexjs/apex.navigation.html#.fn:dialog

第4引数のpTriggeringElementに指定されたHTML要素にapexafterclosedialog、apexafterclosecanceldialogのイベントが発行されると記載されています。つまり、動的アクションのタイミングとして設定されている要素がpTriggeringElementとして指定されていないと、ダイアログのクローズとして作成されている動的アクションは動作しません。

以上より、ページ・アイテムP1_URLとして生成されたJavaScriptを実行する際のthisを、対話モード・レポートのリージョンEmployeesとなるようにします。

色々な方法があるとは思いますが、今回はcallを使ってみます。

pTriggeringElementとして指定するために、対話モード・レポートのリージョンに静的IDとしてempを設定します。


ページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言として、以下を記述します。
function openDialog(url) {
    eval(url);
}

TRUEアクションJavaScriptコードの実行コードを以下に置き換えます。
openDialog.call(apex.jQuery("#emp"),apex.items.P1_URL.value);


以上で対応は完了です。

フォームをクローズしたときにレポートがリフレッシュされるようになりました。

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

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

追記

ダイアログをクローズしたときに、フォームの複数のページ・アイテムの値を元のページに戻す実装を追加してみました。

フォームのページにあるプロセスダイアログを閉じる設定戻すアイテムに、値を戻すページ・アイテムをカンマ区切りで複数設定します。今回の例ではP2_ENAME,P2_JOBとしています。


レポートのページに、フォームから戻されたページ・アイテムの値を保持するページ・アイテムP1_ENAMEP1_JOBを作成しておきます。


イベントがダイアログのクローズのときに実行されるTRUEアクションを作成します。

アクション値の設定設定タイプの設定としてDialog Return Itemを選択します。戻りアイテムP2_ENAME(フォームのページにあるページ・アイテム名)を指定します。

影響を受ける要素選択タイプアイテムを選び、アイテムとしてフォームから戻される値を設定するページ・アイテムP1_ENAMEを指定します。


戻すアイテムが複数ある場合は、TRUEアクションの値の設定を追加します。