2023年9月7日木曜日

Knockoutを無効にした状態でOracle JETのコンポーネントを使用する

Oracle APEXはOracle JETのコンポーネントをチャートの実装に活用していますが、knockout.jsは使用していません。以下のドキュメントに説明があります。

Oracle APEXアプリケーション・ビルダー・ユーザーズ・ガイド Release 22.2
10.1 Oracle APEXとのOracle JETの統合の理解
10.1.2 Oracle JETがOracle APEXと統合する仕組み

ノート:Oracle JETには双方向データ・バインディング(knockout.jsを使用)が用意されていますが、現在のAPEXでは、ツールキットのこの側面はネイティブに使用されません。
Oracle JETのカスタム・エレメントが静的コンテンツとして生成される場合、または動的コンテンツであっても、カスタム・エレメントをIDで特定できる場合は、そのカスタム・エレメントに対応するバインディング・コンテキスト(JavaScriptのオブジェクト)を作成してknockout.jsのapplyBindingsを呼び出すことができます。しかし、レポート(クラシック・レポート、対話モード・レポート、対話グリッド)の列にJETチャートを表示する場合や、カード・リージョンのカード上に表示するケースでは、難しい実装になります。

そのようなケースでもOracle JETのチャートを利用できるように、属性itemsが用意されています。属性itemsにチャートとして表示するデータ(JSONドキュメント)を与えると、そのデータを使ったチャートが描画されます。

ただし、Oracle JETはknockout.jsと一緒に使用することを想定しているためか、Oracle JET API ReferencesのそれぞれのElementsのAttributes一覧にitemsは含まれていません。Type DefinitionsにItemがあれば、それをひとつのオブジェクトとした配列をitemsに与えることができるようです。

本記事では以下のようなアプリケーションを作成し、Knockoutを使用しないJETチャートの実装方法を確認します。


JET Picto Chartをサンプルの実装に使用します。Picto Chart(カスタム・エレメントはoj-picto-chart)のリファレンスより、以下のType DefinitionsのItemを参照します。


Picto Chartとして表示されるそれぞれのアイテムには、表示色とツールチップの情報が必要です。Itemの定義を参照するとcolorとnameがその情報に対応することがわかります。

colorstring<optional>
The color of the item. Does not apply if custom image is specified.

namestring<optional>
The name of the item. Used for default tooltip and accessibility.

以上より属性itemsには以下のようなデータを与えることにします。
[
    {
        "color": 色情報,
        "name": "ツールチップとなる文字列"
    },
    {
        "color": 色情報,
        "name": "ツールチップとなる文字列"
    },
   ... 繰り返し
]
アプリケーションで使用するデータは、Open-Meteo.comのFree Weather APIを呼び出して取得しています。使用にあたっては、ライセンスおよびPricingについて、ご自身で確認していただくようお願いします。Terms & Privacyに説明がありますが、非商用であれば無料で利用できます。

以下より実装について説明します。

Open-Meteo.comのAPIを呼び出す際に引数とする都市のマスター・データを保存する表HMT_CITIESと、その都市ごとの最高気温を保存する表HMT_CITY_TEMPERATURESを作成します。

表の作成には、以下のクイックSQLのモデルを使用します。
# prefix: hmt
cities
	city_name vc80 /nn
	latitude num /nn
	longitude num /nn
	timezone vc20 /nn
	city_temperatures
		date_rec date /nn
		temperature_2m_max num
SQLの生成、SQLスクリプトを保存、レビューおよび実行を順次実施します。表の作成までを行い、アプリケーションの作成は行いません。


表HMT_CITIESにいくつかの都市の情報を保存します。

SQLコマンドを開き、以下を実行します。SQLコマンドで実行できるのは1行のSQLなので、begin/endでINSERT文を囲んでいます。
begin
Insert into HMT_CITIES (CITY_NAME,LATITUDE,LONGITUDE,TIMEZONE) values ('Tokyo',35.6895,139.6917,'Asia/Tokyo');
Insert into HMT_CITIES (CITY_NAME,LATITUDE,LONGITUDE,TIMEZONE) values ('New York',40.7143,-74.006,'America/New_York');
Insert into HMT_CITIES (CITY_NAME,LATITUDE,LONGITUDE,TIMEZONE) values ('London',51.5085,-0.1257,'Europe/London');
Insert into HMT_CITIES (CITY_NAME,LATITUDE,LONGITUDE,TIMEZONE) values ('Berlin',52.5244,13.4105,'Europe/Berlin');
Insert into HMT_CITIES (CITY_NAME,LATITUDE,LONGITUDE,TIMEZONE) values ('Paris',48.8534,2.3488,'Europe/Paris');
Insert into HMT_CITIES (CITY_NAME,LATITUDE,LONGITUDE,TIMEZONE) values ('Sydney',-33.8678,151.2073,'Australia/Sydney');
end;
実行結果に1行が挿入されました。と表示されますが、エラーが発生しなければ全行挿入されています。


アプリケーション作成ウィザードを起動します。アプリケーションの名前はJET without Knockoutとします。

ページの追加をクリックします。

表HMT_CITIESおよびHMT_CITY_TEMPERATURESの内容を手動で編集することは想定していませんが、内容を確認できるようにマスター・ディテールのページを作成します。


マスター・ディテールを選択します。


ページ名はCities and Temperatures、形式に積上げを選択します。(マスター)表にHMT_CITIES、ディテール表にHMT_CITY_TEMPERATURESを指定します。

ページの追加をクリックします。


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


アプリケーションが作成されます。


Open-Meteoよりデータを取り込む処理とPicto Chartに与えるJSONドキュメントを表HMT_CITY_TEMPERATURESより取り出す処理を、PL/SQLパッケージHMT_UTILにまとめました。

以下のコードを実行し、パッケージHMT_UTILを作成します。SQLスクリプトなどを使用すると良いでしょう。

ページCities and TemperaturesにOpen-Meteo.comよりデータを取得する処理を組み込みます。

ページ・デザイナにてページCities and Temperaturesを開きます。

データを取得する年を指定するページ・アイテムP2_YEARを作成します。

識別のタイプは選択リスト、ラベルはYear、検証の必須の値はオンとします。

LOVのタイプにSQL問合せを選択し、SQL問合せとして以下を記述します。現在の年から過去30年を表示の対象として選択できます。

追加値の表示、NULL値の表示はオフとします。


データを取得する月を指定するページ・アイテムP2_MONTHを作成します。

識別のタイプは選択リスト、ラベルはMonth、検証の必須の値はオンとします。ページ・アイテムP2_YEARの右隣に配置するため、レイアウトの新規行の開始をオフにします。

LOVのタイプにSQL問合せを選択し、SQL問合せとして以下を記述します。

追加値の表示、NULL値の表示はオフとします。


登録されているすべての都市を対象として、指定した1ヶ月間の日毎の最高気温のデータを、Open-MeteoのWeather APIより取得します。

ボタンLOADを作成します。

ラベルはLoad from Open-Meteo.comとします。外観のテンプレート・オプションのWidthとしてStretchを選択します。

動作のアクションはデフォルトのページの送信のまま変更しません。


プロセス・ビューを開き、ボタンLOADをクリックしたときに実行されるプロセスを作成します。

識別の名前はLoad from Open-Meteo.com、タイプはコードの実行を選択します。

ソースのPL/SQLコードとして以下を記述します。

サーバー側の条件のボタン押下時にLOADを指定します。


以上でデータのロードができるようになりました。

一旦ページを実行し、2023年8月のデータをロードします。

Yearに2023、Monthに8を選択し、ボタンLoad from Open-Meteo.comをクリックします。

下の対話グリッドに(上の対話グリッドで)選択した都市の最高気温が表示されると、データのロードは成功しています。


ホーム・ページに都市、年、月を選択してJET Picto Chartを表示する機能を実装します。

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

Breadcrumb BarにあるリージョンJET without Knockoutと、Bodyにあるリージョンページ・ナビゲーションを削除します。


Picto Chartを表示する都市を選択するページ・アイテムP1_CITYを作成します。

タイプとして選択リストを選び、ラベルはCityにします。

LOVのタイプとしてSQL問合せを選択し、SQL問合せとして以下を記述します。
select city_name d, id r from hmt_cities order by city_name asc
追加値の表示はオフ、NULL値の表示をオンにしてNULL表示値に- 都市を選択 -と記述します。


Picto Chartを表示する年を選択するページ・アイテムP1_YEARを作成します。

タイプとして選択リストを選び、ラベルはYearにします。ページ・アイテムP1_CITYの右隣に配置するため、レイアウトの新規行の開始をオフにします。

LOVのタイプとしてSQL問合せを選択し、SQL問合せとして以下を記述します。
select to_char(date_rec,'YYYY') d, to_char(date_rec,'YYYY') r
from hmt_city_temperatures where city_id = :P1_CITY
group by to_char(date_rec,'YYYY') order by to_char(date_rec,'YYYY') desc
追加値の表示はオフ、NULL値の表示をオンにしてNULL表示値に- 年を選択 -と記述します。

カスケードLOVの親アイテムにP1_CITYを指定し、親が必要をオンにします。


Picto Chartを表示する月を選択するページ・アイテムP1_MONTHを作成します。

タイプとして選択リストを選び、ラベルはMonthにします。ページ・アイテムP1_YEARの右隣に配置するため、レイアウトの新規行の開始をオフにします。

LOVのタイプとしてSQL問合せを選択し、SQL問合せとして以下を記述します。
select to_char(date_rec,'MM') d, to_char(date_rec,'MM') r
from hmt_city_temperatures 
where city_id = :P1_CITY and to_char(date_rec,'YYYY') = :P1_YEAR
group by to_char(date_rec,'MM') order by to_char(date_rec,'MM') asc
追加値の表示はオフ、NULL値の表示をオンにしてNULL表示値に- 月を選択 -と記述します。

カスケードLOVの親アイテムにP1_CITYとP1_YEARを指定し、親が必要をオンにします。


Picto Chartを表示するリージョンを作成します。

識別のタイトルはPicto Chart、タイプは静的コンテンツです。

ソースのHTMLコードに以下を記述します。属性itemsは動的アクションで設定するため、空の配列を与えています。



プロセス・ビューを開き、属性itemsに設定するデータを返すAjaxコールバックを作成します。

識別の名前はGET_DATA、タイプとしてコードを実行を選択します。

ソースのPL/SQLコードとして以下を記述します。


ページ・アイテムP1_MONTHに値が選択されたときに、動的アクションを実行してPicto Chartの描画を行います。

ページ・アイテムP1_MONTHに動的アクションを作成します。

識別の名前はPicto Chartの更新とします。タイミングのイベントはページ・アイテムのデフォルトである変更です。

クライアント側の条件のタイプとしてアイテムはnullではないを選択し、アイテムにP1_MONTHを選択します。実際はP1_CITY、P1_YEARもnullだとデータの取得に失敗しますが、親が必要をオンにしたカスケードLOVであるためP1_MONTHに値があれば、他のページ・アイテムにも必ず値があります。


TRUEアクションとしてJavaScriptコードの実行を選択します。

設定のコードとして以下を記述します。AjaxコールバックGET_DATAを呼び出して取得したJSON形式のデータ(ノードの色、ツールチップ、形状を含む配列)をPicto Chartのitemsに設定しています。

ページのロード時にもPicto Chartの表示を試みるように、実行の初期化時に実行をオンにします。この場合、動的アクションに設定したクライアント側の条件は適用されません。そのため、TRUEアクションのクライアント側の条件に、動的アクションと同じ条件を設定します。


ページ・プロパティのJavaScriptとCSSのセクションに、Oracle JETを組み込むために必要な設定を行います。

JavaScriptのファイルURLに以下を記述します。

[require jet]

JavaScriptのページ・ロード時に実行に以下を記述します。

require(["ojs/ojcore", "ojs/ojpictochart"], function(oj) {});

CSSのファイルURLに以下を記述します。

#JET_CSS_DIRECTORY#redwood/oj-redwood-notag-min.css

CSSのインラインに以下を記述します。ツールチップの表示に含まれる不要な文字列を隠すために使用します。

.oj-dvt-datatip-value {
    display: none;
}


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

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

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

完