2021年9月9日木曜日

表の内容をNDJSON形式で出力する

 以前に改行で区切ったJSON形式(Newline Delimited JSON)のファイルをロードする記事を書きました。今度は逆に、指定した表の内容をNDJSON形式で出力してみます。

アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。アプリケーションの名前NDJSON生成としました。アプリケーションの作成を実行します。

アプリケーションが作成されたら、ページ・デザイナホーム・ページを開きます。

Content Bodyにリージョンを作成します。識別タイトルNDJSON生成タイプ静的コンテンツとします。

NDJSON形式でデータを出力する表を指定するページ・アイテムを作成します。識別名前P1_TABLE_NAMEタイプテキスト・フィールドとします。ラベルテーブル名とします。

ページ・アイテムP1_TABLE_NAMEに変更があった時点で、その値をセッション・ステートに保存するよう、動的アクションの作成を行います。

識別名前テーブル名の保存とします。P1_TABLE_NAMEにたいして動的アクションの作成を実行していると、タイミングはデフォルトで、イベント変更選択タイプアイテムアイテムP1_TABLE_NAMEとなっています。

TRUEアクション識別アクションサーバー側のコードを実行を選択します。設定PL/SQLコードとしてはnull;を記述します。サーバー側では実際には何も実行されませんが、送信するアイテムとしてP1_TABLE_NAMEを指定することにより、このページ・アイテムがセッション・ステートに保存されます。


NDJSONの生成を実行するボタンを作成します。

識別ボタン名B_CALLとします。ラベルNDJSON生成とします。動作アクションとしてこのアプリケーションのページにリダイレクトを選びます。


ターゲットの設定としては、ページ&APP_PAGE_ID.を指定します。&APP_PAGE_ID,は現在開いているページ番号に置き換えられます。リクエストとしてAPPLICATION_PROCESS=GEN_NDJSONを設定します。Ajaxコールバックとして作成されたプロセスGEN_NDJSONを呼び出します。


NDJSONを生成するプロセスをAjaxコールバックの位置に作成します。以下のコードを記述します。

declare
l_table_name varchar2(128);
l_sql varchar2(800);
l_line varchar2(32767);
l_raw raw(32767);
l_len integer;
l_blob blob;
type t_ndjson is ref cursor;
c_ndjson t_ndjson;
begin
l_table_name := dbms_assert.sql_object_name(:P1_TABLE_NAME);
l_sql := 'select json_object(*) as l from ' || l_table_name;
-- 生成したNDJSON形式のデータはBLOBに書き込む。
dbms_lob.createtemporary(l_blob, TRUE, dbms_lob.session);
dbms_lob.open(l_blob, dbms_lob.lob_readwrite);
-- 一行ずつJSONオブジェクトとして取り出す。
open c_ndjson for l_sql;
loop
fetch c_ndjson into l_line;
exit when c_ndjson%notfound;
l_line := l_line || chr(10); -- 改行の追加
l_raw := utl_raw.cast_to_raw(l_line);
l_len := utl_raw.length(l_raw);
dbms_lob.writeappend(l_blob, l_len, l_raw); -- BLOBに追記
end loop;
close c_ndjson;
dbms_lob.close(l_blob);
-- NDJSONの書き込みの終了。
-- 書き込んだLOBの内容をダウンロード。
sys.htp.init;
sys.htp.p('Content-Length: ' || dbms_lob.getlength(l_blob));
sys.htp.p('Content-Disposition: attachment; filename=' || l_table_name || '.json');
sys.owa_util.http_header_close;
sys.wpg_docload.download_file(l_blob);
apex_application.stop_apex_engine;
dbms_lob.freetemporary(l_blob);
end;

ページ・アイテムP1_TABLE_NAMEを使ってSQLを構築するため、SQLインジェクションの対応としてDBMS_ASSERT.SQL_OBJECT_NAMEで囲み、安全な文字列であることを保証しています。またSQLの中でjson_object(*)という記法を使っています。これはOracle Database 19c以降で利用可能です。それ以前(例えば現行のapex.oracle.comは18cなので、このままでは動作しません)のバージョンでは、この部分を書き換える必要があります。


以上でアプリケーションの完成です。実行すると記事の先頭のGIF動画のように動きます。

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

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

Autonomous Database上のAPEXアプリケーションでSQLトレースを取得する

 Autonomous DatabaseでSQLトレースを取得することができるようになったようです。APEXのアプリケーションのSQLトレースを取る方法を確認してみました。

ルート・コンパートメント直下に、SQLトレースのファイルを保存するバケットを作成することにしました。もちろん、バケットはどこに作成しても構いません。

オブジェクト・ストレージの管理画面を開いて、バケットの作成をクリックします。


バケット名sql-traceとします。デフォルト・ストレージ層として標準を選んで、作成をクリックします。


SQLトレースの出力が確認できればよいだけなので、事前承認リクエストの作成を行います。


事前承認済リクエスト・ターゲットとしてバケットを選びます。アクセス・タイプとしてオブジェクトの書込みを許可を選択します。名前有効期限をそれぞれ設定し、事前承認済リクエストの作成をクリックします。


生成された事前承認済リクエストのURLをクリップボードにコピーしておきます。閉じるをクリックして、ダイアログを閉じます。


これでオブジェクト・ストレージの用意は完了です。

ユーザーADMINにてデータベース・アクションに接続し、開発SQLを開きます。以下のSQLを実行します。

set define off;
ALTER DATABASE PROPERTY SET DEFAULT_LOGGING_BUCKET = '事前承認済リクエストのURL';


Oracle APEXに作成したワークスペースのスキーマがAPEXDEVと仮定して、そのスキーマでALTER SESSION文の実行ができるように権限を与えます。

grant alter session to apexdev;


APEXのアプリケーション・ビルダーに移り、SQLトレースを取得したいアプリケーションのアプリケーション定義セキュリティのタブを開きます。

データベース・セッション初期化PL/SQLコードで、SQL_TRACEを有効にします。

execute immediate 'alter session set sql_trace = true';

PL/SQLコードのクリーンアップで、SQL_TRACEを無効にします。

execute immediate 'alter session set sql_trace = false';

以上を記述して、変更の適用を行います。


以上の設定を行なった後APEXアプリケーションを実行すると、SQLトレースがオブジェクト・ストレージに保存されます。オブジェクト・ストレージ上のファイルを確認してみます。


Oracle APEXのアプリケーションによって、データベースのセッションにクライアントIDモジュールが設定されるため、開発者がそれらを設定する必要はありません。

マニュアルによるとclientIDの後にmoduleNameが続くことになっていますが、APEXのアプリケーションから生成されたSQLトレースでは、その間にデータベース・ユーザーが入っています。これは、APEX固有になるのかマニュアルの記載が違うのかは分かりません。データベース・ユーザー名がパスに含まれていても、特に問題は無いでしょう。

APEXアプリケーションでSQLトレースを取得する場合は、ビューSESSION_CLOUD_TRACEを検索する機会はほとんどないと思います。APEXの場合は、ページをリクエストするごとに異なるデータベース・セッションが割り当たるためです。

以上でAutonomous DatabaseでのOracle APEXアプリケーションで、SQLトレースを取得する方法の紹介は終了です。

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

CSVをつなげて1つのファイルとしてダウンロードする

 1ページに対話モード・レポートを2つ作り、両方を1つのCSVファイルとしてダウンロードしてみます。元にした実装は、Ajaxコールバックを使ったCSVのダウンロードです。

以下、実装手順になります。

サンプル・データセットEMP/DEPTがインストール済みとします。それに含まれる表EMPを対話モード・レポートのソースとします。

最初に空のアプリケーションを作成します。アプリケーションが作成されたら、ホーム・ページに対話モード・レポートを2つ作成します。

最初の対話モード・レポートの静的IDとして、emp1を設定します。

次の対話モード・レポートの静的IDは、emp2とします。


Ajaxコールバックとして、プロセスCSVEXPORTを作成します。PL/SQLコードとして以下を記述します。

declare
l_ctx_emp1 apex_exec.t_context;
l_ctx_emp2 apex_exec.t_context;
l_rid_emp1 number;
l_rid_emp2 number;
l_exp_emp1 apex_data_export.t_export;
l_exp_emp2 apex_data_export.t_export;
begin
-- Generate CSV of IR region emp1
select region_id into l_rid_emp1 from apex_application_page_regions
where application_id = :APP_ID and page_id = 1.
and static_id = 'emp1';
apex_debug.info('region_id of emp1 is ' || l_rid_emp1);
l_ctx_emp1 := apex_region.open_query_context (
p_page_id => 1,
p_region_id => l_rid_emp1 );
l_exp_emp1 := apex_data_export.export (
p_context => l_ctx_emp1,
p_format => apex_data_export.c_format_csv,
p_file_name => 'emp1' );
apex_exec.close(l_ctx_emp1);
-- Generate CSV of IR region emp1
select region_id into l_rid_emp2 from apex_application_page_regions
where application_id = :APP_ID and page_id = 1
and static_id = 'emp2';
apex_debug.info('region_id of emp2 is ' || l_rid_emp2);
l_ctx_emp2 := apex_region.open_query_context (
p_page_id => 1,
p_region_id => l_rid_emp2 );
l_exp_emp2 := apex_data_export.export (
p_context => l_ctx_emp2,
p_format => apex_data_export.c_format_csv,
p_file_name => 'emp2' );
apex_exec.close(l_ctx_emp2);
-- merge BLOB output into 1
dbms_lob.append(l_exp_emp1.content_blob, l_exp_emp2.content_blob);
-- download as a single file.
apex_data_export.download( p_export => l_exp_emp1 );
end;

対話モード・レポートに設定されている検索条件でSQL問合せ行うコンテキストをapex_region.open_query_contextを呼び出して取得します。その問合せからCSVのデータをapex_data_export.exportを呼び出して生成します。

l_exp_emp1には対話モード・レポートemp1のデータ、l_exp_emp2には対話モード・レポートemp2のデータが保持されています。dbms_lob.appendを呼び出して、l_exp_emp2の出力をl_exp_emp1に追記したのち、apex_data_export.downloadを呼び出して、ファイルとしてl_exp_emp1の内容を出力しています。

画面上にAjaxコールバックCSVEXPORTを呼び出すボタンを追加します。

ブレッドクラムのリージョンでボタンの作成を実行します。識別ボタン名B_EXPORTラベルCSVエクスポートとします。動作アクションとして、このアプリケーションのページにリダイレクトを選択します。


ターゲットページには&APP_PAGE_ID. (今回の例では1に置き換わります)、詳細リクエストとして、APPLICATION_PROCESS=CSVEXPORTを設定します。


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

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

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

2021年9月8日水曜日

Oracle Database 21cのメディアからOracle APEXが除かれています

 Oracle Database 21cのオンプレミス版がリリースされました。ダウンロードしてインストールされた方もいらっしゃるかと思います。

その際に気が付づいたかもしれませんが、Oracle Database 21cのメディアからOracle APEXは除かれています。

インストール・メディアV1011496-01.zipの内容を展開すると、以下のようになっています。

OPatch      data           inventory  odbc     plsql          sqldeveloper

QOpatch     dbs            javavm     olap     precomp        sqlj

R           deinstall      jdbc       oml4py   python         sqlpatch

addnode     demo           jdk        opmn     racg           sqlplus

assistants  diagnostics    jlib       oracore  rdbms          srvm

bin         dv             ldap       ord      relnotes       suptools

clone       env.ora        lib        ords     root.sh        ucp

crs         has            md         oss      runInstaller   usm

css         hs             mgw        oui      schagent.conf  utl

ctx         install        network    owm      sdk            xdk

cv          instantclient  nls        perl     slax

ディレクトリapexは含まれていません。

この変更については、Oracleの公式ブログの記事On-premises APEX installations in Oracle Database 21c and beyondにて説明されています。

大まかにいうと以下です。Oracle Databaseのインストール・メディアから除かれただけで、ライセンス、サポート、費用のすべての面で、製品としての扱いに違いはありません。

  1. Oracle Database 21c以降より、オンプレミス向けのインストール・メディアにOracle APEXは含まれません。
  2. Oracle APEXをインストールするには、常にapex.oracle.com/downloadから最新のOracle APEXのインストール・メディアを入手してください。
  3. 現在、Oracle APEXは概ね1年に2回、インストール・メディアを更新しています。データベースに含まれるAPEXは常に古いバージョンであり、更新されることはありません。Oracle APEXのチームはAPEXの利用者に、apex.oracle.com/downloadより最新版をインストールすることを推奨してきました。
  4. ライセンスに変更はありません。Oracle APEXは継続して、Oracle Databaseのライセンスに含まれます。サポートについても同様で、Oracle Databaseのサポート契約が継続している限り、Oracle APEXもサポート対象に含まれます。
  5. Oracle Database 21cは、Oracle APEXの動作保証の対象です。
  6. Oracle APEXはOracle Databaseのライセンスがあれば、追加の費用負担なく利用可能です。今までと違いはありません。
データベースのメディアにAPEXが入っているために、それをインストールする顧客がいました。Oracle APEXの開発チームは、以前のバージョンがインストールされてしまうことを問題と考えていたため、データベースのチームにAPEXをメディアから除くよう依頼していました。Oracle Database 21cからは、要望が通ってAPEXがメディアから除かれました。

そういった事情ですので、ライセンス、サポート、費用といった点に変更はありません。

APEXの一時表を使った画像ビューワー・アプリの作成

 Oracle APEXのページ・アイテムのタイプファイル参照...というのがあります。このページ・アイテムを使って、選択した画像ファイルを表示するアプリケーションを作成してみます。

最初に空のアプリケーションを作成します。アプリケーション作成ウィザードを起動します。名前画像ビューワーとして、アプリケーションの作成を実行します。

アプリケーションが作成されたら、ページ・デザイナにてホーム・ページを開きます。ホーム・ページに画像のアップロードと表示の機能を実装します。


アプリケーションとしてはホーム・ページしか含まれないため、ナビゲーション・メニューやブレッドクラムは不要です。ページ・プロパティ外観ページ・テンプレートとしてMinimail (No Navigation)を選択し、ブレッドクラムのリージョン画像ビューワーを削除します。


Content Bodyでリージョンの作成を実行します。識別タイトル画像ビューワーとし、タイプ静的コンテンツとします。リージョンとしての装飾は不要なので、外観テンプレートとしてBlank with Attributesを選択します。


表示するファイルをローカルのファイル・システムから選択するためのページ・アイテムを作成します。

識別名前P1_FILEタイプとしてファイル参照...を選択します。重要な設定は設定記憶域タイプで、これにはTable APEX_APPLICATION_TEMP_FILESを選択します。この記憶域タイプが選択されている場合、ローカルのファイル・システムからアップロードされるファイルは、Oracle APEXが用意している表APEX_APPLICATION_TEMP_FILESに書き込まれます。記憶域タイプとしてBLOB column specified in Item Source attributeを選んだ場合は、BLOB列を持つ表をあらかじめ作成しておく必要があります。アップロードするファイルを永続的に保存する場合は、後者の設定を行った方がよいでしょう。

それ以外の設定は操作方法や見かけに関するものなので、必ずしもこの通りではなくても構いません。今回の手順では、ラベルとしてファイル設定表示形式Block Dropzoneドロップ・ゾーンのタイトルとして画像ファイルの選択ドロップ・ゾーンの説明として「画像ファイルをドラッグ&ドロップします。」を設定しています。


ページ・アイテムP1_FILEで選択されたファイルは、ページの送信を行うまではアップロードされません。ページ・アイテムP1_FILEの値の変更が行われたとき(ファイルが選択されたときになります)に、動的アクションによってページの送信を実行します。

ページ・アイテムP1_FILEで動的アクションの作成を実行します。識別名前ファイルの選択とします。タイミングはデフォルトで、イベント変更選択タイプアイテムアイテムP1_FILEになります。


TRUEアクション識別アクションとして、ページの送信を選択します。これで選択したファイルのアップロードが行われます。今回のアプリケーションでは使用しませんが、設定リクエスト/ボタン名UPLOADとしています。この名前がリクエストの値として送信されるので、サーバー側に作成する検証プロセスサーバー側の条件で使用することができます。


サーバー側の条件タイプには、リクエストを参照するものがあります。これらの条件として上記で設定したリクエスト/ボタン名を使うことができます。


以上でファイルを選択すると、サーバー側に選択されたファイルがアップロードされるようになりました。アップロードされたファイルは表APEX_APPLICATION_TEMP_FILESより参照できます。

アップロードされたファイルを表示するページ・アイテムを作成します。

識別名前P1_IMAGEとします。タイプとしてイメージの表示を選択します。ラベルラベル画像設定基準としてBLOB Column returned by SQL statementを選択し、SQL文として以下を記述します。表APEX_APPLICATION_TEMP_FILESよりアップロードされたファイルの内容であるBLOB列を検索しています。

select blob_content
from apex_application_temp_files
where name = :P1_FILE

外観テンプレートHiddenを選択し、CSSクラスとしてmy-imageを設定しておきます。CSSクラス自体の記述は後ほど、ページ・プロパティに含めます。


アップロードされたファイルの、ファイル名を表示するページ・アイテムを作成します。

識別名前P1_FILENAMEタイプとして表示のみを選択します。ラベルラベルファイル名とします。ソースタイプとしてSQL問合せ(単一の値を返す)を選択し、SQL問合せとして以下を記述します。

select filename
from apex_application_temp_files
where name = :P1_FILE


同様にアップロードされたファイルの、MIMEタイプを表示するページ・アイテムを作成します。

識別名前P1_MIME_TYPEタイプとして表示のみを選択します。ラベルラベルMIMEタイプとします。ソースタイプとしてSQL問合せ(単一の値を返す)を選択し、SQL問合せとして以下を記述します。

select mime_type
from apex_application_temp_files
where name = :P1_FILE


画像の表示を画面にフィットさせるため、ページ・プロパティCSSインラインに以下を記述します。

.my-image div {
display: flex;
justify-content: center;
}

.my-image img {
width: 30%;
height: auto;
}


以上で完成です。ページを実行し画像ファイルをアップロードすると、以下のように表示されます。


画像の表示にカード・リージョンを使うと、複数の画像を表示することができます。

ページ・アイテムP1_FILE設定複数ファイルの許可ONに変更します。ページ・アイテムP1_IMAGE、P1_FILENAME、P1_MIME_TYPEは削除します。


カード・リージョンを作成します。

識別名前画像とします。タイプカードです。ソース位置ローカル・データベースで、タイプSQL問合せとします。SQL問合せとして以下を記述します。

select id, blob_content, filename, mime_type
from apex_application_temp_files
where name in
(
select column_value
from apex_string.split(:P1_FILE, ':')
)


カード・リージョンのAttributesとして、カード主キー列1IDタイトルFILENAMEサブタイトルとしてMIME_TYPEを指定します。


メディアソースBLOB列とし、BLOB列BLOB_CONTENTを指定します。BLOB属性MIMEタイプ列MIME_TYPEを指定します。


以上で複数ファイルのアップロードと表示を行うアプリケーションは作成できました。ページを実行し、複数の画像ファイルを選択すると、以下のように表示されます。


APEXの一時表APEX_APPLICATION_TEMP_FILESの使い方の紹介は以上になります。

ひとつの画像を表示するアプリケーションのエクスポートは以下です。
https://github.com/ujnak/apexapps/blob/master/exports/image-viewer-single.sql

複数の画像を表示するアプリケーションのエクスポートは以下です。
https://github.com/ujnak/apexapps/blob/master/exports/image-viewer-multi.sql

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

2021年9月7日火曜日

Universal Themeの安全なカスタマイズ方法について

 Oracle APEXのArchitectであるShakeeb Rahmanさんが、先日のOffice Hour - Deep Dive: Universal Theme in APEX 21.1にて、Universal Themeのカスタマイズ方法について紹介しています。

Recommended Approach - 推奨する方法 - として、以下を挙げています。

  • Theme Roller - テーマ・ローラー
  • Template Options - テンプレート・オプション
  • Overriding CSS variables - CSS変数の上書き
  • Scoped CSS snippets where necessary - スコープ付きCSSスニペット - 必要に応じて
テーマ・ローラーおよびテンプレート・オプションはOracle APEXの標準機能であり、サポートされる変更手順です。CSS変数の上書きやCSSスニペットの記述(テーマ・ローラーのカスタムCSSとしての記述を含め)は、開発者によって書かれたコードになります。ですので、そのコード自体はサポートの対象にはなりません。それらの方法によるUIのカスタマイズは、Oracle APEXのバージョンアップの影響が最小限である(もしくはほとんどない)ため、カスタマイズ手順として推奨されています。

推奨されている手順について、Tim Kimberlさんがビデオの中で説明しています。サンプル・データセットのEMP/DEPTから作成されるアプリケーションを使って、ビデオで紹介されている手順を確認してみます。

Oracle APEX 21.1からHTML要素のclass属性としてpage-ページ番号およびapp-アプリケーション別名が指定されるようになりました。

例えば以下のページ番号3、ファセット検索のページのHTML要素は以下から始まります。

<html class="page-3 app-DEMO-EMP-DEPT">


テーマ・ローラーを開いてカスタムCSSとして以下のコードを設定します。ヘッダーのバックバウラウンド色を定義しているCSS変数を赤に変更します。

.page-3 {
--ut-header-background-color: red;
}

.page-3クラスの定義とすることで、ページ番号3に適用範囲を限定しています。


部門ページを開いてみます。部門ページはページ番号5なので、この設定による影響はありません。


また、CSS変数の設定では、他のCSS変数を参照することができます。--ut-palette-successをヘッダーのバックラウンド色に設定します。

.page-3 {
--ut-header-background-color: var(--ut-palette-success);
}

ヘッダーの色が緑になります。


続いて、レポートとして表示される文字の色を変更してみます。ページ・デザイナを開いて、レポート・リージョンのEmployee外観CSSクラスmy-regionを設定します。


テーマ・ローラーのカスタムCSSに以下の記述を追加します。.page-3と.my-regionクラスに設定することにより、ページ番号3およびEmployeeのリージョンに適用範囲を限定しています。

.page-3 .my-region {
--ut-region-text-color: var(--ut-palette-success);
}

リージョンの文字の色が緑になっています。


CSS変数の上書きとスコープ付きCSSスニペットの例は以上になります。

Office HourではOracle APEX 21.1の新しい機能である行CSSクラスについても紹介されていました。

ダッシュボードのチャートが次のように配置されています。


行CSSクラスを定義して、以下のようなレイアウトにしてみます。


最初に開発者ツール・バーよりクイック編集を実行し、4つのチャート・リージョンすべてでライブ・テンプレート・オプションを呼び出し、Body Heightを320pxからAuto - Defaultに変更します。


部署ごとの総給与のリージョンの、レイアウト新規行の開始OFFに変更します。すべてのチャート・リージョンが横一列に並びます。


ページ・プロパティCSSインラインに以下を記述します。同じ行に並んでいるリージョンの高さを一致させます。

.equal-row-height .col {
display: flex;
}

.equal-row-height .t-Region {
flex-grow: 1;
}

定義したCSSクラスequal-row-heightを、行の先頭となるリージョン部門ごとの従業員レイアウト行CSSクラスとして設定します。同じ行に配置される4つのリージョンの高さが同じになります。


以上で行CSSクラスの設定はできました。設定結果の表示は以下になります。

もともとBody Heightはすべて320pxとして設定されていたので、リージョンの高さは一致していました。ここでチャートの職種ごとの合計給与の棒グラフを縦方向に広げてみます。

チャートのAttributesレイアウト高さ500ピクセルとします。


チャートは以下の表示になります。職種ごとの合計給与のチャートは縦長になりますが、同じ行のすべての(チャート)リージョンも同じ高さに変更されます。そのため、Body Heightをそれぞれのリージョンで個別に設定する必要がありません。


行CSSクラスはフォーム上のページ・アイテムの配置でも活用できます。従業員の編集フォームのSalaryとCommisionのページ・アイテムを以下のように強調してみます。


Employeeのフォーム・ページのCSSインラインに以下を記述します。パディングとして20ピクセル、バックグラウンド色を警告色にしています。

.highlight-form-row {
padding: 20px;
background-color: var(--ut-palette-warning-shade);
}


フォームの作成直後は、ページ・アイテムP4_COMMはP4_SALの右隣には配置されていません。ページ・アイテムP4_COMMレイアウト新規行の開始OFFにします。


ページ・アイテムP4_SALレイアウト行CSSクラスとして、highlight-form-rowを設定します。


以上でSalaryとCommissionの入力項目を強調することができました。行CSSクラスが導入される前はサブリージョンを使うなど、非常に手間がかかりました。

Tim Kimberlさんがデモで使用していたアプリケーションを以下からアクセスできるようにしています。
https://apex.oracle.com/pls/apex/japancommunity/r/ut211-demo/generic-component-styles

Universal Themeにて定義されているCSS変数の一部が記載されていますが、リファレンスとして使えるようになるまでは、まだ時間がかかるようです。

以上で今回の記事は終了です。

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