ViewCreator と imuiListTable を使った一覧画面の作り方

このCookBookでは、ViewCreator のルーティング定義と imuiListTable を利用した一覧画面を作成する手順をご紹介します。


ViewCreator ではクエリ定義とデータ参照定義で、様々な一覧画面を簡単に作成可能です。ただ、データ参照定義は基本的に設定ベースでの作成となるため、細かいレイアウト調整に対応しきれないケースがあります。
そのような場合、クエリ定義からデータを取得するだけにして、画面はデータ参照定義ではなく別で用意する、といった使い方ができます。

そのような用途のために、ViewCreatorではクエリ定義の実行結果を REST API で取得するためのルーティング定義を作成可能です。

画面の作り方はいろいろありますが、例えば下記ガイドでは、IM-BloomMaker で画面を作成する手順をご紹介しています。
intra-mart Accel Platform ViewCreator 管理者操作ガイド - IM-BloomMakerを使用した画面作成例

今回は、スクリプト開発の imuiListTable を利用して作成します。

参考)
intra-mart Accel Platform ViewCreator 管理者操作ガイド - ルーティングの作成
スクリプト開発向けタグライブラリ - imuiListTable

完成イメージ

検索条件を入力したり、ページングや列ソートを行うことが可能な一覧画面です。
データは一覧画面に表示する分だけを REST API で都度取得します。

レシピ

データを取得するためのクエリ定義を用意します。

参考)
intra-mart Accel Platform ViewCreator 管理者操作ガイド - クエリの作成

今回の手順では、サンプルデータとして用意されている「日本のデータ」を利用します。

ただし、検索機能を作りたいので、サンプルをそのまま利用するのではなく「抽出条件」を追加します。
sample_prefecture テーブルの name フィールドを「抽出条件」に追加して、抽出方法を「部分一致」に設定します。
条件値は以下のように設定します。

<%REQUEST_PARAMETER(prefecture_name)%>

これは、prefecture_name というキー名で条件値(リクエストパラメータ)を取得するための設定です。

参考)
intra-mart Accel Platform ViewCreator 管理者操作ガイド - 動的パラメータ

追加したら、クエリ定義を保存します。

次に、ルーティング定義(REST API)を作成します。

サイトマップ>ViewCreator>ルーティング定義 を選択します。

「新規作成」ボタンをクリックして、ルーティング定義を作成します。
(カテゴリが無い場合は、新規作成します。)

今回の CookBookでは、以下のように設定します。

ルーティングURLREST API の URLです。「cookbook_240482/japan_data」 とします。
対象クエリ日本のデータ」を設定します。
メソッドGET」とします。
認証方式IMAuthentication」とします。
レスポンス種別JSONに変換して返却」とします。
ルーティング名日本のデータ取得」とします。
ルーティング定義の設定

参考)
intra-mart Accel Platform ViewCreator 管理者操作ガイド - ルーティングの作成

入力後「登録」ボタンをクリックします。これで REST API が登録されました。

次に、登録した REST API について認可設定を行います。
今回は、「認証済みユーザ」全員が利用できるように設定します。

これでルーティング定義の作成は完了です。

どのようなAPIが作成されたのか、確認してみましょう。
「OpenAPI Specification」をクリックします。

ルーティングURLで指定した「cookbook_240482/japan_data」以外に「/cookbook_240482/japan_data?metadata」というエンドポイント(URL)も作成されています。

「cookbook_240482/japan_data?metadata」の方はデータ取得用ではなく、カラムや検索条件等の定義情報を取得するためのエンドポイントです。

実行してみると、以下の結果が返ってきます。
columns 配下はクエリ定義に追加したカラムの情報から生成され、REST API からどのような形式のデータが返されるのかを確認できます。
また、conditions 配下を見ると、どのようなキーで条件値を受け取ることができるかが分かります。
これは、抽出条件の条件値で指定した <%REQUEST_PARAMETER(prefecture_name)%> に従って生成されます。

{
  "columns": [
    {
      "code": "ccd00001",
      "name": "year",
      "caption": "年度",
      "type": "NUMBER"
    },
    {
      "code": "ccd00002",
      "name": "name",
      "caption": "地域名",
      "type": "STRING"
    },
    {
      "code": "ccd00003",
      "name": "name",
      "caption": "都道府県名",
      "type": "STRING"
    },
    {
      "code": "ccd00004",
      "name": "age",
      "caption": "年齢",
      "type": "STRING"
    },
    {
      "code": "ccd00005",
      "name": "area",
      "caption": "面積",
      "type": "NUMBER"
    },
    {
      "code": "ccd00006",
      "name": "population",
      "caption": "人口",
      "type": "NUMBER"
    }
  ],
  "conditions": [
    {
      "key": "prefecture_name",
      "type": "STRING"
    }
  ]
}

「cookbook_240482/japan_data」をクリックすると、エンドポイントの情報が表示されます。

以下の入力項目が自動生成されます。

limit最大取得レコード数
offsetレコード取得開始位置
sortソートキー(クエリ定義のカラム名またはカラムコードを指定できます)
orderソート方向(asc/desc)
REST API呼び出し時に指定可能なクエリパラメータ

利用可能な入力項目は他にもありますが、ここでは説明を割愛します。

クエリ定義の抽出条件に設定した「prefecture_name」も入力項目として生成されています。

これで、データを取得する準備は整ったので、次に一覧画面を imuiListTable を利用して作成します。
jssp で作成するため、以下の2つのファイルを作成します。

  • jssp/src/cookbook_240482/viewcreator/listtable.js
  • jssp/src/cookbook_240482/viewcreator/listtable.html

データは REST API で取得するので listtable.js の実装は特にありません。なにもしない init だけです。

jssp/src/cookbook_240482/viewcreator/listtable.js

function init(req) {

}

listtable.html は以下のように作成してください。

jssp/src/cookbook_240482/viewcreator/listtable.html

<div class="imui-form-container">
  <!-- 検索条件 -->
  <div class="imui-box-operation cf">
    都道府県名:<imart type="imuiTextbox" id="search-prefecture-name" oninput="inputChange()"></imart>
  </div>
  <!-- リストテーブル本体 -->
  <imart id="cookbook_240482" type="imuiListTable" process="csjs" target="csjs_cookbook_240482_Sample" width="100%" height="200" viewRecords="true">
    <pager rowNum="50" />
    <cols>
      <!-- name にはルーティング定義に紐づけられたクエリ定義に追加されているカラムの「カラム名」か「カラムコード」を指定します。 -->
      <col name="year" caption="Year" />
      <col name="name" caption="Region" />
      <col name="ccd00003" caption="Prefecture" />
      <col name="age" caption="Age" />
      <col name="area" caption="Area" />
      <col name="population" caption="Population" />
    </cols>
  </imart>
</div>
<script type="text/javascript">
  /**
   * 検索条件が入力された時に呼ぶfunction
   */
  function inputChange() {
    debounce(300, () => {
      // jqGrid を再描画します
      $("#cookbook_240482").trigger("reloadGrid",[{page:1}]);
    });
  }

  let intervalID;
  function debounce(timer, func) {
    clearTimeout(intervalID)
    intervalID = setTimeout(() => {
      func.apply();
    }, timer);
  }

  /**
   * データ取得用のfunction(imuiListTable の target属性に指定します)
   */
  function csjs_cookbook_240482_Sample(request, response) {

    // クエリのルーティング定義で設定した「ルーティングURL」を指定します。
    const baseUrl = document.querySelector('base').href;
    const queryRoute = baseUrl + 'cookbook_240482/japan_data';
    const queryParams = new URLSearchParams(
      {
        // レコード取得開始位置
        offset : (request.page - 1) * request.rowNum + 1,
        // 最大取得レコード数(一覧に表示するレコード数)
        limit : request.rowNum,
        // ソートキー
        sort : request.sortIndex,
        // ソート方向(desc/asc)
        order : request.sortOrder,

        // 検索条件(prefecture_name)
        // クエリ定義の抽出条件で <%REQUEST_PARAMETER(prefecture_name)%> を部分一致で設定しておきます
        prefecture_name : document.getElementById('search-prefecture-name').value
      }
    );

    // REST APIを呼び出して結果を response に渡します
    (async () => {
      const data = await fetch(queryRoute + '?' + queryParams);
      const json = await data.json();
      response({
        // ページ番号
        page: request.page,
        // レコード総数
        total: json.totalCount,
        // レコードデータ配列
        data: json.data
      });
    })();
  }
</script>

順に説明します。
以下は、検索条件を入力するためのテキストボックスです。何か入力されるごとに inputChange() を実行するように設定しています。

  <!-- 検索条件 -->
  <div class="imui-box-operation cf">
    都道府県名:<imart type="imuiTextbox" id="search-prefecture-name" oninput="inputChange()"></imart>
  </div>

inputChange() でリストの再描画を行います。再描画すると、後述の csjs_cookbook_240482_Sample が実行されます。
ただ、毎回 REST API を呼び出すとサーバに負荷をかけてしまうので、入力後 300 ミリ秒待機するようにしています。

  function inputChange() {
    debounce(300, () => {
      // jqGrid を再描画します
      $("#cookbook_240482").trigger("reloadGrid",[{page:1}]);
    });
  }

次に、リストテーブル本体です。クライアントサイドJavaScirpt で REST API を呼び出したいので process="csjs" とします。
target="csjs_cookbook_240482_Sample" として、csjs_cookbook_240482_Sample 関数でデータ取得処理を行うようにします。
また col 要素の name にはルーティング定義に紐づけられたクエリ定義に追加されているカラムの「カラム名」か「カラムコード」を指定します。
これで REST API が返すデータとリストテーブルのカラムをマッピングします。

  <!-- リストテーブル本体 -->
  <imart id="cookbook_240482" type="imuiListTable" process="csjs" target="csjs_cookbook_240482_Sample" width="100%" height="200" viewRecords="true">
    <pager rowNum="50" />
    <cols>
      <!-- name にはルーティング定義に紐づけられたクエリ定義に追加されているカラムの「カラム名」か「カラムコード」を指定します。 -->
      <col name="year" caption="Year" />
      <col name="name" caption="Region" />
      <col name="ccd00003" caption="Prefecture" />
      <col name="age" caption="Age" />
      <col name="area" caption="Area" />
      <col name="population" caption="Population" />
    </cols>
  </imart>

最後に、データ取得用の csjs_cookbook_240482_Sample 関数です。
csjs_cookbook_240482_Sample の引数で imuiListTable から渡されてくる request.page, request.rowNum, request.sortIndex, request.sortOrder を REST API の引数である offset, limit, sort, order に紐づけます。
ほぼ、そのまま渡せますが、offset だけは request.page と request.rowNum を使って計算しています。

検索条件入力用のテキストボックスの id を search-prefecture-name としたので、document.getElementById('search-prefecture-name').value で取得して、これも同様に REST API の引数である prefecture_name に渡しています。

  /**
   * データ取得用のfunction(imuiListTable の target属性に指定します)
   */
  function csjs_cookbook_240482_Sample(request, response) {

    // クエリのルーティング定義で設定した「ルーティングURL」を指定します。
    const baseUrl = document.querySelector('base').href;
    const queryRoute = baseUrl + 'cookbook_240482/japan_data';
    const queryParams = new URLSearchParams(
      {
        // レコード取得開始位置
        offset : (request.page - 1) * request.rowNum + 1,
        // 最大取得レコード数(一覧に表示するレコード数)
        limit : request.rowNum,
        // ソートキー
        sort : request.sortIndex,
        // ソート方向(desc/asc)
        order : request.sortOrder,

        // 検索条件(prefecture_name)
        // クエリ定義の抽出条件で <%REQUEST_PARAMETER(prefecture_name)%> を部分一致で設定しておきます
        prefecture_name : document.getElementById('search-prefecture-name').value
      }
    );

あとは REST API を呼び出して、戻り値をそのまま引数で受け取った response に渡すだけです。
REST API からはデータだけでなく totalCount からレコード総件数を取得できます。それを response に渡すことで、リストテーブルのページングが可能になります。

    // REST APIを呼び出して結果を response に渡します
    (async () => {
      const data = await fetch(queryRoute + '?' + queryParams);
      const json = await data.json();
      response({
        // ページ番号
        page: request.page,
        // レコード総数
        total: json.totalCount,
        // レコードデータ配列
        data: json.data
      });
    })();
  }

これで、一覧画面の作成は完了です。

最後に、jssp で作成したのでルーティングの定義が必要です。ここでは、画面(html と js)自体は誰でも利用できるように設定します。

参考)
intra-mart Accel Platform スクリプト開発モデル プログラミングガイド - スクリプト開発モデル用ルーティングテーブル

以下のファイルを作成します。

conf/routing-jssp-config/viewcreator_cookbook_240482.xml

<?xml version="1.0" encoding="UTF-8"?>
<routing-jssp-config xmlns="http://www.intra-mart.jp/router/routing-jssp-config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.intra-mart.jp/router/routing-jssp-config routing-jssp-config.xsd ">

  <authz-default mapper="welcome-all" />
  <file-mapping path="/cookbook_240482/viewcreator/listtable" page="cookbook_240482/viewcreator/listtable"></file-mapping>

</routing-jssp-config>

これで完成です。

アプリケーションサーバを再起動して、%コンテキストパス%/cookbook_240482/viewcreator/listtable を開いてみましょう。
一覧画面を表示できましたでしょうか?