ラベル JavaScript の投稿を表示しています。 すべての投稿を表示
ラベル JavaScript の投稿を表示しています。 すべての投稿を表示

2025年6月25日水曜日

Oracle Database 23ai MLEにJIMPをロードし画像をグレースケールに変換する

Oracle CorporationのMartin Backさんが、setTimeout()のpolyfillを作成し、画像処理ライブラリのJIMPをMLEにロードして、画像をグレースケールに変換する手順をブログ記事で公開されています。

Multilingual Engine: polyfill timeouts and intervals

JIMPについては以前にMLEにロードを試みたことがありますが、Martin Backさんの記事にあるようにMLEにはsetTimeout()やfsモジュールが無いため諦めました。

今回はMartin Backさんの記事に沿って、MLEにJIMPとsetTimeout()のpolyfillをロードし、それを使用して画像をグレースケールに変換するAPEXアプリケーションを作成します。JIMPのロードやpolyfillの説明については、上記に掲載したMartin Backさんのブログ記事を参照してください。

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


以下より、実施した作業を記述します。

APEXワークスペースに紐づくスキーマで、MLEモジュールのロードやJavaScriptを実行します。そのため、スキーマにJavaScriptを実行する権限を与えます。これらのスクリプトはDBA権限を持つユーザー(Autonomous DatabaseであればADMIN、オンプレミスであればSYS AS SYSDBA)で実行します。
grant execute on javascript to [APEXワークスペース・スキーマ];
grant execute dynamic mle to [APEXワークスペース・スキーマ];
grant create mle to [APEXワークスペース・スキーマ];
続いて、作業に使用するディレクトリを作成し、プロジェクトを初期化します。ディレクトリ名はdemo-jimpとします。今回の作業で使用したnpmのバージョンは11.3.0です。

npm -v
mkdir demo-jimp
cd demo-jimp
npm init -y


% npm -v         

11.3.0

% mkdir demo-jimp

% cd demo-jimp

demo-jimp % npm init -y

Wrote to /Users/**********/Documents/demo-jimp/package.json:


{

  "name": "demo-jimp",

  "version": "1.0.0",

  "description": "",

  "main": "index.js",

  "scripts": {

    "test": "echo \"Error: no test specified\" && exit 1"

  },

  "keywords": [],

  "author": "",

  "license": "ISC",

  "type": "commonjs"

}




demo-jimp % 


jimpをインストールします。

npm install jimp

demo-jimp % npm install jimp


added 70 packages, and audited 71 packages in 6s


10 packages are looking for funding

  run `npm fund` for details


found 0 vulnerabilities

demo-jimp % 


esbuildをインストールします。demo-mle.jsに画像をグレースケールに変換するファンクションimage2Greyscaleを記述し、esbuildを実行してdemo-mle.jsにJIMPをバンドルします。

npm install --save-exact --save-dev esbuild

demo-jimp % npm install --save-exact --save-dev esbuild



added 26 packages, and audited 97 packages in 2s


10 packages are looking for funding

  run `npm fund` for details


found 0 vulnerabilities

demo-jimp % 


Martin BackさんのGitHubのリポジトリよりdemo-mle.jspolyfill.jsをダウンロードし、src/javascript以下に配置します。

curl -OL https://raw.githubusercontent.com/martincarstenbach/javascript-blogposts/refs/heads/main/timeout-polyfill/src/javascript/demo-mle.js
curl -OL https://raw.githubusercontent.com/martincarstenbach/javascript-blogposts/refs/heads/main/timeout-polyfill/src/javascript/polyfill.js
mkdir -p src/javascript
mv demo-mle.js polyfill.js src/javascript
ls src/javascript

demo-jimp % curl -OL https://raw.githubusercontent.com/martincarstenbach/javascript-blogposts/refs/heads/main/timeout-polyfill/src/javascript/demo-mle.js

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

100  1023  100  1023    0     0   4903      0 --:--:-- --:--:-- --:--:--  4918

demo-jimp % curl -OL https://raw.githubusercontent.com/martincarstenbach/javascript-blogposts/refs/heads/main/timeout-polyfill/src/javascript/polyfill.js

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

100  2996  100  2996    0     0   7682      0 --:--:-- --:--:-- --:--:--  7701

demo-jimp % mkdir -p src/javascript

demo-jimp % mv demo-mle.js polyfill.js src/javascript

demo-jimp % ls src/javascript

demo-mle.js polyfill.js

demo-jimp % 


esbuildを実行しダウンロードしたdemo-mle.jsにJIMPをバンドルします。

npx esbuild src/javascript/demo-mle.js --bundle --outfile=dist/bundle.js --format=esm

demo-jimp % npx esbuild src/javascript/demo-mle.js --bundle --outfile=dist/bundle.js --format=esm


  dist/bundle.js  1008.8kb


Done in 44ms

demo-jimp % 


SQLclでAPEXのワークスペース・スキーマに接続し、MLEモジュールを作成します。

以下は、ローカルで実行しているOracle Database 23ai Freeに、ユーザーWKSP_APEXDEVで接続して作業した例になります。

sql wksp_apexdev/[パスワード]@localhost/freepdb1

demo-jimp % sql wksp_apexdev/********@localhost/freepdb1



SQLcl: 水 6月 25 12:31:27 2025のリリース25.1 Production


Copyright (c) 1982, 2025, Oracle.  All rights reserved.


接続先:

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free

Version 23.8.0.25.04


SQL> 


demo-mle.jsにJIMPをバンドルしたdist/bundle.jsを、MLEモジュールJIMP_TEST_MODULEとして作成します。

mle create-module -replace -module-name jimp_test_module -filename dist/bundle.js

SQL> mle create-module -replace -module-name jimp_test_module -filename dist/bundle.js

MLEモジュールjimp_test_moduleが作成されました

SQL> 


setTimeout()のpolyfillを、MLEモジュールTIMERS_POLYFILLとして作成します。

mle create-module -replace -module-name timers_polyfill -filename src/javascript/polyfill.js

SQL> mle create-module -replace -module-name timers_polyfill -filename src/javascript/polyfill.js

MLEモジュールtimers_polyfillが作成されました

SQL> 


以上で、demo-mle.jsに含まれているファンクションimage2Greyscaleを、データベース内で呼び出せるようになりました。

画像をグレースケールに変換するAPEXアプリケーションを作成します。

変換する画像および変換後の画像を保存する表EBAJ_DEMO_IMAGESを作成します。

SQLワークショップSQLコマンドで、以下のDDLを実行します。
create table ebaj_demo_images (
    id              number generated by default on null as identity
                    constraint ebaj_demo_images_id_pk primary key,
    filename        varchar2(80 char),
    mime_type       varchar2(80 char),
    operation       varchar2(80 char),
    source_image    blob,
    target_image    blob
);

作成したMLEモジュールJIMP_TEST_MODULEおよびTIMERS_POLYFILLを含むMLE環境としてJIMP_ENVを作成します。以下のCREATE MLE ENV文を実行します。
create mle env jimp_env imports (
    'jimp' module jimp_test_module,
    'polyfill' module timers_polyfill
);

使用するデータベース・オブジェクトの準備ができました。

APEXアプリケーションを作成します。

アプリケーション・ビルダーよりアプリケーション作成ウィザードを開きます。アプリケーションの名前demo jimpとします。デフォルトで追加されているホーム・ページ削除します。


ホーム・ページを削除したのちページの追加をクリックし、代わりとなるフォームのページを追加します。

ページの追加をクリックしたときに開く画面でフォームを選択します。


ページ名グレースケール変換とします。に先ほど作成したEBAJ_DEMO_IMAGESを選択します。

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


表EBAJ_DEMO_IMAGESをソースとしたフォームのページが追加されました。

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


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

画像のアップロードやグレースケールへの変換処理は、ページ番号1のグレースケール変換に実装します。


APEXアプリケーションからMLEモジュールを参照できるように、APEXアプリケーションにMLE環境を紐づけます。

アプリケーション定義セキュリティデータベース・セッションのセクションに含まれるMLE環境に、先ほど作成したMLE環境JIMP_ENVを設定します。

変更の適用をクリックします。


ページ・デザイナでページ、グレースケール変換を開きます。


主キー値を保持するページ・アイテムP1_IDセッション・ステートストレージセッションごと(永続)に変更します。

新規に画像がアップロードされる場合、ページ・アイテムP1_IDには空白(null)が渡されます。ページ送信時に実行されるプロセスでは、画像を含む行をデータベースに挿入する際に、INSERT INTO ... RETURNING id INTO :P1_ID という構文を用いて主キーの値を取得し、それをP1_IDに設定します。

しかし、セッション・ステートストレージリクエストごと(メモリのみ)になっていると、プロセスの処理が完了してフォームのページが再描画される際に、P1_IDの値がセッション・ステートに保存されておらず、結果としてP1_IDは空白のままになります。このため、直前にアップロードされた画像をP1_IDの値を用いて参照できません。

通常、フォームはレポート画面から開かれ、フォームを閉じるとレポート画面に戻る構成であるため、このような問題は発生しません。

しかし、今回のアプリケーションでは、フォームを閉じた後に再び同じフォームに戻る動作となっているため、P1_IDの値がページ遷移を超えて維持される必要があります。この対応として、セッション・ステートストレージセッションごと(永続)に変更します。


ページ・アイテムP1_FILENAMEP1_OPERATIONおよびP1_TARGET_IMAGEは非表示に変更します。


今回のAPEXアプリケーションでは使用しない属性ですが、表EBAJ_DEMO_IMAGESの列OPERATIONがつねにgreyscacleになるようにします。

ページ・アイテムP1_OPERATION計算を作成します。

実行ポイントとして送信後を選択し、計算タイプ静的値静的値としてgreyscaleを設定します。これでページ送信時のP1_OPERATIONの値がgreyscaleになります。


アップロードするファイルを選択するページ・アイテムP1_SOURCE_IMAGEタイプファイルのアップロードからイメージ・アップロードに変更します。

ストレージMIMEタイプ列MIME_TYPEファイル名列FILENAMEを設定します。


リージョンEbaj Demo Image外観テンプレートを装飾のないBlank with Attributesに変更します。


テンプレートが変更されたので、ボタンが配置されていたスロットが無くなっています。ボタンについては、Bodyの直下に横並びになるように移動します。

ボタンCANCELは削除し、CREATESAVEDELETEの順に並べます。

すべてのボタンの外観ホットオフにし、テンプレート・オプションWidthStretchに設定します。


ボタンDELETEレイアウト新規行の開始オフにし、ボタンSAVEの右隣に表示させます。


グレースケール変換前の画像を表示するページ・アイテムとしてP1_SOURCEを作成します。

タイプイメージの表示ラベルSource設定基準SQL文で戻されたBLOB列を選択し、SQL文として以下を記述します。
select source_image, null, filename, mime_type
from ebaj_demo_images
where id = :P1_ID

同様にグレースケール変換後の画像を表示するページ・アイテムとしてP1_TARGETを作成します。

ラベルTargetSQL文は以下になります。変換前の画像の右隣に表示させるため、レイアウト新規行の開始オフにします。
select target_image, null, filename, mime_type
from ebaj_demo_images
where id = :P1_ID

以上でユーザー・インターフェースについては完成です。

本記事の本題であるJIMPによるグレースケール処理を実装します。

プロセスを作成し、プロセス・フォームグレースケール変換の下に配置します。

識別名前greyscaleタイプコードを実行とします。ソース言語JavaScript(MLE)を選択し、JavaScriptコードとして以下を記述します。


サーバー側の条件タイプリクエストは値に含まれるを選択し、CREATE SAVEを入力します。ボタンCREATEおよびSAVEをクリックしたときに、プロセスgreyscaleが実行されます。


ボタンDELETEをクリックするとフォームに表示されている行(P1_IDが主キー値である行)が削除されます。フォームに設定されている削除済みの行のデータをリセットするプロセスを作成します。

識別名前resetタイプセッション・ステートのクリアを選択し、設定タイプとして現在のページのすべてのアイテムのクリアを選択します。

サーバー側の条件ボタン押下時DELETEです。


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

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

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

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

2025年5月20日火曜日

対話グリッドの新規行を任意の処理で初期化する

対話グリッドで行の追加を実行する際に、ブラウザ上のJavaScriptで計算した値を列の初期値として設定します。対話グリッドののプロパティとしてデフォルトがありますが、この値はサーバー側で設定するため、データベースに保存されている値か、それを元にした値に限定されます。

今回はサンプル・データセットのEMP/DEPTに含まれる表EMPをデータ・ソースとした対話グリッドを作成し、対話グリッド上で選択した従業員の給与の最大値を、新規行の給与として設定します。

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


以下より実装について紹介します。

空のAPEXアプリケーションを作成し、ホーム・ページに対話グリッドのリージョンを作成します。

ソース表名EMPを指定し、ROWIDを含めるオンにします。表EMPの主キー列はEMPNOですが、対話グリッドでは主キーは自動採番されることを想定しているため、主キー列として列EMPNOは選択できません。


ROWIDを含めるオンにすることにより、主キー列としてROWIDが対話グリッドに追加されます。

EMPNOを選択し、ソース問合せのみ主キーの両方をオフにします。また、列EMPNOは実際には主キーなので、検証必須の値オンにします。


対話グリッドで行の追加をできるようにするため、対話グリッドの属性を開き、編集有効オンにします。


選択した行の主キーの値を保持するページ・アイテムとしてP1_SELECTIONを作成します。

属性タイプ非表示が適切ですが、今回はデバッグしやすいようにタイプをテキスト・フィールドにしています。


対話グリッド上で選択した行の主キー値がページ・アイテムP1_SELECTIONに設定されるように、初期化JavaScriptファンクションに以下を記述し、selectionStateItemとしてP1_SELECTIONを割り当てます。
function( config ) {
    config.defaultGridViewOptions = {
        selectionStateItem: "P1_SELECTION"
    };
    return config;
}

以上で対話グリッドで選択した行のROWIDが、: (コロン)区切りでページ・アイテムP1_SELECTIONに設定されます。


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

タイミングイベントとして行の初期化[対話グリッド]を選択します。


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


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

対話グリッドで行の追加をクリックすると、デフォルトではすべての列が空白になります。サーバー側でデフォルト値を設定できますが、クライアント側のJavaScriptのコーディングにより初期値を設定できると、さらに新規行の入力の手間が減るでしょう。

今回の記事は以上になります。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/set-initial-value-for-new-row-on-interactive-grid.zip

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

2025年5月2日金曜日

チャートのタイトルを動的に変更する

Oracle APEXのチャートのタイトルを動的に変更する方法について調べてみました。設定を確認するために、以下のように動作するAPEXアプリケーションを作成しています。

表EMPをソースとしたバー・チャートを作成しています。選択リストのページ・アイテムで、バー・チャートの表示条件となるJOBを選択し、チャートを更新します。同時にチャートのタイトルを選択したJOBに変更しています。


このアプリケーションのエクスポートは以下に置いてあります。
https://github.com/ujnak/apexapps/blob/master/exports/update-chart-title.zip

チャートのタイトルの変更自体は簡単ですが、その実装方法はOracle APEXの公式なドキュメントに記載されていません。ただし、ChatGPTに聞くと大体教えてくれます。

ChatGPTに「Oracle APEXでJavaScriptでchart widgetを取得する手順を教えて」と聞いたところ、以下のコードを教えてくれました。
var chartWidget = apex.region("myChart").widget();
最後にChatGPTから「何を操作したいか(例:データ更新、再描画、ハイライトなど)も教えていただけますか?」と聞かれたので、「ChartのTitleを更新したい」と伝えたところ、以下のコードを教えてくれました。
var chart = apex.region("myChart").widget();
var currentOptions = chart.ojChart("option");

// タイトルを変更する
chart.ojChart("option", "title.text", "新しいタイトル");

// 必要に応じて再描画(通常は自動で反映されます)
Oracle JETのチャートのリファレンスは以下ですが、Oracle APEXのojChartインターフェース経由でどう使えるのかについては説明されていません。

カード・リージョン、ファセット・リージョン、対話グリッド、マップ・リージョンおよびテンプレート・コンポーネントといったJavaScriptの部分をOracle APEXの開発チームが実装しているコンポーネントについては、APIリファレンスにMethodsの説明があり、それを参照することによりJavaScriptで操作できます。

チャート・リージョンはOracle JETを使用しているため、Oracle APEXの開発チームにオーナーシップがありません。そのため、チャート・リージョンについてはページ・デザイナによる宣言的な設定がサポート範囲で、コードによる操作はサポートされません。そもそも、Oracle JETはOracle Corporationが保守しているオープンソース・プロジェクトであり、Oracle JET自体に一般的な製品サポートは提供されていません。

そういう意味ではコードで操作する場合、チャート・コンポーネントは他のオープンソースのフロントエンドのコンポーネントと同じ扱いになります。メンテナンス性を優先する場合は、使用は避けた方が良い、という点でも同じです。

以下より、今回作成したアプリケーションの実装を紹介します。

アプリケーションはすべてホーム・ページに実装しています。

チャートの表示条件となるページ・アイテムP1_JOB選択リストとして作成しています。LOVタイプSQL問合せを選択し、SQL問合せとして以下を記述しています。LOV表示値戻り値ともに表EMPの列JOBに含まれる個別値となります。

select job d, job r from emp group by job

P1_JOBで選択された戻り値をチャートのタイトルとして設定します。

ページ・アイテムが変更されたときに、チャートをリフレッシュする動的アクションを作成します。


チャート・リージョン識別名前給与として作成しています。

ソース表名EMPを指定し、WHERE句:P1_JOB is null or JOB = :P1_JOBを記述しています。送信するページ・アイテムP1_JOBを設定します。この設定により、リージョンのリフレッシュを実行した際に、P1_JOBが空であれば全従業員の給与がバーチャートに表示され、P1_JOBに値があれば、そのJOBを持つ従業員のみがバー・チャートに表示されます。

チャートをJavaScriptから操作するために、詳細静的IDmyChartを設定します。


チャートの属性タイトルとして&P1_JOB.を設定します。これはチャート、つまりOracle JETのチャート・コンポーネントが表示するタイトルになります。


基本的な設定は以上になります。

これから、チャートのタイトルを描画する処理について考えていきます。

最初にページがロードされると、ページに含まれる置換文字列&P1_JOB.が、ページ・アイテムP1_JOBの値で置き換えられます。

ページ・アイテムP1_JOBセッション・ステートストレージセッションごと(永続)としているため、ページ・ロード時にセッション・ステートに保存されている値が反映されます。これがリクエストごと(メモリーのみ)であれば、(デフォルト計算が未設定であれば)つねにページ・アイテムの値は空白になります。

ページ・アイテムP1_JOBの選択を変更したときに、動的アクションでチャートをリフレッシュしています。リージョンの送信するページ・アイテムP1_JOBが設定されていて、そしてセッション・ステートストレージセッションごと(永続)であるため、送信された値がセッション・ステートに保存されます。結果として、ページ・ロード時のP1_JOBの値は、直近のチャートのリフレッシュ時に送信されたP1_JOBの値になります。


置換文字列&P1_JOB.の置き換えはページ・ロード時にのみ行われます。チャートの属性タイトルに設定した&P1_JOB.は、ページ・ロード後はページ・アイテムP1_JOBの値が変わっても、その値が反映されることはありません。


次にチャート・リージョンの属性詳細初期化JavaScriptファンクションが実行されます。初期化JavaScriptファンクションでは、属性に設定項目が存在しない設定もできます。

今回のアプリケーションでは以下のように記述しています。ページ・デザイナで設定した値が引数optionsとして渡され、必要な変更をoptionsに適用して戻します。
function( options ) {
    apex.debug.info(options);
    const job = apex.items.P1_JOB.value ? apex.items.P1_JOB.value : "すべてのJOB";
    // const job = apex.items.P1_JOB.value;
    if ( options.title ) {
        // titleが初期化済みであれば、textのみを更新する。
        options.title.text = job;
        apex.debug.info("INIT: title.text is replaced, ", job);
    }
    else
    {
        // そうでない場合は中央揃えも含めてtitleを初期化する
        options.title = {
            "halign": "center",
            "text": job
        };
        apex.debug.info("INIT: title.text is created, ", job);
    };
    return options;
}
optionsにどのような値が設定されているか、または、どのような値が設定可能かについては、ドキュメント化されていないため、JavaScriptコンソールにoptionsを印刷するか、または、チャート・ウィジェットからoptionを取り出して確認します。

apex.region("myChart").widget().ojChart("option")

例えば、JavaScriptコンソールなどから実行します。


初期化JavaScriptファンクションに渡されるoptionsは、ページ・デザイナで設定している内容に限定されます。リージョンの属性タイトルに値がない場合は、title自体がnullになるため、options.title.textを設定することはできません。そのため初期化JavaScriptファンクションでは、options.titleに設定がない場合は、水平アラインメント(halign)の設定も含めてoptions.titleを設定しています。

初期化JavaScriptファンクションはページ・ロード時に実行されます。リージョンのリフレッシュ時には実行されません。そのため、ページ・アイテムP1_JOBの値が変更されたときは、動的アクションとして同等の処理を実行する必要があります。動的アクションには引数optionsが渡されないため、apex.region("myChart").widget().ojChart()を呼び出して設定を更新します。

チャート・リージョンに必ずタイトルが設定されている場合は、以下のコードでタイトルだけを変更できます。
const job = this.triggeringElement.value ? this.triggeringElement.value : "すべてのJOB";
// const job = this.triggeringElement.value;
apex.region("myChart").widget().ojChart(
    "option",
    "title.text",
    job
);
タイトルが未設定のときがあれば、以下のコードでタイトルを設定できます。
const job = this.triggeringElement.value ? this.triggeringElement.value : "すべてのJOB";
// const job = this.triggeringElement.value;
apex.region("myChart").widget().ojChart(
    "option",
    "title",
    {
        "halign": "center",
        "text": job
    }
);
同じ変更ですが、引数optionを除いて、以下のように呼び出せるようです。
const job = this.triggeringElement.value ? this.triggeringElement.value : "すべてのJOB";
// const job = this.triggeringElement.value;
apex.region("myChart").widget().ojChart(
    { 
        "title": {
            "halign": "center",
            "text": job
        }
    }
);
今回の記事は以上になります。

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