spring
Dojo

Spring Rooでdgridを使う

More than 1 year has passed since last update.

ブログの方で、より詳しく書いています。ぜひ見てください。

最近、業務アプリの開発フレームワークの選択でSeasar2を見限り、Springに戻ることにした。
Seasar2の便利さに慣れていたところなので、Spring Rooを使ってみているのだが、かゆいところどころか痛いところにも全然手が届かないというもどかしい日々を送っている。

このエントリは、ブログで順を追ってサンプルの開発を掲載しているものを整理したものなので、サンプルプロジェクトが存在する前提になっている。なるべくシンプルに書いているが、興味のある方はブログの方を参照してほしい。

さて本題。

JavaScriptライブラリの選択

Dojoを使ってみよう

Spring Rooで自動生成される一覧表示画面は、ページング機能はあるが、ソートやヘッダ固定のスクロール等に非対応でいまひとつ。

Spring Rooは、Spring-JSとしてクライアント側JavaScriptに採用しているのは、Dojo toolkitというライブラリ。自動生成されたjspx、tagx等を見ると、「dojo」「dijit」「dojox」などの指定がある。

巷ではjQueryの使用例が多いが、UIのテーマを統一して完成度を上げていくには、それなりの苦労を伴う。その点、Dojoはよく使われるUIがウィジェットとして揃っており、一発でテーマを変更できるところがよい。いい機会なので、Dojoを使ってみよう。

また、Dojoには、Gridというまさにグリッドのウィジェットがあるのだが、最近はdgridというライブラリを推しているようで、deplicatedになっている。今回は、dgridを使用して、一覧表示画面を作ってみる。

JSONでの出力を可能に

クライアント側で非同期でデータを要求し、表示できるように、サーバ側にJSONデータの授受機能を用意しよう。

生成されたソースコードなどのリソースは、ブログ[Spring Roo] 13. JSONでの応答機能の追加を参照してください。

エンティティ

まず、エンティティクラスに、JSON変換機能を追加する。これは簡単で、Rooのjson addコマンドを使う。

json add --class ~.domain.OnlineMemo

実行すると、OnlineMemoクラスに@RooJsonアノテーションが追加され、ITDコードとして、OnlineMemo_Roo_Json.ajが作られる。

コントローラ

次に、コントローラにJSONデータ授受機能を追加する。これも、Rooのweb mvc json addコマンドを使用する。

web mvc json add --jsonObject OnlineMemo --class ~.web.OnlineMemoController

すると、OnlineMemoControllerに@RooWebJsonアノテーションが追加され、OnlineMemoController_Roo_Controller_Json.ajが作られる。

コントローラに追加された各メソッドは、HTTPヘッダに”Accept=application/json”が設定されている時に、元々から用意されているCRUDの各メソッドの代わりに動作する。

なので、コンソールなどから、

curl -i -H "Accept: application/json" http://localhost:8080/RooTest/onlinememos

と入力すると、

$ curl -i -H "Accept: application/json" http://localhost:8080/RooTest/onlinememoes
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=utf-8
Content-Length: 514
Date: Fri, 28 Feb 2014 10:42:59 GMT

[{"account":{"email":"shuji@rbx.jp","id":3,"loginId":"shuji","passwordHash":"$2a$10$/uhjqlEZiabEhIG0dpgfkuli1etluDnIAA3EpqYFxqvApbl9ML0jW","version":6},"created":1391763494252,"id":2,"memo":"めも。","modified":1391698800000,"title":"memo1","version":1},{"account":{"email":"test@rbx.jp","id":6,"loginId":"test","passwordHash":"$2a$10$e6b6XrhGKwkYykuJi7YszOQGWUBfG4/mszY8jMdU.iq8UZtZyy43.","version":1},"created":1392792426779,"id":23,"memo":"テスト","modified":1392735600000,"title":"テスト","version":0}]

と返ってくる。これは、後でdgridがapplication/jsonで要求することになるものだ。

特定のフィールドを出力しない

こうして簡単にJSONの出力ができるようになるのだが、毎回必要ではない情報や、パスワードハッシュ等、永遠に出してほしくない情報もある。

Spring RooのJSONライブラリには、Flexjsonが採用されている。このサイトを参照してみる。

メソッド毎に制御する

この方法では、一覧には必要ないが、個別表示では必要、というケースに対応できる。

Account_Roo_Json.java
    public static String Account.toJsonArray(Collection<Account> collection) {
        return new JSONSerializer().exclude("*.class").serialize(collection);
    }

としているところで、excludeに追加指定してやればよい。

Account_Roo_Json.javaのtoJsonArrayをAccount.javaにコピーして、以下のようにしてみよう。

Account.java
    public static String toJsonArray(Collection<Account> collection) {
        return new JSONSerializer().exclude("*.class", "loginId").serialize(collection);
    }

ちなみに、"*.class"の指定を削除すると、各オブジェクトにclassとして、そのクラスのフルネームが出力される。

フィールド自体を制御する

パスワードハッシュなど、JSONで出したくない項目を指定するには、エンティティにそのフィールドのgetterを作り、@JSON(include=false)アノテーションをつける。

Account.java
    @JSON(include = false)
    public String getPasswordHash() {
        return passwordHash;
    }

他にも、Flexjsonでinclude、excludeの指定方法はいろいろな組み合わせができそうだ。

これで、サーバ側でのJSON対応が整った。

dgrid使用の準備

今度は、クライアント側でのグリッドの組み込みを行う。

生成されたソースコードなどのリソースは、ブログ[Spring Roo] 14. グリッドを使う(dgrid)を参照してください。

最新Dojoライブラリの組み込み

Spring RooにSpring-JSとして付属しているDojoライブラリは、Spring Web Flowに含まれているのだが、これはバージョン1.5程度で、dgridのサンプルが動かない程度に古い。なので、最新のDojoライブラリを組み込んでしまおう。

Spring Rooアプリは実行時に、webmvc-config.xmlの「mvc:resources」定義にあるディレクトリを「resources」として公開する。dojoもこの中に含まれているのだが、これを上書きしてしまえばいい。

単純に、dojoライブラリのフォルダを、META-INF/web-resourcesにコピーすると、巨大なファイル数の前にSTSがいうことを聞かなくなってしまう。Upgrading the Version Of Dojo Used By Rooというサイトにあった方法で、以下の手順でJARを作り、リンクするようにする。

  1. Webアプリとは別に、JARアーカイブを作るためのMavanプロジェクトを作る(dojo-jsとする)。
  2. dojo-jsプロジェクトのsrc/main/resourcesに、META-INFディレクトリを作る。
  3. META-INFに最新のdojoライブラリのファイル群をコピーする(src/main/resources/META-INF/dojo-release-1.9.3となる)。
  4. Mavenプロジェクトを、「Run As」「Maven build」でGoalにinstallを指定して実行し、JARアーカイブを作る(targetにdojo-js-1.9.3.jarというファイルができる)。
  5. WebアプリのMaven設定ファイル(pom.xml)で、JARアーカイブへの依存を定義する。
  6. Webアプリのmvc:resourceの場所に、このJARアーカイブを指定する。

Mavenプロジェクトの作成

Mavenプロジェクトの作成

Mavenプロジェクトの設定

Mavenプロジェクトの設定

ビルドの設定

ビルドの設定

生成されたJARファイル

生成されたJARファイル

これで、最新版のDojo 1.9.3が使えるようになる。

dgridの組み込み

さて、これから、dgridを組み込む。

dgridサイトのInstallationを参考にして、以下のサイトから各ライブラリをダウンロードし、先ほどのdojo-jsプロジェクトに追加する。

以下のような配置になるようにする。

dgridを組み込んだdojo-js

dgridを組み込んだdojo-js

この状態で、Maven buildを実行しておく。

dgrid版一覧表示画面の作成

元々の一覧表示画面をそのままとっておいて、新たにdgrid版を作ってみよう。

Rooプロジェクトに戻って、以下の手順で作業を行う。

  1. tags/form/fields/table.tagxをコピーして、tags/form/fields/dgrid.tagxを作る。
  2. dgrid.tagxを、dgridを使用するように書き換える。
  3. views/onlinememoes/list.jspxをコピーして、views/onlinememoes/dgrid.jspxを作る。
  4. dgrid.jspxを、先ほど作ったdgrid.tagxを使うように書き換える。
  5. views/onlinememoes/views.xmlのonlinememoes/listの定義を、list.jspxではなくてdgrid.jspxに書き換える。

グリッド表示のカスタムタグを作る

dgrid.tagx
<jsp:root 
    xmlns:c="http://java.sun.com/jsp/jstl/core" 
    xmlns:fn="http://java.sun.com/jsp/jstl/functions" 
    xmlns:util="urn:jsptagdir:/WEB-INF/tags/util" 
    xmlns:spring="http://www.springframework.org/tags" 
    xmlns:form="http://www.springframework.org/tags/form" 
    xmlns:fmt="http://java.sun.com/jsp/jstl/fmt" 
    xmlns:jsp="http://java.sun.com/JSP/Page" 
    version="2.0">
    <jsp:output omit-xml-declaration="yes" />

<!-- いくつかのパラメータは今後のために残しておく。 -->

    <jsp:directive.attribute name="id" type="java.lang.String" required="true" rtexprvalue="true" description="The identifier for this tag (do not change!)" />
    <jsp:directive.attribute name="path" type="java.lang.String" required="true" rtexprvalue="true" description="Specify the URL path" />
    <jsp:directive.attribute name="typeIdFieldName" type="java.lang.String" required="false" rtexprvalue="true" description="The identifier field name for the type (defaults to 'id')" />
    <jsp:directive.attribute name="create" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'create' link into table (default true)" />
    <jsp:directive.attribute name="update" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'update' link into table (default true)" />
    <jsp:directive.attribute name="delete" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'delete' link into table (default true)" />
    <jsp:directive.attribute name="render" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Indicate if the contents of this tag and all enclosed tags should be rendered (default 'true')" />
    <jsp:directive.attribute name="cssClass" type="java.lang.String" required="false" rtexprvalue="true" description="Specify css class" />
    <jsp:directive.attribute name="cssStyle" type="java.lang.String" required="false" rtexprvalue="true" description="Specify css style" />

    <spring:url value="${path}" var="json_url" />

    <c:if test="${empty render or render}">
<script type="text/javascript" language="JavaScript">

// OnDemandGridの生成
require([
    "dojo/request",
    "dojo/store/Memory",
    "dgrid/OnDemandGrid"
    ], function (request, Memory, OnDemandGrid) {
    request("${json_url}", {
        handleAs: "json"
    }).then(function (response) {
        // Once the response is received, build an in-memory store
        // with the data
        var store = new Memory({ data: response });

        // Create an instance of OnDemandGrid referencing the store
        var grid = new OnDemandGrid({
            store: store,
            columns: gridColumns
        }, "${id}");

        grid.startup();
    });
});

</script>
<div id="${id}" class="${cssClass}" style="${cssStyle}"/>

    </c:if>

</jsp:root>

一覧表示画面を作る

views/onlinememoes/dgrid.jspx
<pre title="views/onlinememoes/dgrid.jspx" class="lang:xhtml">
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page" 
    xmlns:page="urn:jsptagdir:/WEB-INF/tags/form" 
    xmlns:table="urn:jsptagdir:/WEB-INF/tags/form/fields" version="2.0">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <jsp:output omit-xml-declaration="yes"/>
    <h3>dgrid</h3>
    <script type="text/javascript" language="JavaScript">

    // カラムの設定。
    // ブラウザのJavaScriptで実行されるので、JavaScriptの変数としてカラムを定義する。
    var gridColumns = [
                { field: "id", label: "ID"},
                { field: "title", label: "タイトル"},
                { field: "memo", label: "内容"},
                { field: "account.loginId", label: "アカウント"}
            ];

    </script>
    <!-- dgridが定義されているタグを指定する -->
        <table:dgrid id="grid_jp_rbx_test_roo_domain_OnlineMemo" path="/onlinememoes">
        </table:dgrid>
</div>

出力するJSPの切り替え

views.xmlを以下のように修正して、一覧の出力JSPXを、dgrid.jspxに切り替える。

views/onlinememoes/views.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN" "http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
    <definition extends="default" name="onlinememoes/list">
        <!-- 一覧表示にdgrid.jspxを使う -->
        <put-attribute name="body" value="/WEB-INF/views/onlinememoes/dgrid.jspx"/>
<!-- 
        <put-attribute name="body" value="/WEB-INF/views/onlinememoes/list.jspx"/>
 -->
    </definition>
<definition extends="default" name="onlinememoes/show">
        <put-attribute name="body" value="/WEB-INF/views/onlinememoes/show.jspx"/>
    </definition>
<definition extends="default" name="onlinememoes/create">
        <put-attribute name="body" value="/WEB-INF/views/onlinememoes/create.jspx"/>
    </definition>
<definition extends="default" name="onlinememoes/update">
        <put-attribute name="body" value="/WEB-INF/views/onlinememoes/update.jspx"/>
    </definition>
</tiles-definitions>

実行結果

前回の改修で、ヘッダにAccept:application/jsonを指定されたGETリクエストについては、listJsonが実行されるようになっているので、これでよいはずだ。

実行結果1

しかし、このままでは、アカウントが空っぽだ。

ネストされたJSONへの対応

これは、アカウントのログインIDを表示するようにdgrid.jspxで「account.loginId」と指定したつもりが、dgridがJSONのネストしたプロパティ指定に対応していないからだ。

応答のJSONとカラム指定

応答のJSONは、以下のようになっている。

応答JSONの抜粋
  {
    "account" : {
      "email" : "shuji@rbx.jp",
      "id" : 3,
      "loginId" : "shuji",
      "version" : 6
    },
    "accountLoginId" : "shuji",
    "created" : 1391763494252,
    "id" : 2,
    "memo" : "めも。",
    "modified" : 1391698800000,
    "title" : "memo1",
    "version" : 1
  }

一方、dgridに指定しているフィールドの定義は、以下のようにした。

views/onlinememoes/dgrid.jspx
    // カラムの設定。
    // ブラウザのJavaScriptで実行されるので、JavaScriptの変数としてカラムを定義する。
    var gridColumns = [
                { field: "id", label: "ID"},
                { field: "title", label: "タイトル"},
                { field: "memo", label: "内容"},
                { field: "account.loginId", label: "アカウント"}
            ];

「アカウント」としてログインIDを表示したいので、ドットで区切ってみたが、見事に無視された格好だ。これができないと不便。

dgridライブラリの修正

dojo-jsプロジェクトのdgrid/Grid.jsのrenderRow関数を以下のように修正する。

dgrid/Grid.js
        renderRow: function(object, options){
            var self = this;
            var row = this.createRowCells("td", function(td, column){
                var data = object;
                // Support get function or field property (similar to DataGrid)
                if(column.get){
                    data = column.get(object);
                }else if("field" in column && column.field != "_item"){
// getObjectして入れ子のオブジェクトを取得する。
//                  data = data[column.field];
                    data = dojo.getObject(column.field, false, data);
                }

修正が済んだら、dojo-jsプロジェクトをビルドして、Webアプリを起動して試してみよう。

実行結果2

カラムヘッダをクリックしてソートしてみた。

実行結果3

まとめ

長い道のりだった…。

ここまでで、最新のフロントエンドJavaScriptライブラリの組み込みと、グリッドの試用ができた。しかし、このままでは、編集や削除のためのリンクがないなど、とりあえず感が拭えない。

次回からは、このグリッドをさらに使い込んでみよう。

ブログの方で、より詳しく書いています。ぜひ見てください。