2022年6月7日火曜日

スマート・フィルタを複数のリージョンの検索条件に適用する

 複数のカード・リージョンの検索にスマート・フィルタを適用する方法について、Oracle APEXの開発チームのシニア・アーキテクトのSteve Muenchさんが、実装方法を個人の記事で紹介していたので試してみました。

スマート・フィルタを使って従業員を検索し、左のカード・リージョンに一覧しています。右のカード・リージョンには、検索された従業員が所属している部門を一覧しています。

アプリケーションの実装にはAlways FreeのAutonomous DatabaseのAPEX 21.2を使用しています。サンプル・データセットEMP/DEPTに含まれる表EMP(左のカード・リージョンのソース)および表DEPT(右のカード・リージョンのソース)を、データとして使用しています。

では、アプリケーションの作成手順を紹介します。

アプリケーション作成ウィザードを起動します。アプリケーションの名前複数リージョン・スマート・フィルタとします。

ホーム・ページを削除し、代わりにスマート・フィルタのページを追加します。

ページの追加で、スマート・フィルタを選択します。


ページ名従業員検索とし、検索結果はレポートとして表示されるようレポートを選択します。ソースとなるとして、EMPを選択します。

ページの追加を実行します。


アプリケーションの作成を実行します。

スマート・フィルタが実装されたページを含んだ、アプリケーションが作成されます。


これからの作業は、ページ番号1従業員検索のページに実施します。


作成したアプリケーションを実行してみます。デフォルトで作成されたスマート・フィルタは、そのまま使用します。


ページ・デザイナを開いて、従業員を一覧するカード・リージョンを作成します。

識別タイトル従業員とします。タイプカードを選択します。ソース表名EMPを選択します。


カード・リージョン従業員Attributesを開き、カードに表示される内容を設定します。カード主キー列1としてEMPNOを選択します。タイトルとしてENAMEサブタイトルとしてJOBを選択します。


もう一つカード・リージョンを作成します。検索された従業員が所属している部門の一覧を、カードとして一覧します。

識別タイトル部門とします。タイプカードです。ソース表名DEPTを選択します。リージョン従業員の右横に配置するため、レイアウト新規行の開始OFFにします。


カード・リージョン部門Attributesを開き、カードに表示される内容を設定します。カード主キー列1としてDEPTNOを選択します。タイトルとしてDNAMEサブタイトルとしてLOCを選択します。


新たに追加したリージョン従業員部門の検索条件は、リージョン検索結果に表示される結果から作成されます。そのためにパイプライン表関数empno_empを作成します。表関数の引数としてリージョン検索結果が必要になるため、詳細静的IDとしてEmployeesを設定します。


スマート・フィルタが適用された結果より、従業員番号を取り出す表関数emono_empの定義は以下です。APEX_REGION.OPEN_QUERY_CONTEXTを呼び出しています。


SQLワークショップSQLスクリプトを呼び出して実行し、ファンクションempno_empおよび、このファンクションが使用するタイプt_empno_emp_rowt_empno_emp_tabを作成します。


カード・リージョンに、このパイプライン表関数を使った条件を設定します。

リージョン従業員ソースWHERE句は、以下になります。
empno in (
    select empno from table(
        empno_emp(
            p_app_id => :APP_ID
            , p_page_id => :APP_PAGE_ID
            , p_region => 'Employees'
        )
    )
)


リージョン部門のソースのWHERE句は、以下になります。
deptno in (
    select deptno from emp where empno in (
        select empno from table(
            empno_emp(
                p_app_id => :APP_ID
                , p_page_id => :APP_PAGE_ID
                , p_region => 'Employees'
            )
        )
    )
)

スマート・フィルタが適用され、リージョン検索結果が更新された後に、カード・リージョンの従業員部門を更新します。

リージョン検索結果で、動的アクションの作成を行います。

作成された動的アクションの識別名前従属リージョンの更新とします。タイミングイベントリフレッシュ後選択タイプリージョンリージョンとして検索結果を選択します。


TRUEアクション識別アクションとして、リフレッシュを選択します。影響を受ける要素選択タイプリージョンリージョンはカード・リージョンである従業員です。


同様に、リージョン部門リフレッシュするTRUEアクションも作成します。作成済みのアクションを重複させ、リージョン部門に変更すると良いでしょう。


この時点でアプリケーションを実行すると、カード・リージョン従業員と部門の検索結果を確認することができます。


動作が確認できたら、最後にクラシック・レポートのリージョン検索結果を非表示にします。カード・リージョン従業員部門は、このリージョンに依存しているためサーバー側の条件で非表示にすることはできません。

リージョン検索結果を非表示にするため、ページ・プロパティCSSインラインに以下を記述します。
#Employees {
    display: none
}


 この状態でアプリケーションを実行すると、記事の先頭のGIF動画の動作をします。

ただし、リージョン検索結果は画面上は表示されないだけで、HTMLとしては生成されています。


そのため、リージョン検索結果Attributesの設定を調整し、無駄な処理を極力行わないようにします。

リージョン検索結果Atrributesを開き、レイアウト行数ページ区切りのタイプページ区切りなし(すべての行を表示)部分ページ・リフレッシュONとします。


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

今回はカード・リージョンを使用しましたが、リフレッシュをサポートしているリージョンは同様に使用できます。例えばチャートです。また、スマート・フィルタファセット検索は同じ仕組みを使っています。そのため、ファセット検索のページでも、同様の実装が可能です。

今回作成したアプリケーションのエクスポートを以下に置きました。

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

対話グリッドで選択した行を引数としてプロセスを実行する

 ダイアログとして開かれた対話グリッド上で選択した列を引数にして、プロセスを実行したいという相談がありました。

元々は対話グリッドは別のページに作成し、それをモーダル・ダイアログとして開こうとしていたのですが、(できなくはないが)それだと実装が難しくなります。

代わりにインライン・ダイアログで実装してみました。Always FreeのAutonomous Databaseで動くAPEX 21.2上で、アプリケーションを作成しています。

作成したアプリケーションは、以下のような動作をします。

最初は対話グリッド上で選択した行に含まれる従業員名を、直接ページ・アイテムに設定します。その後、対話グリッド上では従業員番号を取り出し、サーバー側に送信して従業員名に変換した上でページ・アイテムに設定します。

アプリケーションで作成するデータは、サンプル・データセットEMP/DEPTに含まれる表EMPを使用します。

アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。名前プロセス呼び出しとします。アプリケーションはホーム・ページに実装します。


ページ・デザイナホーム・ページを開きます。

Body静的コンテンツのリージョン従業員を作成します。


対話グリッド上で選択した、従業員の名前を保持するページ・アイテムの作成を行います。

識別名前P1_ENAMEタイプテキスト・フィールドラベル従業員名とします。


従業員を選択するために使用する、対話グリッドのリージョンの作成を行います。

識別タイトル従業員選択タイプ対話グリッドソース表名としてEMPを指定します。外観テンプレートInline Dialogを選択します。


行を選択するために使用するので、Attributes編集有効OFFにします。


インライン・ダイアログである従業員選択のリージョンを開く、ボタンの作成を行います。

識別ボタン名B_SELECTラベル従業員選択動作アクションとして動的アクションで定義を選択します。


ボタンに、動的アクションの作成を行います。

識別名前従業員選択をクリックとします。タイミングイベントクリック選択タイプボタンボタンB_SELECTとなります。


TRUEアクション識別リージョンを開く影響を受ける要素は、選択タイプリージョンリージョン従業員選択とします。これで、ボタンを押すと対話グリッドが開きます。


対話グリッド上で行を選択したときに実行される、動的アクションの作成を行います。

識別名前従業員の選択とします。タイミングイベント選択の変更[対話グリッド]選択タイプリージョンリージョン従業員選択とします。


最初は、従業員名を直接、ページ・アイテムP1_ENAMEに設定します。

TRUEアクション識別アクション値の設定を選択します。設定タイプの設定JavaScript ExpressionJavaScript式に以下を指定します。

this.data.selectedRecords[0][1]

選択される行は複数可であるため配列ですが、今回は常に1行です。そのため、最初の引数は常に次の引数列ENAMEの位置を表す1になります。(位置ではなく列名を指定したいときはmodelインターフェースを介す必要があります。こちらの記事では対話グリッドのmodelインターフェースを使用しています。)影響を受ける要素選択タイプアイテムアイテムP1_ENAMEになります。実行オプション初期化時に実行OFFクライアント側の条件タイプJavaScript式JavaScript式として以下を記述します。

this.data.selectedRecords.length > 0

行が選択されているときに限定して、値の設定(つまりP1_ENAMEへの値の設定)が実行されます。


対話グリッドのAttributes外観最初の行の選択OFFにします。この設定がONの場合、対話グリッドを開いた時点で最初の行が選択されます。その時点で対話グリッドのイベント選択の変更[対話グリッド]が発火し、動的アクション行の選択が実行されてしまいます。


値の設定を行った後に、対話グリッドのリージョンを閉じます。

TRUEアクションを作成し、識別アクションとしてリージョンを閉じるを選択します。影響を受ける要素選択タイプリージョンリージョン従業員選択を選びます。クライアントの条件は、値の設定と同じく、タイプJavaScript式JavaScript式は以下になります。

this.data.selectedRecords.length > 0


以上で、対話グリッド上で選択した行の従業員名が、ページ・アイテムP1_ENAMEに設定されます。

これから、今回の目的であるサーバー側のプロセスを呼び出す実装を行います。

Ajaxコールバックとして、プロセスget_enameを作成します。引数x01として従業員番号を受け取り、それより従業員名を検索します。結果をJSONフォーマットで呼び出し元に返します。

declare
   l_response varchar2(4000);
begin
   select json_object(ename) into l_response
   from emp
   where empno = apex_application.g_x01;
   htp.p(l_response);
   apex_debug.info(l_response);
exception
    when others then
        htp.p('{}');
end;

呼び出し側を変更します。

TRUEアクションの値の設定を、JavaScriptコードの実行に変更します。設定コードでは、先ほど作成したAjaxコールバックget_enameを呼び出すコードを記述します。
apex.server.process(
    "get_ename"    // Simple Echoback writen in PL/SQL is called, running inside DB server.
    , {
        x01: this.data.selectedRecords[0][0]  // The argument on the browser passed to the server process.
    }
    ,{
        success: function( data ) 
        {
            apex.items.P1_ENAME.setValue(data.ENAME);
        },
        error: function ( jqXHR, textStatus, errorThrown )
        {
            // do something here
        }
    }
);

以上で、アプリケーションは完成です。アプリケーションを実行すると、記事の最初のGIF動画の動作をします。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。

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

2022年6月6日月曜日

APEXセッションのデバッグ情報取得スクリプトのADB対応

 最近、問題があってOracle APEXのセッションに関する情報を取得する必要がありました。こちらからダウンロードできるOracle APEXのダウンロード・メディア(ZIPファイル)の、apex/utilities/debug以下に、デバッグ情報を取得するためのスクリプトが含まれています。

情報取得のためのスクリプトは、以下の5つです。

activity.sqld0.sqld1.sqld2.sqlds.sql

スクリプトの使用方法は、Oracle CorporationでOracle APEXを開発しているChristian Neumuellerさんが、彼のブログで説明しています。

Debugging APEX Authentication Issues

https://chrisonoracle.wordpress.com/2020/04/03/debugging-apex-authentication-issues/

元スクリプトはAPEXのスキーマに直接アクセスしているため、そのままではADBでは動きません。また、activity.sqlについては、内部表を公開している標準ビューに列DEBUG_PAGE_VIEW_IDが含まれていないため、ADBに対応させることができません。

それ以外のスクリプトは少々の変更で、ADBで動作させることができました。

d0.sqlシステム・パラメータSYSTE_DEBUG_LEVELを9に設定する(またはデバッグを解除する)スクリプトです。ADBの管理者ユーザーADMINで、変更無しで実行できます。すべてのAPEXセッションで完全トレースが取られるため、全体のパフォーマンスが大幅に低下します。そのため、完全トレースの設定はできる限りセッション単位で行なうべきです(以前に、セッションを選んでデバッグ・レベルを設定するAPEXアプリを作成しています)。ただし、ソーシャル・サインインで使用されるapex_authentication.callback(およびSAML認証で使用されるapex_authentication.saml_callback)のデバッグ・メッセージを取得する方法は、これ以外にはありません。

SQL> @d0

Changed debug level from "" to "9"


SQL> 

d1.sqlをADBで動作させるための差分は以下です。patchコマンドを使って、差分を適用します。

SESSION_IDds.sqlPAGE_VIEW_IDd2.sqlに渡して、さらに詳細のデータを取得します。

d2.sqlをADBで動作させるための差分は以下です。

ds.sqlをADBで動作させるための差分は以下です。


activity.sqlについては、以下のように手順を変えて情報を取得します。

APEX_ACTIVITY_LOGはAPEXセッションが生成されていないとデータが検索できないため、最初にAPEX_SESSION.CREATE_SESSIONを呼び出します。ユーザーは管理者ロールを持っている必要があります。

exec apex_session.create_session(アプリケーションID,ページID,'ユーザー名');
select * from apex_activity_log where session_id = セッションID order by time_stamp desc;


以上になります。

2022年6月2日木曜日

表示のみアイテムの表示色を変更する

 タイプ表示のみのページ・アイテムの表示色を、入力値の条件によって変更するにはどうしたらよいか、という質問があったので実装してみました。

同じ結果が得られますが、3つの異なる実装方法を試してみます。

レベルには0から10までの値を入力します。ステータスとして、レベルが0-4までは緑で安全5-10赤で危険と表示します。


アプリケーション作成ウィザードを起動し、ホーム・ページのみを含む空のアプリケーションを作成します。名前表示のみアイテムの色付けとしました。

試験実装にはAlways FreeのAutonomous DatabaseのAPEX 21.2を使用しています。実装に使用している動的アクションのクライアント側の条件は、APEX 21.1から追加された機能であるため、それ以前のバージョンでは利用できません。コードによる実装では、ネームスペースapex.itemsを使っているため、APEX 21.2以降のバージョンでのみ動作します。

宣言的実装


レベルを入力するページ・アイテムとしてP1_LEVEL1ステータスを表示するページ・アイテムとしてP1_STATUS1を作成します。P1_STATUS1の表示は、P1_LEVEL1に設定する動的アクションで実行します。TRUEアクションにクライアント側の条件を設定することにより、レベルの入力値に依存して、表示を変更します。

リージョンを作成し、識別名前宣言的実装タイプ静的リージョンとします。

作成したリージョンに、ページ・アイテムP1_LEVEL1を作成します。識別タイプ数値フィールドラベルレベル設定最小値0最大値10数字の位置合せLeftにします。


続いて、ページ・アイテムP1_STATUS1を作成します。識別タイプ表示のみラベルステータスとします。ページの送信時に送信OFFにします。OFFの場合spanタグで囲まれたテキストとして値が生成されます。ONの場合はinputタグのvalue属性として値が設定されます。そのため、CSSクラスを設定しても効果がありません。


ページ・アイテムP1_LEVEL1に数値が入力されたときにP1_STATUS1の表示を決めるように、P1_LEVEL1に動的アクションを作成します。

作成した動的アクションの識別名前を、ステータスの設定1とします。タイミングは、イベント変更選択タイプアイテムアイテムP1_LEVEL1になります。


ステータス安全という文字列を設定するTRUEアクションを作成します。

識別アクションとして、値の設定を選択します。設定タイプの設定Static Assignment安全です。影響を受ける要素として、選択タイプアイテムアイテムとしてP1_STATUS1を指定します。これで、ページ・アイテムP1_STATUS1に安全という文字が設定されます。

実行オプション初期化時に実行ONにします。この設定によりページ・ロード時(ページ・アイテムP1_LEVEL1は変更されていない)に、このアクションが評価されます。実行されるかどうかは、この後に設定するクライアント側の条件に依存します。

クライアント側の条件タイプとしてアイテム < 値を選択し、アイテムP1_LEVEL15を設定します。このTRUEアクションは、P1_LEVEL1の値が5より小さいときに実行されます。


作成したTRUEアクションを重複させ、表示色を初期化するTRUEアクションに変更します。

識別アクションクラスの削除に変更し、設定クラスとしてu-dangerを指定します。それ以外の設定は、重複元の安全という文字を設定したTRUEアクションと同じです。


u-dangerはOracle APEXのUniversal Themeにて定義されているCSSクラスになります。標準で定義されている危険色を使用することにより、標準が変更されたときに、ここで指定した色も合わせて変更されます。

TRUEアクションを再度重複させます。設定アクションクラスの追加設定クラスu-successとします。これでアイテムの表示色が緑になります。

これらのTRUEアクションのクライアント側の条件はすべて同じく、P1_LEVEL1が5より小さいです。


反対に5以上のときに、ステータスを赤で危険と表示するTRUEアクションを作成します。

作成済みの3つのTRUEアクションを選択し、重複を実行します。


作成された3つのTRUEアクションのクライアント側の条件タイプを、アイテム >= 値に変更します。


重複したTRUEアクション値の設定の、設定を安全から危険へ変更します。


クラスの削除u-dangerからu-successへ変更します。


クラスの追加u-successからu-dangerへ変更します。


1つめの実装は以上で完了です。


コードによる実装



先ほどと同じ条件で、リージョンコードによる実装、ページ・アイテムP1_LEVEL2、ページ・アイテムP1_STATUS2を作成します。

リージョン宣言的実装重複させ、識別タイトルコードによる実装に変更します。レイアウト新規行の開始OFFにします。同時にコピーされたページ・アイテムP1_LEVEL1_1をP1_LEVEL2、P1_STATUS1_1をP1_STATUS2に変更します。動的アクションはコピーされません。


ページ・アイテムP1_LEVEL2に動的アクションを作成します。識別名前ステータスの設定2とします。作成する動的アクションは、ページ・アイテムP1_LEVEL1の設定と同様なので、スクリーンショットは割愛します。

作成するTRUEアクションJavaScriptコードの実行です。以下のコードを、設定コードに記述します。
if (apex.items.P1_LEVEL2.value < 5) {
    apex.items.P1_STATUS2.setValue("安全");
    apex.items.P1_STATUS2.element.removeClass("u-danger");
    apex.items.P1_STATUS2.element.addClass("u-success");
}
else
{
    apex.items.P1_STATUS2.setValue("危険");
    apex.items.P1_STATUS2.element.removeClass("u-success");
    apex.items.P1_STATUS2.element.addClass("u-danger");
}
実行オプション初期化時に実行ONにします。


以上で、実装は完了です。

見かけ上の動作は変わりませんが、大きな違いがあります。

宣言的にTRUEアクションを並べた場合、この通りの順番でTRUEアクションが実行/終了することは保証されません。そのため、実行の順番に関わらず、結果は同じになるようにTRUEアクションを定義する必要があります


 一塊のコードで記述している場合は、コードに記載された通りの順番で実行されます。実行順を保証したい場合は、JavaScriptのコードを記述する必要があります。


宣言的実装2


クライアント側の条件をTRUEアクションに設定する代わりに、動的アクションに設定することもできます。

コードによる実装のときと同様の準備を行い、リージョン宣言的実装2、ページ・アイテムP1_LEVEL3P1_STATUS3を作成します。

動的アクションの作成も同様に行いますが、クライアント側の条件を設定します。

識別名前ステータスの設定 - 安全とし、クライアント側の条件タイプアイテム < 値アイテムP1_LEVEL35とします。


もう一つ、動的アクションを作成します。

識別名前ステータスの設定 - 危険とし、クライアント側の条件タイプアイテム >= 値アイテムP1_LEVEL35とします。


それぞれの動的アクションに、同じ効果を得るためのTRUEアクションを作成します。動的アクションクライアント側の条件を設定するため、TRUEアクションにはクライアント側の条件は設定しません。


こちらの実装も以上で完了です。

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

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