2022年8月30日火曜日

Grid.jsを使ってみる

 オープン・ソースのJavaScriptプラグインであるGrid.jsをOracle APEXで使ってみます。Grid.jsの紹介には"It works with most JavaScript frameworks, including React, Angular, Vue and VanillaJS."とあります。Oracle  APEXはJavaScriptフレームワークとは謳っていませんが、Grid.jsを組み込むことは可能です。

以前にOracle APEXでHighchartsを使う方法CKEditor5のInline Editorを使う方法を紹介しています。それと似た作業になります。

サンプル・データセットEMP/DEPTに含まれる表EMPを、Grid.jsにて表示します。


Grid.jsが受け付ける形式でデータを返すRESTサービスを作成します。

Grid.js側のコードは、ExamplesのCustom HTTP clientの例を参考にして記述します。


この例のコメントとして、RESTサービスが返すべきデータのフォーマットが記載されています。


正直なところ、上記のコードからはフォーマットがピンと来なかったので、Examplesのコードに含まれているURLにアクセスして、出力されるレスポンスを確認しました。


表EMPのデータを上記のフォーマットで返すSELECT文は以下になります。
select 
  json_object(
    'data' value coalesce(json_arrayagg(line),'[]') format json
    , 'total' value count(*)
  ) 
from (
  select json_object(empno, ename, job, sal, comm, hiredate) as line
  from emp
);
SQLワークショップSQLコマンドより上記のSQLを実行することにより、出力されるJSONオブジェクトを確認できます。


上記のSELECT文の結果を返す、RESTfulサービスのGETハンドラを作成します。

SQLワークショップRESTfulサービスを開きます。

モジュールgridjsを作成します。モジュール・パス/gridjs/とします。続いてURIテンプレートemp/をモジュールgridjsに作成します。

テンプレートemp/にGETハンドラを作成します。ソースとして以下を記述します。

declare
l_json clob;
begin
select
json_object(
'data' value coalesce(json_arrayagg(line),'[]') format json
, 'total' value count(*)
) into l_json
from (
select json_object(empno, ename, job, sal, comm, hiredate) as line
from emp
);
htp.p(l_json);
end;


完全なURLは、Grid.jsを初期化するコードにサーバー側のURLとして指定するため、コピーをして後で参照できるようにしておきます。

RESTfulサービスを、APEXセッションからのみ呼び出しができるように保護します。

ORDSの権限としてgridjsを作成します。ロールとしてRESTful Servicesを選択します。保護されたモジュールとしてgridjsを選択します。選択するロールはRESTful Servicesでなくてもかまいません(専用のロールを新規に作る方が望ましいでしょう)。ここで選択したロールを、APEXのユーザーに割り当てます。


ユーザーとグループの管理を開き、RESTfulサービスへアクセスするユーザーを編集します。グループ割当てとしてRESTful Servicesを割り当てます。


以上でデータのソースとなるRESTfulサービスの準備ができました。

APEXアプリケーションを作成し、Grid.jsを組み込みます。

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

アプリケーションの名前Grid.jsとします。


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

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


Grid.jsのドキュメントのInstallにに含まれるjsdelivrのセクションを参照し、以下の設定を行います。

ページ・プロパティJavaScriptファイルURLとして以下を設定します。

https://cdn.jsdelivr.net/npm/gridjs/dist/gridjs.umd.js

CSSファイルURLとして以下を設定します。

https://cdn.jsdelivr.net/npm/gridjs/dist/theme/mermaid.min.css


Bodyにグリッドを表示するリージョンを作成します。

識別タイトルGridタイプとして静的コンテンツを選択します。外観テンプレートとして装飾の少ないBlank with Attributes (No Grid)を選択します。詳細静的IDとしてgridを設定します。


作成したリージョンGridを対象として、Grid.jsを初期化します。

ページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言として、以下を記述します。RESTfulサービスの認証に使用します。

let apexSession = apex.env.APP_ID + ',' + apex.env.APP_SESSION;

ページ・ロード時に実行として、以下を記述します。

const grid = new gridjs.Grid({
columns: [
{
id: "EMPNO",
name: "従業員番号"
},
{
id: "ENAME",
name: "従業員名"
},
{
id: "JOB",
name: "ジョブ"
},
{
id: "SAL",
name: "給与"
},
{
id: "COMM",
name: "手当"
},
{
id: "HIREDATE",
name: "採用日"
}
],
server: {
url: "&G_DATA_SOURCE_URL.",
data: (opts) => {
return new Promise((resolve, reject) => {
// let's implement our own HTTP client
const xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status === 200) {
const resp = JSON.parse(this.response);
// columnsのidを含めているのでJSONをそのまま返す。
resolve( resp );
} else {
reject();
}
}
};
xhttp.open("GET", opts.url, true);
xhttp.setRequestHeader("Apex-Session", apexSession);
xhttp.send();
});
}
}
/* ,
pagination: {
limit: 5
}
*/
}).render(document.getElementById("grid"));
// グリッドをリフレッシュする。
apex.actions.add([
{
name: "force-render",
action: function ( event, element, args ) {
grid.forceRender();
}
}
]);


コード中で使用されている置換文字列G_DATA_SOURCE_URLを、アプリケーション定義置換に設定します。置換値はRESTfulサービスの完全なURLです。


グリッドをリフレッシュするボタンを作成します。

識別ボタン名B_REFRESHラベルリフレッシュとします。動作アクションとして動的アクションで定義を選択します。詳細カスタム属性としてdata-action="#action$force-render"を設定します。


Grid.jsのグリッドを初期化するリージョン、正確にはdiv要素に子要素が含まれていると初期化に失敗します。そのため、ここで作成するボタンB_REFRESHをリージョンGridに含めることはできません。エラーが発生しないようにするために、リージョンGridの静的ID空白にし、ソースHTMLコードとして

<div id="grid"></div>

を記述することもできます。


以上でAPEXアプリケーションへのGrid.jsの組み込みは完了です。

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

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

追記

RESTfulサービスの代わりにAjaxコールバックを使った実装例です。

AjaxコールバックとしてプロセスGET_EMPを作成します。ソースPL/SQLコードは、RESTfulサービスのGETハンドラのコードと同じです。


作成したAjaxコールバックを呼び出すように、ページ・プロパティJavaScriptページ・ロード時に実行を以下に変更します。

const grid = new gridjs.Grid({
columns: [
{
id: "EMPNO",
name: "従業員番号"
},
{
id: "ENAME",
name: "従業員名"
},
{
id: "JOB",
name: "ジョブ"
},
{
id: "SAL",
name: "給与"
},
{
id: "COMM",
name: "手当"
},
{
id: "HIREDATE",
name: "採用日"
}
],
server: {
data: () => {
return new Promise((resolve, reject) => {
apex.server.process( "GET_EMP", {},
{
success: function( resp ) {
resolve( resp );
},
error: function( jqXHR, textStatus, errorThrown ) {
reject();
}
});
})
}
}
/* ,
pagination: {
limit: 5
}
*/
}).render(document.getElementById("grid"));
// グリッドをリフレッシュする。
apex.actions.add([
{
name: "force-render",
action: function ( event, element, args ) {
grid.forceRender();
}
}
]);