スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Spring MVC 一覧から各行の編集画面を呼び出す

今までの記事で、ログイン画面、登録画面、検索画面を作成してきた。

今回は、検索画面の結果一覧の各行に「編集」リンクを追加して、編集画面から更新できるようにしてみる。

それに伴い、以下の対応も併せて行い、アプリケーションらしくしてみる。
 ①今まで説明の都合上、TOPを登録画面にしていたが、検索画面に変更する。
 ②編集画面と作成済の登録画面は構成がほぼ同じのため共通化してみる。
 ③登録画面は検索画面にリンクを貼って、そこから呼び出すように変更する。

なお、今回の記事でベースとなっている過去の記事は以下の通り
■検索に関する過去の記事
Spring MVC Spring Data JPAを使って検索する

■登録に関する過去の記事
Spring MVC Spring Data JPAを使って登録する
Spring MVC ファイルアップロードを実装する[DB登録編]

■画面レイアウトに関する過去の記事
Spring MVC Tilesを使って画面をレイアウトする

■国際化に関する過去の記事
Spring MVC メッセージの国際化に対応する

◎動作検証にあたっての各バージョンは以下の通り
  • SpringFramework 3.2.8.RELEASE
  • Java 1.7
  • Tomcat 7.0

1.BookController.java

package jp.co.sample.book.controller;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import javax.validation.Valid;

import jp.co.sample.book.entity.BookEntity;
import jp.co.sample.book.form.BookForm;
import jp.co.sample.book.form.BookSearchForm;
import jp.co.sample.book.service.BookService;
import jp.co.sample.util.BookUtil;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping("/book")
public class BookController {

private static Logger logger = LoggerFactory.getLogger(BookController.class);

@Value("${upload.fileSize}")
private int uploadFileSize;

@Autowired
protected BookService<BookEntity> bookService;

@RequestMapping(method = RequestMethod.GET)
public String index() {
logger.info("index start");
return "redirect:/book/list";
}

@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(BookSearchForm form, Model model) {
logger.info("list start");
List<BookEntity> entites = bookService.findAll();
model.addAttribute("books", entites);
return "list";
}

@RequestMapping(value = "/list/search", method = RequestMethod.GET)
public String search(BookSearchForm form, Model model) {
logger.info("search start");
List<BookEntity> entites = bookService.findByNameLike(form.getBookName());
model.addAttribute("books", entites);
return "list";
}

@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add (BookForm form) {
logger.info("add start");
return "editBook";
}

@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
public String edit (@PathVariable("id") long bookId, BookForm form, Model model) {
logger.info("edit start");
BookEntity entity = bookService.findById(bookId);
model.addAttribute("book", entity);
return "editBook";
}

@RequestMapping(value = "/update", method = RequestMethod.POST)
public String update(@Valid BookForm form, BindingResult result, Model model,
@RequestParam(value="uploadFile",required=false) MultipartFile file) {

logger.info("update start");
if (file.getSize() > uploadFileSize) {
result.rejectValue("cover", "error.fileSize",
new Object[]{uploadFileSize},"File size error");
}
if (result.hasErrors()) {
return "editBook";
}

if (!file.isEmpty()) {
byte[] fileContent = null;
InputStream is = null;
try {
is = file.getInputStream();
fileContent = IOUtils.toByteArray(is);
form.setCover(fileContent);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(is);
}
}

BookEntity entity = BookUtil.copyProperties(form);
//登録・編集の判定
if (entity.getBookId() > 0) {
bookService.editEntity(entity);
} else {
bookService.addEntity(entity);
}
model.addAttribute("book", entity);
return "result";
}

@RequestMapping(value = "/cover/{id}", method = RequestMethod.GET)
@ResponseBody
public byte[] getCover (@PathVariable("id") long bookId) {
logger.info("getCover start");
BookEntity entity = bookService.findById(bookId);
return entity.getCover();
}
}

44行目の「public String index()」メソッドであるが、「return "redirect:/book/list";」としているようにリダイレクトして、TOPの検索画面に一覧を表示するようにしている。
65行目の「public String add (BookForm form)」メソッドは、新規登録と編集画面を共通化した「editBook」を返すようにしている。
71行目の「public String edit (省略)」メソッドについては、検索結果一覧の各行のIDを受け取り、「model.addAttribute("book", entity);」にてエンティティをJSPから参照できるようにしている。
79行目の「 public String update(省略)」メソッドについては、新規登録と編集の両方に対応している。新規登録と編集の判定方法については、新規登録ではIDが未設定(初期値0となる)のため、「if (entity.getBookId() > 0)」(107行目)を判定条件としている。

2.BookDaoImpl.java(関連箇所のみ掲載)

@Repository
@Transactional
public class BookDaoImpl implements BookDao<BookEntity> {

private static Logger logger = LoggerFactory.getLogger(BookDaoImpl.class);

@PersistenceContext
private EntityManager manager;

@Override
public void editEntity(BookEntity entity) {
logger.info("editEntity start");
manager.merge(entity);
}
//他のメソッドは省略

今回新たに追加したeditEntityメソッドのみ掲載している。新規の場合は「manager.persist(entity);」であったが、更新の場合は「manager.merge(entity);」となる。

3.view.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Cnfiguration 2.1//EN"
"http://tiles.apache.org/dtds/tiles-config_2_1.dtd">

<tiles-definitions>
<definition extends="default" name="editBook">
<put-attribute name="body" value="/WEB-INF/view/editBook.jsp"/>
</definition>

<definition extends="default" name="list">
<put-attribute name="body" value="/WEB-INF/view/list.jsp"/>
</definition>
</tiles-definitions>

画面レイアウトの設定で、defaultには「header/body/footer」という構成が定義されており、「editBook.jsp」と「list.jsp」をbodyとするようにしている。

4.messages_ja.properties

NotEmpty={0}は必須入力です
bookId=ID
bookName=タイトル
price=価格
cover=カバー
welcome=ようこそ!
error.fileSize=ファイルサイズが{0}バイトを超えています
button.update=更新
button.search=検索
link.add=新規登録
link.edit=編集

国際化対応により日本語の設定をしている。この後掲載するJSPにて使用している。

5.messages_en.properties

NotEmpty={0} is required input.
bookId=Id
bookName=Title
price=Price
cover=Cover
welcome=Welcome!
error.fileSize=File size exceeds {0} bytes.
button.update=Update
button.search=Search
link.add=Create
link.edit=Edit

こちらは英語の設定。

6.list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>
<link href="<c:url value="/css/bootstrap.min.css" />" rel="stylesheet">
<link href="<c:url value="/css/bootstrap-theme.min.css" />" rel="stylesheet">
<script src="<c:url value="/js/bootstrap.min.js" />"></script>
<title>list</title>
</head>
<body>
<spring:url value="/book/cover" var="varBookCoverUrl"/>
<spring:url value="/book/add" var="varBookAddUrl"/>
<spring:url value="/book/edit" var="varBookEditUrl"/>
<spring:message code="bookId" var="varBookId"/>
<spring:message code="bookName" var="varBookName"/>
<spring:message code="price" var="varPrice"/>
<spring:message code="cover" var="varCover"/>
<spring:message code="button.search" var="varButtonSearch"/>
<spring:message code="link.add" var="varLinkAdd"/>
<spring:message code="link.edit" var="varLinkEdit"/>

<div class="container-fluid">
<div class="row">
<form:form
action="${pageContext.request.contextPath}/book/list/search"
method="get" modelAttribute="bookSearchForm">
<form:label path="bookName" class="col-sm-3 text-right">${varBookName}</form:label>
<form:input path="bookName" class="col-sm-6"/>
<div class="col-sm-3">
<input type="submit" value="${varButtonSearch}" class="btn btn-info btn-sm"/>
</div>
</form:form>
</div>
<hr/>
<div class="row">
<div class="col-sm-9"></div>
<div class="col-sm-3 text-right">
<a href="${varBookAddUrl}">${varLinkAdd}</a>
</div>
</div>
<table class="table table-striped table-bordered">
<tr>
<th>${varBookId}</th>
<th>${varCover}</th>
<th>${varBookName}</th>
<th>${varPrice}</th>
<th></th>
</tr>
<c:forEach items="${books}" var="book">
<tr>
<td>${book.bookId}</td>
<td><img src="${varBookCoverUrl}/${book.bookId}"/></td>
<td>${book.bookName}</td>
<td>${book.price}</td>
<td>
<a href="${varBookEditUrl}/${book.bookId}">${varLinkEdit}</a>
</td>
</tr>
</c:forEach>
</table>
</div>
</body>
</html>

44行目の「<a href="${varBookAddUrl}">${varLinkAdd}</a>」にて、「新規登録」リンクを設定。
62行目の「<a href="${varBookEditUrl}/${book.bookId}">${varLinkEdit}</a>」にて各行に「編集」リンクを設定している。

7.editBook.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<link href="<c:url value="/css/bootstrap.min.css" />" rel="stylesheet">
<link href="<c:url value="/css/bootstrap-theme.min.css" />" rel="stylesheet">
<script src="<c:url value="/js/bootstrap.min.js" />"></script>
<title>editBook</title>
</head>
<body>
<spring:message code="bookId" var="varBookId"/>
<spring:message code="bookName" var="varBookName"/>
<spring:message code="price" var="varPrice"/>
<spring:message code="cover" var="varCover"/>
<spring:message code="button.update" var="varButtonUpdate"/>

<c:if test="${not empty book.bookName}">
<c:set var="varBookNameValue" value="${book.bookName}" />
</c:if>
<c:if test="${not empty book.price}">
<c:set var="varPriceValue" value="${book.price}" />
</c:if>

<div class="container-fluid">
<form:form
action="${pageContext.request.contextPath}/book/update"
method="post" modelAttribute="bookForm" class="well" enctype="multipart/form-data">
<c:if test="${not empty book.bookId}">
<div class="form-group">
<form:label path="bookId" class="control-label">${varBookId}</form:label>
<form:input path="bookId" value="${book.bookId}" class="form-control" readonly="true"/>
</div>
</c:if>
<div class="form-group">
<form:label path="bookName" class="control-label">${varBookName}</form:label>
<form:input path="bookName" value="${varBookNameValue}" class="form-control" />
<p class="help-block"><font color="red"><form:errors path="bookName" /></font></p>
</div>
<div class="form-group">
<form:label path="price" class="control-label">${varPrice}</form:label>
<form:input path="price" value="${varPriceValue}" class="form-control"/>
<p class="help-block"><font color="red"><form:errors path="price" /></font></p>
</div>
<div class="form-group">
<label for="uploadFile" class="control-label">${varCover}</label>
<input name="uploadFile" type="file" class="form-control"/>
<p class="help-block"><font color="red"><form:errors path="cover" /></font></p>
</div>
<div class="form-group">
<input type="submit" value="${varButtonUpdate}" class="btn btn-primary"/>
</div>
</form:form>
</div>
</body>
</html>

新規登録と編集を共通化したJSPとなる。共通化する方法はいろいろあると思うが、今回は「c:if」タグ(22~27行目)と「form:input」タグのvalue属性(36,41,46行目)を使い、エンティティが存在する編集の場合は値が表示され、存在しない新規登録の場合は何も表示されないようにしてみた。
また、bookIdについては、新規登録では不要なため、「c:if」(33~38行目)タグで同様に制御している。編集でも変更できないよう「readonly="true"」(36行目)としている。
なお、アップロードファイルの項目(50,51行目)については、前回登録時のパス情報を保持していないため、再度選択し直すようにしている。

8.画面
①検索画面(TOP)
初期一覧画面

IDの1と2については、編集画面にてカバーを更新している。3については、新規登録したデータとなる。

②登録画面
新規画面

登録画面の初期表示であるが、IDが表示されず、各項目も空となっている。

③編集画面
編集画面

ID2の「編集」リンクをクリックして表示された状態であるが、「タイトル」と「価格」に値が設定されている。

今回の対応により、まだまだいろいろと不足しているが、アプリケーションらしくなってきたと思う。

■過去のSpring関連記事
Spring関連記事 Index

スポンサーサイト

コメントの投稿

非公開コメント

プロフィール

bookmount8

Author:bookmount8
システムエンジニア。サーバーサイドでjavaを扱うことが多い。最近は、ミドルやフロント周りも関心あり。

最新記事
カテゴリ
検索フォーム
最新コメント
月別アーカイブ
これまでの訪問者数
ブロとも申請フォーム

この人とブロともになる

RSSリンクの表示
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。