JavaScript EE: 第 3 回 JSP で Java Scripting API を使用する

Web サーバー上でも、Web ブラウザーでも動作する JavaScript コードを作成する

この連載の前の 2 回の記事では、JavaScript ファイルをサーバー上で実行し、Ajax でリモート JavaScript 関数を呼び出す方法を説明しました。今回の記事では、サーバー・サイドの JavaScript コードを JSP (Java™Server Pages) 技術で使用する方法、JavaScript が無効にされた Web ブラウザーでも機能する Ajax (Asynchronous JavaScript and XML) ユーザー・インターフェースを作成する方法を紹介します。紹介するサンプル・コードは、皆さん独自のアプリケーションで再利用できる小さな JSP タグ・ライブラリーや、(Web サーバー上または Web ブラウザーで実行可能な) JavaScript コードによって生成される動的 Web フォームで構成されています。

Andrei Cioroianu, Senior Java Developer and Consultant, Devsphere

Andrei Cioroianu は、Java EE 開発および Web 2.0/Ajax コンサルティング・サービスを提供する会社、Devsphere の創設者です。1997年から Java および Web 技術を使っている彼は、商用製品、カスタム・アプリケーション、そしてオープンソース・フレームワークの複雑な技術的問題の解決とライフサイクル全般の管理という分野で 10 年以上、専門的経験を積んでいます。彼への連絡には、www.devsphere.com のコンタクト・フォームを利用してください。



2009年 6月 02日

同じ JavaScript コードをサーバーとクライアントの両方で実行できるということは、大きなメリットになります。そのメリットとは、Ajax クライアントと Ajax 以外のクライアントに共通の 1 つのコード・ベースを保守できるようになること、そしてより柔軟な開発が可能になることです。例えば、他の開発者に分析させたくはない JavaScript コードを開発しているとしたら、そのコードをサーバー上で実行することで、知的財産を守るとともにセキュリティー上のリスクを最小限にすることができます。その後、コードをリリースする準備が整った時点で、その JavaScript コードをクライアントに移すことで、アプリケーションのパフォーマンスを改善することができます。

ページ・コンテキストとスクリプト・コンテキスト

Java Scripting API と JSP (JavaServer Pages) は 2 つの独立した Java 技術ですが、簡単に統合することができます。このそれぞれの技術は、明確に定義されたコンテキストでコードを実行します。JSP 技術では JSP の一連の暗黙オブジェクトである pageContextpagerequestresponseoutsessionconfig、そして application にアクセスすることができます。第 1 回ではこれらのオブジェクトをサーブレットによって実行される JavaScript ファイルにエクスポートする方法を説明したので、今回の記事では JSP ページによって実行されるスクリプトにエクスポートする方法を説明します。

JavaScript エンジンは、アプリケーション・コードのなかで定義されているスクリプトの変数や関数を維持管理するために、異なるタイプのコンテキストを使用します。スクリプトのなかで変数を設定していたり、関数を定義していたりする場合、そのスクリプトに続いて同じコンテキストで実行するスクリプトでは、前のスクリプトの変数および関数を使用することができます。そのため次のセクションで説明するように、HTTP リクエストの処理中は同じ 1 つのスクリプト・コンテキストを使用するのが妥当です。

JavaScript 言語で作成されたスクリプトは、公開フィールドにアクセスして、任意の Java オブジェクトの public メソッドを呼び出すことができます。さらに、get メソッドや set メソッドを呼び出す代わりに、object.property 構文を使用して JavaBean プロパティーの値を取得、変更することも可能です。JavaScript コードで Java オブジェクトを使うのは至って簡単なことで、唯一欠けているものといえば、JSP のページ・コンテキストと JavaScript コンテキストとの間でオブジェクトを受け渡すための一連のカスタム・タグだけです。これらのカスタム・タグは、この記事で説明するように数行のコードだけで実装することができます。

Web ページでサーバー・サイドの JavaScript コードを使用する方法

このセクションでは、Ajax/HTTP リクエスト全体をとおして JavaScript のコンテキストを管理する方法、そして JSP のページ・コンテキストと JavaScript のコンテキストとの間で変数を受け渡す方法を説明します。

JSP オブジェクトを JavaScript コードで使用できるようにする

連載の第 1 回では、サーバー上で JavaScript ファイルを実行するために使用する Java Scripting API ベースの Java サーブレットを紹介しました。このセクションで説明する JSUtil というクラスも同じく Java Scripting API を使用して、JSP ページの実行中に JavaScript コード・フラグメントを実行します。まず始めに必要な作業は、ScriptEngineManager オブジェクトを作成し、ScriptEngine インスタンスを取得することです (リスト 1 を参照)。

リスト 1. JSUtil の getScriptEngine() メソッド
package jsee.util;

import javax.script.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import java.io.*;

public class JSUtil {
    private static ScriptEngine engine;
    
    public static synchronized ScriptEngine getScriptEngine() {
        if (engine == null) {
            ScriptEngineManager manager = new ScriptEngineManager();
            engine = manager.getEngineByName("JavaScript");
        }
        return engine;
    }
    ...
}

リスト 2 に記載する createScriptContext() メソッドは、ScriptContext インスタンスを初期化し、ページ・コンテキストから JSP の暗黙オブジェクトを取得して、これらのオブジェクトをスクリプト・コンテキストの変数として設定します。この操作によって、スクリプト・コンテキストで実行される JavaScript コードから以下の暗黙オブジェクトにアクセスできるようになります。

  • pageContext オブジェクトは、ページをスコープとする変数のリポジトリーで、このオブジェクトには他のすべての暗黙オブジェクトを取得するためのメソッドがあります。
  • page オブジェクトは、現行のリクエストを処理するサーブレット・クラスのインスタンスです。
  • request オブジェクトは、HTTP リクエストのパラメーターとヘッダーを取得するために使用します。
  • response オブジェクトは、HTTP レスポンスのヘッダーを設定するために使用するオブジェクトで、JSP コードのなかでライターとして使える out を提供します。
  • out オブジェクトは、JSP ページの出力に使用されます。
  • session オブジェクトは、リクエストが行われてから次のリクエストが行われるまで、そのリクエストを行ったユーザーに関連する状態を保持します。
  • config オブジェクトは、JSP を実装するサーブレットの構成を表します。
  • application オブジェクトは、すべてのユーザーが共有する Bean インスタンスを格納し、web.xml ファイルに指定された初期化パラメーターを取得するために使用されます。
リスト 2. JSUtil の createScriptContext() メソッド
public class JSUtil {
    ...
    public static ScriptContext createScriptContext(PageContext pageContext) {
        ScriptContext scriptContext = new SimpleScriptContext();
        int scope = ScriptContext.ENGINE_SCOPE;
        scriptContext.setAttribute("pageContext", pageContext, scope);
        scriptContext.setAttribute("page", pageContext.getPage(), scope);
        scriptContext.setAttribute("request", pageContext.getRequest(), scope);
        scriptContext.setAttribute("response", pageContext.getResponse(), scope);
        scriptContext.setAttribute("out", pageContext.getOut(), scope);
        scriptContext.setAttribute("session", pageContext.getSession(), scope);
        scriptContext.setAttribute("config", pageContext.getServletConfig(), scope);
        scriptContext.setAttribute("application",
                pageContext.getServletContext(), scope);
        scriptContext.setWriter(pageContext.getOut());
        return scriptContext;
    }
    ...
}

HTTP リクエストを処理する間は、単一のスクリプト・コンテキストを使用する必要があります。そうすることで、どの JavaScript フラグメントでも前に実行されたスクリプトが定義する変数と関数を使用できるようになります。これを実現する簡単な方法は、スクリプト・コンテキストを request オブジェクトの属性として格納することです。getScriptContext() メソッド (リスト 3 を参照) では、スクリプト・コンテキストに jsee.ScriptContext という属性名を使用しています。

リスト 3. JSUtil の getScriptContext() メソッド
public class JSUtil {
    ...
    public static ScriptContext getScriptContext(PageContext pageContext)
            throws IOException {
        ServletRequest request = pageContext.getRequest();
        synchronized (request) {
            ScriptContext scriptContext
                = (ScriptContext) request.getAttribute("jsee.ScriptContext");
            if (scriptContext == null) {
                scriptContext = createScriptContext(pageContext);
                request.setAttribute("jsee.ScriptContext", scriptContext);
            }
            return scriptContext;
        }
    }
    ...
}

runScript() メソッド (リスト 4 を参照) は、スクリプト・エンジンの eval() メソッドを使用して、指定の JavaScript フラグメントを現行の HTTP リクエストのコンテキストで実行します。スクリプト例外がスローされると、runScript() はソース・コードに続いてスタック・トレースを出力し、ServletException をスローして JSP ページの実行を停止します。

リスト 4. JSUtil の runScript() メソッド
public class JSUtil {
    ...
    public static void runScript(String source, PageContext pageContext)
            throws ServletException, IOException {
        try {
            getScriptEngine().eval(source, getScriptContext(pageContext));
        } catch (ScriptException x) {
            ((HttpServletResponse) pageContext.getResponse()).setStatus(500);
            PrintWriter out = new PrintWriter(pageContext.getOut());
            out.println("<pre>" + source + "</pre>");
            out.println("<pre>");
            x.printStackTrace(out);
            out.println("</pre>");
            out.flush();
            throw new ServletException(x);
        }
    }

}

カスタム・タグを使用して JavaScript コード・フラグメントを実行する

script.tag ファイル (リスト 5 を参照) では、JavaScript コードの一部をサーバー上、クライアント上、またはその両方で実行することができます。どこで実行するのかは、runat 属性の値によって決まります。runatclient または both に設定されている場合、タグ・ファイルが出力するのは HTML <script> 要素です。この要素には、Web ページの <js:script></js:script> の間に配置したコードが含まれます。一方、runat 属性が server または both に設定されている場合には、JavaScript コード・フラグメントが source という JSP 変数として取得され、この変数の値が JSUtil クラスの runScript() メソッドに渡されます。

リスト 5. script.tag ファイル
<%@ attribute name="runat" required="true" rtexprvalue="true" %>
<%@ tag body-content="scriptless" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:if test="${runat == 'client' or runat == 'both'}">
    <script type="text/javascript">
        <jsp:doBody/>
    </script>
</c:if>

<c:if test="${runat == 'server' or runat == 'both'}">
    <jsp:doBody var="source"/>
    <%
        jsee.util.JSUtil.runScript(
                (String) jspContext.getAttribute("source"),
                (PageContext) jspContext);
    %>
</c:if>

<js:script> タグがどのように機能するのかを理解する簡単な方法は、単純なサンプルを見ることです。リスト 6 に記載するサンプル ScriptDemo.jsp ページは、<a> 要素を返す link() という関数を定義しています。runat 属性は both に設定されているため、この関数は Web サーバー上でも、Web ブラウザーでも実行することができます。

サーバー・サイドで JSP ページが処理されている間、println(link(request.requestURL)) の呼び出しによって現行ページへのリンクが出力されます。request オブジェクトには (サーバー上の) JavaScript コードからアクセスすることができます。これは、タグ・ファイルによって呼び出される runScript() メソッドを持つ JSUtil クラスが、コンテキストを初期化するからです。そのため、JavaScript エンジンが request.requestURL 式を評価するときには、実際には HttpServletRequest インスタンスの getRequestURL() メソッドを呼び出すことになります。

クライアント・サイドでは、document.writeln(link(location)) の呼び出しによって ScriptDemo.jsp ページへの別のリンクが出力されます。location プロパティーによって取得されるこのページの URL は、Web ブラウザーでのみ有効です。

リスト 6. サンプル ScriptDemo.jsp
<%@ taglib prefix="js" tagdir="/WEB-INF/tags/js" %>

<js:script runat="both">
    function link(url) {
        return '<a href="' + url + '">' + url + '</a>';
    }
</js:script>

<js:script runat="server">
    println(link(request.requestURL));
</js:script>

<br>

<js:script runat="client">
    document.writeln(link(location));
</js:script>

リスト 7 にサンプル ScriptDemo.jsp ページによって生成された HTML 出力を記載します。以下のように、この出力には link() 関数、サーバー・サイドで生成された <a> 要素、そしてクライアント・サイドで 2 つ目のリンクを出力する JavaScript コードが含まれています。

リスト 7. ScriptDemo.jsp が生成した出力
<script type="text/javascript">
    function link(url) {
        return '<a href="' + url + '">' + url + '</a>';
    }
</script>
    
<a href="http://localhost:8080/jsee/ScriptDemo.jsp">
http://localhost:8080/jsee/ScriptDemo.jsp</a>

<br>

<script type="text/javascript">
    document.writeln(link(location));
</script>

変数を取得、設定する

サーバー・サイドの JavaScript コードを Web ページで使用するときには、JSP のコンテキストとスクリプト・コンテキストとの間での変数の受け渡しが必要になってきます。get.tag ファイル (リスト 8 を参照) が実装する <js:get> というカスタム・タグは、スクリプト変数を JSP の変数としてエクスポートします。エクスポートされる JSP の変数の名前を指定するのは var 属性です。

この変数は JSP の属性としてタグ・ファイルに渡されることから、変数の名前は JSP のコンテキストから取得されます。スクリプト変数の値はスクリプト・コンテキストの getAttribute() メソッドによって取得されます。続いて jspContext.setAttribute() によって、varAlias という名前の JSP の変数がタグ・ファイルのページ・スコープ内に作成されます。タグ・ファイルで使用されている <%@variable%> ディレクティブは JSP コンテナー (例えば Tomcat) に対し、タグ・ファイルを呼び出すページに varAlias 変数をエクスポートする際に、var 属性で指定された変数名を使うように指示します。この手法により、呼び出されたタグ・ファイルは <js:get> タグを使って JSP のページ・スコープに JSP の変数を定義することができます。

リスト 8. get.tag ファイル
<%@ attribute name="var" required="true" rtexprvalue="false" %>
<%@ variable name-from-attribute="var" alias="varAlias" scope="AT_END"
    variable-class="java.lang.Object" %>
<%@ tag body-content="empty" %>

<%
    String var = (String) jspContext.getAttribute("var");
    Object value = jsee.util.JSUtil.getScriptContext(
            (PageContext) jspContext).getAttribute(var);
    jspContext.setAttribute("varAlias", value);
%>

set.tag ファイル (リスト 9 を参照) によって実装される <js:set> タグが取る属性は 2 つ (変数名と値) で、この 2 つの属性が、スクリプト・コンテキストの setAttribute() メソッドによって JavaScript 変数を設定するときに使用されます。オプションの value 属性が指定されていなければ、タグ・ファイルは <jsp:doBody var="value"/> を使用して JSP ページの <js:set></js:set> の間に置かれたコードを実行します。

リスト 9. set.tag ファイル
<%@ attribute name="var" required="true" rtexprvalue="false" %>
<%@ attribute name="value" required="false" rtexprvalue="true"
    type="java.lang.Object"%>
<%@ tag body-content="scriptless" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:if test="${empty value}">
    <jsp:doBody var="value"/>
</c:if>

<%
    String var = (String) jspContext.getAttribute("var");
    Object value = jspContext.getAttribute("value");
    jsee.util.JSUtil.getScriptContext((PageContext) jspContext)
        .setAttribute(var, value, javax.script.ScriptContext.ENGINE_SCOPE);
%>

リスト 10 に記載するサンプル VarDemo.jsp は、<js:set> タグを使用して 2 つのスクリプト変数を初期化します。初期化された変数は <js:script> を用いてサーバー上で実行される JavaScript コード・フラグメントの中で変更されます。次に、2 つの変数の新しい値を <js:get> タグによって取得し、これらの値を JSTL の <c:out> タグを使用してページの出力に組み込みます。

リスト 10. サンプル VarDemo.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="js" tagdir="/WEB-INF/tags/js" %>

<js:set var="x" value="1"/>
<js:set var="y">2</js:set>

<js:script runat="server">
    x++;
    y--;
</js:script>

<js:get var="x"/>
<js:get var="y"/>

<c:out value="x = ${x}"/> <br>
<c:out value="y = ${y}"/>

任意のブラウザーで機能する Ajax UI の作成方法

このセクションでは Ajax と DHTML をベースとした動的フォームを紹介します。JavaScript が Web ブラウザーで無効になっていても、DynamicForm.jsp ページによって生成されたユーザー・インターフェースはサーバー・サイドでスクリプト・コードを実行することにより機能し続けます。送信されたフォームのデータを処理するにはサーバー・サイドの JavaScript ファイル BackendScript.jss を使用します。

コードを理解しやすくするために、このフォームは単純なものにしておきます。このサンプルの第一の目標は、複雑な実際のアプリケーションに適用できる、いくつかの Web 手法を実演することです。

動的フォームを開発する

図 1 に示すスクリーンショットには、フィールド数が可変の入力フィールドと、Add、Remove、Send というラベルが付いた 3 つのボタンで構成されるサンプル・フォームが示されています。Add ボタンは新しい入力フィールドをフォームに追加し、Remove ボタンは最後のフィールドを削除し、Send ボタンはフォームのデータをサーバーに送信します。

図 1. 単純な動的フォーム
単純な動的フォーム

DynamicForm.jsp をサーバー上で実行すると、outputForm() という JavaScript 関数によって初期の入力フィールドが生成されます。リスト 11 に示すように、これらのフィールドが配置されるのは <div> 要素のなかです。この要素の内容は、ユーザーが Add または Remove ボタンをクリックすると Web ブラウザーで呼び出される updateForm() 関数によって変更されます。ユーザーが Send ボタンをクリックした場合は submitForm() 関数によってユーザーの入力がサーバーに送信されます。この場合に使用されるのは、XMLHttpRequest です。

JavaScript が無効になっていると、ユーザーがボタンをクリックしても関数は呼び出されません。そのため、この場合にはユーザーがボタンをクリックすると、Web ブラウザーがデータをサーバーに送信します。JavaScript が有効になっていれば、updateForm() 関数と submitForm() 関数が false を返すので、Web ブラウザーがデータをサーバーに送信することはありません。

リスト 11. DynamicForm.jsp のフォーム
<html>
<head>
<title>Dynamic Form</title>
    ...
</head>
<body>
    <b>Dynamic Form</b><br>
    <form name="dynamicForm" method="POST" action="DynamicForm.jsp">
        <div id="inputDiv">
            <js:script runat="server">
                outputForm();
            </js:script>
        </div>
        <input type="submit" name="add" value="Add"
                onclick="return updateForm('add')">
        <input type="submit" name="remove" value="Remove"
                onclick="return updateForm('remove')">
        <input type="submit" name="send" value="Send"
                onclick="return submitForm()">
    </form>
</body>
</html>

DynamicForm.jsp ページには、Web サーバー上、または Web ブラウザーで実行可能ないくつかの関数が含まれています。そのうちの htmlEncode() および buildInput() ルーチン (リスト 12 を参照) は、単一の <input> 要素を生成するために使用されます。

リスト 12. DynamicForm.jsp の htmlEncode() および buildInput() 関数
<%@ taglib prefix="js" tagdir="/WEB-INF/tags/js" %>
...
<js:script runat="both">
    function htmlEncode(value) {
        return value ? value.replace(/&/g, "&amp;").replace(/"/g, "&quot;")
            .replace(/</g, "&lt;").replace(/>/g, "&gt;") : "";
    }

    function buildInput(name, value) {
        var content = '<input type="text"';
        if (name)
            content += ' name="' + htmlEncode(name) + '"';
        if (value)
            content += ' value="' + htmlEncode(value) + '"';
        content += ' size="30">';
        return content;
    }
    ...
</js:script>

buildForm() 関数 (リスト 13 を参照) が引数に取るパラメーターは、値のリストとコマンドの 2 つです。リストの最後の値は、指定されたコマンドが remove の場合には無視されます。cmd パラメーターが add の場合には、返される HTML ストリングの最後に空のフィールドが追加されます。

リスト 13. DynamicForm.jsp の buildForm() 関数
<js:script runat="both">
    ...
    function buildForm(values, cmd) {
        var n = values.length;
        if (cmd == 'remove' && n > 1)
            n--;
        var content = '';
        for (var i = 0; i < n; i++)
            content += buildInput("inputField", String(values[i])) + '<br>';
        if (cmd == 'add')
            content += buildInput("inputField") + '<br>';
        return content;
    }
</js:script>

サーバー・サイドのコードを実装する

DynamicForm.jsp ページには、Web サーバー上でのみ実行される 2 つの JavaScript 関数も含まれています。リスト 14 に記載するそのうちの 1 つ、getInputValues() ルーチンは、HTTP メソッドが POST である場合には inputField パラメーターの値を返します。

リスト 14. DynamicForm.jsp の getInputValues() 関数
<js:script runat="server">
    function getInputValues() {
        if (request.getMethod().toUpperCase().equals("POST"))
            return request.getParameterValues("inputField");
        else
            return null;
    }
    ...
</js:script>

もう 1 つのサーバー・サイドの関数は outputForm() です (リスト 15 を参照)。前述したように、ユーザーがボタンをクリックしたときに JavaScript が無効になっていると、Web ブラウザーがフォームのデータをサーバーに送信します。この場合、どのボタンがクリックされたのかを outputForm() が検出して cmd 変数を設定します。

コマンドが send の場合、DynamicForm.jsp ページは連載第 1 回で紹介した JSServlet クラスによって実行されるスクリプト BackendScript.jss の出力を組み込みます。このスクリプトが返すのは、JSON フォーマットでエンコードした値のリストです。

BackendScript.jss の出力はサーバー・サイドで実行される JavaScript フラグメントの一部となります。なぜなら、<jsp:include page="BackendScript.jss"/><js:script runat="server"></js:script> の間に配置されているためです。

最後に outputForm() 関数が buildForm() によって HTML 入力フィールドのリストを生成し、最終的なストリングを出力します。

リスト 15. DynamicForm.jsp の outputForm() 関数
<js:script runat="server">
    ...
    function outputForm() {
        var cmd = "";
        if (request.getParameter("add") != null)
            cmd = "add";
        else if (request.getParameter("remove") != null)
            cmd = "remove";
        else if (request.getParameter("send") != null)
            cmd = "send";
        var values = null;
        if (cmd == "send") {
            values = <jsp:include page="BackendScript.jss"/>;
        } else {
            values = getInputValues();
            if (!values)
                values = ["", "", ""]; // default input values
        }
        println(buildForm(values, cmd));
    }
</js:script>

リスト 16 に記載する BackendScript.jss は、paramValues.inputField によって入力値を取得し、配列をソートします。ソートされた配列は、toSource() によって JSON に変換されます。paramValues 変数は、init.jss スクリプトに含まれるリクエスト・パラメーターの値で初期化されます。このスクリプトは連載第 1 回で説明したように、JSServlet によって実行されます。

リスト 16. BackendScript.jss ファイル
var values = paramValues.inputField;
if (values)
    println(values.sort().toSource());
else
    println("[]");

クライアント・サイドで JavaScript を使用する

DynamicForm.jsp ページには、Web ブラウザー内でのみ実行するように設計されたルーチンがいくつかあります。そのうちの 1 つ、getInputValues() 関数 (リスト 17 を参照) は document.forms.dynamicForm.inputField 式を使って、入力フィールドを表す DOM オブジェクトの配列を取得します。入力フィールドが 1 つしかない場合、この式が実際に返すのは 1 つの DOM オブジェクトです。そのため、getInputValues()fields が配列であるかどうかを検証するために、length プロパティーの有効性をテストします。入力フィールドの値は、ストリングの配列として返されます。

リスト 17. DynamicForm.jsp の getInputValues() 関数
<js:script runat="client">
    function getInputValues() {
        var values = new Array();
        var fields = document.forms.dynamicForm.inputField;
        if (typeof fields.length == "number")
            for (var i = 0; i < fields.length; i++)
                values[i] = fields[i].value;
        else if (fields)
            values[0] = fields.value;
        return values;
    }
    ...
</js:script>

setInputValues() 関数 (リスト 18 を参照) は、フォームの入力フィールドの値を設定します。ユーザーには、この関数が呼び出された後で Web ブラウザー上に新しい値が表示されることになります。

リスト 18. DynamicForm.jsp の setInputValues() 関数
<js:script runat="client">
    ...
    function setInputValues(values) {
        var fields = document.forms.dynamicForm.inputField;
        if (typeof fields.length == "number")
        for (var i = 0; i < fields.length; i++)
            fields[i].value = values[i];
        else if (fields)
            fields.value = values[0];
    }
    ...
</js:script>

ユーザーが Add ボタンまたは Remove ボタンをクリックすると、Web ブラウザーは updateForm() 関数 (リスト 19 を参照) を呼び出します。この関数は、これらのボタンの onclick 属性と一緒に使用されます。入力フィールドのリストは buildForm() 関数によって生成されます。この関数は、フィールドの現在の値を保持します。新しい HTML コンテンツを Web ページに挿入する際には、inputDiv 要素の innerHTML プロパティーが使用されます。

リスト 19. DynamicForm.jsp の updateForm() 関数
<js:script runat="client">
    ...
    function updateForm(cmd) {
        document.getElementById('inputDiv').innerHTML
            = buildForm(getInputValues(), cmd);
        return false;
    }
    ...
</js:script>

JavaScript が有効になっている場合、onclick 属性に return submitForm() が含まれる Send ボタンをユーザーがクリックすると、Web ブラウザーは submitForm() 関数 (リスト 20 を参照) を呼び出します。この JavaScript 関数が使用するのは、連載第 2 回で紹介した小規模な Ajax ライブラリーです。このライブラリーのソース・コードについては、xhr.js ファイルを参照してください。

DynamicForm.jsp ページでは、XHR オブジェクトが作成されて xhr 変数に格納されます。XHR() コンストラクターは以下の 3 つのパラメーターを取ります。

  • HTTP メソッド。この例では POST です。
  • HTTP リクエストの URL。つまり、BackendScript.jss です。
  • ブール値のパラメーター。リクエストが非同期であるかどうかを示します。この例では同期リクエストなので、、関数はすぐに制御を返さずに HTTP レスポンスを待機します。
リスト 20. DynamicForm.jsp の submitForm() 関数
<script src="xhr.js" type="text/javascript">
</script>
...
<js:script runat="client">
    ...
    var xhr = new XHR("POST", "BackendScript.jss", false);

    function submitForm() {
        var request = xhr.newRequest();
        if (!request)
            return true;
        var values = getInputValues();
        for (var i = 0; i < values.length; i++)
            xhr.addParam("inputField", values[i]);
        function processResponse() {
            if (xhr.isCompleted()) {
                var newValues = eval(request.responseText);
                setInputValues(newValues);
            }
        }
        xhr.sendRequest(processResponse);
        return false;
    }
</js:script>

submitForm() 関数は、xhr.newRequest() によって新しい XMLHttpRequest インスタンスを作成します。このオブジェクトを作成できない場合 (つまり、Web ブラウザーが Ajax をサポートしない場合) には、Send ボタンの onclick 式が true を返せるように submitForm()true を返します。これは、Web ブラウザーがフォームのデータをサーバーに送信することを意味します。

新しい XMLHttpRequest が正常に作成された場合は、次のステップとして入力フィールドの値が取得され、xhr.addParam() によってリクエスト・パラメーターとして追加されます。

内部関数 processResponse() はコールバックとして使用されます。HTTP リクエストが完了し、ステータス・コードが 200 となっていれば、受信したレスポンスのテキストが評価されます。そして、レスポンスに含まれている配列が setInputValues() 関数に渡されることによって、入力フィールドの値が更新されます。

xhr.sendRequest() の呼び出しによって、XMLHttpRequest インスタンスの send() メソッドが呼び出されます。続いて submitForm()false を返し、Send ボタンの onclick 式が false を返せるようにします。つまり、Web ブラウザーがフォームのデータをサーバーに送信してはならないことを示しています。この振る舞いは正当なものです。なぜなら JavaScript が有効な場合、入力値は XMLHttpRequest によって送信されるため、ブラウザーが同じデータを再び送信しないようにしなければならないためです。

まとめ

この記事では JSP ページ内に組み込まれたサーバー・サイドの JavaScript コードを実行する方法、そして JSP のページ・コンテキストと JavaScript コンテキストとの間で変数を受け渡す方法を説明しました。また、Web ブラウザーで JavaScript が無効になっていても機能する Ajax ユーザー・インターフェースを作成する方法も習得できたはずです。アプリケーションが Ajax 対応の Web ブラウザーだけでなく、Ajax 対応でないクライアント・デバイスもサポートしなければならないときには、この記事で学んだ手法を適用してください。


ダウンロード

内容ファイル名サイズ
Sample application for this articlejsee_part3_src.zip26KB

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Java technology
ArticleID=405345
ArticleTitle=JavaScript EE: 第 3 回 JSP で Java Scripting API を使用する
publish-date=06022009