Struts2+Thymeleaf3で表形式出力

# Struts2で表形式の出力をする方法

Struts2で表形式の出力をする一番単純な方法は java.util.List 実装クラス(ArrayList)に格納して、アクセサ(getメソッド)を用意します。

```java:SnipetAction.java
List<SampleDTO> results;

public List<SampleDTO> getResults {
  return results;
}
```

こうすると、View側も繰り返し処理用のタグで results の名前で扱えます。
今回はView側のテンプレートに、Struts2標準のJSPではなく、Thymeleaf3を選びました。

## Struts2-Thymeleaf3プラグインの入手と設定

[Qiita:Struts2.5でThymeleaf3を使う](http://qiita.com/alpha_pz/items/5a43b4abb07141becbcd) で公開しております。
mavenリポジトリにも公開してますので、例えばEclipseのmavenプラグインを使って、アーティファクト名(jar)を、

struts2-thymeleaf3-plugin

と入れるとサジェストしてくれるでしょう。

## Actionクラス

Struts2-Conventionプラグインを使ったActionクラスにしております。アノテーションベースなのでActionクラス用に設定ファイルを作りません。

```java:DisplayListAction.java
package serendip.sturts.thymeleaf.struts2_thymeleaf_sampleapp.actions;

import java.util.List;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

import com.opensymphony.xwork2.ActionSupport;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import serendip.sturts.thymeleaf.struts2_thymeleaf_sampleapp.model.ProductService;
import serendip.sturts.thymeleaf.struts2_thymeleaf_sampleapp.model.SampleProduct;

/**
 * <code>Set welcome message.</code>
 */
@Namespace("/")
@ParentPackage("struts-thymeleaf")
@Results({@Result(name=ActionSupport.SUCCESS,type="thymeleaf",location="list")})
@Log4j2
public class DisplayListAction extends ActionSupport {

	@Action("list")
    public String execute() throws Exception {

		ProductService service = new ProductService();

		products = service.search();
		log.info("- search:{}" , products);

        return SUCCESS;
    }

	@Getter @Setter
	List<SampleProduct> products;

}
```

このActionクラスでは、検索処理を模したProductServiceから、検索結果として SampleProduct インスタンスを複数取得したListに格納します。

### Actionクラスから呼び出す ProductService

本来ならデータベースなど外部ストレージから検索結果を取得したものを返すクラスですが、今回は定型的なものを返す、モッククラスを作成しました。

```java:ProductService.java
package serendip.sturts.thymeleaf.struts2_thymeleaf_sampleapp.model;

import java.util.Arrays;
import java.util.List;

/**
 * @author A-pZ
 *
 */
public class ProductService {
	public List<SampleProduct> search() {
		List<SampleProduct> resultList =

		Arrays.asList(
			new SampleProduct("A-1", "試作品X-290PA1", 10, true, true),
			new SampleProduct("A-2", "試作品X-290PA2", 20, true, true),
			new SampleProduct("B-3", "試作品X-B3", 10, true, true),
			new SampleProduct("B-4", "試作品X-B4", 30, true, true),
			new SampleProduct("C-5", "試作品X-C5", 10, true, false),
			new SampleProduct("C-6", "試作品X-C6", 40, true, false),
			new SampleProduct("D-7", "試作品X-D7", 10, false, true),
			new SampleProduct("D-8", "試作品X-D8", 50, false, true),
			new SampleProduct("E-9", "試作品X-E9", 10, true, true),
			new SampleProduct("Z-1000", "試作品Z-1000", 0, false, false)
		);

		return resultList;
	}
}
```

検索結果に使われるSampleProductクラスは以下のとおりです。

```java:SampleProduct.java
package serendip.sturts.thymeleaf.struts2_thymeleaf_sampleapp.model;

import java.io.Serializable;

import lombok.Data;

/**
 * @author A-pZ
 *
 */
@Data
public class SampleProduct implements Serializable {
	public SampleProduct(String id, String name, long stock, boolean secret, boolean editable) {
		super();
		this.id = id;
		this.name = name;
		this.stock = stock;
		this.secret = secret;
		this.editable = editable;
	}
	private String id;
	private String name;
	private long stock;
	private boolean secret;
	private boolean editable;
}
```

## Thymeleaf3のテンプレートHTML

Actionクラスから、検索結果は **products** の名前で扱っていますので、テンプレートでも products で参照します。

Struts2-Thymeleaf3-pluginで扱うThymeleafテンプレートは、Thymeleaf3テンプレートそのままで、特に変わったことはしていません。

```html:list.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:th="http://www.thymeleaf.org"
	xmlns:sth="http://serendip.thymeleaf">
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
</head>
<body>
<div class="container">
	<table class="table table-bordered">
		<thead>
			<th>id</th>
			<th>製品名</th>
			<th>在庫数</th>
			<th>公開可？</th>
			<th>編集可？</th>
		</thead>
		<tbody>
			<tr th:each="product : ${products}">
				<td th:text="${product.getId()}"></td>
				<td th:text="${product.getName()}"></td>
				<td th:text="${product.getStock()}"></td>
				<td th:text="${product.isSecret()?'👍':''}"></td>
				<td th:text="${product.isEditable()?'👍':''}"></td>
			</tr>
		</tbody>
	</table>
</div>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
</body>
</html>
```

secretとisEditableについてはbooleanなので、trueなら👍の絵文字を出すようにしています。

繰り返し表示もとても簡単ですね！

## 出力結果

今回の例では bootstrap 4.0.0-alpha6 で出しました。

他にもナビゲーションバーやデフォルトのスタイルを当てると、次のような画面になるでしょう。

![struts2-thymeleaf3-list-display.png](https://qiita-image-store.s3.amazonaws.com/0/13268/3fcfb385-644e-9819-e13f-71dbae9f3b73.png)