Javaフレームワーク「Play Framework」とActiveReportsJSで帳票を出力する

先日公開した記事では、2回にわたってJavaのWebアプリケーションフレームワーク「Play Framework」でWeb APIを作成する方法をご紹介しました。

今回はJavaScript帳票ライブラリ「ActiveReportsJS(アクティブレポートJS)」を使用して、Play FrameworkのWebアプリケーションから帳票を出力する方法をご紹介したいと思います。

Web APIの作成

事前準備

最初に帳票に出力するデータを返却するWeb APIを作成します。事前準備として、前述のWeb APIの作成方法の記事のうち、前編の手順と、後編の「データベースの作成」までの手順を行っておきます。今回は「ReportingApp」という名前でプロジェクトを作成しました。

Playプロジェクトの作成

H2データベースの設定が完了したら、Ebeanが必要とする「javax-api」を使用するため、「build.sbt」を変更します。

lazy val root = (project in file(".")).enablePlugins(PlayJava, PlayEbean)
・・・(中略)
libraryDependencies ++= Seq(
      guice,
      jdbc,
      "com.h2database" % "h2" % "1.4.199",
      // To provide an implementation of JAXB-API, which is required by Ebean.
      "javax.xml.bind" % "jaxb-api" % "2.3.1",
      "javax.activation" % "activation" % "1.1.1",
      "org.glassfish.jaxb" % "jaxb-runtime" % "2.3.2",
      )

変更後はコマンドプロンプトより以下のコマンドを実行して、変更を反映させます。

sbt eclipse

モデルの作成

次にEBeanのモデルを作成していきます。最初にEBeanを格納するパッケージを「conf/application.conf」で指定します。

ebean.default="models.*"

次に「app」フォルダの下に「models」フォルダを作成して、そこに新しく「Invoice.java」を追加します。

package models;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;

import io.ebean.Finder;
import io.ebean.Model;

@Entity
public class Invoice extends Model {
	@Id
	public Long id;

	@NotNull
	public String billno;

	public String slipno;

	public String customerid;

	public String customername;

	public String products;

	public Long number;

	public Long unitprice;

	public Date date;

	public static Finder<Long, Invoice> find = new Finder<Long, Invoice>(Invoice.class);
}

もし上記のコードを追加したときにエラーが表示される場合は、こちらの記事にあるビルド・パスの構成の手順を行ってください。

コントローラーの作成

次に「app/controllers/HomeController.java」を以下のように修正し、Web APIが実行された際の処理を記述します。

package controllers;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.ebean.Finder;
import models.Invoice;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Result;

public class HomeController extends Controller {

	public Result index() {
		return ok(views.html.index.render());
	}

	// 参照  
	public Result select(Long id) {

		Finder<Long, Invoice> finder = new Finder<Long, Invoice>(Invoice.class);

		// JSON変換用クラス
		ObjectMapper mapper = new ObjectMapper();
		String js = "";

		if (id == 0) {
			List<Invoice> list = finder.all();

			try {
				// JSON文字列に変換
				js = mapper.writeValueAsString(list);
			} catch (JsonProcessingException e) {
				e.printStackTrace();
			}
		} else {
			Invoice dto = finder.byId(id);

			try {
				// JSON文字列に変換
				js = mapper.writeValueAsString(dto);
			} catch (JsonProcessingException e) {
				e.printStackTrace();
			}
		}
		return ok(js).as("application/json");
	}

	// 登録
	public Result create(Http.Request request) {

		JsonNode json = request.body().asJson();
		Invoice dto = new Invoice();
		dto.billno = json.get("billno").textValue();
		dto.slipno = json.get("slipno").textValue();
		dto.customerid = json.get("customerid").textValue();
		dto.customername = json.get("customername").textValue();
		dto.products = json.get("products").textValue();
		dto.number = json.get("number").longValue();
		dto.unitprice = json.get("unitprice").longValue();

		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		try {
			dto.date = sdf.parse(json.get("date").textValue());

		} catch (ParseException e) {
			e.printStackTrace();
		}
		dto.save();

		// JSON変換用クラス
		ObjectMapper mapper = new ObjectMapper();
		String js = "";
		try {
			// JSON文字列に変換
			js = mapper.writeValueAsString(dto);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}

		return ok(js).as("application/json");
	}

	// 更新
	public Result update(Long id, Http.Request request) {

		JsonNode json = request.body().asJson();

		Finder<Long, Invoice> finder = new Finder<Long, Invoice>(Invoice.class);
		Invoice dto = finder.byId(id);
		dto.billno = json.get("billno").textValue();
		dto.slipno = json.get("slipno").textValue();
		dto.customerid = json.get("customerid").textValue();
		dto.customername = json.get("customername").textValue();
		dto.products = json.get("products").textValue();
		dto.number = json.get("number").longValue();
		dto.unitprice = json.get("unitprice").longValue();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		try {
			dto.date = sdf.parse(json.get("date").textValue());

		} catch (ParseException e) {
			e.printStackTrace();
		}
		dto.update();
		return select(id);

	}

	// 削除
	public Result delete(Long id) {

		Finder<Long, Invoice> finder = new Finder<Long, Invoice>(Invoice.class);
		Invoice dto = finder.byId(id);
		dto.delete();

		return ok("user deleted");
	}
}

routesファイルの変更

Webブラウザからの各種HTTPメソッドの呼び出しに対応できるよう、「conf/routes」ファイルに以下を追記します。

# 参照(全件)用メソッド
GET     /Invoices           controllers.HomeController.select(id: Long=0)
# 参照(1件)用メソッド
GET     /Invoices/:id       controllers.HomeController.select(id: Long)
# 登録用メソッド
POST    /Invoices           controllers.HomeController.create(request: Request)
# 更新用メソッド
PUT     /Invoices/:id       controllers.HomeController.update(id: Long, request: Request)
# 削除用メソッド
DELETE  /Invoices/:id       controllers.HomeController.delete(id: Long)

CORSフィルタの有効化

次に異なるドメインからもAPIを実行できるように、「conf/application.conf」ファイルに以下の設定を追加し、CORSフィルタを有効化します。

play.filters.enabled += "play.filters.cors.CORSFilter"

Web APIの実行

実際にWeb APIを実行して動作を確認します。コマンドプロンプトからプロジェクトのフォルダにcdコマンドで移動し、以下のコマンドでサーバを起動します。

sbt run

サーバが起動したら、ブラウザに「http://localhost:9000」とURLを入力します。
画面に表示されている[Apply this script now!]をクリックすることで「conf/evolutions/default/1.sql」にエボリューション・スクリプトが作成されます。

エボリューション・スクリプトの実行
スクリプトファイル

次にPostmanなどのツールを使用してリクエストを送信し、テストしてみます。

POST(登録)

http://localhost:9000/Invoices」に対してPOSTリクエストを実行してデータを登録します。以下のJSONをBodyに追加して実行します。日付データはUNIX時間で保持しています。

{
    "billno": "WS-DF502",
    "slipno": "GB465",
    "customerid": "1",
    "customername": "長崎カントリーフーズ",
    "products": "コーヒー 250 ml",
    "number": 100,
    "unitprice": 100,
    "date": "2021-10-05 00:00:00"
}

GET(参照)

http://localhost:9000/Invoices」に対してGETリクエストを実行します。先ほど登録したデータが取得されます。

PUT(更新)とDELETE(削除)

今回は使用しませんが、「http://localhost:9000/Invoices/1」に対してPUTとDELETEのリクエストを実行することで、更新と削除を行うこともできます。以下の例では「customername」の項目を更新しています。

{
    "billno": "WS-DF502",
    "slipno": "GB465",
    "customerid": "1",
    "customername": "長崎カントリーフーズ(更新後)",
    "products": "コーヒー 250 ml",
    "number": 100,
    "unitprice": 100,
    "date": "2021-10-05 00:00:00"
}

DELETE(削除)の実行例は以下のとおりです。

テストデータの登録

この後作成する請求書に表示するテストデータとして、あらかじめ以下のJSONデータをWeb APIから登録しておきます。
※ 登録は1件ずつ実行してください。

{
    "billno": "WS-DF502",
    "slipno": "GB465",
    "customerid": "1",
    "customername": "長崎カントリーフーズ",
    "products": "コーヒー 250 ml",
    "number": 100,
    "unitprice": 100,
    "date": "2021-10-05 00:00:00"
},
{
    "billno": "WS-DF502",
    "slipno": "GB465",
    "customerid": "1",
    "customername": "長崎カントリーフーズ",
    "products": "紅茶 350 ml",
    "number": 300,
    "unitprice": 120,
    "date": "2021-10-05 00:00:00"
},
{
    "billno": "WS-DF502",
    "slipno": "DK055",
    "customerid": "1",
    "customername": "長崎カントリーフーズ",
    "products": "炭酸飲料 (オレンジ) 350 ml",
    "number": 200,
    "unitprice": 120,
    "date": "2021-10-09 00:00:00"
}

レポートファイルの作成

次にActiveReportsJSで出力する帳票のレイアウト情報などを含んだレポートファイルを作成します。今回は以下のGitHubで公開しているものを改修して使用します。

なお、レポートファイルの編集にはActiveReportsJSのデザイナのインストールが必要です。無償で使えるトライアル版もございますので、是非お試しください。

GitHubからダウンロードしたファイルから、「reports/Invoice_green_ipa.rdlx-json」をデザイナで開くと、以下のようなレイアウトのレポートファイルが表示されます。

GitHubから取得したサンプルレポートファイル

データソースの作成

ダウンロードしたレポートファイルはテスト用の埋め込みのJSONデータを使用しているので、新しくデータソースを追加します。以下のようにデザイナ右側の[データ]タブからデータソースの削除と追加を行います。
※ この操作を行う前に、前半で作成したPlay FrameworkのWeb APIを起動しておいてください。

「データソースの編集」のダイアログが開くので、「データプロバイダ」に「Remote JSON」を選択し、「エンドポイント」に以下のURLを設定し、[変更を保存]をクリックします。

http://localhost:9000/Invoices
エンドポイントの設定

データセットの作成

データソースを作成したらデータセットを作成します。デザイナ右側のメニューから作成したデータソースの右側にある[+]アイコンをクリックします。

データセットの追加

「新規データセット」ダイアログで、「JSONパス」に以下の値を設定し、[検証]をクリックすると、データベースフィールドが9件作成されます。

$.*

次に、製品の単価と購入数量から購入金額を計算する計算フィールドを追加します。
「計算フィールド」の右側の[+]アイコンをクリックして計算フィールドを追加したら、フィールド名に「price」、値に以下の式を設定し、[変更を保存]をクリックして、ダイアログを閉じます。

{unitprice * number}

日付データの変換

今回作成する請求書に表示する日付データはUNIX時間で保存されているので、Tableコントロールの詳細行の、「日付」の項目に設定されている値「{date}」を以下の式に変更し、ActiveReportsJSがサポートする「ISO 8601」のフォーマットに変更します。

{DateTime.Parse(DateAdd("s", date / 1000, "1970-01-01 00:00:00+00:00"))}

デザイナ上でプレビューの実行

以上でデータソース/データセットの設定は完了です。このレポートファイルはすでにTextBoxなどのレポートコントロールと各フィールドとのバインド設定が完了していますので、このままプレビューが実行可能です。
H2データベースに登録したデータが帳票に表示されるのが確認できます。

帳票出力機能の追加

レポートファイルの改修が完了したら、こちらを使用してPlay Frameworkに帳票出力機能を追加していきます。

静的ファイルの配置

ActiveReportsJSのJavaScriptファイルやCSSファイルなどの静的ファイルを「public」フォルダ配下の「javascripts」フォルダと「stylesheets」フォルダに配置していきます。 「javascripts」フォルダ配下には新しく「locales」フォルダも作成し、ActiveReportsJSのトライアル版、または製品版のZipファイルに含まれる「dist」フォルダから以下のファイルをコピーします。
(※1)印のファイルは、各エクスポート処理を行う場合に必要です。

  • javascripts
    • ar-js-core.js
    • ar-js-viewer.js
    • ar-js-pdf.js(※1)
    • ar-js-xlsx.js(※1)
    • ar-js-html.js(※1)
  • javascripts/locales
    • ar-js-locales.js
  • stylesheets
    • ar-js-ui.css
    • ar-js-viewer.css

次に同じ「public」フォルダ配下に「reports」フォルダを作成し、先ほど修正したレポートファイル「Invoice_green_ipa.rdlx-json」を配置します。

さらに同じ「public」フォルダ配下に「fonts」フォルダを作成し、GitHubからダウンロードしたファイルから、レポートファイル中で使用しているフォントファイル「ipag.ttf」をコピーして配置します。
※ PDFエクスポートを行わない場合は、フォントファイルの配置は不要です。

静的ファイルの配置

Scalaテンプレートの作成

次にPlay FrameworkのビューとなるScalaテンプレートを作成します。「views/index.scala.html」ファイルの内容を以下のように修正し、ActiveReportsJSのJavaScriptファイルやCSSファイルなどの静的ファイルへの参照を追加します。また、divタグで帳票を表示するビューワの領域を定義します。

<!DOCTYPE html>
<html lang="ja">

<head>
	<title>ActiveReportsJSビューワ</title>
	<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
	<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
	<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/ar-js-ui.css")" />
	<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/ar-js-viewer.css")" />
	<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
	<script type="text/javascript" src="@routes.Assets.versioned("javascripts/ar-js-core.js")"></script>
	<script type="text/javascript" src="@routes.Assets.versioned("javascripts/ar-js-viewer.js")"></script>
	<script type="text/javascript" src="@routes.Assets.versioned("javascripts/ar-js-pdf.js")"></script>
	<script type="text/javascript" src="@routes.Assets.versioned("javascripts/ar-js-xlsx.js")"></script>
	<script type="text/javascript" src="@routes.Assets.versioned("javascripts/ar-js-html.js")"></script>
	<script type="text/javascript" src="@routes.Assets.versioned("javascripts/locales/ar-js-locales.js")"></script>

</head>

<body>
	<div id="ARJSviewerDiv" style="height: 100vh"></div>
</body>

</html>

テンプレートの中で参照している「public/javascript/main.js」の中にビューワの初期化処理を記述します。

document.addEventListener("DOMContentLoaded", function() {

	const viewer = new ActiveReports.Viewer('#ARJSviewerDiv', {
		language: 'ja'
	});
	// PDFエクスポートを行う場合はフォントの登録を行う
	var IPAGothic = {
		name: "IPAゴシック",
		source: "/assets/fonts/ipag.ttf"
	};
	GC.ActiveReports.Core.FontStore.registerFonts(IPAGothic);
	viewer.open("/assets/reports/Invoice_green_ipa.rdlx-json");
})

ビルドと実行

以上でビューの設定も完了です。以下のコマンドを実行してアプリケーションを起動します。

sbt run

http://localhost:9000/」にアクセスすると、以下のようにブラウザ上で帳票が出力されます。サイドメニューからPDFなど各種形式への保存も可能です。
※ インメモリでH2データベースを使用しているので、アプリケーションを再起動した際に、エボリューション・スクリプトの実行とデータの登録が再度必要です。

おわりに

以上がPlay FrameworkでActiveReportsJSを使用して帳票を出力する方法でした。フロントエンドで動作する帳票ライブラリであるActiveReportsJSは様々なWebアプリケーションフレームワークとの連携が可能ですので、気になった方は是非無料のトライアル版をお試しください。