2008年5月26日月曜日

Spring MVCでチェックボックスを使う

前回はradioボタンでしたが、今回はチェックボックスを使ってみます。
コントローラはSimpleFormControllerを使います。
チェックボックスで受け付けるため、配列が入力されることが前提となります。
また件数可変の一覧表示画面から、spring:bindタグを使って動的にデータをBeanに入れています。

【手順概要】

変更対象選択用Bean(SelectProducts)を追加します。

SelectInventorysControllerを追加します。

変更対象複数選択画面を追加します。

springapp-servlet.xmlにエントリーを追加します。

メッセージを追加します。

リンクを追加します。

価格設定用Bean(SetPrices)を追加します。

バリデータ(SetPricesValidator)を追加します。

SetPricesFormControllerを追加します。

価格設定用画面を追加します。

メッセージを追加します。

springapp-servlet.xmlにエントリーを追加します。

【手順詳細】

SelectProducts
チェックボックスなので配列を取ります。
package springapp.service;

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.domain.Product;

public class SelectProducts {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

private List<Product> products = null;

private int[] ids = null;

public List<Product> getProducts() {
return products;
}

public void setProducts(List<Product> products) {
this.products = products;
}

public int[] getIds() {
return ids;
}

public void setIds(int[] ids) {
this.ids = ids;
StringBuffer log = new StringBuffer();
for(int i = 0; i < ids.length; i++) {
log.append("id[");
log.append(i);
log.append("]=");
log.append(ids[i]);
log.append("; ");
}
logger.info(log);
}
}

SelectInventorysFormControllerを追加します。
package springapp.web;

import org.springframework.validation.BindException;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.service.ProductManager;
import springapp.service.SelectProducts;

public class SelectInventorysFormController extends SimpleFormController {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private ProductManager productManager;

protected Object formBackingObject(HttpServletRequest request)
throws ServletException {
SelectProducts selectProducts = new SelectProducts();
logger.info("productManager:" + productManager.getProducts().size());
selectProducts.setProducts(productManager.getProducts());
logger.info("selectProd:" + selectProducts.getProducts().size());
return selectProducts;
}

@Override
protected ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
logger.info("Executed.");
SelectProducts sp = (SelectProducts) command;
StringBuffer log = new StringBuffer();
log.append("SelectProduct.getIds():");
int[] i = sp.getIds();
for (int j = 0; j < i.length; j++) {
log.append("id[");
log.append(j);
log.append("]=");
log.append(i[j]);
log.append("; ");
}
logger.info(log);
HttpSession session = request.getSession();
session.setAttribute("SelectProducts", sp);
return new ModelAndView(new RedirectView(getSuccessView()));
}

public void setProductManager(ProductManager productManager) {
this.productManager = productManager;
}

public ProductManager getProductManager() {
return productManager;
}

}

変更対象複数選択画面を追加します。
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head><title><fmt:message key="title"/></title></head>
<body>
<h1><fmt:message key="selectproducts.heading"/></h1>
<h3>Products</h3>
<form:form method="post" commandName="selectProducts">
<c:forEach items="${selectProducts.products}" var="prod" varStatus="row">
<input type="checkbox" id="ids" name="ids" value="<c:out value="${prod.id}"/>">
<c:out value="${prod.description}"/>
<i>$<c:out value="${prod.price}"/></i><br><br>
</c:forEach>
<input type="submit" align="center" value="Execute">
</form:form>
<br>
<a href="<c:url value="hello.htm"/>">back</a><br>
<br>
</body>
</html>

springapp-servlet.xmlにエントリーを追加します。
    <bean name="/selectproduct.htm" class="springapp.web.SelectInventoryFormController">
<property name="sessionForm" value="true"/>
<property name="commandName" value="selectProduct"/>
<property name="commandClass" value="springapp.service.SelectProduct"/>
<property name="formView" value="selectproduct"/>
<property name="successView" value="setprice.htm"/>
<property name="productManager" ref="productManager"/>
</bean>

メッセージを追加します。
selectproducts.heading=Select Products :: SpringApp

トップページ(hello.jsp)にリンクを追加します。
<br><a href="<c:url value="selectproducts.htm"/>">Select products</a>

価格設定用Bean(SetPrices)を追加します。
妙にリッチな作りになっていますが(結果的に無意味だった)試行錯誤の結果です。
恐らく(面倒くさいので検証していませんが)setProducts(List<Product> products) だけでよいのではないかと思います。(あるべき姿は後日調査予定)
package springapp.service;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.domain.Product;

public class SetPrices {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private List<Product> products = new ArrayList<Product>();
private int[] ids = null;
private String[] descriptions = null;
private double[] prices = null;
private static final int DEFALUT_NUM = 3;
private int position = 0;

public SetPrices(int num) {
initialize(num);
}

public SetPrices() {
initialize(DEFALUT_NUM);
}

private void initialize(int number) {
ids = new int[number];
descriptions = new String[number];
prices = new double[number];
}

public void setProducts(List<Product> products) {
this.products = products;
}

public void addProduct(Product prod) {
products.add(prod);
ids[position] = prod.getId();
descriptions[position] = prod.getDescription();
prices[position] = prod.getPrice();
position++;
}

public List<Product> getProducts() {
return products;
}

public int[] getIds() {
return ids;
}

public void setIds(int[] ids) {
this.ids = ids;
}

public String[] getDescriptions() {
return descriptions;
}

public void setDescriptions(String[] descriptions) {
this.descriptions = descriptions;
}

public double[] getPrices() {
return prices;
}

public void setPrices(double[] prices) {
this.prices = prices;
}
}

バリデータ(SetPricesValidator)を追加します。
package springapp.service;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.domain.Product;

public class SetPricesValidator implements Validator {

private int DEFAULT_MIN_PRICE = 1;
private int minPrice = DEFAULT_MIN_PRICE;

/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

public boolean supports(Class clazz) {
return SetPrices.class.equals(clazz);
}

public void validate(Object obj, Errors errors) {
SetPrices ps = (SetPrices) obj;
for (Product product : ps.getProducts()) {
logger.info("Validating ...");
if (product.getPrice() == null) {
logger.info("Price is null.");
errors.rejectValue("prices", "error.price-not-specified", null,
"Value required.");
} else {
logger.info(product);
if (product.getPrice() <= minPrice) {
errors.rejectValue("prices", "error.too-cheap",
new Object[] { new Integer(minPrice) },
"Value too low.");
}
}
logger.info("Validation end.");
}
}

public void setMinPrice(int i) {
minPrice = i;
}

public int getMinPercentage() {
return minPrice;
}

public void setMaxPercentage(int i) {
minPrice = i;
}

public int getMaxPercentage() {
return minPrice;
}

}

SetPricesFormControllerを追加します。
package springapp.web;

import java.util.List;

import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.domain.Product;
import springapp.service.ProductManager;
import springapp.service.SelectProducts;
import springapp.service.SetPrices;

public class SetPricesFormController extends SimpleFormController {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

private ProductManager productManager;

public ModelAndView onSubmit(Object command) throws ServletException {
List<Product> products = ((SetPrices) command).getProducts();

for (Product product : products) {
productManager.setPrice(product.getId(), product.getPrice());
}

return new ModelAndView(new RedirectView(getSuccessView()));
}

protected Object formBackingObject(HttpServletRequest request)
throws ServletException {
SetPrices ret = new SetPrices();
SelectProducts sp = (SelectProducts) request.getSession().getAttribute(
"SelectProducts");
int[] i = sp.getIds();
for (Product product : productManager.getProducts()) {
for (int j = 0; j < i.length; j++) {
if (product.getId() == i[j]) {
ret.addProduct(product);
break;
}
}
}
return ret;
}

public void setProductManager(ProductManager productManager) {
this.productManager = productManager;
}

public ProductManager getProductManager() {
return productManager;
}

}

価格設定用画面を追加します。
恐らくはここがキモになります。
spring:bindタグを使っています。
参考サイト:Dynamic list binding in Spring MVC
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<title><fmt:message key="title"/></title>
<style>
.error { color: red; }
</style>
</head>
<body>
<h1><fmt:message key="setprices.heading"/></h1>
<form:form method="post" commandName="setPrices">
<c:forEach items="${setPrices.products}" varStatus="row">
<spring:bind path="setPrices.products[${row.index}].id">
<input type="hidden"
name="<c:out value="${status.expression}"/>"
id="<c:out value="${status.expression}"/>"
value="<c:out value="${status.value}"/>" />
</spring:bind>
<spring:bind path="setPrices.products[${row.index}].description">
<c:out value="${status.value}"/>
</spring:bind>
<spring:bind path="setPrices.products[${row.index}].price">
<input type="text"
name="<c:out value="${status.expression}"/>"
id="<c:out value="${status.expression}"/>"
value="<c:out value="${status.value}"/>" />
</spring:bind>
<br>
</c:forEach>
<form:errors path="prices" cssClass="error"/>
<br>
<input type="submit" align="center" value="Execute">
</form:form>
<a href="<c:url value="hello.htm"/>">Home</a>
</body>
</html>

メッセージを追加します。
error.price-not-specified=Price not specified!!!
setprices.heading=Set Prices :: SpringApp

springapp-servlet.xmlにエントリーを追加します。
    <bean name="/setprices.htm" class="springapp.web.SetPricesFormController">
<property name="sessionForm" value="true"/>
<property name="commandName" value="setPrices"/>
<property name="commandClass" value="springapp.service.SetPrices"/>
<property name="validator">
<bean class="springapp.service.SetPricesValidator"/>
</property>
<property name="formView" value="setprices"/>
<property name="successView" value="hello.htm"/>
<property name="productManager" ref="productManager"/>
</bean>

以上。疲れた・・・

0 件のコメント: