2021年9月6日月曜日

単一行ビューのアクション・メニューよりRefreshを除く

 対話グリッドの静的IDとしてempが設定が設定されているとします。

ページ・プロパティJavaScriptページ・ロード時に実行に以下のコードを記述します。これで単一行ビューのアクション・メニューからRefreshが除かれます。

let grid = apex.region("emp").widget();
let sv$ = grid.interactiveGrid("getViews", "grid").singleRowView$;
let actions = sv$.recordView("getActions");
actions.remove("refresh-record");


ページを実行すると、Refreshがなくなっていることが確認できます。


単一行ビューのリフレッシュでなければ、対話グリッドのJavaScript初期化コードから無効化できます。

function(config) {
config.initActions = function(actions) {
actions.remove("refresh"); // アクションのDataのRefresh
actions.remove("row-refresh"); // 行メニューのRefresh
actions.remove("selection-refresh"); // アクションのセレクションのRefresh
actions.remove("refresh-record"); // これは効かない
}
return config;
}


アクションデータリフレッシュはこちらです。

アクションセレクションリフレッシュです。


行メニューリフレッシュです。


先程のJavaScript初期化コードが記述されていると、上記すべてがメニューから除かれます。

もしかしたらdefaultSingleRowOptionsactionsContextを設定することで、単一行ビューのメニューをカスタマイズできるかもしれません。試してみましたが成功しなかったので、メニューが初期化された後に、actionsメニューから取り除きました。

以上になります。

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

メディアクエリの活用してアプリケーションを自動ダークモードに対応させる

Oracle APEXを開発しているTim Kimberlさんが、先日のOffice Hour - Deep Dive: Universal theme in APEX 21.1にて、デバイスの設定に従って、アプリケーションの外観を自動的に切り替える方法を紹介しています。CSSのメディアクエリを使って、ライト・モードとダーク・モードに切り替えています。


サンプル・データセットのEMP/DEPTから自動的に作成されるアプリケーションを使って、Office Hourで説明されている実装を確認してみます。Oracle APEX 21.1でユーザー・インターフェースのテーマとしてVitaが選択されていれば、どのようなアプリケーションでも実装の確認に使用できます。


開発者ツール・バーのカスタマイズよりテーマ・ローラーを実行し、グローバル色ヘッダー・アクセント50783c(緑)に変更します。(Timのビデオの操作を確認するための操作です。異なる設定を行うこともできます。)


変更したテーマを名前を付けて保存します。今回はスタイル名Evergreenとして保存します。


Evergreenがカレントのテーマになります。ダーク・モードのスタイルはVita Darkを元に作成します。カレントとなるテーマとして、Vita Darkを選択します。


Vita Darkのヘッダー・アクセント50783c(緑)に変更します。


変更したスタイルをEvergreen Darkとして、名前を付けて保存します。


カレントのテーマEvergreenに戻し、保存します。


テーマ・ローラによる変更が保存されているファイル名を確認します。

共有コンポーネントテーマを開きます。


Universal Theme - 42 *のリンクをクリックします。Oracle APEX 5.xより、提供されるテーマはUniversal Themeのみです。Oracle APEX 5.x以前に作成されたアプリケーションまたはカスタマイズしたテーマを使用している場合を除き、Universal Theme以外の選択はありません。


テーマの編集画面が開くので最底部のスタイルのセクションまでスクロールし、スタイルのEvergreen Darkを開きます。


テーマ・スタイルEvergreen Darkの出力CSSファイルURLをクリップボードにコピーします。取消をクリックして、テーマ・スタイルを閉じます。


アプリケーション・プロパティの編集をクリックする、もしくは、共有コンポーネントアプリケーション定義属性を開きます。


ユーザー・インターフェースのタブを開き、カスケード・スタイルシートのセクションに移動します。ファイルURLとしてメディアクエリを使ってダーク・モードのテーマ・スタイルであるファイルを指定します。CSSのファイル名は、先ほどクリップボードにコピーしたファイル名を指定します。

[media="(prefers-color-scheme: dark)"]#THEME_DB_IMAGES#31836887163503540672.css

デバイスの設定がダーク・モードであれば(preferes-color-schemeがdarkであれば)、ダーク・モードのスタイルを定義したCSSファイルが読み込まれます。ファイルURLを追加したら、変更の適用をクリックします。


以上で設定は完了です。macOSの場合、以下のような効果が得られます。


以上になります。CSSのメディア・クエリはダーク・モード以外でも色々な活用方法があるでしょう。

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

2021年9月3日金曜日

Google Fontsを使う

 Oracle APEXを開発しているTim Kimberlさんが、先日のOffice Hour - Deep Dive: Universal theme in APEX 21.1にてGoogle Fontsを使用する方法を紹介しています。

たまに違うフォントを使いたいと思うことはあります。試しにGoogle Fontsに含まれている日本語フォントのNoto Sans JPとNoto Serif JPを使ってみました。以下に行った作業を記載します。

最初にGoogle Fontsのページを開き、使用するフォントを選びます。

https://fonts.google.com/?sort=popularity

言語としてJapaneseを選び、フォント名をNotoで検索しました。Noto Sans JPNoto Serif JPが見つかりました。


Noto Sans JPを開いて、Regular 400Bold 700を選択しました。確認のためにSelected familyを表示させています。


Browse Fontsのタブをクリックして戻り、Noto Serif JPを開きます。同様にRegular 400Bold 700を選択しました。


CSSよりインポートする場合は、@importの記載を使います。
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&family=Noto+Serif+JP:wght@400;700&display=swap');


フォント・ファミリの名前はそれぞれNoto Sans JPNoto Serif JPになります。

日本語の記載があるOracle APEXのアプリケーションを開き、フォントを切り替えてみます。開発者ツール・バーからテーマ・ローラーを開きます。


カスタムCSSとして以下を記載し保存します。ベース・フォントをNoto Sans JPに変更します。

/* フォントのインポート */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&family=Noto+Serif+JP:wght@400;700&display=swap');
/* ベースのフォント・ファミリを変更する */
:root {
--a-base-font-family: 'Noto Sans JP';
}


Noto Sans JPに変更した表示です。


--a-base-font-familyの指定をNoto Sans JPからNoto Serif JPに変更します。表示は以下になります。


ページに対する恒久的な変更は、ページ・プロパティCSSインライン@importとベース・フォント--a-base-font-familyの指定を記述します。


アプリケーション全体に適用するには、静的アプリケーション・ファイルまたは静的ワークスペース・ファイルとしてCSSを記述します。


アプリケーション定義属性ユーザー・インターフェースカスケード・スタイルシートファイルURLとして設定します。


Google Fontsの使用方法の紹介は以上になります。

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

Menu Popupの紹介

 Universal Themeについて調べていて、Menu Popupという実装を知りました。Universal Themeのサンプル・アプリケーションに含まれています。以下のような動作をするボタンを作ることができます。


Universal Themeのサンプル・アプリケーションに含まれているMenu Popupは、こちらからアクセスできます。

以下の手順で実装します。

最初に共有コンポーネントリストを作成します。


サンプル・アプリケーションのMenu PopupではUT - Sample Menu PopupUT - Sample Menu Popup (Actions)の、2つのリストが使用されています。


リスト自体の定義には、Menu Popupだからといって特別なことはありません。

リストを作成したら、そのリストをソースとしたリージョンを作成します。リージョンのタイプリストソースリストにはUT - Sample Menu Popupが指定されています。外観テンプレートとしてBlack with Attributesを選択します。詳細静的IDとしてactionsを設定しています。


このリージョンであるリストを、ボタンのクリックで開きます。ボタンの外観CSSクラスとしてjs-menuButtonを指定します。これは決め打ちです。動作アクションとして動的アクションで定義を選択します。ただし、動的アクション自体の定義は不要です。詳細カスタム属性としてdata-menu="actions_menu"を設定します。actionsの部分は、リストが実装されているリージョンの静的IDになります。


サンプル・アプリケーションに含まれている2つめのMenu Popupの設定です。ソースリストUT - Sample Menu Popup (Actions)詳細静的IDcustomizedになっています。


ボタンでは、詳細カスタム属性としてdata-menu="customized_menu"が設定されています。


Menu Popupの実装方法の説明は以上になります。

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

2021年9月1日水曜日

対話グリッドの列の文字の色を条件によって変更する - JavaScriptによる実装

 テンプレート・ディレクティブを使った設定ではなく、JavaScriptで単一行ビューの項目の色を変更する処理を実装してみました。部門番号(DEPTNO)に依存して従業員名(ENAME)の色を変更します。こちらの記事で作成したアプリケーションに機能を追加しています。

実装はしてみましたが、テンプレート・ディレクティブを使った方が実装としては良いでしょう。JavaScriptによるカスタム実装の場合、現状で年に2回発生するOracle APEXのアップデートの際に、毎回処理に影響がないことを確認しないといけません。

そういった点は置いておいて、せっかく調べたので純粋に技術的な観点で実装方法を紹介します。


動的アクションによる実装


単一行ビューに切り替わる場合、大抵は行が選択されています。なので、選択の変更タイミング動的アクションを実行します。

最初に対話グリッドに静的IDを設定します。静的IDはempとします。


対話グリッドEMP動的アクションの作成を行います。識別名前行の選択とします。タイミングイベント選択の変更[対話グリッド]選択タイプリージョンリージョンEMPとします。


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

let view =
apex.region("emp").widget().interactiveGrid("getCurrentView");
if (view.singleRowMode) {
let model = this.data.model;
let record = this.data.selectedRecords[0];
let deptno = model.getValue(record, "DEPTNO");
let elem = this.triggeringElement;
let enameElems = elem.getElementsByClassName("u-bold");
if (enameElems.length == 1) {
switch(deptno) {
case '10':
if (enameElems[0].classList.contains("u-success-text")) {
enameElems[0].classList.remove("u-success-text");
}
if (enameElems[0].classList.contains("u-warning-text")) {
enameElems[0].classList.remove("u-warning-text");
}
if (! enameElems[0].classList.contains("u-danger-text")) {
enameElems[0].classList.add("u-danger-text");
}
break;
case '20':
if (enameElems[0].classList.contains("u-success-text")) {
enameElems[0].classList.remove("u-success-text");
}
if (enameElems[0].classList.contains("u-danger-text")) {
enameElems[0].classList.remove("u-danger-text");
}
if (! enameElems[0].classList.contains("u-warning-text")) {
enameElems[0].classList.add("u-warning-text");
}
break;
case '30':
if (enameElems[0].classList.contains("u-warning-text")) {
enameElems[0].classList.remove("u-warning-text");
}
if (enameElems[0].classList.contains("u-danger-text")) {
enameElems[0].classList.remove("u-danger-text");
}
if (! enameElems[0].classList.contains("u-success-text")) {
enameElems[0].classList.add("u-success-text");
}
break;
}
}
}



ENAMEfieldCssClassesとしてu-boldが設定されていることを前提としています。getElementsByClassNameを呼び出して列ENAMEを要素として取り出すために使います。ですので、効果の無いCSSクラスでも問題ありません。列ENAMEが要素として取り出せるのであれば、他の方法(タグから取得するなど)でもかまいません。



アクションの入れ替えによる実装


グリッド・ビューで行を選択した後に単一行ビューに切り替えると(グリッド・ビューで選択したときにすでにイベントが発生しているため)、選択の変更[対話グリッド]のイベントが発生しません。動的アクションが実行されないと列ENAMEに色が付かず、黒で表示されます。その状況を補完するため、単一行ビューへの切り替えを行のアクション・メニューから呼び出したときに、列ENAMEに色をつける処理も同時に実行します。

ページ・プロパティJavaScriptページ・ロード時に実行に、以下のコードを記述します。

let view = apex.region("emp").widget().interactiveGrid("getViews", "grid"),
menu$ = view.rowActionMenu$;

// 行メニューの設定をかえる 。
menu$.menu("option").items[0] =
{
type:"action",
label:"Alt Single Row View",
action: function(menu, element) {
// 最初に単一行ビューに切り替える。
apex.region( "emp" ).widget().interactiveGrid( "getActions" ).invoke( "single-row-view" );
// 単一ビューに切り替えた後は、すべてPureなJavaScriptによるDOMの操作を行なう。
let elem = document.getElementById("emp");
let enameElems = elem.getElementsByClassName("u-bold");
let deptnoElems = elem.getElementsByClassName("this-deptno");
let deptnoValElems = deptnoElems[0].getElementsByClassName("a-RV-fieldValue");
let deptno = deptnoValElems[0].innerText;
if (enameElems.length == 1) {
switch(deptno)
{
case '10':
if (enameElems[0].classList.contains("u-success-text")) {
enameElems[0].classList.remove("u-success-text");
}
if (enameElems[0].classList.contains("u-warning-text")) {
enameElems[0].classList.remove("u-warning-text");
}
if (! enameElems[0].classList.contains("u-danger-text")) {
enameElems[0].classList.add("u-danger-text");
}
break;
case '20':
if (enameElems[0].classList.contains("u-success-text")) {
enameElems[0].classList.remove("u-success-text");
}
if (enameElems[0].classList.contains("u-danger-text")) {
enameElems[0].classList.remove("u-danger-text");
}
if (! enameElems[0].classList.contains("u-warning-text")) {
enameElems[0].classList.add("u-warning-text");
}
break;
case '30':
if (enameElems[0].classList.contains("u-warning-text")) {
enameElems[0].classList.remove("u-warning-text");
}
if (enameElems[0].classList.contains("u-danger-text")) {
enameElems[0].classList.remove("u-danger-text");
}
if (! enameElems[0].classList.contains("u-success-text")) {
enameElems[0].classList.add("u-success-text");
}
break;
}
}
}
};


部門番号の値を取り出すために、列DEPTNOfieldCssClassesとしてthis-deptnoを設定しています。


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

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

実装を推奨しているわけではありませんが、Oracle APEXのアプリケーション作成の参考になれば幸いです。

対話グリッドの単一ビューのレイアウトを調整する

 対話グリッドの単一ビューのレイアウトを調整する方法がありました。以下に紹介します。

表示のみの場合での単一ビューの表示です。

編集モードのときの表示の例です。


対話グリッドはサンプル・データセットのEMP/DEPTに含まれる表EMPを、データ・ソースとして作成しています。こちらの記事で作成しているアプリケーションを基にしています。

列EMPNOのdefaultGridColumnOptionsに、fieldColSpanの設定を追加します。
function(options){
    options.defaultGridColumnOptions = {
        cellTemplate: '{case DEPTNO/}{when 10/}<div class="u-danger-text">&EMPNO.</div>{when 20/}<div class="u-warning-text">&EMPNO.</div>{when 30/}<div class="u-success-text">&EMPNO.</div>{otherwise/}&EMPNO.{endcase/}',
        fieldColSpan: 2,
    };
    return options;
}

fieldColSpanについては、マニュアルのこちらに説明があります。単一ビューの横幅は12のグリッドに分割されています。列EMPNOが、そのうちのいくつのグリッドを使用するかをfieldColSpanとして設定しています。ここではを設定しています。この行はEMPNOで始まるので、残りのグリッドは10です。続く項目のfieldColSpanが10以下であれば、一行に含まれるため改行はされません。

列ENAMEのfieldColSpanは6、そして従業員名を太字にするためfieldCssClassesu-boldを設定します。
function(options){
    options.defaultGridColumnOptions = {
        fieldCssClasses: "u-bold",
        fieldColSpan: 6
    };
    return options;
}

defaultGridColumnOptionsに設定可能なプロパティはrecordViewのfieldsに定義されていますが、これはレポート形式の表示と単一ビューの双方に適応されます。(例えばcellTemplateは両方に適用されます。) 実装した結果から、fieldCssClassesとfieldColSpanについては、単一ビューでのみ有効なようです。

JOBのfieldColSpanには4を設定します。
function(options){
    options.defaultGridColumnOptions = {
        fieldColSpan: 4
    };
    return options;
}

列MGR、HIREDATE、SAL、COMMのfieldColSpanは3を設定します。列DEPTNOにfieldColSpanは設定しません。
function(options){
    options.defaultGridColumnOptions = {
        fieldColSpan: 3
    };
    return options;
}

対話グリッド自体のJavaScript初期化コードで、defaultSingleRowOptionsformCssClassesを設定します。マニュアルのExampleの記載どおりの設定で、CSSクラスのu-Form--labelsAboveにてラベルを項目の上部に配置し、u-Form--stretchInputsで項目をfieldColSpanで指定した幅に広げます。(このCSSクラスはUniversal ThemeのLayout Modifiersだと思うのですが、残念なことに説明を見つけることができませんでした。)
function(config) {
    config.defaultSingleRowOptions = {
        formCssClasses: "u-Form--labelsAbove u-Form--stretchInputs"
    };
    return config;
}

このままだとラベルの配置が右寄せになるので、左寄せに変えます。ページのプロパティのCSSインラインで、u-Form-labelContainerクラスのスタイルtext-alignを左寄せ(left)に上書きします。
.a-RV .u-Form.u-Form--labelsAbove .u-Form-labelContainer {
    text-align: left !important;
}

以上の設定で、最初に載せた単一ビューのレイアウトになります。

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

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