今回は、前回作成した店舗エンティティ(Shop) のデータを一覧表示するビューを作成します。 一覧表示のために jQuery の dataTables プラグインを利用します。このプラグインを使えば、ページング機能や列ごとのソートなど、一般的に必要な機能を簡単に実現できます。(使い方についての詳細は次を参照してください:http://www.datatables.net)
メニューの変更
まずは、このシリーズの1回目で作成したメニュー・フラグメント (frag01.html :: navbar)を修正して、店舗一覧を表示する機能(/shop/list)を追加します。shops テーブルにデータを1000件追加する機能(/shop/add1000)、全件削除する機能(/shop/deleteAll)も追加します。
WEB-INF/views/fragment/frag01.html (navbar フラグメント):
dataTables プラグインの追加
メニューと同じように HTMLヘッダ・フラグメント (frag01.html :: html)を修正して、dataTables プラグインを使用できるようにします(jquery.dataTables.css と jquery.dataTables.js を追加)。
WEB-INF/views/fragment/frag01.html (htmlhead フラグメント):
dataTables に JSON形式でデータを渡す
実際のアプリでは一度に何万件ものデータを取得して、何千ページもページを切り替えるような仕様はユーザーにとって使いにくいものです。そのため、検索やフィルタ処理で取得件数を数十件から多くても数百件に絞り込むことが考えられます。この程度の件数であればビューの表示時間は許容範囲内に抑えられます。このアプリでは、dataTables のデータ取得を ajax 対応にして、何千ページものページングを行う仕様の場合でも、実際に取得する件数は指定ページの1ページ分の行数だけ取得する事で、レスポンスを一定時間内に抑えるようにします。dataTables に1ページ分のデータを渡すオブジェクトを作成します。
com.itrane.mvcdemo.comand.DataTableObject:
コントローラの追加
ShopController クラスを新規作成して、店舗データ追加機能、店舗一覧を表示する機能を作成します。
com.itrane.mvcdemo.controller.ShopController:
ビューの作成
ShopController の listShop( ) メソッドの実行の結果表示されるビューを作成します。
WEB-INF/views/shop/list_shop.html:
実行結果
アプリケーション起動後、店舗データを1000件追加して、店舗一覧の10ページを表示した結果は次のようになります:
まとめ
データの一覧を表示して、選択行に対して様々な操作を行うのは、アプリケーションの一般的な機能です。dataTables プラグインを使用する事で、データ一覧機能として要求される様々な機能や外観を簡単に実現できます。アプリによっては特別な要件を満たす統一したデザインのテーブル機能を独自に開発する方が簡単ですが、多くの場合は dataTables をカスタマイズして簡単に対応できます。
ソースコード
このコードは GitHub-branch:mvcdemo04 からダウンロードできます。
メニューの変更
まずは、このシリーズの1回目で作成したメニュー・フラグメント (frag01.html :: navbar)を修正して、店舗一覧を表示する機能(/shop/list)を追加します。shops テーブルにデータを1000件追加する機能(/shop/add1000)、全件削除する機能(/shop/deleteAll)も追加します。
WEB-INF/views/fragment/frag01.html (navbar フラグメント):
...上記のコードでは、「店舗一覧」、「全件削除」、「1000件追加」の各メニューで @{...} 式を使って、リンク URL を設定します。<a>タグの th:href 属性の url 式 "@{/shop/list}"は href 属性で "/mvcdemo/shop/list"を指定するの同じです。
<!-- ナビゲーション・バー -->
<div th:fragment="navbar">
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/}">Spring MVC デモ</a>
</div>
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">About</a></li>
<li class="dropdown"><a href="#"class="dropdown-toggle"
data-toggle="dropdown"> 店舗管理 <b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li><a th:href="@{/shop/list}">店舗一覧(ajax)</a></li>
<li class="divider"></li>
<li><a th:href="@{/shop/deleteAll}">全件削除</a></li>
<li><a th:href="@{/shop/add1000}">1000件追加</a></li>
</ul></li>
</ul>
</div>
</div>
</nav>
</div>
...
dataTables プラグインの追加
メニューと同じように HTMLヘッダ・フラグメント (frag01.html :: html)を修正して、dataTables プラグインを使用できるようにします(jquery.dataTables.css と jquery.dataTables.js を追加)。
WEB-INF/views/fragment/frag01.html (htmlhead フラグメント):
...
<!-- HTMLヘッダ -->
<head th:fragment="htmlhead">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title th:text="${title}">(title)</title>
<!-- CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="//cdn.datatables.net/1.9.4/css/jquery.dataTables.css" />
<!-- HTML5 shim, IE6-8で HTML5 要素をサポートする -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- JavaScript : jquery, bootstrap, custom -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="//cdn.datatables.net/1.9.4/js/jquery.dataTables.js"></script>
</head>
...
dataTables に JSON形式でデータを渡す
実際のアプリでは一度に何万件ものデータを取得して、何千ページもページを切り替えるような仕様はユーザーにとって使いにくいものです。そのため、検索やフィルタ処理で取得件数を数十件から多くても数百件に絞り込むことが考えられます。この程度の件数であればビューの表示時間は許容範囲内に抑えられます。このアプリでは、dataTables のデータ取得を ajax 対応にして、何千ページものページングを行う仕様の場合でも、実際に取得する件数は指定ページの1ページ分の行数だけ取得する事で、レスポンスを一定時間内に抑えるようにします。dataTables に1ページ分のデータを渡すオブジェクトを作成します。
com.itrane.mvcdemo.comand.DataTableObject:
publicclass DataTableObject<T> implements Serializable {このオブジェクトは、データベースから取得した1ページ分の店舗データ aaData と、dataTables のページングを制御するための sEcho, iTotalRecords, iTotalDisplay の値を保持します。Spring MVC ではコントローラからビューへデータを様々な方法で送る事ができます。ここでは、上記オブジェクトを Json形式に変換してビューへ送ります。
privatestaticfinallong serialVersionUID = 1L;
private List<T> aaData;
privateint sEcho;
private Long iTotalRecords;
private Integer iTotalDisplayRecords;
//getter , setter は省略
...
}
コントローラの追加
ShopController クラスを新規作成して、店舗データ追加機能、店舗一覧を表示する機能を作成します。
com.itrane.mvcdemo.controller.ShopController:
@Controller上記の各メソッドについて、簡単に説明します。
@RequestMapping("/shop")
publicclass ShopController {
finalstaticprivate Logger logger = LoggerFactory.getLogger(ShopController.class);
@Autowired
private ShopService shopService;
/**
* 店舗一覧ビューを表示する.
* @return ModelAndView: 表示ビューは shop_list.html
*/
@RequestMapping(value = "/list")
public ModelAndView shopList() {
logger.debug("");
ModelAndView mav = new ModelAndView("/shop/shop_list");
return mav;
}
/**
* 店舗一覧ビューの dataTables で指定ページのデータを取得する.
* @param HttpServletRequest: dataTables の表示ページ、表示件数、ソート列等を取得。
* @return String: DataTableObject<Shop> の JSon形式
*/
@RequestMapping(value = "/page", produces="text/html;charset=UTF-8")
public @ResponseBody String getShopPage(HttpServletRequest request) {
Map<String, String[]> params = request.getParameterMap();
String sortCol = params.get("mDataProp_"
+ getParam("iSortCol_0", params, 0))[0];
String sortDir = getParam("sSortDir_0", params, "");
int iDisplayLength = getParam("iDisplayLength", params, 10);
int pageNum = getParam("iDisplayStart", params, 0) / iDisplayLength;
logger.debug("sortCol="+sortCol+", sortDir=" + sortDir
+ ", page=" + pageNum + ", size=" + iDisplayLength);
List<Shop> shopList = shopService.findByPage(pageNum,
iDisplayLength, sortDir, new String[] {sortCol} );
long total = shopService.countShops();
DataTableObject<Shop> dt = new DataTableObject<Shop>();
dt.setAaData(shopList);
dt.setiTotalDisplayRecords((int)total);
dt.setiTotalRecords(total);
dt.setsEcho(getParam("sEcho", params, 0));
return toJson(dt);
}
privateint getParam(String key, Map<String, String[]> params, int def) {
String[] vals = params.get(key);
if (vals==null) {
return def;
} else {
return Integer.valueOf(vals[0]);
}
}
private String getParam(String key, Map<String, String[]> params, String def) {
String[] vals = params.get(key);
if (vals==null) {
return def;
} else {
return vals[0];
}
}
private String toJson(Object dt){
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(dt);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
/**
* 店舗テーブルの全データを削除する.
* @return ModelAndView: 表示ビューは shop_list.html
*/
@RequestMapping(value="/deleteAll", method=RequestMethod.GET)
public ModelAndView delAll() {
logger.debug("");
ModelAndView mav = new ModelAndView("/shop/shop_list");
shopService.deleteAll();
return mav;
}
/**
* 店舗テーブルに1000件のデータを追加する.
* @return ModelAndView: 表示ビューは shop_list.html
*/
@RequestMapping(value="/add1000", method=RequestMethod.GET)
public ModelAndView add1000() {
logger.debug("");
ModelAndView mav = new ModelAndView("/shop/shop_list");
//1000件追加処理
long cnt = shopService.countShops();
List<Shop> shops = new ArrayList<Shop>(1000);
for (long i=cnt+1; i<=cnt+1000; i++) {
Shop s = new Shop();
s.setName("店舗" + (10000000 + i));
s.setPhone("03" + (10000000 + i));
s.setEmail("tenpo" + (10000000 +i) + "@mvc.com");
s.setKaitenBi("2014/01/01");
int n = (int)((i - cnt) % 80) + 1;
s.setEmplNumber(10+80%n);
shops.add(s);
}
shopService.create(shops);
return mav;
}
}
- shopList( ) メソッド:
店舗一覧ビュー "/shop/shop_list.htm"を表示します。 - page( ) メソッド:
店舗一覧ビューで、dataTables ページング制御の各ボタンが押されたときに呼び出される。押されたボタンに応じたリクエストパラメータが渡されるのでそれを解析して、データベースから該当ページのデータを取得します。List<Shop> shopList = shopService.findByPage(pageNum,
取得したデータと dataTables の制御用データを、DataTableObject インスタンスに設定し、toJson( ) メソッドで Json 形式のデータに変換して、ビューに送ります。
iDisplayLength, sortDir, new String[] {sortCol} ); - deleteAll( ) メソッド:
店舗テーブル(shops)の全データを削除します。shopService.deleteAll();
- add1000( ) メソッド:
店舗テーブルに自動生成したデータ1000件を追加します。long cnt = shopService.countShops();
List<Shop> shops = new ArrayList<Shop>(1000);
for (long i=cnt+1; i<=cnt+1000; i++) {
Shop s = new Shop();
...
shops.add(s);
}
shopService.create(shops);
ビューの作成
ShopController の listShop( ) メソッドの実行の結果表示されるビューを作成します。
WEB-INF/views/shop/list_shop.html:
<!DOCTYPE html>上記のコードのは javascript のパートと HTML パートで構成されています。
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:include="fragment/frag01 :: htmlhead"
th:with="title='Spring MVC デモ'"></head>
<style>
.contents { margin-top: 30px; }
.table-container{ width:1100px; }
.btn_edit{ font-size: 14px; }
.btn_delete{ font-size: 14px; }
.alignRight { text-align: right; }
</style>
<script>
<!--
$(document).ready(function () {
var oTable = $("#shops").dataTable({
"bProcessing": true,
"bServerSide": true,
"bLengthChange": true,
"sAjaxSource": "/mvcdemo/shop/page.html",
"aLengthMenu": [[10, 20,],[10, 20]],
"aoColumns": [
{"sTitle": "ID", "mData": "id", "sWidth": 40, "sClass": "center" },
{"sTitle": "店舗名", "mData": "name", "sWidth": 200 },
{"sTitle": "電話番号", "mData": "phone", "sWidth": 70 },
{"sTitle": "メール", "mData": "email", "sWidth": 240 },
{"sTitle": "開店日", "mData": "kaitenBi", "sWidth": 60 },
{"sTitle": "従業員数", "mData": "emplNumber", "sWidth": 100, "sClass": "alignRight",
"fnCreatedCell": function (td, cellData, rowData, row, col) {
if ( cellData > 15 ) {
$(td).css('color', 'red');
}
}
},
],
"fnServerData": function(sSource, aoData, fnCallback) {
$.ajax({
"dataType" : 'json',
"contentType" : "application/json;charset=UTF-8",
"type" : "GET",
"url" : sSource,
"data" : aoData,
"success" : fnCallback
});
},
"oLanguage": {
"sLengthMenu": "表示 _MENU_",
"sSearch": "検索:",
"oPaginate": {
"sFirst": "先頭 ",
"sLast": "最後",
"sNext": "次 ",
"sPrevious": "前"
},
"sInfo": "_START_件 ~ _END_件 / _TOTAL_件"
},
"sPaginationType" : "full_numbers"
}); //dataTables
});
-->
</script>
<body>
<!-- $$$ Navigation Bar $$$$$ -->
<div th:include="fragment/frag01 :: navbar"></div>
<div class="contents">
<div class="container">
<!-- $$$ Contents Header $$$$$ -->
<div class="row">
<div class="col-lg-12">
<h1 class="page-header"> 店舗一覧
<small>(ajaxバージョン)</small>
</h1>
</div>
</div>
<!-- $$$ Contents Body $$$$$ -->
<div class="row">
<div class="col-lg-12">
<div class="table-container">
<table id="shops"class="display">
<thead>
<tr>
<th>id</th>
<th>店舗名</th>
<th>電話番号</th>
<th>メール</th>
<th>開店日</th>
<th>従業員数</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<br /><br />
</div>
<!-- $$$ Contents Footer $$$$$ -->
<div th:include="fragment/frag01 :: footer"></div>
</div>
</div>
</div> <!-- Container END -->
</div> <!-- Contents END -->
</body>
</html>
- HTML のパート:重要なのは <table>..</table> の部分です。 dataTables プラグインによって、<tbody>..</tbody> の部分が取得データで置き換えられます。
- javascript のパート:テーブルの表示形式や、データの取得方法を指定します。
主な部分について説明します
実行結果
アプリケーション起動後、店舗データを1000件追加して、店舗一覧の10ページを表示した結果は次のようになります:
まとめ
データの一覧を表示して、選択行に対して様々な操作を行うのは、アプリケーションの一般的な機能です。dataTables プラグインを使用する事で、データ一覧機能として要求される様々な機能や外観を簡単に実現できます。アプリによっては特別な要件を満たす統一したデザインのテーブル機能を独自に開発する方が簡単ですが、多くの場合は dataTables をカスタマイズして簡単に対応できます。
ソースコード
このコードは GitHub-branch:mvcdemo04 からダウンロードできます。