コンピュータクワガタ

かっぱのかっぱによるコンピュータ関連のサイトです

Spring MVCのformタグを簡単にまとめました

Spring MVCの勉強をしていて、Springカスタムタグの日本語情報が少なかったので簡単にまとめまてみました。今回はformタグです。

Springのバージョンは3.1.2を基準にしています。3.1.*系であれば多分動きますが、違う場合には注意が必要です。

記事での説明はあってないようなものなので、動くサンプルを見てもらうのが手っ取り早いと思います。サンプルは、githubに置いてあります。
https://github.com/kuwalab/spring-form-sample
EclipseWTPの動的Webプロジェクトとして作成しています。

共通の属性

formタグのほとんどで共通している属性があります。
ドキュメントを見るとたくさんあるように見えますが、ほとんどがHTMLに標準的に持っている属性そのままか名称を変えたもの(class→cssClass、style→cssStyle)です。

属性 説明
cssClass HTML標準のclass属性
cssStyle HTML標準のsytle属性
dir HTML標準のdir属性
htmlEscape 描画する値のHTMLエスケープ
id HTML標準のid属性
lang HTML標準のlang属性
on〜イベント名 イベントはJavaScriptで処理するので使わない
tabindex HTML標準のtabindex
title HTML標準のtitle属性

form:formタグ

form関連のタグはこのform:formタグ以下に、他のformタグを配置していきます。formプリフィックスがついているだけで、HTMLのformタグと同じ形になります。

formタグは標準の属性と別に以下の属性を持ちます。ほとんどがHTMLの標準formがそのまま属性になっています。違うのは、commandNameとmodelAttributeの2つなります。

属性 説明
action HTML標準のaction属性
commandName Controller側で指定した@ModelAttributeの名称。通常これを使わずに、modelAttribute属性を使います。
enctype HTML標準のenctype属性
method HTML標準のmethod属性
modelAttribute SpringのController側で定義し、このformに関連づけるmodelAttribute
name HTML標準のname属性
target HTML標準のtarget属性

今の理解だと、commandName属性はあまり使わずに、modelAttributeを使います。modelAttributeはControllerでModelにセットする属性値(Model.addAttributeの引数)を指定します。

formの部品共通の属性

checkbox等のformの内部の要素には共通になる部分も以下のように多くあります。ほとんどがHTML標準の属性ですが、一部独自の属性があります。

Springの独自の部分としては、cssErrorClassとlabel、pathになります。

属性 説明
accesskey HTML標準のaccesskey属性
cssErrorClass このフィールドでエラーがある場合に適用されるclass属性
disabled HTML標準の属性。trueの場合に有効になる。
label HTMLのlabelタグを適切な形で配置する。具体的には以下で確認
path form:formで指定したmodelAttributeの中のフィールドのうち、この要素に割り当てるフィールドの名称
value HTML標準のvalue属性

form:checkboxタグ

共通の属性とformの部品共通の属性だけで構成されています。

単純な例

1つのcheckboxを作る例で見てみます。Controllerは以下のように作成します。
CheckboxModelはformのmodelAttributeに渡すためのクラスです。今回は初期値をtrue(checkboxがチェックされた状態)にするために渡しています。

@Controller
public class FormController {
    @RequestMapping(value = "/checkbox", method = RequestMethod.GET)
    public String product(Model model) {
        CheckboxModel cm = new CheckboxModel();
        cm.setSendMail(true);
        model.addAttribute("checkboxModel", cm);

        return "checkbox";
    }
}

CheckboxModelは以下の通りです。

public class CheckboxModel {
    private boolean sendMail;

    public boolean isSendMail() {
        return sendMail;
    }

    public void setSendMail(boolean sendMail) {
        this.sendMail = sendMail;
    }
}

Controller内でsendMailをtrueにセットし、checkboxにチェックが入った状態にします。checkbox Viewで表示するcheckbox.jspは以下の通りです。

<form:form modelAttribute="checkboxModel">
 <form:checkbox path="sendMail" label="今後XX社からのメールを受け取る" />
</form:form>

pathにsendMailを指定しているため、checkboxModel.sendMailを参照して初期値の設定(チェックされた状態)にしています。

上記の例で実際に出力されるHTMLは以下のようになります。(以下すべてそうですが、HTMLの改行やブランクは見やすいように適宜入れています)
labelに関しては、checkboxとは別のタグとして生成され、for属性で関連するidを指定されます。ブラウザの標準的な挙動になりますが、チェックボックスだけでなくラベルの文字列をクリックしてもチェックボックスの状態が変わります。

<form id="checkboxModel" action="/form/checkbox" method="post">
 <input id="sendMail1" name="sendMail" type="checkbox" value="true" checked="checked"/><label for="sendMail1">今後XX社からのメールを受け取る</label><input type="hidden" name="_sendMail" value="on"/>
</form>

form:checkboxesタグ

実際にcheckboxを生成するのはform:checkboxよりもこちらのform:checkboxesを使うことがほとんどだと思います。
form:checkboxesタグは名前の通りJavaの配列やMapから複数のcheckboxを作ることができます。

checkboxesやoptions等の複数のform要素を扱う場合に、共通で使われる属性は以下の通りです。

属性 説明
delimiter checkbox間の区切り文字。デフォルトはなし
element それぞれのcheckboxを囲む要素。デフォルトはspan
itemLabel labelを出力するitemsで指定したクラスのプロパティ名
items checkboxを作るための配列やMap
itemValue valueを出力するitemsで指定したクラスのプロパティ名

使い方としては、1つのcheckboxに相当するクラスを作成します。クラスには、checkboxのlabelとvalue属性に対応するプロパティを用意します。itemsにはそのクラスのListやMap、配列を指定します。また、そのクラスのどのプロパティをlabelやvalueに割り当てるのかを、itemLabelとitemValueに指定します。

基本的な例

最初にcheckboxで表示するための(itemsで指定する)Bookクラスを作ります。Bookクラスではlabelにあたる書名とvalueにあたるISBNを管理します。

public class Book {
	private String isbn;
    private String title;

    // コンストラクタ/setter/getterは省略
    // 以下同様
}

次に画面表示用のModelクラスです。checkboxkは同じパラメータで複数選択されることがあるため配列で受け取ります。

public class CheckboxesModel {
    private String[] selectedIsbns;
}

コントローラは以下のようにします。初期値を設定する場合にはModel用のクラスに初期値の値(今回はISBNが111111111)をセットしておきます。コントローラではcheckboxに表示するためのBookクラスのListを作成しています。この値はformの初期値に使用するModelとは別のクラスとして作成します。

@Controller
public class FormController {
    @RequestMapping(value = "/checkboxes", method = RequestMethod.GET)
    public String checkboxes(Model model) {
        List<Book> books = new ArrayList<>();

        books.add(new Book("1234567890", "Spring入門"));
        books.add(new Book("1111111111", "Java入門"));
        books.add(new Book("9999999999", "Servlet/JSP入門"));

        CheckboxesModel cm = new CheckboxesModel();
        // 初期値の設定
        cm.setSelectedIsbns(new String[] { "1111111111" });
        model.addAttribute("checkboxesModel", cm);
        model.addAttribute("books", books);

        return "checkboxes";
    }

    @RequestMapping(value = "/checkboxesPost", method = RequestMethod.GET)
    public String checkboxesPost(@ModelAttribute CheckboxesModel checkboxesModel) {

        for (String s : checkboxesModel.getSelectedIsbns()) {
            System.out.println(s);
        }

        return "forward:/checkboxes";
    }
}

JSPは以下のように記述します。

<form:form modelAttribute="checkboxesModel" action="checkboxesPost" method="get">
 <form:checkboxes path="selectedIsbns" items="${books}" itemLabel="title" itemValue="isbn" />
 <input type="submit" value="送信">
</form:form>

items属性にcontrollerでセットした、booksを指定します。checkboxのlabelにBookクラスのtitleプロパティの値を使用するため、itemLabelにtitleを指定します。またitemValueにはisbnを指定します。

実際に出力されるHTMLは以下のようになります。かなり改行を入れています。formの中はほとんど改行は入っていません。

<form id="checkboxesModel" action="checkboxesPost" method="get">
 <span>
  <input id="selectedIsbns1" name="selectedIsbns" type="checkbox" value="1234567890"/>
  <label for="selectedIsbns1">Spring入門</label>
 </span>
 <span>
  <input id="selectedIsbns2" name="selectedIsbns" type="checkbox" value="1111111111" checked="checked"/>
  <label for="selectedIsbns2">Java入門</label>
 </span>
 <span>
  <input id="selectedIsbns3" name="selectedIsbns" type="checkbox" value="9999999999"/>
  <label for="selectedIsbns3">Servlet/JSP入門</label>
 </span>
 <input type="hidden" name="_selectedIsbns" value="on"/>
 <input type="submit" value="送信">
</form>
少し属性を足す

特別な装飾をせずにcheckboxesを使用すると出力されるcheckboxタグの間に空白がないため、非常に見づらいものになります。そのため、checkboxesを含む複数の要素を出力するformカスタムタグにはdelimiter属性があります。

delimiter属性を使うと、出力される要素の間に任意の文字列を出力できます。delimiterの文字列はHTMLエスケープされずにそのまま出力されます。

例えば、以下のようにdelimiter属性を指定することで赤い「|」を区切り文字として出力できます。

<form:checkboxes path="selectedIsbns" items="${books}" itemLabel="title" itemValue="isbn"
 delimiter="&nbsp;<span style='color: #f00;'>|</span>&nbsp;" />

出力されるHTMLは以下のようになります。

<form id="checkboxesModel" action="checkboxesPost" method="get">
 <span>
  <input id="selectedIsbns1" name="selectedIsbns" type="checkbox" value="1234567890"/>
  <label for="selectedIsbns1">Spring入門</label>
 </span>
 <span>&nbsp;<span style='color: #f00;'></span>&nbsp;
  <input id="selectedIsbns2" name="selectedIsbns" type="checkbox" value="1111111111" checked="checked"/>
  <label for="selectedIsbns2">Java入門</label>
 </span>
 <span>&nbsp;<span style='color: #f00;'></span>&nbsp;
  <input id="selectedIsbns3" name="selectedIsbns" type="checkbox" value="9999999999"/>
  <label for="selectedIsbns3">Servlet/JSP入門</label>
 </span><input type="hidden" name="_selectedIsbns" value="on"/>
 <input type="submit" value="送信">
</form>

form:radiobuttonタグ

radiobuttonの属性はcheckboxと同様に標準のタグで構成されています。

画面表示用のモデルを作成します。checkboxと違い選択された値を文字列で受け取るためString型で定義します。radiobuttonのvalue値がこの値と等しいものが選択された状態になります。

public class RadiobuttonModel {
    private String sendMail;
}

コントローラは以下のようになります。ここではradiobuttonの初期値を「send」にしています。

@Controller
public class FormController {
    @RequestMapping(value = "/radiobutton", method = RequestMethod.GET)
    public String Radiobutton(Model model) {
        RadiobuttonModel rm = new RadiobuttonModel();
        rm.setSendMail("send");
        model.addAttribute("radiobuttonModel", rm);

        return "radiobutton";
    }
}

JSPは以下のようになります。path属性で指定したプロパティの値とvalue属性の値が一致する場合に選択された状態になります。

<form:form modelAttribute="radiobuttonModel">
 <form:radiobutton path="sendMail" label="今後XX社からのメールを受け取る" value="send" />
</form:form>

form:radiobuttonsタグ

複数の中から1つ選択する普通のradiobuttonです。属性はcheckboxesと同じです。

サンプル

checkboxesと同様にListを用意します。また、raidiobuttonの初期値をisbnを9999999999とします。

@Controller
public class FormController {
    @RequestMapping(value = "/radiobuttons", method = RequestMethod.GET)
    public String Radiobuttons(Model model) {
        List<Book> books = new ArrayList<>();

        books.add(new Book("1234567890", "Spring入門"));
        books.add(new Book("1111111111", "Java入門"));
        books.add(new Book("9999999999", "Servlet/JSP入門"));

        RadiobuttonsModel rm = new RadiobuttonsModel();
        rm.setSelectedIsbn("9999999999");
        model.addAttribute("radiobuttonsModel", rm);
        model.addAttribute("books", books);

        return "radiobuttons";
    }
}

modelは以下です。

public class RadiobuttonsModel {
    private String selectedIsbn;
}

JSPもcheckboxesとほぼ同様になります。

<form:form modelAttribute="radiobuttonsModel">
 <form:radiobuttons path="selectedIsbn" items="${books}" itemLabel="title" itemValue="isbn" delimiter="&nbsp;" />
</form:form>

form:inputタグ

テキストの入力フィールドを作成するタグです。属性は標準的なものに加えて以下のものがあります。基本的にはHTML標準の属性です。

属性 説明
autocomplete HTML標準のautocomplete属性
maxlength HTML標準のmaxlength属性
readonly HTML標準のreadonly属性
size HTML標準のsize属性
サンプル

サンプルでは、Validatorを含めた例で確認します。

まずはコントローラーから確認します。inputメソッドが表示用で、inputSendメソッドが画面からsubmitされたときに呼び出されるメソッドになります。

inputメソッドでは今までのように初期値表示用の設定をしています。入力フォームは文字列ですが、ModelのプロパティをintやIntegerなどの型にすることもできます。しかし、そのままフォームに数値以外の値を入力すると型変換のエラーとなりスタックトレースが出力されてしまいます。そのため、型変換のエラーを適切に処置するため@ModelAttributeをつけたメソッドを作成します。このメソッドを作成することで、エラーをSpringで処置できるようになります。また、メッセージリソースに適切なキーを設定することにより該当のフィールドエラーにすることができます。以下のControllerのInputModelメソッドがある場合とない場合の動作の違いを見ていただくとわかると思います。

@Controller
public class FormController {
    @ModelAttribute("inputModel")
    public InputModel initInputModel() {
        return new InputModel();
    }

    @RequestMapping(value = "/input", method = RequestMethod.GET)
    public String input(Model model) {
        InputModel im = new InputModel();
        im.setName("花中島 太郎");
        model.addAttribute("inputModel", im);

        return "input";
    }

    @RequestMapping(value = "/inputSend", method = RequestMethod.POST)
    public String inputSend(
            @Valid @ModelAttribute("inputModel") InputModel inputModel,
            Errors errors) {
        if (errors.hasErrors()) {
            return "input";
        }

        return "forward:/";
    }
}

ModelはStringとIntegerの2つの項目です。ageにはBean Validatorのアノテーションを設定し、0〜200までの値のみ入力可能としています。

public class InputModel {
    private String name;
    @Min(0)
    @Max(200)
    private Integer age;
}

JSPはValidationのエラー表示用のform:errorタグを追加しています。

<form:form modelAttribute="inputModel" action="inputSend" method="post">
 <form:errors path="*" element="div" />
 <form:label path="name">名前:</form:label><form:input path="name" size="40" /><br>
 <form:label path="age">年齢:</form:label><form:input path="age" size="5" cssErrorClass="error" /><br>
 <input type="submit" value="送信">
</form:form>

form:passwordタグ

password用の入力フィールド。入力した内容がわからないようになります。属性はform:inputと同様ですが、showPassword属性が追加されています。

属性 説明
showPassword パスワードを表示するかどうか。デフォルトはfalse

サンプルはform:inputとほぼ同様のため省略します。

form:textareaタグ

複数行のテキスト入力のフィールドです。

属性 説明
cols 列数
rows 行数

サンプルはform:inputとほぼ同様のため省略します。

form:selectタグ

form:selectタグの属性はcheckboxesの属性に追加して以下のものがあります。

属性 説明
multiple HTML標準のmultiple属性
size HTML標準のsize属性

例は今までと同様です。JSPのみmultipleやsizeを使ったものにしています。

@Controller
public class FormController {
    @RequestMapping(value = "/select", method = RequestMethod.GET)
    public String select(Model model) {
        List<Book> books = new ArrayList<>();

        books.add(new Book("1234567890", "Spring入門"));
        books.add(new Book("1111111111", "Java入門"));
        books.add(new Book("9999999999", "Servlet/JSP入門"));

        SelectModel sm = new SelectModel();
        sm.setSelectedIsbn("1111111111");
        List<String> selectedIsbns = new ArrayList<String>();
        selectedIsbns.add("1234567890");
        selectedIsbns.add("9999999999");
        sm.setSelectedIsbns(selectedIsbns);
        model.addAttribute("selectModel", sm);
        model.addAttribute("books", books);

        return "select";
    }
}
public class SelectModel {
    private String selectedIsbn;

    private List<String> selectedIsbns;
}
<form:form modelAttribute="selectModel">
 <form:select path="selectedIsbn" items="${books}" itemLabel="title" itemValue="isbn" /><br>
 <form:select path="selectedIsbns" items="${books}" itemLabel="title" itemValue="isbn" size="3" multiple="true" />
</form:form>

form:optionsタグ

form:optionsタグはHTMLのoptionタグがそうであるように、単独では使えません。必ずform:selectタグの中に書くようにします。

属性は、checkboxesと同じです。サンプルも同様です。

@Controller
public class FormController {
    @RequestMapping(value = "/options", method = RequestMethod.GET)
    public String options(Model model) {
        List<Book> books = new ArrayList<>();

        books.add(new Book("1234567890", "Spring入門"));
        books.add(new Book("1111111111", "Java入門"));
        books.add(new Book("9999999999", "Servlet/JSP入門"));

        OptionsModel om = new OptionsModel();
        om.setSelectedIsbn("9999999999");
        model.addAttribute("optionsModel", om);
        model.addAttribute("books", books);

        return "options";
    }
}
public class OptionsModel {
    private String selectedIsbn;
}
<form:form modelAttribute="optionsModel">
 <form:select path="selectedIsbn">
  <form:options items="${books}" itemLabel="title" itemValue="isbn" />
 </form:select><br>
</form:form>

まとめ

Springのカスタムタグは、非常にコンパクトによく考えられて作られているように感じます。タグの使用仕様ではなくSpring MVCの仕様ですが、ModelにString以外のinteger等の数値型を指定することができるのも非常にいいです。

参考

今回の記事は、以下のSpring本がなければできませんでした。ある程度Webアプリを作った人であれば、十分に読め、かつDIの基本からAOP、Spring MVCまでをもれなく記載されている良書です。

Spring3入門 ――Javaフレームワーク・より良い設計とアーキテクチャ

Spring3入門 ――Javaフレームワーク・より良い設計とアーキテクチャ

さらに、偶然発見した以下の資料も非常に参考になりました。
http://d.hatena.ne.jp/tatsu-no-toshigo/20121023