Bootstrap with Spring MVC
今さらながら、GW中にBootstrapにやっと触れた。世の中の潮流に少し乗れたかな?
自分の知識では、「jQuery Mobile」で知識が止まっており、さあ、作業に取り掛かろうとしたけど、jQuery Mobileは使いつらすぎた。
せっかく本も買ったけど、意味なかった。
結局、CSS Frameworkとして「Bootstrap」「HTML Kick start」を候補に挙げて、今回は人気のあるBootstrapを採用してみました。
使用したBoostrapのバージョンは「3.1.1」です。
Bootstrapを使ってみた感想
機能が多く、jQuery UIの機能もほぼあるため、Bootstrap単体でことが足りて便利だと思います。
ただし、バージョンごとに記述方法が異なっており、ネットに転がっている情報をそのまま鵜呑みにすると、デザインくずれが発生します。
その点、「HTML Kick start」は非常にシンプルで、習得もしやすい。
ただし、動的な動作をさせたい場合は、他のライブラリを使ったりする必要があります。
「Bootstrap」「HTML Kick start」も、公式ページのドキュメントだけで十分で、習得も半日もあればできました。
使うだけならば、30分ちょっとでそれなりのものが作れます。
Spring MVCでBootsrapを利用する場合の問題点
Bootstrapでフォームのコントローラでエラー状態のデザインを表現するためには、inputタグやメッセージをdivで囲む必要があります。
そのため、囲むdivタグのclass属性において、入力項目ごとにエラーがあるか判定する必要があり、非常にJSPなどが汚くなってしまいます。
さらに、入力項目が大量にあると、その個数分、判定処理が必要になります。
<%================= 改善前のEL式、JSTLを使用した場合 ================%> <%-- フォームに対してエラーがあるか判定し、結果を変数に格納する --%> <spring:hasBindErrors name="searchCondtiionCommand"> <c:if test="${errors.getFieldErrorCount('keyword') > 0}"> <c:set var="hasErrorKeyword" value="true"/> </c:if> </spring:hasBindErrors> <form:form modelAttribute="searchCondtiionCommand" action="..." method="POST"> <!-- divのclass属性にエラーを判定し、専用の値を設定する --> <div class="form-fontrol${hasErrorKeyword ?' has-error has-feedback':''}"> <form:label path="keyword" class="control-label" >キーワード</form:label> <form:input id="keyword" path="keyword" class="form-control" placeholder="search" maxlength="100"/> <!-- アイコンを設定する際に、エラーがあるか判定する必要がある --> <c:if test="${hasErrorKeyword}"> <span class="glyphicon glyphicon-remove form-control-feedback"></span> </c:if> <form:errors path="keyword" cssClass="help-block with-errors"/> </div> <button type="submit" name="search" class="btn btn-primary"> <span class="glyphicon glyphicon-search"></span> <spring:message code="label.search"/> </button> </form:form>
解決策
解決策として、カスタムタグを自作します。
Spring MVCのカスタムタグと同様に、属性「path」で入力項目を指定し、色々と判定をできるようにします。
- <form2:element>というカスタムタグで、指定した入力項目にエラーがあるか判定し、エラーがある場合、専用のclass属性を出力できるようにする。
- <form2:hasErrors>というカスタムタグで、指定した入力項目にエラーがあるか判定し、エラーがある場合、そのタグの中身を評価する。
<%================= 改善後の自作のカスタムタグを使用した場合 ================%> <%@ taglib uri="http://spring-webmvc/modules/tags/form2" prefix="form2" %> <form:form modelAttribute="searchCondtiionCommand" action="..." method="POST"> <!-- divのclass属性にエラーを判定し、専用の値を設定する--> <form2:element path="keyword" element="div" cssClass="form-group" cssErrorClass="form-group has-error has-feedback"> <form:label path="keyword" class="control-label" >キーワード</form:label> <form:input id="keyword" path="keyword" class="form-control" placeholder="search" maxlength="100"/> <!-- アイコンを設定する際に、エラーがあるか判定する必要がある --> <form2:hasErrors path="keyword"> <span class="glyphicon glyphicon-remove form-control-feedback"></span> </form2:hasErrors> <%-- アイコンは↓の方法でも出力可能。エラー時のみ出力する。 <form2:element path="keyword" element="span" cssClass="glyphicon glyphicon-remove form-control-feedback" outIfError="true" /> --%> <form:errors path="keyword" cssClass="help-block with-errors"/> </form2:element><!-- /form-group --> <button type="submit" name="search" class="btn btn-primary"> <span class="glyphicon glyphicon-search"></span> <spring:message code="label.search"/> </button> </form:form>
カスタムタグ<form2:element>
SpringMVCのカスタムタグ用の抽象クラス「org.springframework.web.servlet.tags.form.AbstractHtmlElementTag」を使用します。
このクラスは、HTMLを出力するための抽象クラスです。
また、「AbstractDataBoundFormElementTag」を継承しており、入力項目ごとの属性「path」で関連付けられた値を処理するためのメソッドがそろっています。
SpringMVCのカスタムタグの共通属性「path」「cssClass」「cssErrorClass」などの処理は、抽象クラスないで予め実装されているため、定義は必要ありません。
import javax.servlet.jsp.JspException; import org.springframework.util.StringUtils; import org.springframework.web.servlet.tags.form.AbstractHtmlElementTag; import org.springframework.web.servlet.tags.form.TagWriter; /** * カスタムタグ<form2:element>の実体クラス。 */ @SuppressWarnings("serial") public class ElementTag extends AbstractHtmlElementTag { /** * カスタムタグの属性「element」の値。 * ・出力するタグ名を保持する * ・ただし、属性「elementError」が指定されていればその値を利用する。 */ private String element; /** * カスタムタグの属性「elementError」の値。 * ・エラー時に出力するタグ名を保持する */ private String elementError; /** * カスタムタグの属性「outIfError」の値。 * ・エラー時にしか出力しないかどうかのフラグ。 */ private boolean outIfError; private TagWriter tagWriter; @Override protected int writeTagContent(final TagWriter tagWriter) throws JspException { this.tagWriter = tagWriter; if(outIfError && !getBindStatus().isError()) { // エラー時のみにしか出力しない場合、処理を終了。 return EVAL_BODY_INCLUDE; } if(getBindStatus().isError()) { if(StringUtils.hasLength(getElementError())) { tagWriter.startTag(getElementError()); } else { tagWriter.startTag(getElement()); } } else { tagWriter.startTag(getElement()); } // 属性の設定 writeDefaultAttributes(tagWriter); // 開始タグの出力 tagWriter.forceBlock(); return EVAL_BODY_INCLUDE; } @Override public int doEndTag() throws JspException { if(outIfError && !getBindStatus().isError()) { // エラー時のみにしか出力しない場合、処理を終了。 return EVAL_PAGE; } // 終了タグの出力 this.tagWriter.endTag(); return EVAL_PAGE; } @Override protected String getName() throws JspException { // This also suppresses the 'id' attribute (which is okay for a <label/>) return null; } @Override protected String resolveId() throws JspException { Object id = evaluate("id", getId()); if (id != null) { return super.resolveId(); } return null; } public String getElement() { return element; } public void setElement(String element) { this.element = element; } public String getElementError() { return elementError; } public void setElementError(String elementError) { this.elementError = elementError; } public boolean isOutIfError() { return outIfError; } public void setOutIfError(boolean outIfError) { this.outIfError = outIfError; } }
<!-- tldファイル --> <?xml version="1.0" encoding="UTF-8"?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"> <description>Spring Framework JSP Form Tag Library for Another</description> <tlib-version>1.0</tlib-version> <short-name>form2</short-name> <uri>http://spring-webmvc/modules/tags/form2</uri> <tag> <description>Renders field errors in an HTML custom tag.</description> <name>element</name> <tag-class>sample.web.tags.ElementTag</tag-class> <body-content>JSP</body-content> <attribute> <description>HTML tag</description> <name>element</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML error tag</description> <name>elementError</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>output tag, when has error. default false.</description> <name>outIfError</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>Path to errors object for data binding</description> <name>path</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Standard Attribute</description> <name>id</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> ・・・以降は、HTMLの属性の定義なので省略。 他のSpring用のカスタムタグの定義を参照。 <tag> </taglib>
カスタムタグ<form2:hasError>
SpringMVCのカスタムタグ用の抽象クラス「org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag」を使用します。
入力項目ごとの属性「path」で関連付けられた値を処理するためのメソッドがそろっています。
エラーがあるかどうかの判定だけのため、非常にシンプルです。
import javax.servlet.jsp.JspException; import org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag; import org.springframework.web.servlet.tags.form.TagWriter; /** * カスタムタグ<form2:hasErrors>の実体クラス。 */ @SuppressWarnings("serial") public class HasErrorsTag extends AbstractDataBoundFormElementTag { @Override protected int writeTagContent(final TagWriter tagWriter) throws JspException { if(getBindStatus().isError()) { return EVAL_BODY_INCLUDE; } else { return SKIP_BODY; } } @Override public int doEndTag() { return EVAL_PAGE; } }
<!-- tldファイル --> <?xml version="1.0" encoding="UTF-8"?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"> <description>Spring Framework JSP Form Tag Library for Another</description> <tlib-version>1.0</tlib-version> <short-name>form2</short-name> <uri>http://spring-webmvc/modules/tags/form2</uri> <tag> <description>Provides Errors instance in case of field errors.</description> <name>hasErrors</name> <tag-class>sample.web.tags.HasErrorsTag</tag-class> <body-content>JSP</body-content> <attribute> <description>Path to errors object for data binding</description> <name>path</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>