Java Enterprise Edition 5 プラットフォームには、拡張性があり、成熟したユーザーインタフェースコンポーネントモデルを提供する JavaServer Faces (JSF) テクノロジが含まれています。このモデルのデザインによって、アプリケーション開発者は JSF テクノロジに用意されている標準のコンポーネントを拡張して簡単にカスタムコンポーネントを作成し、アプリケーションにまたがってそれらコンポーネントを再利用できます。このエントリでは、AJAX 要求を処理するサーブレットを導入することにより、JSF コンポーネントに AJAX サポートを組み込む方法について説明します。静的リソースおよび動的リソースにアクセスするためのさまざまな方法については、「静的リソースおよび動的リソースへのアクセス」を参照してください。代替のソリューションおよび関連するソリューションについては、「JavaServer Faces テクノロジと AJAX の使用」を参照してください。
開発者が JSF コンポーネントの一部として AJAX サポートを組み込む方法は複数あります。その 1 つとして、サーブレットを使用して、カスタムコンポーネントの AJAX 要求を遮断し、処理する方法があります。JSF カスタムコンポーネントに必要なリソースは、アプリケーションバンドルにパッケージ化し、直接アクセスされるようにします。
ここでは、サーブレットを使用して JSF アプリケーションに AJAX サポートを組み込む方法について説明します。この方法では、クライアント側で AJAX 対話を実行するために必要な JavaScript を生成するカスタムコンポーネントを作成し、Java サーブレットの形式で AJAX 要求を処理するサーバー側コンポーネントを作成します。
この方法は、次の場合に使用できます。
この方法には、次の問題があります。
以上の理由により、現時点では、サードパーティーのライブラリを使用する方法が推奨されます。この方法については、「静的リソースおよび動的リソースへのアクセス」で詳しく説明しています。新しいテクノロジについてはまだ調査中であり、推奨される方法に変更があった場合はエントリを更新します。
このコンポーネント機能の簡単な例については、standalone.jsp を参照してください。1 つの JSP ページにすべてのスクリプトと機能が含まれています。
/bp-jsf-example/CompAServlet?itemId=test1A2) サーブレットで、要求が受信され、要求の itemId パラメータが取得され、適切な応答が作成および送信されます。
このコンポーネントは、詳細情報を表示するポップアップなので、表示される部分を実際に構成するマークアップを生成する必要があります。これには複数の方法があります。1 つは、レンダリング内でマークアップをハードコードし、名前空間の規則に従うために必要な値を設定する方法です。ここでは、コンポーネントの ID を前に付けて、複数のインスタンスが衝突しないようにします。コンポーネント開発者は、ResponseWriter の startElement メソッドと endElement メソッドを使用して、レンダリング内でテンプレートを直接コーディングすることもできます。コンポーネントをツール対応にし、IDE で視覚的に利用できるようにする場合は、これがもっとも良い方法です。startElement 呼び出しがそれぞれコンポーネントに関連付けられるからです。これにより、ツールで個々のコンポーネントに属するマークアップを追跡しやすくなります。Map requestMap=context.getExternalContext().getRequestMap();
Boolean scriptRendered=(Boolean)requestMap.get(RENDERED_SCRIPT_KEY);
// リソースが生成済みかどうかを確認する
if (scriptRendered != null && scriptRendered.equals(Boolean.TRUE)) {
return;
}
// 生成済みであることを示すフラグを requestMap に付ける
requestMap.put(RENDERED_SCRIPT_KEY, Boolean.TRUE);
String contextRoot=context.getExternalContext().getRequestContextPath();
// javascript ファイルのマークアップタグを生成する
writer.startElement("script", component);
writer.writeAttribute("type", "text/javascript", null);
writer.writeAttribute("src", contextRoot + "/compA.js", null);
writer.endElement("script");
writer.write("\n");
writer.write("<div class=\"bpui_compA_popTop\">");
writer.write(" <div class=\"bpui_compA_cornerTL\"><div class=\"bpui_compA_cornerTR\">");
writer.write(" <center><font color=\"white\"><b><span id=\"" + clientId + "_title\">title</span></b></font></center>");
writer.write(" </div></div>");
writer.write("</div>");
writer.write("<div class=\"bpui_compA_popMid\">");
writer.write(" <table border=\"0\" style=\"width: 100%\">");
writer.write(" <tr>");
writer.write(" <td>");
writer.write(" <table border=\"0\" bgcolor=\"#ffffff\" cellpadding=\"5\" cellspacing=\"5\">");
writer.write(" <tr>");
writer.write(" <td><span id=\"" + clientId + "_message\">Value</span></td>");
writer.write(" </tr>");
writer.write(" </table>");
writer.write(" </td>");
writer.write(" </tr>");
writer.write(" </table>");
writer.write("</div>");
writer.write("<div class=\"bpui_compA_popBot\">");
writer.write(" <div class=\"bpui_compA_cornerBL\"><div class=\"bpui_compA_cornerBR\">");
writer.write(" </div></div>");
writer.write("</div>");
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/xml;charset=UTF-8");
PrintWriter out = response.getWriter();
String itemId=request.getParameter("itemId");
response.setHeader("Pragma", "No-Cache");
response.setHeader("Cache-Control", "no-cache,no-store,max-age=0");
response.setDateHeader("Expires", 1);
if(itemId != null) {
out.println("<response>");// カスタム応答コードがここに入る
...
out.println("</response>");
} else {
out.println("<response>");
out.println("<title><![CDATA[REQUEST ERROR]]></title>");
out.println("<message><![CDATA[The query parameter 'itemId' required]]></message>");
out.println("</response>");
}
out.flush();
out.close();
}
<!-- 標準コンポーネントファミリのカスタムレンダリングを登録する -->
<render-kit>
<renderer>
<component-family>javax.faces.Output</component-family>
<renderer-type>CompA</renderer-type>
<renderer-class>com.sun.javaee.blueprints.components.ui.example.CompARenderer</renderer-class>
</renderer>
</render-kit>
<ui:compA id="pop1" url="CompAServlet?itemId="/>
<a href="#" onmouseover="bpui.compA.showPopup('pop1', event, 'test1A')"
onmouseout="bpui.compA.hidePopup('pop1')" style="cursor: pointer"><b>Mouse over link to see popup (test1A)</b></a><br/>
<small><i>(With a Servlet fulfilling AJAX Request)</i></small><br/><br/>>
<div id="pop1" class="bpui_compA_popup">
<div class="bpui_compA_popTop">
<div class="bpui_compA_cornerTL"><div class="bpui_compA_cornerTR">
<center>
<font color="white">
<b><span id="pop1_title">title</span></b></font>
</center>
</div></div>
</div>
<div class="bpui_compA_popMid">
<table border="0" style="width: 100%">
<tr>
<td>
<table border="0" bgcolor="#ffffff" cellpadding="5" cellspacing="5">
<tr>
<td><span id="pop1_message">Value</span></td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<div class="bpui_compA_popBot">
<div class="bpui_compA_cornerBL"><div class="bpui_compA_cornerBR">
</div></div>
</div>
</div>
<script type="text/javascript">
bpui.compA['pop1']=new bpui.compA.createPopup('pop1', '/bp-jsf-example/CompAServlet?itemId=');
</script>
新しい bpui.compA.createPopup オブジェクトを作成し、その結果を bpui.compA 名前空間の pop1 プロパティーに格納する JavaScript コードがあります。このオブジェクトは JavaScript クロージャーであり、bpui.compA.showPopup 関数によってポップアップがトリガーされるときに取得されます。この関数については、次の項で説明します。
// コンポーネントの ID および AJAX URL とともにコールバック関数を維持するクロージャーを作成する
bpui.compA.createPopup=function(componentId, urlx) {
this.componentId=componentId;
this.urlx=urlx;
this.ajaxReturnFunction=function() {
// 応答の準備ができていることを確認する
if (bpui.compA.req.readyState == 4) {
// 応答が有効であることを確認する
if (bpui.compA.req.status == 200) {
// 応答の情報をポップアップに挿入する
var resultx=bpui.compA.req.responseXML.getElementsByTagName("response")[0];
document.getElementById(componentId + "_title").innerHTML=resultx.getElementsByTagName("title")[0].childNodes[0].nodeValue;
document.getElementById(componentId + "_message").innerHTML=resultx.getElementsByTagName("message")[0].childNodes[0].nodeValue;;
// 新しい情報が挿入されたポップアップを表示する
document.getElementById(componentId).style.visibility='visible';
} else if (bpui.compA.req.status == 204){
// エラーの場合は警告を表示する
alert("204 returned from AJAX call");
}
}
}
}
bpui.compA.showPopup=function(popupx, eventx, itemId) {
// イベントに基づいてポップアップを配置し、ポップアップが常にフラッシュしないようにタイムアウトを設定する
var xx=0;
var yy=0;
if (!eventx) var eventx=window.event;
if (eventx.pageX || eventx.pageY){
xx=eventx.pageX;
yy=eventx.pageY;
} else if (eventx.clientX || eventx.clientY) {
xx=eventx.clientX + document.body.scrollLeft;
yy=eventx.clientY + document.body.scrollTop;
}
document.getElementById(popupx).style.left= (xx + 3) + "px";
document.getElementById(popupx).style.top=yy + "px";
// ポップアップは常に表示するのではなく、マウスを 1 秒以上置いた場合にのみ表示する
bpui.compA.timeout=setTimeout("bpui.compA.showPopupInternal('" + popupx + "', '" + itemId + "')", 1000);
}
bpui.compA.showPopupInternal=function(popupx, itemId) {
// AJAX 要求を初期化する
bpui.compA.req=bpui.compA.initRequest();
// 表示されている正しいポップアップオブジェクトを取得する
popObject=bpui.compA[popupx];
// itemId 値と URI を連結する
url=popObject.urlx + escape(itemId);
// 正しいポップアップのコールバック関数を設定する
bpui.compA.req.onreadystatechange = popObject.ajaxReturnFunction;
bpui.compA.req.open("GET", url, true);
// 要求を送信する
bpui.compA.req.send(null);
}