2023年9月8日金曜日

JETチャートを各種レポートに組み込む

Oracle APEXに含まれていないOracle JETのチャートを、クラシック・レポート、対話モード・レポート、対話グリッドおよびカードに組み込んでみます。

JET Picto Chartをサンプルに使用します。APEXアプリケーションは、こちらの記事で作成したJET without Knockoutのアプリケーションに、それぞれのレポートのページを追加します。

最初にクラシック・レポートのページを作成します。


JET Picto Chartを組み込むページには、ページ・プロパティに以下を設定します。

JavaScriptのファイルURLとして以下を設定します。

[require jet]

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

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

CSSのファイルURLに以下を設定します。

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

CSSのインラインに以下を設定します。

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


クラシック・レポートのソースのSQL問合せとして、以下のSELECT文を記述します。列TEMPERATURESにJET Picto Chartのitemsに与えるJSON形式のデータが返されます。


列TEMPERATURESのタイプはプレーン・テキスト、列の書式のHTML式として以下を記述します。


クラシック・レポートの属性を開き、ページ区切りの部分ページ・リフレッシュをオフにします。クラシック・レポートにJETチャートを表示する場合、この設定は必須です。


以上で、クラシック・レポートにJETチャートを表示することができます。

続いて対話モード・レポートのページを作成します。


ソースのSQL問合せはクラシック・レポートと同じです。


列TEMPERATURESのタイプと列の書式のHTML式も、クラシック・レポートと同じです。


対話モード・レポートでは、属性のパフォーマンスの遅延ロードをオフにします。JETチャートの描画は非同期に行われるため、遅延ロードがオンだとJETチャートが表示されません。


しかし、対話モード・レポートで遅延ロードをオフにすることで抑止される処理はレポートの初回表示時のみです。ページ送りによるレポートの描画は必ず遅延ロードが適用されます。結果として、ページ送りをしたときはJETチャートが表示されません。

対話モード・レポートのリフレッシュ後に、ページのリロードを実行することが、ワークアラウンドのひとつです。

対話モード・レポートに動的アクションを作成します。

タイミングのイベントにリフレッシュ後を選択します。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定のコードに以下を記述します。

location.reload(false);


以上で、対話モード・レポートでJETチャートが表示されるようになります。

JETチャートが非同期で表示される点については、そのJETチャートをラップするWebコンポーネントを作成することにより対応可能です。Oracle CorporationにてAPEXを開発しているRonny Weißさんがドイツで開かれたカンファレンスAPEXConnect 2023にて、その実装を紹介されています。この実装については、また別の機会にまとめたいと思います。

対話グリッドではクラシック・レポートや対話モード・レポートとは異なり、遅延ロードの設定に関わらずJETチャートは表示されます。


対話グリッドのソースのSQL問合せは以下になります。

チャートのデータとなる列とチャートを表示する列を別にする必要があるため、これまでのレポートのソースSQLに列CHARTを追加しています。


列TEMPERATURESの属性のタイプは非表示に変更します。


列CHARTの識別のタイプをHTML式に変更し、ヘッダーは(非表示にした列TEMPERATURESの代わりに)Temperatures、設定のHTML式として以下を記述します。

置換文字列として#TEMPERATURES#の代わりに&TEMPERATURES.を使います。


以上で、対話グリッドでJETチャートが表示されるようになります。

最後に、カード・リージョンでJETチャートを表示します。


カード・リージョンのソースのSQL問合せは、クラシック・レポートおよび対話モード・レポートと同じです。


カードの属性を開きます。

タイトルの列としてCITY_NAME、サブタイトルの列としてMONTHを選択します。本体の拡張フォーマットをオンに変更し、HTML式に対話グリッドで使用した式を記述します。


カード・リージョンも対話グリッドと同様に、遅延ロードの設定に関わらずJETチャートは表示されます。

各種レポートやカード・リージョンへのJETチャートの組み込みについての説明は以上です。

今回作成したレポートやカードのページは、APEXアプリケーションJET with Knockoutに追加しています。
https://github.com/ujnak/apexapps/blob/master/exports/jet-without-knockout.zip

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

完

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のアプリケーション作成の参考になれば幸いです。

完

2023年9月5日火曜日

東京の最高気温を表示するJET Picto Chartをoj-bind-for-eachとArrayTreeDataProviderを使うように変更する

以前に東京の最高気温をJET Picto Chartで表示するアプリケーションを作成しました。こちらの記事ですが、Oracle APEXの動的コンテンツとして複数月の表示を実装しています。

この実装をOracle JETのoj-bind-for-eachとArrayTreeDataProviderを使って、静的コンテンツとして実装し直します。

リージョンTemperaturesのタイプを静的コンテンツに変更し、ソースのHTMLコードに以下を記述します。



ページ・プロパティのJavaScriptのページ・ロード時に実行を以下に書き換えます。


AjaxコールバックのGET_DATAのソースのPL/SQLコードを以下に書き換えます。


以上で作業は完了です。動作自体は、動的コンテンツで実装したものと同じです。

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

Oracle JETについては、英語のOracle JET Cookbookくらいしか説明が見当たらないため、APEXのアプリケーションに組み込むのは骨の折れる作業です。これまでの記事で、Oracle APEXでOracle JETを使うことが少しでも容易になれば幸いです。

完

Oracle JETのoj-bind-for-each要素とArrayTreeDataProviderを使ってネストされた表示を実装する

Oracle JET CookbookにあるForEach Binding (Nested)をOracle APEXで実装してみます。

カテゴリが複数あり、それぞれのカテゴリに複数の製品が含まれています。カテゴリ単位の繰り返しは、ArraryTreeDataProviderをデータ・プロバイダとしたoj-bind-for-each要素で実装します。カテゴリに含まれている製品は、ArrayTreeDataProviderのメソッドgetChildDataProviderを呼び出して取得したデータ・プロバイダを使って表示します。

作成されたアプリケーションは以下のように動作します。


Oracle JET CookbookにあるForEach Binding (Nested)のページは以下です。


以下より実装手順を説明します。

以下のクイックSQLのモデルより、表AG_CATEGORIESとAG_PRODUCTSを作成します。Oracle JET Cookbookでは、produce.jsonとしてサンプル・データが提供されていますが、少量なので手入力することにして、データ・ロードの実装は省きます。
# prefix: ag
categories
    name vc80 /nn
    products
        name vc80 /nn
SQLの生成、SQLスクリプトを保存、レビューおよび実行を順次実施します。表の作成までを行い、アプリケーションは作成しません。


アプリケーション作成ウィザードを起動します。アプリケーションの名前はJET Bind For Each (Nested)とします。

ホーム・ページの編集をクリックし削除を実行したのち、ページの追加を行います


追加するページとしてマスター・ディテールを選択します。


ページ名はFruits and Vegetables、形式は積上げを選択します。表(マスター表)としてAG_CATEGORIES、ディテール表としてAG_PRODUCTSを選択します。

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


その他の設定はせず、アプリケーションの作成を実行します。


アプリケーションが作成されます。今回はすべての機能をページFruits and Vegetablesに実装します。


ページ・デザイナでページFruits and Vegetablesを開きます。

Breadcrumb BarにあるJET Bind For Each (Nested)を削除します。JET Bind For Each (Nested)の上でコンテキスト・メニューを表示させ、削除を実行します。


積み上げ形式のマスター・ディテール編集画面は、マスター表AG_CATEGORIESとディテール表AG_PRODUCTSをデータ・ソースとした2つの対話グリッドから作られます。これらの対話グリッドのデータの整合性を保つため、対話グリッドにある保存のボタンを非表示にし、代わりに一度のクリックで表AG_CATEGORIESとAG_PRODUCTSの両方の保存を行なうボタンSAVE(保存)が作成されます。

このボタンSAVEの動作のアクションはページの送信です。ページの送信ではHTTPのPOSTリクエストがサーバーに発行され、ページの再ロードが行われます。Oracle JETで実装したリージョンはページを再ロードせずに更新されるようにしたいので、マスター・ディテールでの保存を少々変更します。

対話グリッドAg Productsを選択し、プロパティ・エディタの属性タブを開きます。

ツールバーのコントロールの保存ボタンにチェックを入れます。


プロセス・ビューを開きプロセスAG_PRODUCTS - 対話グリッド・データの保存を選択します。

サーバー側の条件のボタン押下時をSAVEから- 選択 -(つまり無指定)に戻します。


以上で表AG_PRODUCTSについては、ページの送信をせずにデータ操作ができるようになりました。

アプリケーションを実行し、サンプルのデータを入力します。必ずしもproduce.jsonと同じデータでなくても、アプリケーションの動作は確認できます。


Oracle JETによる描画を行なうリージョンを作成します。

識別のタイトルはFor Each (Nested)、タイプとして静的コンテンツを選択します。ソースのHTMLコードに以下を記述します。Oracle JET Cookbookのdemo.htmlとほぼ同じ内容です。

余計な装飾を除くため、外観のテンプレートとしてBlank with Attributes (No Grid)を選択します。


リージョンの描画に使うデータは、Ajaxコールバックを呼び出して取得します。

プロセス・ビューを開き、Ajaxコールバックにプロセスを作成します。

識別の名前はGET_DATA、タイプとしてコードを実行を選択します。ソースのPL/SQLコードとして以下を記述します。



以下のようなJSONのデータが返されます。

Oracle JET Cookbookのサンプルには属性childrenが含まれています。メソッドgetChildDataProviderが扱う属性のデフォルトがchildrenであるため、特別な指定が不要になります。今回はArrayTreeDataProviderのインスタンス作成時にchildrenAttributeとして属性productsを明示しているため、productsが含まれています。
[
  {
    "name": "Fruit",
    "products": [
      {
        "name": "Orange"
      },
      {
        "name": "Apple"
      },
      {
        "name": "Banana"
      }
    ]
  },
  {
    "name": "Vegetable",
    "products": [
      {
        "name": "Celery"
      },
      {
        "name": "Spinach"
      },
      {
        "name": "Corn"
      }
    ]
  }
]
レンダリング・ツリーに戻ります。

ページ・プロパティのJavaScriptのファイルURLに以下を記述します。

[require jet]

ファンクションおよびグローバル変数の宣言に以下を記述します。

var simple;

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



対話グリッドAg Productsでデータを変更し保存したときに、JETの表示を更新します。

対話グリッドAg Productsに動的アクションを作成します。

識別の名前はonSAVE Productsとします。タイミングのイベントとして保存[対話グリッド]を選択します。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定のコードに以下の1行を記述します。

apex.actions.invoke("update-nested");


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

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

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

完