目次


容易になった Ajax と Java 開発

第 3 回 DOM、JavaScript、JSP タグ・ファイルを使って UI 機能を作成する

ユーザー・フレンドリーで安全に行える Web フォームの検証を JavaScript と JSP タグ・ファイルを使って実装する

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: 容易になった Ajax と Java 開発

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:容易になった Ajax と Java 開発

このシリーズの続きに乞うご期待。

クライアント・サイドの検証が役立つ理由は、エラーを修正させるためにユーザーにフォームを戻すといった事態を少なくしたり、あるいはそのような事態を完全になくしたりできるからです。そうは言っても、JavaScript ベースの検証だけでは十分とは言えません。データがプログラムによってサーバーに送信された場合や、ユーザーのブラウザーで JavaScript が無効にされている場合には、JavaScript ベースの検証は行われません。そこで、この記事ではクライアント・サイドとサーバー・サイドの両方に検証を実装する方法を紹介します。

クライアント・サイドのバリデーター階層の作成

このセクションでは、JavaScript のコンストラクターとプロトタイプを使用して、Web ブラウザーでのユーザー入力を検証するオブジェクトの階層を作成する方法を説明します。このセクションに記載するコードはいずれもサンプル・アプリケーション (「ダウンロード」を参照) の valid.js ファイルに含まれています。

基本バリデーターを開発する

JavaScript では、あらゆる関数を new 演算子とともに使用してオブジェクトを作成することができます。いわゆる「コンストラクター」は、このようにして作成されたオブジェクトのプロパティーを適切に初期化するために、this を使用します。リスト 1 に記載する Validator() 関数は、フォームの名前、要素名、ID、そしてメッセージという 4 つのパラメーターを引数に取ります。これらのパラメーターの値が、現行のオブジェクトにプロパティーとして保存されます。

同じコンストラクターで作成されたオブジェクトは、prototype オブジェクトに保存されたプロパティーを共有することができます。リスト 1 で言うと、defaultMsgIndex が、new Validator(...) で作成されたすべてのインスタンスが共有するプロパティーです。ユーザーがオブジェクトのプロパティーまたはメソッドにアクセスしようとすると、JavaScript エンジンはまず、オブジェクトのメンバーのなかで対象を検索します。メンバーのなかにプロパティーあるいはメソッドが見つからない場合には、固有のプロトタイプを持つ可能性のあるオブジェクトのプロトタイプ等を検証します。このことから、後で説明するようにプロトタイプ・チェーンを使用して継承を実装することができます。この記事のサンプルでは、Validator がオブジェクト階層のベースです。

リスト 1. Validator() コンストラクター
function Validator(formName, elemName, outId, msg) {
    if (formName)
        this.formName = formName;
    if (elemName)
        this.elemName = elemName;
    if (outId)
        this.outId = outId;
    if (msg)
        this.msgList = ["", msg];
}

Validator.prototype.defaultMsgIndex = 0;

すべての JavaScript 関数は、あらゆるオブジェクトのメソッドになり得ます。オブジェクトのコンストラクターで使用しなければならないのは this.methodName = functionName だけですが、通常、メソッドは同じコンストラクターで作成されるすべてのインスタンスが共有するため、constructorName.prototype.methodName を使用してメソッドをコンストラクターのプロトタイプと関連付けることができます (リスト 2 を参照)。

リスト 2. Validator の getFormValues() メソッド
Validator.prototype.getFormValues = function() {
    var elemValues = new Array();
    var form = document.forms[this.formName];
    var formElements = form.elements;
    for (var i = 0; i < formElements.length; i++) {
        var element = formElements[i];
        if (element.name == this.elemName) {
            var elemType = element.type.toLowerCase();
            if (elemType == "text" || elemType == "textarea"
                    || elemType == "password" || elemType == "hidden")
                elemValues[elemValues.length] = element.value;
            else if (elemType == "checkbox" && element.checked)
                elemValues[elemValues.length]
                     = element.value ? element.value : "On";
            else if (elemType == "radio" && element.checked)
                elemValues[elemValues.length] = element.value;
            else if (elemType.indexOf("select") != -1)
                for (var j = 0; j < element.options.length; j++) {
                    var option = element.options[j];
                    if (option.selected) {
                        elemValues[elemValues.length]
                            = option.value ? option.value : option.text;
                    }
                }
        }
    }
    return elemValues;
}

リスト 2 の getFormValues() メソッドが返すのは、elemName プロパティーの値と名前が一致するフォーム要素の値です。Web ブラウザーで HTML フォームを表す DOM オブジェクトが document.forms[this.formName] によって取得されると、getFormValues() はフォームの要素を繰り返し処理し、各要素をその HTML タイプに応じて処理します。

要素のタイプがテキスト、パスワード、あるいは隠しフィールドであれば、getFormValues() メソッドは返された配列に要素の値をそのまま代入するだけです。要素のタイプがチェック・ボックスまたはラジオ・ボタンの場合は、その HTML 要素が選択されている場合にだけ値を代入します。要素がリストであれば、返された配列には、選択された項目の値が保存されることになります。

階層の各バリデーターには、単一の値を検証するメソッドが必要になります。基本 Validator の場合、verify() メソッド (リスト 3 に記載) は 0 を返しますが、その他のバリデーターはこのメソッドを各バリデーター固有のメソッドに置き換えます。verify() によって返された値を使用して、msgList でメッセージが検索されます。

リスト 3. Validator の verify() メソッド
Validator.prototype.verify = function(value) {
    return 0;
}

showMsg() メソッド (リスト 4 を参照) は、innerHTML プロパティーを設定することによって、メッセージを HTML 要素に挿入します。HTML 要素を表すオブジェクトは、document.getElementById(this.outId) により取得されます。挿入されるメッセージはhtmlEncode() 関数によってエンコードされます。この補助関数のコードについては、encode.js を参照してください。

リスト 4. Validator の showMsg() メソッド
Validator.prototype.showMsg = function(index) {
    document.getElementById(this.outId).innerHTML
        = htmlEncode(this.msgList[index]);
}

リスト 5 に記載する execute() メソッドは getFormValues() によって返された値を繰り返し処理し、それぞれの値に対して verify() を呼び出します。execute() メソッドの完了時に msgIndex0 であれば、ユーザー入力は有効だとみなされます。この場合、showMsg() は出力要素の innerHTML プロパティーに空のストリングを保存するという方法で、以前のすべてのメッセージをクリアします。0 でない場合には、showMsg() が呼び出され、Web ページにエラー・メッセージが表示されます。

リスト 5. Validator の execute() メソッド
Validator.prototype.execute = function() {
    var msgIndex = this.defaultMsgIndex;
    var elemValues = this.getFormValues();
    for (var i = 0; i < elemValues.length; i++)
        if (elemValues[i]) {
            msgIndex = this.verify(elemValues[i]);
            if (msgIndex != 0)
                break;
        }
    this.showMsg(msgIndex);
    return msgIndex == 0;
}

具体的なバリデーターを実装する

valid.js ファイルには、必須フィールドのバリデーターが含まれます。RequiredValidator() コンストラクター (リスト 6 を参照) が引数に取るパラメーターは、Validator() 関数が引数に取るパラメーターと同じです。オブジェクト・プロパティーを Validator() で初期化するために、RequiredValidator() コンストラクターは base() というメソッドを設定します。base() を利用して Validator() を呼び出した後、RequiredValidator() は現行のオブジェクトから base() メソッドを削除します。このメソッドは、もう必要ないためです。

リスト 6. RequiredValidator() コンストラクターとそのプロトタイプ設定
function RequiredValidator(formName, elemName, outId, msg) {
    this.base = Validator;
    this.base(formName, elemName, outId, msg);
    delete this.base;
}

RequiredValidator.prototype = new Validator();

RequiredValidator.prototype.defaultMsgIndex = 1;

RequiredValidator.prototype.verify = function(value) {
    return value.length > 0 ? 0 : 1;
}

すべての RequiredValidator オブジェクトのプロトタイプは Validator インスタンスです。Validator() コンストラクターに渡されるパラメーターは 1 つもないため、このインスタンスにはプロパティーがありません。このプロトタイプを設定することで、RequiredValidatorValidator のプロトタイプに定義されたプロパティーとメソッドを「継承」することになります。

例として、ユーザーが RequiredValidator オブジェクトの execute() メソッドを呼び出すと、JavaScript エンジンはまずこの RequiredValidator オブジェクトのメソッドのなかで、execute() メソッドを検索します。しかし、このオブジェクトには execute() メソッドがないことから、次の検索場所はオブジェクトのプロトタイプとなり、そのなかにもなければ、次にプロトタイプのプロトタイプが検索されるといった具合に続きます。このサンプルでは、RequiredValidatorのプロトタイプは Validator インスタンスで、このインスタンスのプロトタイプに execute() メソッドがあります。

RequiredValidator のプロトタイプに含まれる defaultMsgIndex プロパティーは 1 に設定されます。これは、要素の値が欠如している場合に、エラーを通知する必要があるためです。verify() メソッドは値が空でなければ 0 を返し、必須フィールドの値が入力されていなければ 1 を返します。

リスト 7 に示す LengthValidator は、RequiredValidator と非常によく似ています。LengthValidator() コンストラクターが引数に取る 7 つのパラメーターは、フォームの名前、検証対象の値を持つ要素の名前、エラー・メッセージの出力に使用する要素の ID、検証対象の値の最小長と最大長、そして 2 つのエラー・メッセージです。RequiredValidator の場合と同様、LengthValidator のプロトタイプは新しい Validator インスタンスです。verify() メソッドは値が有効であれば 0 を返し、値が最小長に満たない場合には 1 を、最大長を超える場合は 2 を返します。

リスト 7. LengthValidator() コンストラクターとそのプロトタイプ設定
function LengthValidator(formName, elemName, outId, min, max, msgMin, msgMax) {
    this.base = Validator;
    this.base(formName, elemName, outId);
    delete this.base;
    this.min = min;
    this.max = max;
    this.msgList = ["", msgMin, msgMax];
}

LengthValidator.prototype = new Validator();

LengthValidator.prototype.verify = function(value) {
    if (value.length < this.min)
        return 1;
    if (value.length > this.max && this.max > 0)
        return 2;
    return 0;
}

Web ページのバリデーターを容易に管理できるように、valid.js ファイルでは、バリデーターの配列をラップするオブジェクトを作成する ValidatorList() コンストラクター (リスト 8 を参照) を定義しています。add() メソッドが配列にバリデーター・インスタンスを追加すると、execute() メソッドがバリデーターのリストを繰り返し処理して、指定されたフォーム名とオプション要素名と一致するプロパティーを持つバリデーターそれぞれの execute() メソッドを呼び出します。したがって、ValidatorListexecute() メソッドを使用すれば、フォーム全体、あるいはフォームの 1 つの要素だけを検証することができます。

リスト 8. ValidatorList() コンストラクターとそのプロトタイプ設定
function ValidatorList() {
    this.validatorArray = new Array();
}

ValidatorList.prototype.add = function(validator) {
    this.validatorArray[this.validatorArray.length] = validator;
}

ValidatorList.prototype.execute = function(formName, elemName) {
    var valid = true;
    for (var i = 0; i < this.validatorArray.length; i++) {
        var validator = this.validatorArray[i];
        if (validator.formName == formName)
            if (!elemName || validator.elemName == elemName)
                if (!validator.execute())
                    valid = false;
    }
    return valid;
}

var pageValidators = new ValidatorList();

サーバー・サイドのバリデーター・ライブラリーの作成

前のセクションでは、クライアント・サイドのユーザー入力を JavaScript を使用して検証する方法を説明しました。フォームのデータがサブミットされた後の適切なプラクティスとしては、セキュリティーのため、あるいは JavaScript がユーザーのブラウザーで無効に設定されている場合に備え、サーバー・サイドでユーザー入力をもう一度検証することです。このセクションでは、バリデーターとして機能する一連の JSP タグ・ファイルについて説明します。

JSP 構文を使用するタグ・ファイルはページ全体で再利用できるため、サーバーに検証を実装するにはタグ・ファイルが簡単な手段となります。さらに、ユーザー入力を検証するタグ・ファイルと同じファイルで、Web ブラウザーでの検証を行う JavaScript コードを生成することもできます。このように、タグ・ファイルでサーバーでの検証を実装すれば、Web ページで 1 度だけバリデーターの属性を指定すればよいだけなので、コーディングする量が減ることになります。

JSP タグ・ファイルを使用してバリデーターを実装する

前のセクションで紹介した JavaScript バリデーターは、innerHTML を利用してメッセージを表示するため、Web ページに <span> 要素が必要になります。これらの要素は、errMsg.tag ファイル (リスト 9 に記載) によって生成されます。

リスト 9. errMsg.tag ファイル
<%@ attribute name="id" required="true" rtexprvalue="true" %>
<%@ tag body-content="scriptless" %>

<span id="${id}" class="ValidationError"><jsp:doBody/></span>

エラー・メッセージに使用する ValidationError スタイル・クラスは、valid.css ファイル (リスト 10 を参照) が定義します。

リスト 10. valid.css ファイル
.ValidationError { color: red; }

required.tag ファイル (リスト 11 を参照) は、サーバー・サイドでの必須フィールドの検証を実装し、クライアント・サイドで RequiredValidator オブジェクトをセットアップする JavaScript コード・フラグメントを生成します。このタグ・ファイルが定義する属性は、必須フィールドの名前、そしてオプション・エラー・メッセージの 2 つです。

msg 属性が指定されていない場合、required.tag ファイルは <dvu:useDefaultMsg/> によって別のタグ・ファイルを呼び出し、バリデーターのデフォルト・メッセージが含まれる defaultValidMsg マップを設定します。その上で、必須メッセージが msg 変数に保存されます。

続いて required.tag ファイルは <dvu:errMsg> を使用して、エラー・メッセージがある場合にこれをラップする <span> 要素を生成します。指定された名前を持つリクエスト・パラメーターがない場合、つまりユーザーが必須フィールドを空白のままにした場合には、このタグ・ファイルは <c:out> によってメッセージを出力し、<dfu:addError> タグを使用してマップにエラーを追加します。これについては、連載の第 2 回で説明したとおりです。

その後で、required.tag ファイルは数行の JavaScript コードを生成します。このコードが Web ブラウザーで実行されると RequiredValidator オブジェクトが作成され、JavaScript バリデーターのリストである、pageValidators に追加されます。RequiredValidator については、この記事の前のセクションで説明したとおりです。RequiredValidator() コンストラクターに渡されるストリング・パラメーターは<da:jstring> タグによってエンコードされます。

リスト 11. required.tag ファイル
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="msg" required="false" rtexprvalue="true" %>
<%@ tag body-content="empty" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>
<%@ taglib prefix="dvu" tagdir="/WEB-INF/tags/dynamic/valid/util" %>

<c:if test="${empty msg}">
    <dvu:useDefaultMsg/>
    <c:set var="msg" value="${defaultValidMsg.required}"/>
</c:if>

<c:set var="outId" value="${name}Required"/>
<dvu:errMsg id="${outId}">
    <c:if test="${formPost && empty param[name]}">
        <c:out value="${msg}"/>
        <dfu:addError name="${name}" msg="${msg}"/>
    </c:if>
</dvu:errMsg>

<script type="text/javascript">
    pageValidators.add(new RequiredValidator(
        <da:jstring value="${formName}"/>, <da:jstring value="${name}"/>,
        <da:jstring value="${outId}"/>, <da:jstring value="${msg}"/>));
</script>

length.tag ファイル (リスト 12 に記載) も同じくバリデーターで、このファイルはリクエスト・パラメーターの長さを検証します。検証されるパラメーターは通常、サブミットされたフォーム要素の値です。このタグ・ファイルは、要素の名前、最小長と最大長、そして 2 つのオプション・エラー・メッセージという 5 つの属性を受け入れ、required.tag の場合と同じく、<dvu:errMsg> によって生成された <span> 要素のなかにすべてのエラー・メッセージを出力します。length.tag が生成する JavaScript コードは、クライアント・サイドで LengthValidator オブジェクトを作成します。

リスト 12. length.tag ファイル
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="min" required="false" rtexprvalue="true"
    type="java.lang.Long" %>
<%@ attribute name="max" required="false" rtexprvalue="true"
    type="java.lang.Long" %>
<%@ attribute name="msgMin" required="false" rtexprvalue="true" %>
<%@ attribute name="msgMax" required="false" rtexprvalue="true" %>
<%@ tag body-content="empty" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>
<%@ taglib prefix="dvu" tagdir="/WEB-INF/tags/dynamic/valid/util" %>

<dvu:useDefaultMsg/>
<c:if test="${empty msgMin}">
    <c:set var="msgMin" value="${defaultValidMsg.too_short}"/>
</c:if>
<c:if test="${empty msgMax}">
    <c:set var="msgMax" value="${defaultValidMsg.too_long}"/>
</c:if>

<c:set var="outId" value="${name}Length"/>
<dvu:errMsg id="${outId}">
    <c:if test="${formPost && !empty param[name]}">
        <c:if test="${min != null && fn:length(param[name]) < min}">
            <c:out value="${msgMin}"/>
            <dfu:addError name="${name}" msg="${msgMin}"/>
        </c:if>
        <c:if test="${max != null && fn:length(param[name]) > max}">
            <c:out value="${msgMax}"/>
            <dfu:addError name="${name}" msg="${msgMax}"/>
        </c:if>
    </c:if>
</dvu:errMsg>

<script type="text/javascript">
    pageValidators.add(new LengthValidator(<da:jstring value="${formName}"/>,
        <da:jstring value="${name}"/>, <da:jstring value="${outId}"/>,
        ${min != null ? min : 0}, ${max != null ? max : 0},
        <da:jstring value="${msgMin}"/>, <da:jstring value="${msgMax}"/>));
</script>

JSP タグ・ファイルを使用してリソース・バンドルを作成する

Java™ アプリケーションでは通常、メッセージはリソース・バンドルに保存されます。リソース・バンドルは一般に、.properties ファイルとしてコーディングされて CLASSPATH に配置されます。リソース・バンドルで何らかの変更を行った場合には、.properties ファイルが確実にリフレッシュされるようにアプリケーションを再起動しなければなりません。アプリケーションを再起動しなくても済むようにするには、メッセージとその他のテキスト・リソースを JSP タグ・ファイルに配置するという方法を使えます。この場合、タグ・ファイルが変更されると、アプリケーション・サーバーが自動的にタグ・ファイルをリロードすることになります。

リスト 13 に記載する useDefaultMsg.tag ファイルは、バリデーターのデフォルト・メッセージを定義し、これらのメッセージを JSP の request スコープ内の <jsp:useBean> で作成された java.util.HashMap オブジェクトの中にグループ化します。メッセージは、JSTL の <c:set> タグによってマップに配置されます。リクエストごとに、useDefaultMsg.tag ファイルは ${pageContext.request.locale} で取得したユーザーの選択ロケールに応じて、バリデーターが使用するメッセージ・マップを選択します。

リスト 13. useDefaultMsg.tag ファイル
<%@ tag body-content="scriptless" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<c:if test="${defaultValidMsg_en == null}">
    <jsp:useBean id="defaultValidMsg_en" scope="request" class="java.util.HashMap">
        <c:set target="${defaultValidMsg_en}" property="required"  value="Required"/>
        <c:set target="${defaultValidMsg_en}" property="too_short" value="Too short"/>
        <c:set target="${defaultValidMsg_en}" property="too_long"  value="Too long"/>
    </jsp:useBean>
    <c:if test="${!initParam.debug}">
        <c:set var="defaultValidMsg_en" scope="application"
            value="${requestScope.defaultValidMsg_en}"/>
    </c:if>
</c:if>
...
<c:if test="${defaultValidMsg == null}">
    <c:choose>
        <c:when test="${fn:startsWith(pageContext.request.locale, 'en')}">
            <c:set var="defaultValidMsg" scope="request"
                value="${defaultValidMsg_en}"/>
        </c:when>
        ...
    </c:choose>
</c:if>

上記のサンプルは、自動的にリロード可能なリソースを実装する方法を説明するためのもので、実際のアプリケーションではこのメカニズムを強化して、より単純な構文でリソースを定義できるようにするはずです。例えば、メッセージごとに target 属性を指定しなくても済むように、<c:set> の代わりにカスタム・タグを使用するなどです。また、リソースをロケール別のファイルに配置して、簡単に変換できるようにするという方法も考えられます。

開発中には、web.xml ファイルで debug パラメーターを true に設定して (リスト 14 を参照)、リクエストごとに、すべてのメッセージ・マップが再作成されるようにしてください。このようにすることで、変更する際に Web アプリケーションを再起動する必要がなくなります。本番環境に移す際には、debug パラメーターを false に切り替え、application スコープ内でのマップのキャッシュを有効にします。

リスト 14. web.xml ファイル
<web-app ...>

    <context-param>
        <param-name>skin</param-name>
        <param-value>default</param-value>
    </context-param>

    <context-param>
        <param-name>debug</param-name>
        <param-value>true</param-value>
    </context-param>

</web-app>

JavaScript バリデーターを Web フォームに対して有効にする

これまで、クライアント・サイドとサーバー・サイドの両方でユーザー入力を検証する方法を見てきました。次は、JavaScript ベースの検証を Web ブラウザーで有効にする方法を説明します。フォーム要素の onblur 属性を使用すると、要素がキーボード・フォーカスを失ったとき、つまりユーザーが入力したばかりの値を検証する絶好のタイミングで、JavaScript コードを実行することができます。さらに、<form> タグの onsubmit 属性では、サブミット・ボタンがクリックされた直後に JavaScript 関数を呼び出せるだけでなく、ユーザーの入力が有効でない場合には false を返すことによってフォームのサブミットをキャンセルすることさえできます。

連載第 2 回では、一連の JSP タグ・ファイルを使用して HTML フォームを生成する方法を説明しました。フォーム・タグ・ファイルは attrList.tag ファイル (リスト 15 に記載) を使ってフォーム要素の動的属性を生成することから、検証のサポートを追加するためにこれらの HTML フォームを変更する必要はありません。attrList.tag にデフォルト・スタイルを追加する方法については第 2 回で説明したので、今回はこのファイルを変更して onblur 属性と onsubmit 属性を追加し、これらの属性に JavaScript バリデーターを実行するための関数呼び出しを含めます。

まず、HTML 要素に追加する必要があるclassonblur、および onsubmit 属性を、addMap という名前の java.util.HashMap インスタンスに配置します。すると、attrList.tagmap および addMap コレクションの要素を繰り返し処理して、要素の HTML 属性を生成します。

リスト 15. attrList.tag ファイル
<%@ attribute name="tag" required="true" rtexprvalue="true" %>
<%@ attribute name="map" required="true" rtexprvalue="true"
    type="java.util.Map" %>
<%@ tag body-content="empty" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<jsp:useBean id="addMap" class="java.util.HashMap"/>
<c:if test="${!empty initParam.skin}">
    <c:set target="${addMap}" property="class"
        value="${initParam.skin}_${tag}"/>
</c:if>
<c:if test="${tag=='input_text' || tag=='input_password' || tag=='input_radio'
        || tag=='textarea' || tag=='select'}">
    <c:set target="${addMap}" property="onblur"
        value="pageValidators.execute('${formName}', this.name)"/>
</c:if>
<c:if test="${tag=='form'}">
    <c:set target="${addMap}" property="onsubmit"
        value="return pageValidators.execute('${formName}')"/>
</c:if>

<c:forEach var="attr" items="${map}">
    <c:if test="${addMap[attr.key] == null}">
        ${attr.key}="${fn:escapeXml(attr.value)}"
    </c:if>
    <c:if test="${addMap[attr.key] != null}">
        <c:set var="sep" value="${fn:startsWith(attr.key, 'on') ? ';' : ' '}"/>
        <c:set target="${addMap}" property="${attr.key}"
            value="${attr.value}${sep}${addMap[attr.key]}"/>
    </c:if>
</c:forEach>

<c:forEach var="attr" items="${addMap}">
    ${attr.key}="${fn:escapeXml(attr.value)}"
</c:forEach>

サンプル・アプリケーションへの検証コードの追加

連載第 2 回では、フォーム・タグ・ファイルをベースとした単純な JSP のサンプルを紹介しました。このセクションでは、その Web フォームに検証コードを追加する方法を説明します。

Web フォームを更新する

リスト 16 に、SupportForm.jsp ページを変更したバージョンを記載します。このバージョンで最初に目に留まる変更は、<%@page%> ディレクティブでしょう。ここには autoFlush="false" 属性と buffer="64kb" 属性が設定されています。デフォルトでは JSP ページのバッファーは 8kb で、バッファーが動的に生成された HTML でいっぱいになると自動的にフラッシュされます。バッファーがフラッシュされると、ページは <jsp:forward> を使用できなくなります。

SupportForm.jsp ページで使用される <df:form> タグを実装する form.tag ファイルは、ユーザー入力が有効な場合には、<jsp:forward> を使って HTTP リクエストを SupportConfirm.jsp という別のページにディスパッチします。検証コードが追加された状態では、SupportForm.jsp が生成する HTML のサイズは 8kb を上回ります。そのため、転送が正常に機能するようにページのバッファーを増やしているというわけです。さらに、autoFlush を無効にして、バッファーに Web ページが生成する HTML 全体を収めるだけの大きさがない場合に、簡単にデバッグできるようにしています。

SupportForm.jsp ページのヘッダーも変更されていることに気付くはずです。このヘッダーで宣言しているのは、バリデーター・タグのライブラリー (接頭辞 dv)、valid.css ファイル、そして HTML エンコーディングとクライアント・サイドの検証に使用される JavaScript 関数が含まれる 2 つの JavaScript ファイル (encode.jsvalid.js) です。SupportForm.jsp ページ全体で、フォーム要素のほとんどは <dv:required> に続いていて、そのうちの一部の要素では入力されたストリングの長さを検証するために <dv:length> を使用しています。

リスト 16. SupportForm.jsp サンプル
<%@ page autoFlush="false" buffer="64kb"%>
<%@ taglib prefix="df" tagdir="/WEB-INF/tags/dynamic/forms" %>
<%@ taglib prefix="dv" tagdir="/WEB-INF/tags/dynamic/valid" %>

<jsp:useBean id="supportBean" scope="request" class="formsdemo.SupportBean"/>

<html>
<head>
    <title>Support Form</title>
    <link rel="stylesheet" href="forms.css" type="text/css">
    <link rel="stylesheet" href="valid.css" type="text/css">
    <script src="encode.js" type="text/javascript">
    </script>
    <script src="valid.js" type="text/javascript">
    </script>
</head>
<body>

    <h1>Support Form</h1>

    <df:form name="supportForm" model="${supportBean}"
            action="SupportConfirm.jsp">

        <p>Name:
        <dv:required name="name"/>
        <dv:length name="name" max="60"/> <br>
        <df:input name="name" type="text" size="40"/>

        <p>Email:
        <dv:required name="email"/>
        <dv:length name="email" max="60"/> <br>
        <df:input name="email" type="text" size="40"/>

        <p>Versions:
        <dv:required name="versions"/> <br>
        <df:select name="versions" multiple="true" size="5">
            <df:option>2.0.1</df:option>
            <df:option>2.0.0</df:option>
            <df:option>1.1.0</df:option>
            <df:option>1.0.1</df:option>
            <df:option>1.0.0</df:option>
        </df:select>

        <p>Platform:
        <dv:required name="platform"/> <br>
        <df:input name="platform" type="radio" value="Windows"/> Windows <br>
        <df:input name="platform" type="radio" value="Linux"/> Linux <br>
        <df:input name="platform" type="radio" value="Mac"/> Mac <br>

        <p>Browser:
        <dv:required name="browser"/> <br>
        <df:select name="browser" size="1">
            <df:option value=""></df:option>
            <df:option value="IE">IE</df:option>
            <df:option value="Firefox">Firefox</df:option>
            <df:option value="Netscape">Netscape</df:option>
            <df:option value="Mozilla">Mozilla</df:option>
            <df:option value="Opera">Opera</df:option>
            <df:option value="Safari">Safari</df:option>
        </df:select>

        <p><df:input name="crash" type="checkbox"/> Causes browser crash

        <p>Problem:
        <dv:required name="problem"/>
        <dv:length name="problem" min="100" max="2000"
            msgMin="Cannot have less than 100 characters"
            msgMax="Cannot have more than 2000 characters"/> <br>
        <df:textarea name="problem" rows="10" cols="40"/>

        <p><df:input name="submit" type="submit" value="Submit"/>

    </df:form>

</body>
</html>

生成された HTML を分析する

SupportForm.jsp ページが生成した HTML (リスト 17 にその一部を記載) を見てみると、クライアント・サイドの検証に関係する以下の属性とコード・フラグメントがあることがわかります。

  • <form> 要素には onsubmit 属性があり、フォーム全体が有効な場合にだけ true を返す JavaScript 呼び出しが含まれます。
  • <input><textarea><select> の各要素は onblur 属性を使用して、キーボード・フォーカスが失われると同時にそれぞれの値を検証します。
  • 各バリデーター・タグは以下のものを生成します。
    • エラー・メッセージが挿入される <span> 要素
    • バリデーター・オブジェクトを作成する JavaScript コード
リスト 17. SupportForm.jsp によって生成された HTML
<html>
<head>
    <title>Support Form</title>
    <link rel="stylesheet" href="forms.css" type="text/css">
    <link rel="stylesheet" href="valid.css" type="text/css">
    <script src="encode.js" type="text/javascript">
    </script>
    <script src="valid.js" type="text/javascript">
    </script>
</head>
<body>

    <h1>Support Form</h1>

    <form name="supportForm" method="POST" class="default_form"
            onsubmit="return pageValidators.execute('supportForm')">
        ...
        <p>Email: 
        <span id="emailRequired" class="ValidationError">
        </span>
        <script type="text/javascript">
            pageValidators.add(new RequiredValidator(
                "supportForm", "email", "emailRequired", "Required"));
        </script>
        <span id="emailLength" class="ValidationError">
        </span>
        <script type="text/javascript">
            pageValidators.add(new LengthValidator(
                "supportForm", "email", "emailLength", 0, 60, 
                "Too short", "Too long"));
        </script>
        <br>
        <input name="email" type="text" size="40" class="default_input_text"
            onblur="pageValidators.execute('supportForm', this.name)">
        ...
    </form>

</body>
</html>

まとめ

この記事では、JavaScript のコンストラクターとプロトタイプを使用して、クライアント・サイドのバリデーター階層を作成する方法を学びました。また、JSP タグ・ファイルをベースとしたサーバー・サイドのバリデーターとリソース・バンドルを作成する方法もわかったはずです。この連載の次回の記事では、JSF のようなコンポーネントを JSP タグ・ファイルを使って作成する方法を説明します。お楽しみに。


ダウンロード可能なリソース


関連トピック

  • 第 2 回では、セットアップと構成を最小限にするための規則と JSP タグ・ファイルを使用して、HTML フォームを作成する方法を紹介しました。
  • Web 2.0 開発のツールと情報が満載の developerWorks Web development ゾーンにアクセスしてください。
  • developerWorks Ajax resource center には、Ajax 関連の記事が次々と追加されています。Ajax アプリケーションを今すぐ始めるのに役立つ資料もここから入手できます。
  • JavaScript ツールと資料をお探しなら、developer.mozilla.org にアクセスしてください。
  • JSP のホーム・ページにアクセスして、JavaServer Pages に関する豊富な資料を入手してください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Java technology
ArticleID=332746
ArticleTitle=容易になった Ajax と Java 開発: 第 3 回 DOM、JavaScript、JSP タグ・ファイルを使って UI 機能を作成する
publish-date=07222008