2023年1月26日木曜日

カスタム属性data-parent-elementを使ったインライン・ポップアップの実装

 Philipp Hartenfellerさんが、彼のブログに以下の記事を載せています。

Oracle APEX anchored inline popups for data input

カスタム属性のdata-parent-elementを指定することにより、開いたボタンにインライン・ポップアップをくっつけることができます。

自分でも実装のサンプルを作ってみました。


data-parent-elementを指定してない場合、インライン・ポップアップは以下のように画面の真ん中に表示されます。


インライン・ポップアップの設定が出来ていれば、ボタンに静的IDを付加した後、インライン・ポップアップのリージョンのカスタム属性を設定するだけです。

data-parent-element="#ボタンの静的ID"


似たような設定を扱ったことがあったなと思ったのですが、ありました。カスタム属性data-menuを使った実装です。


data-parent-elementはユニバーサル・テーマのリファレンスに説明がありませんが、Philipp Hartnenfellerさんの記事によるとAPEX 19.2でも動作した、とのことです。

以下よりサンプル・アプリケーションの実装について紹介します。

サンプル・データセットのEMP/DEPTがインストール済み、空のAPEXアプリケーションを作成したところから作業を始めます。デフォルトで作成されるホーム・ページにインライン・ポップアップを実装します。

役職一覧となるLOVを作成します。共有コンポーネントを開きます。


LOVを開きます。


作成済みのLOVが一覧されます。作成をクリックします。


LOVの作成は最初からを選択します。

次へ進みます。


作成するLOVの名前をLIST_OF_JOBSとします。タイプはDynamicです。

次へ進みます。


データ・ソースはローカル・データベース、ソース・タイプとしてSQL Queryを選択します。SQL SELECT文を入力として、以下を記述します。表EMPの列JOBの個別の値を求めます。

select distinct job from emp


戻り列、表示列ともにJOBを選択します。

作成をクリックします。


共有コンポーネントのLOVとしてLIST_OF_JOBSが作成されました。


続けて、通常のインライン・ポップアップを実装します。

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

役職の値を保持するページ・アイテムP1_JOBを作成します。タイプに選択リストを選びます。ラベルは役職とします。

LOVのタイプは共有コンポーネント、LOVとして先ほど作成したLIST_OF_LOBSを選択します。追加値の表示はOFFにします。

このページ・アイテムP1_JOBの値は、インライン・ポップアップを開いて設定します。そのため、読取り専用は常時にします。


このページ・アイテムはタイプが選択リストなので、わざわざインライン・ポップアップを実装する必要はありません。あくまで実装のサンプルです。

インライン・ポップアップを開くボタンSELECT_JOBを作成します。ラベルは役職の選択とします。

動作のアクションとして動的アクションで定義を選択します。


役職を選択するインライン・ポップアップを作成します。

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

識別のタイトルを役職の選択、タイプは静的コンテンツとします。レイアウトの位置としてDialogs, Drawers and Popupsを選択します。外観のテンプレートとしてInline Popupを選択し、その後、テンプレート・オプションを開きます。


テンプレート・オプションの共通の一般のAuto Height、Remove Page Overlay、Display Popup Calloutにチェックを入れ、SizeをNoneに変更します。


インライン・ポップアップのリージョンに、役職の選択を行なうページ・アイテムを作成します。

識別の名前はP1_JOB_SELECT、タイプとしてラジオ・グループを選択します。ラベルは役職一覧とします。

LOVのタイプは共有コンポーネント、LOVとしてLIST_OF_JOBSを選択します。追加値の表示はOFF、NULL値の表示はONで、NULL表示値は未選択とします。


以上で画面として表示されるコンポーネントは作成できました。

続いてインライン・ポップアップを開く動的アクションと、それを閉じる動的アクションを作成します。

開く方の動的アクションは、ボタンSELECT_JOBに作成します。

作成した動的アクションの識別の名前はonClick SELECT_JOBとします。タイミングはデフォルトでイベントがクリック、選択タイプがボタン、ボタンがSELECT_JOBとなります。


2つのTRUEアクションを作成します。

最初にページ・アイテムP1_JOBの値を、インライン・ポップアップのページ・アイテムP1_JOB_SELECTに初期値として設定します。

識別の名前はP1_JOB -> P1_JOB_SELECT、アクションとして値の設定を選択します。

設定のタイプの設定にJavaScript Expressionを選択し、JavaScript式として以下を記述します。

apex.items.P1_JOB.getValue()

影響を受ける要素の選択タイプにアイテムを選び、アイテムとしてP1_JOB_SELECTを指定します。以上で、設定で取得した値が影響を受ける要素のアイテムに設定されます。

実行の初期化時に実行はOFFにします。


インライン・ポップアップを開くTRUEアクションを作成します。

識別の名前はポップアップを開く、アクションとしてリージョンを開くを選択します。

影響を受ける要素の選択タイプとしてリージョンを選択し、リージョンに役職の選択を選びます。このリージョンが開かれる対象になります。

実行の初期化時に実行はOFFです。


ポップアップを閉じる動的アクションは、ページ・アイテムP1_JOB_SELECTに作成します。

識別の名前はonChange P1_JOB_SELECTとします。タイミングはデフォルトで、イベントが変更、選択タイプがアイテム、アイテムがP1_JOB_SELECTになります。


TRUEアクションはポップアップを開いたときとは反対に、ページ・アイテムP1_JOB_SELECTの値をP1_JOBに設定します。

識別の名前はP1_JOB_SELECT -> P1_JOBとします。設定のJavaScript式は以下に代わり、影響を受ける要素のアイテムはP1_JOBになります。

apex.items.P1_JOB_SELECT.getValue()


次のTRUEアクションで、インライン・ポップアップを閉じます。

識別の名前をポップアップを閉じる、アクションとしてリージョンを閉じるを選択します。


以上で、通常のインライン・ポップアップが実装できました。

動作は以下のようになります。


これからカスタム属性data-parent-elementを設定します。

ボタンSELECT_JOBに静的IDを設定します。

静的IDはSELECT_JOBとします。


インライン・ポップアップのリージョンの詳細のカスタム属性として、以下を設定します。

data-parent-element="#SELECT_JOB"


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

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

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

完

ページ・プロパティのページ・ロード時に実行について

 すこし気になったので、ページ・ロード時に呼ばれるJavaScriptの順番を確認してみました。


JavaScript:ファンクションおよびグローバル変数の宣言


生成されるページの先頭部分に<script type="text/javascript">として挿入されます。一番最初に実行されます。



JavaScript:ファイルURL


ファイルおよびグローバル変数の宣言の後に<script src="ファイル名">として、ファイルの読み込みが指定されます。


Document: DOMContentLoadedイベント




document.addEventListener('DOMContentLoaded', (event) => {
    console.log('DOM fully loaded and parsed');
});


一番最初にイベントとして発生します。そのため、JavaScriptのファイルURLで指定されるファイル内に記述するか、ファンクションおよびグローバル変数の宣言に含める必要があります。


動的アクションのページのロード



APEXで設定できる処理の中では、一番早く実行されています。後述するイベントtheme42ready、apexreadyendよりも早いので、ページの読み込みが完了する前に処理が実行されます。


JavaScript: ページ・ロード時に実行



生成されるページの末尾部分に<script type="text/javascript">として挿入されます。ページ読み込みの最後(ページ読み込みは未完)に実行されます。


イベント:theme42ready




apex.jQuery(window).on('theme42ready', function() {
        console.log('Do something after UI elements are rendered on the page.');
 });

ユニバーサル・テーマで定義されているイベントです。ユニバーサル・テーマの初期化が終了したときに発生します。


イベント:apexreadyend




apex.jQuery( apex.gPageContext$ ).on( "apexreadyend", function( e ) {
     // code here
} );

APEXとしてページ・ロードが完了したときに発生します。theme42readyより後になります。

標準のページ・ロード・イベントとは異なり、DOMの準備ができるまでは待ちません。遅延ロードの設定がされたコンポーネントであれば、そのコンポーネントの準備は待つ(データのロードは待たない)とのことです。


Window:loadイベント




window.onload = (event) => {
     console.log('page is fully loaded');
};

一番最後に実行されます。

ページ全体が、スタイルシートや画像などのすべての依存するリソースを含めて読み込まれたときに発生します。

完

2023年1月25日水曜日

リッチ・テキスト・エディタでHTMLを読み込むだけで元のHTMLが変更される

 現行のOracle APEXは、リッチ・テキスト・エディタとしてCKEditor5を使用しています。

このエディタは読み込んだHTMLを編集するのではなく、一旦、内部的なモデルに従ったデータに変換し、変換したデータを編集します。HTMLからCKEditor5のモデルに変換する処理をUpcast conversionと呼んでいます。モデルからHTMLに変換する処理はDowncast conversionです。マークダウンの場合でも同様に、Upcast conversionでマークダウンから内部モデルに変換、Downcast conversionでモデルからマークダウンに変換しています。

このような仕組みなので、CKEditor5で記述していると危険なHTMLの記述が含まれる可能性は低く、より安全であるといえます。

しかし、生成されるHTMLはCKEditor5のGUIの機能に制限されますし、他のツールで記述したHTMLもCKEditor5を通すと、CKEditor5のモデルで表現できない部分は削除されます。

ただしこれでは使いにくい場合もあるため、Oracle APEX 22.2の新機能として、リッチ・テキスト・エディタにプロパティとしてカスタムHTMLの許可(Allow Custom HTML)が追加されました。HTMLに記述されている属性(Attributes)、クラス(class)、スタイル(style)が保存の対象となっています。初期化JavaScriptファンクションを記述することにより、詳細な制御も可能です。

https://ckeditor.com/docs/ckeditor5/latest/api/module_html-support_generalhtmlsupport-GeneralHtmlSupportConfig.html

CKEditor5によって保存されるHTMLを確認するためのAPEXアプリケーションを作成しました。

https://apex.oracle.com/pls/apex/r/japancommunity/ckeditor5-test/

一番下のページ・アイテムP1_SOURCEに以下のHTMLを記述し、ボタンSave P1_SOURCEをクリックします。

<span class="u-danger">u-dangerを設定したテキスト</span>

リッチ・テキスト・エディタのプロパティカスタムHTMLの許可をONにしていると、classが削除されないためu-dangerの指定通りバックグランドが赤く表示されます。そうでない方のリッチ・テキスト・エディタでは、class指定は無視されます。


ボタンSave P1_CKEDIT as P1_SOURCEを押すと、classが削除されたデータが保存されます。そのため、ページ・アイテムP1_SOURCEの値は以下に変更されます。

<p>u-dangerを設定したテキスト</p>


カスタムHTMLの許可をOFFのままHTMLの記述を維持するにはHTMLの挿入を呼び出し、HTMLスニペットとして記述します。


HTMLスニペットとして記述したHTMLは、CKEditor5によって変更されません。

HTMLスニペットは<div class="raw-html-embed">の子要素になります。


この記事で使用しているAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/ckeditor5-test.zip

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

完

2023年1月24日火曜日

OpenAI Whisperを使った文字起こしアプリの作成(5) - ブラウザからの音声入力

 ブラウザで音声を録音し、そのデータを使って直接WhisperのAPIを呼び出して文字起こしをした結果を、ページ・アイテムに設定する実装を追加してみました。

以下のような動作になります。

Startボタンを押してからStopボタンを押すまでに、結果としてTextに文字起こしされる言葉を録音しています。


Whisperを実行するコードに、CORSの対応を追加します。

Whisper APIを呼び出すページはAPEXのサーバーが生成しています。OriginがAPEXのサーバーであれば、Whisper APIを呼び出せるように許可を与えています。

次に共有コンポーネントの静的アプリケーション・ファイルを作成します。

ファイル名はpage-actions.jsとします。内容は以下になります。今回は記述するJavaScriptのコードを、ひとつのファイルにまとめています。

参照はコピーしておきます。

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

JavaScriptのファイルURLを設定します。

音声レコーダーとなるリージョンと、START、STOP、TRANSCRIBEの3つボタンを作成します。

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

識別のタイトルはAudio Playerとします。タイプに静的コンテンツを選択し、ソースのHTMLコードとして以下を記述します。

<audio id="player" controls></audio>

リージョンの修飾を最小限にするため、外観のテンプレートとしてBlank with Attributesを選択します。


録音を開始するボタンSTARTを作成します。

識別のボタン名はSTART、ラベルはStartとします。レイアウトの位置として、レコーダーの左に配置されるよう、Previousを選択します。

動作のアクションに動的アクションで定義を選択し、詳細のカスタム属性にて実行するAPEXアクションを指定します。

data-action="#action$start-audio-recording"


録音を停止するボタンSTOPを作成します。

識別のボタン名はSTOP、ラベルはStopとします。レイアウトの位置として、レコーダーの右に配置されるよう、Nextを選択します。

動作のアクションに動的アクションで定義を選択し、詳細のカスタム属性にて実行するAPEXアクションを指定します。

data-action="#action$stop-audio-recording"

同様に、Whisper APIを呼び出し文字起こしを実行するボタンTRANSCRIBEを作成します。

動作の指定である詳細のカスタム属性は、以下の記述になります。

data-action="#action$whisper-transcribe?target=P1_TEXT&url=&G_TRANSCRIBE_URL."

以上で実装は完了です。アプリケーションを実行すると、最初のGIF動画のように動作します。

例えば日報を音声入力するとしても、いちいちファイルに保存するのは現実的ではないと感じたので、JavaScriptで実装してみました。以前に紹介したアプリケーションのエクスポートは置き換えています。

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

完

OpenAI Whisperを使った文字起こしアプリの作成(4) - APEXアプリの作成

 Whisperによる文字起こしを行うAPIが作成できました。このAPIを呼び出すAPEXアプリケーションを作成します。

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

アプリケーション作成ウィザードを起動します。

名前はWhisper Transcribeとします。すべての機能はデフォルトで作成されるホーム・ページに実装します。そのため、設定は変更せずにアプリケーションの作成を実行します。

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

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

最初に音声ファイルを指定するページ・アイテムを作成します。

Bodyにページ・アイテムを作成します。

識別の名前はP1_FILE、タイプとしてファイル参照...を選択します。

設定のファイルをパージするタイミングとして、End of Requestを選択します。セッション・ステートのストレージとしてリクエストごと(メモリーのみ)を選択します。

音声ファイルのアップロードとAPI呼び出しによる文字起こしは、1回のHTTPリクエストで完結します。そのため、アップロードした音声ファイルを処理完了後に保持する必要はありません。

音声ファイルのアップロードと文字起こしを実行するボタンを作成します。

作成したボタンのボタン名はSUBMIT、ラベルはSubmitとします。動作のアクションはデフォルトでページの送信になります。


文字起こしの結果を保持するページ・アイテムを作成します。

作成したページ・アイテムの識別の名前はP1_TEXT、タイプとしてテキスト領域を選択します。ラベルはTextとします。

セッション・ステートのデータ型はCLOB、ストレージとしてセッションごと(永続)を選択します。このように設定すると、ページをリロードしても文字起こしの結果が維持されます。


ボタンSUBMITをクリックしたときに、文字起こしのAPIを呼び出す処理を実装します。

以下のコードを実行し、パッケージUTL_WHISPERを作成します。

左ペインでプロセス・ビューを開き、文字起こしのAPIを呼び出すプロセスを作成します。

作成したプロセスの識別の名前はTRANSCRIBE、タイプとしてAPIの呼出しを選択します。設定のパッケージとしてUTL_WHISPER、プロシージャまたはファンクションとしてTRANSCRIBEを選択します。

サーバー側の条件のボタン押下時にSUBMITを選択し、ボタンが押されたときにUTL_WHISPERのプロシージャTRANSCRIBEが呼び出されるようにします。


パラメータのp_file、p_textはデフォルトでページ・アイテムP1_FILE、P1_TEXTが割り当たります。

パラメータp_urlは、値のタイプをアイテム、アイテムとしてG_TRANSCRIBE_URLを指定します。G_TRANSCRIBE_URLは置換文字列として、この後に値を設定します。


アプリケーション定義の置換を開き、置換文字列をG_TRANSCRIBE_URLとして、置換値にWhisperによる文字起こしを行うAPIを実装したURLを設定します。


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

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

無料の範囲だと処理に時間がかかるため、実用レベルで使用するのは難しいとは思います。とはいえ、どのように使えるのかという評価はできるのではないでしょうか。

次の記事で、JavaScriptから直接Whisperを呼び出すような実装を追加します。

続く