2022年8月19日金曜日

JavaScriptのコードを書いて対話グリッドを操作する

 ボタンをクリックして対話グリッドに新規行を追加したい、という相談がありました。対話グリッドを操作するためのAPIは概ね公開されているため、JavaScriptでコードを書けば大抵の操作は可能です。かなり凝ったUIなどを実装できますが、アプリケーションの保守性は下がります。

確認のために実装してみました。対話グリッドのソースとして、サンプル・データセットEMP/DEPTに含まれる表EMPを使用しています。

ページ・アイテムP1_ENAMEP1_SALを作成し、それぞれ新規行の列ENAMESALの初期値として扱えるようにします。また、対話グリッドの静的IDとしてempを設定しています。

JavaScript API ReferenceのWidgets - interactiveGridのPre-defined actions used by the Interactive Grid widgetの表より、呼び出すアクションを見つけます。新規行の挿入はアクションrow-add-rowまたはselection-add-rowによって実行できます。それぞれの動作は、以下のように説明されています。

row-add-row = Insert a row straight after the current row.

selection-add-row = Insert a row straight after any selected rows. If no rows are selected, or if cell selection mode is enabled, the new row will be added at the beginning.

アクションrow-add-rowを実行するには、ボタンのクリックで実行される動的アクションとして、以下のJavaScriptコードを記述します。

apex.region("emp").call("getActions").invoke("row-add-row");


新規行を挿入するだけであれば、これで完了です。

Oracle APEX 22.1より、ボタンなどから直接アクション(actions.action)を呼び出すことができるようになりました。この新しい設定では動的アクションの代わりに、ボタンの詳細カスタム属性data-action=としてアクションを直接指定します。

data-action="[emp]row-add-row"


row-add-rowの代わりにアクションselection-add-rowを呼び出すと、新規行は対話グリッドの先頭ではなく、選択されている行の下に挿入されます。

次にページ・アイテムP1_ENAME、P1_SALの値を初期値として、新規行に設定します。

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

識別名前New Recordとします。タイミングイベント行の初期化[対話グリッド]を選択します。選択タイプリージョンリージョンEmployeesです。


TRUEアクションとして、ページ・アイテムの値を列に設定するコードを記述します。ただし、タイミングイベント行の初期化[対話グリッド]なので、作成したボタンのクリック以外、例えば、対話グリッド上で行の追加をクリックしたとき、および、すでにある行をクリックして編集モードにしたときも、列の値を初期値で置き換えてしまいます

それを避けるためにisDefaultRequiredというフラグを変数として定義します。

TRUEアクションとしてJavaScriptコードの実行を選択します。設定コードとして以下を記述します。
isDefaultRequired = false;
let model = this.data.model;
let r = this.data.record;
model.setValue(r, "ENAME", apex.items.P1_ENAME.value);
model.setValue(r, "SAL", apex.items.P1_SAL.value);
クライアント側の条件タイプとしてJavaScript式を選択し、JavaScript式isDefaultRequiredと記述します。isDefaultRequiredがtrueのときのみ、初期値の設定を行います。


グローバル変数isDefaultRequiredを定義します。ページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言に、以下を記述します。

var isDefaultRequired = false;


ボタンを押したときに初期値を設定するため、TRUEアクションのコードにフラグの変更を含めます。
isDefaultRequired = true;
apex.region("emp").call("getActions").invoke("row-add-row");

以上で新規行を作成したときに、列に初期値が設定されるようになりました。

この他に、対話グリッドのmodelインターフェースのinsertNewRecordを呼び出す方法がOracle Communityにポストされています。


回答として記述されているコードで使用しているinsertNewRecordのメソッドは、新規行のレコードIDを返却します。そのため、そのレコードに調節初期値を設定できます。初期値を設定するために、対話グリッドに動的アクションを作成する必要はありません。

ボタンを押した時に新規行の作成と初期値の設定を同時に行うコードは、以下になります。
// 対話グリッドの静的IDにempが設定されている。
let gridView = apex.region("emp").widget().interactiveGrid("getViews").grid;
// カレント・ビューはgrid以外にchartなどもあり得る。
// let gridView = apex.region("emp").call("getCurrentView");
let model = gridView.model;
// 最終行を見つける。
// let row$ = apex.region("emp").widget().find(".a-GV-row").last();
// 先頭行を見つける。
// let row$ = apex.region("emp").widget().find(".a-GV-row").first();
// 行の選択を変更する。
// gridView.view$.grid("setSelection", row$);
// 選択されている行を取り出す。
let sel = gridView.getSelectedRecords();
// 選択されている行の下に一行追加する。
let rId = model.insertNewRecord(null, sel[sel.length - 1]);
// レコードIDからレコード・オブジェクトを取得する。
let r = model.getRecord(rId);
// 追加された行に列に値を設定する。
model.setValue(r, 'ENAME', apex.items.P1_ENAME.value);
model.setValue(r, 'SAL', apex.items.P1_SAL.value);
新規行を作成する位置もコード内で指定します。上記のコードでは、現在選択されている行の下に新規行を作成します。


ちなみに対話グリッドに限りませんが、Oracle APEXのフロントエンドのコンポーネントのソースコードにアクセスすることもできます。対話グリッドのmodelインターフェースのコードは、以下より参照できます。

https://static.oracle.com/cdn/apex/22.1.0/libraries/apex/model.js

ソース・コードはOracle APEXのダウンロード・メディアにも含まれています。

先ほど対話グリッドの動的アクションとして、新規行の初期値を設定しました。このためにフラグisDefaultRequiredを作成しています。Oracle APEX 22.1で新設されたdata-action=の指定で、初期値を設定するコーディングを行なってみます。

ページ・プロパティJavaScriptページ・ロード時に実行に、以下のコードを記述します。
apex.actions.add([
{
    name: "my-insert-new-record",
    action: function( event, element, args) {
        let gridView = apex.region("emp").widget().interactiveGrid("getViews").grid;
        let model = gridView.model;
        let sel = gridView.getSelectedRecords();
        let rId = model.insertNewRecord(null, sel[sel.length - 1]);
        let r = model.getRecord(rId);
        model.setValue(r, 'ENAME', apex.items.P1_ENAME.value);
        model.setValue(r, 'SAL', apex.items.P1_SAL.value);
    }
}
]);
アクションとしてmy-insert-new-recordを作成しています。このアクションでは新規行の作成以外に、初期値の設定も行なっています。


このアクションを呼び出すには、ボタン詳細カスタム属性として、以下を記述します。

data-action="#action$my-insert-new-record"


以上で、ボタンをクリックしたときにアクションmy-insert-new-recordが実行され、対話グリッドに新規行が作成されます。

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

対話グリッドの操作を行うボタンは8つあります。

B_ROW_ADD_ROW = アクションrow-add-rowを動的アクションから呼び出す。
B_DATA_ACTION_ROW_ADD_ROW = アクションrow-add-rowをdata-actionより呼び出す。
B_SELECTION_ADD_ROW = アクションselection-add-rowを動的アクションから呼び出す。
B_DATA_ACTION_SELECTION_ADD_ROW = アクションselection-add-rowをdata-actionより呼び出す。
B_INSERT_NEW_RECORD = insertNewRecordを動的アクションより呼び出す。
B_SELECTION_ADD_ROW_LAST = アクションselect-add-rowを呼び出し、対話グリッドの一番下に新規行を作成する。
B_ACTION_LINK = アクションmy-insert-new-recordを呼び出す。
B_REFRESH = アクションrefreshを呼び出す。

最近のアプリケーションでは、SPA = Single Page Applicationのようにページ遷移を行わない、または、極力減らすような実装が増えていると思います。APEXの場合、動的アクションを多用することになります。この場合、いろいろな場所にJavaScriptのコードが分散して書かれるため、アプリケーションの保守性が下がります。actions.actionのアクションの活用により、このような状況が改善されることが期待されています。JavaScriptのコードはページ・プロパティに記述されるか静的ファイルに記述されることになり、ページ中のコンポーネントに埋め込むことを避けることができます。

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

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