2020年6月19日金曜日

Oracle APEXアプリケーションのグローバル化(9) - 置換文字列とショートカット

Oracle APEXアプリケーションを多言語に対応させるための色々な機能を紹介してきました。このトピックの最後に置換文字列とショートカットを使用したメッセージの翻訳方法を紹介します。

実用的な理解を得るために、これらの機能を使用した簡単なアプリケーションを作成します。まず、アプリケーション・ビルダーから作成をクリックし、新規アプリケーションの作成を行います。アプリケーション作成ウィザードでは、アプリケーションに適当な名前を指定し、その他はデフォルトのままでアプリケーションの作成を実行します。


3ページだけの、何も機能がないアプリケーションが作成されます。


共有コンポーネントグローバリゼーションのセクションにあるテキスト・メッセージを開き、以下の4つのメッセージを登録します。

 名前 言語 テキスト  JavaScriptで使用 
 T_HELLO  ja こんにちは!  いいえ
 T_HELLO en Hello! いいえ
 T_CONFIRM  ja 本当に? はい
 T_CONFIRM en  Are you sure?  はい

以上の4つのメッセージを登録すると、以下のようにリストされます。


このようにして登録したメッセージを置換文字列、またはショートカットから利用する方法を実装していきます。

置換文字列による翻訳


登録済みのテキスト・メッセージは、&APEX_TEXT$メッセージ名.という形式の置換文字列として使用することができます。上記の例をとると、以下のように置き換わります。

&APEX_TEXT$T_HELLO. = こんにちは! (言語として日本語が選択されているとき)
&APEX_TEXT$T_HELLO. = Hello! (言語として英語が選択されているとき)

ホーム・ページに静的リージョンを追加して確認します。ソースのテキストとしては以下を入力します。
置換文字列の確認<br/><b>&APP_TEXT$T_HELLO.</b>

アプリケーションを実行して結果を確認します。


アプリケーションのプライマリ言語が日本語なので、&APP_TEXT$T_HELLO.こんちには!へ置き換わっていることが確認できます。

ショートカットによる翻訳


共有コンポーネントショートカットというコンポーネントが含まれています。これは置換文字列と似ていますが、置き換える文字列を動的に生成することができます。その機能のひとつとして、メッセージの動的翻訳が提供されています。


ショートカットを開いて、動作を確認するために2つのショートカット、T_HELLOとT_CONFIRMを登録します。


登録可能なショートカットのタイプには以下があります。
  • PL/SQLファンクション本体
  • HTMLテキスト
  • エスケープされた特殊文字付きHTMLテキスト
  • イメージ
  • JavaScriptにより一重引用符がエスケープされたテキスト
  • メッセージ
  • JavaScriptにより一重引用符がエスケープされたメッセージ
上記の中で、メッセージJavaScriptにより一重引用符がエスケープされたメッセージが動的な翻訳に使用できるタイプです。

名前T_HELLOタイプメッセージショートカットとしてT_HELLO(これはテキスト・メッセージとして登録されている名前)を指定して、ショートカットT_HELLOを作成します。


同様にして、名前T_CONFIRMタイプJavaScriptにより一重引用符がエスケープされたメッセージショートカットとしてT_CONFIRMを指定して、ショートカットT_CONFIRMを作成します。


これでショートカットが登録されました。

マニュアルの記載によると、ショートカットを使用できる箇所は以下になります。
  • 「HTMLテキスト(ショートカットあり)」として定義されたリージョンのリージョン・ソース属性。
  • リージョンのヘッダーおよびフッターのテキスト属性。
  • アイテム・ラベル属性、要素の前のテキスト、要素の後のテキストおよびデフォルト値属性。
  • リージョン・テンプレート属性。
ホーム・ページにタイトルショートカットタイプ静的コンテンツであるリージョンを追加し、ショートカットによる文字列の表示を確認します。その他の情報は以下を指定します。ショートカット名はダブルクオーテーションで囲みます。

ソースのテキスト:<p>本文:"T_HELLO"</p>
ヘッダー・テキスト: ヘッダー"T_HELLO"
フッター・テキスト: フッター"T_HELLO"


ここで、マニュアルの記載にある「HTMLテキスト(ショートカットあり)」をリージョンに定義します。リージョンの属性を開き、出力形式HTMLであることを確認し、ショートカットを開くONにします。


設定が完了したらアプリケーションを実行し、結果を確認します。ショートカットが日本語のメッセージに置き換わっていることが確認できます。


ページ・アイテムについてもショートカットが使えます。P1_SHORTCUTというテキスト・フィールドを追加して、置き換え可能な箇所にショートカットを指定してみます。ラベル前のテキスト後のテキストタイプ静的としたデフォルト値に"T_HELLO"を指定します。


 アプリケーションを実行して、表示がどうなるかを確認します。


指定可能な箇所のすべてで、ショートカットが置き換わっていることが確認できます。

リージョン・テンプレートへのショートカットの適用については実際の作業は行いません。以下のように、リージョン・テンプレートの定義に直接ショートカットを埋め込むことができます。


JavaScriptでのショートカットの利用は以下の手順にそって行います。

最初にショートカットをグローバル変数に設定します。ページのプロパティにあるファンクションおよびグローバル変数の宣言に以下を記述します。
var msg_confirm = '"T_CONFIRM"';
JavaScriptにてmsg_confirmを表示するダイアログを開くことで設定の確認を行います。

動的アクションを実行するボタンを作成します。アクション動的アクションで定義になります。


ボタンをクリックしたタイミングで、動的アクションを実行します。


アクションとしてJavaScriptコードの実行を選び、実行するアクションのJavaScriptのコードとして以下を指定します。
apex.message.confirm( msg_confirm, function( okPressed ) {
    console.log(okPressed ? 'Ok' : 'Cancel');
});

以上で実装は完了です。アプリケーションを実行し、追加したボタンをクリックします。msg_confirm、元々はT_CONFIRMとして定義されたショートカット(そのショートカットはT_CONFIRMとして定義された翻訳済みのテキスト・メッセージ)が表示されていることが確認できます。


最後にアプリケーションの言語を英語に変更して、置換文字列やショートカットが英語に切り替わるか確認します。この確認のためだけにアプリケーション言語を追加するのも手間なので、アプリケーションのプライマリ言語を英語(en)に変更します。


置換文字列およびショートカットで指定された箇所がすべて英語になっていることが確認できます。


以上でOracle APEXで作成したアプリケーションを多言語に対応させるために利用可能な機能についての解説は終了です。

2020年6月18日木曜日

Oracle APEXアプリケーションのグローバル化(8) - 日時データ型と現在時刻

Oracle APEXのアプリケーションへタイムゾーンを適用する方法について説明してきました。この記事では、Oracle APEXのアプリケーションを作成する上で必要な、Oracle Databaseの日時データ型と現在時刻の扱いについて説明してみます。

日時データ型の表示フォーマットについて


日時データ型がOracle APEXのアプリケーションでどのように認識されているかを目視で確認するために、今まで使用してきたサンプル・アプリケーションのグローバリゼーションの設定を変更します。


日時データ型に関する書式設定を以下のように指定します。アプリケーション日付書式はそのままで変更しません。

 対象 書式 セッション設定
 アプリケーション日付書式 YYYY/MM/DD HH24:MI NLS_DATE_FORMAT  
 アプリケーションのタイムスタンプ書式 YYYY/MM/DD HH24:MI:SS.FF3 NLS_TIMESTAMP_FORMAT
 アプリケーションのタイムスタンプ・タイムゾーン書式 YYYY/MM/DD HH24:MI:SS.FF3 TZR NLS_TIMESTAMP_TZ_FORMAT

ここで指定された書式はページ処理の開始時点で、以下のSQL文と同等の処理によりセッションに設定されます。
alter session set NLS_DATE_FORMAT = 'YYYY/MM/DD HH24:MI';
alter session set NLS_TIMESTAMP_FORMAT = 'YYYY/MM/DD HH24:MI:SS.FF3';
alter session set NLS_TIMESTAMP_TZ_FORMAT = 'YYYY/MM/DD HH24:MI:SS.FF3 TZR';
結果としてOracle APEXのアプリケーションでも扱う日時データ型にしたがって(ページ・アイテムやレポートの列に書式マスクが設定されていない限り)、セッションに設定された書式が適用されます。

上記の書式が適用されたレポートを確認します。


DATE型にはアプリケーション日付書式(NLS_DATE_FORMAT)、TIMESTAMP型およびTSLTZ型(TIMESTAMP WITH LOCAL TIME ZONE型)にはアプリケーションのタイムスタンプ書式(NLS_TIMESTAMP_FORMAT)、TSTZ型(TIMESTAMP WITH TIME ZONE型)にはアプリケーションのタイムスタンプ・タイムゾーン書式(NLS_TIMESTAMP_TZ_FORMAT)が適用されていることが確認できます。

グローバリゼーションの設定でアプリケーションの日時書式のみ空白にしています。これはセッションの設定には使用されず、APP_DATE_TIME_FORMATという置換文字列としてアプリケーション内で利用することができます。


書式マスクを個別に指定する場所に、置換文字列として&APP_DATE_TIME_FORMAT.を指定することにより、書式の変更を一箇所で出来るようになります。

現在時刻について


Oracle Databaseでは、現在の時刻を取得するために使用できるリテラルにいくつか種類があります。
  • SYSDATE
  • SYSTIMESTAMP
  • CURRENT_DATE
  • CURRENT_TIMESTAMP
  • LOCALTIMESTAMP
Oracle APEXのアプリケーションがこれらの現在時刻を表す日時リテラルをどのように認識するか確認するために、レポートとして表示させてみます。

Datetimesレポートのページに、対話モード・レポートのリージョンを追加します。ページ・デザイナにてDatetimesレポートのページを開き、コンポーネント・ギャラリより対話モード・レポートをDatetimesレポートの直下に配置されるよう、ドラッグ&ドロップします。


レポートとして表示するSQLとして以下を与えます。
select sysdate, systimestamp, current_date, current_timestamp, localtimestamp,
       dbtimezone, tz_offset(sessiontimezone)
from dual
対話モード・レポートの設定は以下のようになります。タイトル現在時刻としています。ソース位置ローカル・データベースタイプSQL問合わせとして、上記のSQLを設定します。


作成したレポートを、ページを実行して確認してみます。タイムゾーン・オフセットはAsia/Tokyoに設定されています。


見やすく記述します。日本時間の6月18日14時47分に実行しています。

 SYSDATE 2020/06/18 05:47
 SYSTIMESTAMP 2020/06/18 05:47:38.111 +00:00
 CURRENT_DATE 2020/06/18 14:47
 CURRENT_TIMESTAMP  2020/06/18 14:47:38.111 ASIA/TOKYO 
 LOCALTIMESTAMP 2020/06/18 14:47:38.111
 DBTIMEZONE -05:00
 TZ_OFFSET(SESSIONTIMEZONE)  +09:00

タイムゾーン・オフセットをUS/Easternに切り替えて表示してみます。


見やすく記述します。日本時間の6月18日14時52分に実行しています。

 SYSDATE 2020/06/18 05:52
 SYSTIMESTAMP 2020/06/18 05:52:41.988 +00:00
 CURRENT_DATE 2020/06/18 01:52
 CURRENT_TIMESTAMP  2020/06/18 01:52:41.988 US/EASTERN 
 LOCALTIMESTAMP 2020/06/18 01:52:41.988
 DBTIMEZONE -05:00
 TZ_OFFSET(SESSIONTIMEZONE)  -04:00


1. SYSDATE


データベースが稼働しているオペレーティング・システムから得られる時刻をDATE型で返します。タイムゾーンを含まないので、Oracle APEXのアプリケーション利用者はサーバーと同じタイムゾーンからアクセスしていることが前提でなければ、現在時刻として扱うことはできません。


2. SYSTIMESTAMP


データベースが稼働しているオペレーティング・システムから得られる時刻をTSTZ型(TIMESTAMP WITH TIME ZONE型)で返します。時刻のみではなくタイムゾーンも含みますが、オペレーティング・システムから得られるタイムゾーンになります。

ユーザーのタイムゾーンでの時刻で表示するにはSYSTIMESTAMP AT LOCALとして、AT LOCAL演算子を適用する必要があります。SYSTIMESTAMP AT LOCALの結果はCURRENT_TIMESTAMPになるため、そのような場合はCURRENT_TIMESTAMPを使用すべきでしょう。

ユーザーのタイムゾーンで扱う必要のない場合、例えばプロシージャやトリガー内で使用できます。


3. CURRENT_DATE


セッションのタイムゾーン・オフセットを適用した現在時刻をDATE型で返します。Oracle APEXアプリケーションを使用しているユーザーのタイムゾーンでの現在時刻になります。


4. CURRENT_TIMESTAMP


セッションのタイムゾーン・オフセットを適用した現在時刻をTSTZ型(TIMESTAMP WITH TIME ZONE型)で返します。時刻部分がユーザーのタイムゾーンでの時刻表示になり、かつ、タイムゾーンの情報も含まれます。


5. LOCALTIMESTAMP

セッションのタイムゾーン・オフセットを適用した現在時刻をTIMESTAMP型で返します。時刻部分がユーザーのタイムゾーンでの時刻表示になります。

アプリケーションを作成する上では、DATE型が必要であればCURRENT_DATE、TIMESTAMP型が必要であればLOCALTIMESTAMP、タイムゾーンの情報が必要であればCURRENT_TIMESTAMP、またはSYSTIMESTAMPを使うことになります。

TSLTZ型とDBTIMEZONE


DBTIMEZONEというのはデータベースを作成するときに指定するタイムゾーンです。TSLTZ型はこのDBTIMEZONEで指定されたタイムゾーンの時刻として保存されます。タイムゾーン自体の情報は含みません。

内部表現を確認してみます。先ほどと同様な手順で対話モード・レポートを作成します。ソースとなるSQLとして以下を設定します。
select "場所", 
    "TSLTZ型",
    "TSLTZ型" at time zone dbtimezone as "内部時刻", 
    dump("TSLTZ型") as "内部表現",
    dbtimezone,
    tz_offset(sessiontimezone)
from test_datetimes
レポートの結果は以下になります。


東京その1のデータは以下になっています。

 場所 東京その1
 TSLTZ型 2020/06/21 09:00:00.000
 内部時刻 2020/06/20 19:00:00.000 -05:00
 内部表現 Typ=231 Len=7: 120,120,6,20,20,1,1
 DBTIMEZONE  -05:00
 TZ_OFFSET(SESSIONTIMEZONE)  +09:00

TSLTZ型は内部的には2020/06/20 19:00:00.000(-05:00はデータとしては含まれない)として保存されています。実際にはTyp=231 Len=7: 120,120,6,20,20,1,1となっています。データが保存されるとき、取り出されるときに、DBTIMEZONEとSESSIONTIMEZONEの差分をオフセットとして適用します。

気を付けなければならないことは、TSLTZ型を使うとタイムゾーンが保存されないことです。一旦、TSLTZ型に保存された後はデータの入力がどのタイムゾーンから行われたのか確認する術が無くなります。であればTSTZ型を使うと良いのですが、TSTZ型ではほとんどの操作でタイムゾーンの変換が必要になり、パフォーマンス上好ましくはありません。

DBTIMEZONEで時刻を保存しても、時刻にタイムゾーンの情報を含んでも、結局は同じ時刻を表していることに違いはありません。そのため、アプリケーション上の要件としてはTIMESTAMP WITH LOCAL TIME ZONE型で不足はないでしょう。

時刻を表示する際、つねにタイムゾーンも同時に表示させる、または、同一のユーザでもタイムゾーンを切り替えてデータの入力を行うといったアプリケーションでは、TSTZ型を使った方がよいかもしれません。

2020年6月16日火曜日

Oracle APEXアプリケーションのグローバル化(7) - APIを使用したタイムゾーン設定

自動タイムゾーンについて説明したこちらの記事の続きです。夏時間に対応するため、Oracle APEXが提供しているAPIを使ったタイムゾーンの設定をするようにアプリケーションを改変していきます。

自動タイムゾーンをOFFにする


APIの呼び出しでタイムゾーンを設定するので、自動タイムゾーンOFFにします。グローバリゼーション属性の設定になります。


この画面へたどり着くパスはいくつかあります。ひとつ目のパスは、アプリケーション・プロパティの編集です。


共有コンポーネントからもたどり着くことができます。アプリケーション・ロジックに含まれるアプリケーション定義属性を開いてグローバリゼーションを選ぶ、または、グローバリゼーションに含まれるグローバリゼーション属性を選びます。


空白の非モーダル・ダイアログのページを作成する


タイムゾーンをユーザーのプリファレンスのひとつとして設定するページを作成します。ページの作成を開始します。


空白ページをクリックします。


ページ番号名前プリファレンスページ・モード非モーダル・ダイアログとします。オプションとして作成できる静的コンテンツ・リージョンのリージョン1プリファレンスとして作成します。へ進みます。



このページとナビゲーション・メニュー・エントリを関連付けないを選びへ進みます。


作成されるページの確認画面が表示されるので、終了をクリックします。


これで、非モーダル・ダイアログのページが作成されました。

タイムゾーンを設定するページ・アイテムを作成する


タイムゾーンの指定を行うページ・アイテムP7_TIMEZONEを作成します。

名前P7_TIMEZONEタイプ選択リスト(タイムゾーン名の一覧から選択します)、ラベルタイムゾーンとし、選択時のページ・アクションはデフォルトのNoneのままにします。選択時のアクションは後に動的アクションで設定します。選択リストとなるLOVは、タイプSQL問合わせとして、以下のSQLを設定します。
select tzname as d, tzname as r
from v$timezone_names
group by tzname
order by tzname


さらに、詳細の保存されていない変更の警告無視にします。値の変更は動的アクションによって行われるため、ページ上の変更がデータベースに反映されていない、ということは発生しません。ソースの設定ですが、タイププリファレンスとし、プリファレンス名MY_TIMEZONEとします。使用セッション・ステートの既存の値を常に置換セッション・ステートの保持リクエストごと(メモリーのみ)とします。


次に、このページ・アイテムの値が変更されたときに実行される動的アクションを定義します。動的アクションのタイミングは、イベントとしては変更選択タイプアイテム、そしてアイテムP7_TIMEZONEになります。これでP7_TIMEZONEが変更されたときに、アクションが実行されます。


実行されるアクションとして、以下のPL/SQLコードを指定します。APEX_UTIL.SET_SESSION_TIME_ZONEを呼び出すことで、タイムゾーンを設定しています。また、MY_TIMEZONEというプレファレンスに、設定したタイムゾーンを記憶することで新たにサインインした際に、設定済みのタイムゾーンを回復できるようにします。
begin
    apex_util.set_session_time_zone(:P7_TIMEZONE);
    apex_util.set_preference('MY_TIMEZONE', :P7_TIMEZONE, :APP_USER);
end;
アクションPL/SQLコードの実行送信するアイテムとしてP7_TIMEZONEを含めます。


最後にダイアログをクローズするためのボタンを追加します。ボタンを作成し、動作アクション動的アクションで定義します。


動的アクションを作成し、ボタンクリックされたときに、アクションが実行される設定を行います。


実行されるアクションは、ダイアログを閉じるです。


これで、タイムゾーンを設定する非モーダル・ページは完成です。

ナビゲーション・バーへプレファレンスを開くメニューを追加する


共有コンポーネントナビゲーション・バー・リストを開きます。


デスクトップ・ナビゲーション・バーを開きます。


&APP_USER.つまりサインインしたユーザー名をクリックしたときに表示されるメニューとして、プリファレンスのエントリを作成します。エントリの作成をクリックします。


親リスト・エントリ&APP_USER.とします。順序は分割線より上に表示させるため15とし、ターゲットとなるページとして7を指定します。そして、リスト・エントリの作成をクリックします。


追加したメニューがリストに表示されます。


メニューについてはこれで完了です。

サインイン時にプリファレンスからタイムゾーンを回復する


アプリケーション・プロセスとして、以下のコードを認証後に実行することで、プリファレンスMY_TIMEZONEからタイムゾーンの設定を回復します。
declare
   l_my_tz varchar2(40);
begin
    l_my_tz := apex_util.get_preference('MY_TIMEZONE', :APP_USER);
    if l_my_tz is not null then
        apex_util.set_session_time_zone(l_my_tz);
    end if;
end;
共有コンポーネントアプリケーション・プロセスを開きます。


アプリケーション・プロセスの一覧画面から、作成を実行します。


名前タイムゾーンの回復ポイント認証後とします。へ進みます。


最初に示したコードを入力してへ進みます。


 条件タイプは無指定にして(- 条件タイプの選択 - という表示のままにしておく)、プロセスの作成をクリックします。

登録したプロセスがリストの表示されます。


これで、サインイン時にプリファレンスとして設定されているタイムゾーンが回復します。

動作確認をする


アプリケーションを実行して、Datetimesレポートを開きます。その後、右上のユーザー名をクリックし、追加したプリファレンスが含まれるメニューを表示させます。


開いたページでタイムゾーンとしてUS/Easternを選びます。ダイアログは開いたままで構いません。

Datetimesレポートに戻り、変更したタイムゾーンを反映させるため、ブラウザの機能を使ってページの再読み込み(リロード)を行います。タイムゾーン・オフセットがUS/Easternになっていることを確認します。


タイムゾーン・オフセットが-04:00のときのTSLTZ型の表示と比較してみましょう。


ニューヨーク冬以外の時刻はタイムゾーン・オフセットが-04:00でもUS/Easternでも同じ表示です。ニューヨーク冬のみ表示が異なり、TSTZ型の表示と一致するタイムゾーン・オフセットUS/Easternが、オフセットを-05:00と扱っています。つまり夏時間は適切に扱われています。

タイムゾーンがUS/Easternの状態で、2020/12/21 00:00を追加します。ニューヨーク冬2とします。


今度はTSLTZ型、TSTZ型の双方とも、2020/12/21 00:00として時刻が表示されます。


サインアウトして再度サインインしたときに、タイムゾーンがきちんと回復するかどうか試してみてください。回復するはずです。

夏時間を扱う場合は夏時間に対応したタイムゾーンを設定する必要がある、というのが今回のポイントです。