JSF と CSS、JavaScript を使用して作成する Ajax アプリケーション: 第 2 回 動的な JSF フォーム

標準 JSF コンポーネントにおける JavaScript サポートの詳細

この 2 回連載の第 1 回目の記事では、Java™ 開発者である著者、Andrei Cioroianu が JSF (JavaServer Faces) コンポーネントのスタイル属性を使用し、これらの属性にデフォルト値を設定する方法を紹介しました。2 回目となるこの記事では、標準 JSF コンポーネントが持つ JavaScript 関連の属性を使用する方法を取り上げます。DOM (Document Object Model) API、JavaScript、そして CSS (Cascading Style Sheets) をベースとしたいくつかの Web テクニックを学んでださい。また、Web ページを更新しないで、オプションの JSF コンポーネントを非表示にしたり表示したりする方法、Web ブラウザーで実行されるクライアント・サイドの検証を実装する方法、さらに Web フォームの入力要素に関するヘルプ・メッセージを表示するカスタム・コンポーネントを開発する方法を紹介します。

Andrei Cioroianu, Senior Java Developer and Consultant, Devsphere

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



2008年 2月 12日

イベントの処理とユーザー・インターフェースの更新

多くのJSF HTML コンポーネントには、コード・フラグメントを指定できる JavaScript 関連の属性があります。これらの属性に指定されたコード・フラグメントが Web ブラウザーで実行されるのは、特定の UI イベントが発生した場合です。例えば、標準 JSF コンポーネントがサポートするマウス・イベントには以下の 7 つのタイプがあります。

  • onmouseover
  • onmouseout
  • onmousemove
  • onmousedown
  • onmouseup
  • onclick
  • ondblclick

UI コンポーネントにキーボード・フォーカスが置かれたり、あるいはフォーカスがそこから離れたりすると、そのコンポーネントは onfocus 属性、onblur 属性で捕捉可能なイベントを生成します。onkeydownonkeyuponkeypress はキーが押されたり、離されたりすると発生するイベントです。さらに <h:form> コンポーネントが受け入れる onsubmit 属性と onreset 属性、そして入力コンポーネントが持つ onchange 属性と onselect 属性は、フォーム要素の状態が変わったときに JavaScript を呼び出すために使用することができます。

また、JSF コンポーネントによってレンダリングされるのでなく、JSF ページに直接組み込まれた HTML 要素が持つ JavaScript 関連の属性を使用することもできます。例えば、<body> タグには onload および onunload 属性があります。Web ブラウザーでページのロードが完了すると onload イベントが起動され、ユーザーがページを離れると onunload イベントが発生します。

標準的な JavaScript イベント・ハンドラーは Web ブラウザーで DOM API を使用して、JSF コンポーネントによってレンダリングされた HTML 要素のプロパティーを更新します。HTML 要素を表すオブジェクトは DOM Core API を使って簡単に見つけることができます。例えば、ID が既知の要素を見つけるには document.getElementById(...) を使用します。

DOM HTML API はこの DOM Core API を拡張し、HTML 文書固有のメソッドとプロパティーを追加します。Web ブラウザー内のフォームを表すオブジェクトを取得するには document.forms.myFormId を使用し、それから myForm.elements を使ってフォームの要素を表すオブジェクトの配列を取得します。非常に有用なプロパティーは className です。このプロパティーでは、HTML 要素の class 属性を変更することができます。

DOM HTML 仕様 (「参考文献」を参照) には、クライアント・サイドのページを構成する要素に関して、その要素を表すオブジェクトの標準的なプロパティーおよびメソッドがすべて記述されています。IE、Firefox、Netscape、Safari、Opera をはじめとする、ほとんどの Web ブラウザーでは、HTML 要素の内容を変更するための追加プロパティー (innerHTMLなど) をサポートします。

このセクションでは、JSF HTML コンポーネントが持つ JavaScript 関連の属性を使用する方法、そして DOM HTML API を使ってユーザー・インターフェースを更新する方法を説明するためのサンプルを紹介します。

JSF ページ内にスクリプトを配置する

HTML の <script> 要素を使用すると、JavaScript コードを通常のあらゆる Web ページと同じように JSF ページに挿入することができます (リスト 1 を参照)。JavaScript コードを使って document.write() により Web ブラウザー内に HTML コンテンツを生成することもできますが、このような使い方が必要になる場合はほとんどありません。大抵は <script> 要素をページのヘッダー内に配置して、ここに onclickonsubmitonchange などのイベント属性から呼び出される JavaScript 関数を含めることになります。また、<noscript> 要素を使用すれば、ブラウザーで JavaScript が無効にされている場合にユーザーに警告することもできます。

リスト 1. <script> タグの使用方法
<html>
    <head>
        <script type="text/javascript">
            function myEventHandler(...) {
                ...
            }
        </script>
    </head>
    <body>
        <noscript>
            This page requires JavaScript.
        </noscript>
        ...
    </body>
</html>

Apache MyFaces Tobago

HTML タグではなく JSF コンポーネントを使いたいという場合は、MyFaces Tobago の <tc:script> コンポーネントを使用して、このコンポーネントで <script> 要素をレンダリングすることもできます。

同じ関数を複数のページで呼び出すには、JavaScript コードを .js ファイルに配置します。外部スクリプトは、<script> タグの src 属性を使用して Web ページにインポートする必要があります (リスト 2 を参照)。この場合、スクリプトの URL に /faces/ というプレフィックスが追加されていないことを確認してください。このプレフィックスは、src 属性内で相対 URI を使用すると追加されることがありますが、この問題を避ける最も簡単な方法は .faces というサフィックスを使うことです。一方、/faces/ を使って JSF ページを指定したいという場合には、<script> タグの src 属性にコンテキスト・パスが含まれる JavaScript ファイルの絶対 URI を指定してください。

リスト 2. 外部スクリプトのインポート
<script type="text/javascript"
    src="${pageContext.request.contextPath}/scripts/MyScript.js">
</script>
<script type="text/javascript"
    src="<%=request.getContextPath()%>/AnotherScript.js">
</script>

オプション JSF コンポーネントを非表示にしたり表示したりする

この連載の第 1 回では、styleClass 属性を使用してサーバー・サイドで JSF コンポーネントのスタイル・クラスを設定する方法を説明しましたが、JavaScript と DOM を使えば、クライアント・サイドでもスタイル・クラスを設定または変更することができます。以下のサンプルで説明するのは、display という CSS プロパティーを使用してオプション・コンポーネントのグループを非表示にしたり表示したりする方法です。この単純な検索フォーム (図 1 を参照) には、必須テキスト・フィールド、2 つのチェック・ボックス、そして1 つのドロップダウン・リストが含まれます。ユーザーが More Options にチェック・マークを付けると、Match Case および Language コンポーネントが含まれるパネルが表示されます。ユーザーが More Options のチェック・マークを外すと、この 2 つのオプション・コンポーネントは表示されなくなります。

図 1. サンプル SearchForm
サンプル SearchForm

サンプル SearchForm.jsp (リスト 3 を参照) では、標準 JSF コンポーネントを使って Web フォームを作成します。オプション・コンポーネントは、HTML テーブルとしてレンダリングされる <h:panelGrid> コンテナー内に配置されます。オプション・パネルをクライアント・サイドで表示/非表示にする手段として使うのは、updatePanelClass() という JavaScript 関数です。名前が示すように、この関数は <h:panelGrid> によってレンダリングされる <table> 要素のスタイル・クラスを変更します。updatePanelClass() 関数は、ユーザーが More Options というラベルの付いたチェック・ボックスのステータスを変更するたびに呼び出されますが、これは updatePanelClass() 呼び出しが <h:selectBooleanCheckbox> コンポーネントの onclick 属性内にコーディングされているためです。

リスト 3. サンプル SearchForm.jsp
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
<html>
<head>
    <title>Search Form</title>
    ...
</head>
<body onload="initForm()">
    <h1>Search Form</h1>
    <h:form id="searchForm">
        <h:panelGrid columns="1" border="0" cellspacing="5">
            <h:panelGroup>
                <h:outputLabel value="Text: " for="text"/>
                <h:inputText id="text" value="#{searchBean.text}"
                    required="true" requiredMessage="Required" size="20"/>
                <h:message for="text"/>
            </h:panelGroup>
            <h:panelGroup>
                <h:selectBooleanCheckbox id="moreOptions"
                    value="#{searchBean.moreOptions}"
                    onclick="updatePanelClass()"/>
                <h:outputLabel value="More Options" for="moreOptions"/>
            </h:panelGroup>
            <h:panelGrid id="optionsPanel"
                    columns="1" border="0" cellspacing="5">
                <h:panelGroup>
                    <h:selectBooleanCheckbox id="matchCase"
                        value="#{searchBean.matchCase}"/>
                    <h:outputLabel value="Match Case" for="matchCase"/>
                </h:panelGroup>
                <h:panelGroup>
                    <h:outputLabel value="Language: " for="language"/>
                    <h:selectOneMenu id="language" value="#{searchBean.language}">
                        <f:selectItem itemValue="English" itemLabel="English"/>
                        <f:selectItem itemValue="Spanish" itemLabel="Spanish"/>
                        <f:selectItem itemValue="French" itemLabel="French"/>
                    </h:selectOneMenu>
                </h:panelGroup>
            </h:panelGrid>
            <h:commandButton id="search" value="Search"
                action="#{searchBean.searchAction}"/>
        </h:panelGrid>
    </h:form>
</body>
</html>
</f:view>

リスト 4 は、SearchForm.jsp ページによって生成された HTML です。

HTML 要素をレンダリングするコンポーネントが、searchForm という ID を持つ JSF フォーム内でネストされている場合に、標準 JSF コンポーネントがどのようにして、この HTML 要素の ID に searchForm: プレフィックスを追加しているかに注目してください。

リスト 4. SearchForm.jsp によって生成された HTML
<html>
<head>
    <title>Search Form</title>
    ...
</head>
<body onload="initForm()">
    <h1>Search Form</h1>
    
<form id="searchForm" name="searchForm" method="post" 
    action="/jsf12js/SearchForm.faces" 
    enctype="application/x-www-form-urlencoded">
...
<table border="0" cellspacing="5">
<tbody>
<tr>
<td><label for="searchForm:text">Text: </label>
<input id="searchForm:text" type="text" 
    name="searchForm:text" size="20" /></td>
</tr>
<tr>
<td><input id="searchForm:moreOptions" type="checkbox" 
    name="searchForm:moreOptions" checked="checked" 
    onclick="updatePanelClass()" />
<label for="searchForm:moreOptions">More Options</label></td>
</tr>
<tr>
<td><table id="searchForm:optionsPanel" border="0" cellspacing="5">
<tbody>
<tr>
<td><input id="searchForm:matchCase" type="checkbox" 
    name="searchForm:matchCase" />
<label for="searchForm:matchCase">Match Case</label></td>
</tr>
<tr>
<td><label for="searchForm:language">Language: </label>
<select id="searchForm:language" name="searchForm:language" size="1">
    <option value="English">English</option>
    <option value="Spanish">Spanish</option>
    <option value="French">French</option>
</select></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td><input id="searchForm:search" type="submit" 
    name="searchForm:search" value="Search" /></td>
</tr>
</tbody>
</table>
...
</form>
</body>
</html>

リスト 5 に示す SearchForm.jsp ページの <style> 要素には、スタイル・クラスと JavaScript 関数の両方が含まれるヘッダーがあります。visible クラスはオプション・パネルの左側余白を設定するだけで、他に設定は必要ありませんHTML の表はデフォルトで表示されるからです。一方の hidden クラスは display という CSS プロパティーを none に設定して表を非表示にします。

リスト 5. SearchForm.jsp のスタイル・クラス
<style type="text/css">
    .visible { margin-left: 40px; }
    .hidden  { display: none; }
</style>

updatePanelClass() 関数 (リスト 6 を参照) は、document.getElementById() を使って searchForm:moreOptions チェック・ボックスおよび searchForm:optionsPanel 表を指定します。panel オブジェクトは <h:panelGrid id="optionsPanel"> によってレンダリングされる <table id="searchForm:optionsPanel"> 要素を表し、checkbox オブジェクトは <h:selectBooleanCheckboxid="moreOptions"> によってレンダリングされる <input id="searchForm:moreOptions"> 要素を表します。この updatePanelClass() 関数はまた、checkbox オブジェクトのステータスを checked という DOM プロパティーから取得し、className という DOM プロパティーを使って panel 表のスタイル・クラスを設定します。

リスト 6. SearchForm.jsp の updatePanelClass() 関数
function updatePanelClass() {
    var checkbox = document.getElementById("searchForm:moreOptions");
    var panel = document.getElementById("searchForm:optionsPanel");
    panel.className = checkbox.checked ? "visible" : "hidden";
}

SearchForm.jsp ページのヘッダーには initForm() 関数も含まれます (リスト 7 を参照)。この関数呼び出しは <body> 要素の onload 属性内にコーディングされています。この関数はフォームのテキスト・フィールドを指定して focus() を呼び出します。そのため、ページがブラウザーにロードされると、ユーザーは最初にコンポーネントをクリックしなくても、すぐにテキストの入力を開始することができます。続いて initForm()updatePanelClass() を呼び出して、オプション・パネルのクラスを初期化します。

リスト 7. SearchForm.jsp の initForm() 関数
function initForm() {
    var text = document.getElementById("searchForm:text");
    text.focus();
    updatePanelClass();
}

サンプル SearchForm.jsp の入力コンポーネントの値は単純な Bean のプロパティーにバインドされていて、Search ボタンによって searchAction() という名前のアクション・メソッドがトリガーされます。SearchBean クラスのコードはリスト 8 のとおりです。

リスト 8. SearchBean クラス
package jsfcssjs;

public class SearchBean implements java.io.Serializable {
    private String text;
    private boolean moreOptions;
    private boolean matchCase;
    private String language;
    
    public SearchBean() {
    }
    
    public String getText() {
        return text;
    }
    
    public void setText(String text) {
        this.text = text;
    }

    ...

    public String searchAction() {
        System.out.print("Text: " + text);
        if (moreOptions) {
            if (matchCase)
                System.out.print(", Match Case");
            System.out.print(", Language: " + language);
        }
        System.out.println();
        return null;
    }
    
}

クライアント・サイドの検証を実装する

JSF フレームワークは、サーバー・サイドで実行する複数のバリデーターを提供します。JSF バリデーターがエラーを検出すると、そのエラーを修正できるようにフォームがユーザーに返されます。このようなエラーのあるフォームが送信されるのを最小限に抑えるためには、JavaScript コードを使ってクライアント・サイドでもユーザーの入力を検証するという方法を採れます。以下のサンプルで説明するのは、テキスト域コンポーネントに入力されたデータの最大長をテストする方法です。ユーザーのテキスト入力に併せて現在入力されている長さも表示されます。ユーザーが Submit ボタンをクリックすると JavaScript 関数によって入力が検証されます。

サンプル ClientValidation
サンプル ClientValidation

maxlength 属性の使用

<h:inputText> コンポーネントでは、maxlength 属性によって入力されるデータの長さを制限することができます。この 1 行の入力フィールドとは異なり、複数の行からなる <h:inputTextarea> コンポーネントにはmaxlength 属性がありません。レンダリングされた HTML の <textarea> タグでは、この属性をサポートしないためです。

サンプル ClientValidation.jsp の JSF フォーム (リスト 9 を参照) に含まれる <h:inputTextarea> コンポーネントは、onkeyup 属性を使用します。そのため、ユーザーがキーを押すたびに validateText() という JavaScript 関数が呼び出されます。この関数はページがブラウザーにロードされた時点でも呼び出されます。これは、<body> タグの onload 属性に validateText() 呼び出しが含まれるからです。ユーザー入力がクライアント・サイドで検証されるとしても、<h:inputTextarea> コンポーネントはユーザーのブラウザーで JavaScript が無効にされている場合に備え、サーバー・サイドでも required 属性と <f:validateLength> バリデーターを使用して入力テキストを検証します。サーバー・サイドでの検証は、悪意のあるユーザーに対する防御策としても必要です。

リスト 9. サンプル ClientValidation.jsp
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
<html>
...
<body onload="validateText()">
    <h1>Client-Side Validation</h1>
    <h:form id="validForm" onsubmit="return validateForm()">
        <h:panelGrid columns="1" border="0" cellspacing="5">
            <h:panelGroup>
                <h:outputText value="Text (max #{textBean.maxLength} chars): "/>
                <f:verbatim><span id="charCount"></span></f:verbatim>
            </h:panelGroup>
            <h:panelGroup>
                <h:inputTextarea id="textArea" value="#{textBean.text}"
                        required="true" rows="5" cols="30"
                        onkeyup="validateText()">
                    <f:validateLength maximum="#{textBean.maxLength}"/>
                </h:inputTextarea>
                <h:message for="textArea"/>
            </h:panelGroup>
            <h:commandButton id="submit" value="Submit"
                action="#{textBean.submitAction}"/>
        </h:panelGrid>
    </h:form>
</body>
</html>
</f:view>

validateText() 関数 (リスト 10 を参照) は document.forms.validForm を使ってページのフォームを指定し、form.elements["validForm:textArea"] を使ってテキスト域を表すオブジェクトを取得します。すると JavaScript コードが入力されたテキストの長さをテストし、ユーザー入力が有効でない場合にはエラー・メッセージを返します。さらに、validateText()ClientValidation.jsp<span id="charCount"> 要素に含まれる innerHTML プロパティーを設定して、現行の長さを表示します。スタイル・クラスについても span 要素の className という DOM プロパティーを使って更新します。

リスト 10. ClientValidation.jsp の validateText() 関数
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
<html>
<head>
    <title>Client-Side Validation</title>
    <style type="text/css">
        .valid { color: green; }
        .error { color: red; }
    </style>
    <script type="text/javascript">
        function validateText() {
            var form = document.forms.validForm;
            var textArea = form.elements["validForm:textArea"];
            var length = textArea.value.length;
            var maxLength = <h:outputText value="#{textBean.maxLength}"/>;
            var error = null;
            if (length == 0)
                error = "Text cannot be empty.";
            else if (length > maxLength)
                error = "Text cannot have more than "
                    + maxLength + " characters."
            var span = document.getElementById("charCount");
            span.innerHTML = length;
            span.className = (error == null) ? "valid" : "error";
            return error;
        }
        ...
    </script>
</head>
...
</html>
</f:view>

リスト 10 の JavaScript コードでは maxLength 変数を初期化するために、ID が textBean に設定された Bean と同じ名前を持つプロパティーが使用されます。このプロパティーの値を指定するのは、faces-config.xml ファイルです (リスト 11 を参照)。

リスト 11. faces-config.xml での TextBean の構成
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" ... version="1.2">

    <managed-bean>
        <managed-bean-name>textBean</managed-bean-name>
        <managed-bean-class>jsfcssjs.TextBean</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>maxLength</property-name>
            <value>100</value>
        </managed-property>
    </managed-bean>
    ...
</faces-config>

サンプル ClientValidation.jsp には、validateForm() という名前のもう 1 つの JavaScript 関数が含まれます (リスト 12 を参照)この関数は Submit ボタンがクリックされると呼び出されます。<h:form>onsubmit 属性の中で使用されている return キーワードに注目してください (validateForm() 呼び出しの直前)。戻り値が false の場合はユーザー入力が有効でないことを意味し、Web ブラウザーはフォーム・データをサーバーに送信しません。validateForm()alert() によってユーザーにエラーを通知するため、これは望ましい結果です。無効な入力を送信しても意味はありません。

リスト 12. ClientValidation.jsp の validateForm() 関数
function validateForm() {
    var error = validateText();
    if (error)
        alert(error);
    return error == null;
}

ユーザーの入力が有効な場合、validateForm()true を返し、Web ブラウザーがフォーム・データをサーバーに送信します。すると、JSF フレームワークは TextBean クラスの submitAction() メソッドを呼び出します (リスト 13 の参照)。

リスト 13. TextBean クラス class
package jsfcssjs;

public class TextBean implements java.io.Serializable {
    private String text;
    private int maxLength;
    
    ...

    public String submitAction() {
        System.out.println("Length: " + text.length());
        return null;
    }

}

<h:inputTextarea> コンポーネントに対して上記で実装した最大長の検証は、サンプル ClientValidation.jsp 内にコーディングされています。こうするのが最も簡単な方法ですが、このコードを再使用するには変更が必要になります。特定のページに固有でない機能を実装する場合には、JavaScript コードを外部 .js ファイルに移し、アプリケーションのどのページからでもそのコードの関数を呼び出せるようにするのが賢い方法です。また、JavaScript ベースのメカニズムをセットアップするカスタム JSF コンポーネントを開発する方法や、カスタム属性を既存のコンポーネントに追加するという方法もあります。そこで、次のセクションでは標準 JSF コンポーネントを拡張する一般的 UI 機能を実装する方法を紹介します。

カスタム属性による新しい UI 機能の有効化

連載の第 1 回目では、JSF コンポーネントのデフォルト・スタイルを設定するためのカスタム・コンポーネントを作成する方法を紹介しました。このセクションではそれと同じ手法で、今度は JavaScript 関連の属性を設定する方法を紹介します。また、ここに記載するサンプルで使用するカスタム属性は、<f:attribute> タグを使って標準 JSF コンポーネントに追加します。

カスタム JSF コンポーネントを作成する

これから説明するのは、<f:attribute name="helpOnFocus" value="..."> が含まれるネストされたすべてのコンポーネントの onfocus 属性と onblur 属性を変更するカスタム・コンポーネントの作成方法です (リスト 14 を参照)。onfocus および onblur 属性の JavaScript 式は、Web ブラウザーでフォーム要素にキーボード・フォーカスが置かれたとき、そしてフォーカスが離れたときに評価されます。<js:helpOnFocus> というタグを持つこのカスタム・コンポーネントは、レンダリングされた <input> 要素にフォーカスが置かれると、ネストされたそれぞれの入力コンポーネントの onfocus 属性を変更して <div id="helpOnFocus"> 要素内にヘルプ・メッセージを表示するようにします。ネストされたコンポーネントの onblur 属性についても、<input> 要素からキーボード・フォーカスが離れたときにヘルプ・メッセージをクリアするように変更します。

リスト 14. JSF ページでの <js:helpOnFocus> コンポーネントの使用
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="js" uri="/js.tld" %>
...
<script type="text/javascript" src=".../HelpOnFocus.js">
</script>
...
<js:helpOnFocus>
    ...
    <h:inputText ...>
        <f:attribute name="helpOnFocus" value="... Help message ..."/>
    </h:inputText>
    ...
</js:helpOnFocus>
...
<div id="helpOnFocus">
</div>
...

第 1 回で記載した SetupComponent クラスは JSF コンポーネント・ツリーをトラバースして、ネストされたコンポーネントがレンダリングされる直前にその属性を変更できるようにします。HelpOnFocusComponent クラス (リスト 15 を参照) は SetupComponent を継承して setup() メソッドを実装します。このメソッドは、ネストされたコンポーネントの rendered プロパティーが true に設定されている場合に呼び出されます。getAttribute() メソッドは、コンポーネントの属性マップから 1 つの属性の値を取得します。insertCall() メソッドは指定された属性内に関数呼び出しを組み込んで、既存の値を保持しますが、属性内にすでに関数呼び出しが含まれている場合には、何も行いません (このようなことが起こるのは、アプリケーションがフォーム送信後にユーザーに対して同じフォームを返した場合です)。この場合、カスタム・コンポーネントは以前に送信されたリクエストを処理している間に、ネストされたコンポーネントに onfocus 属性および onblur 属性を追加しています。JSF フレームワークはリクエストの処理後もすべてのコンポーネントの状態を保管するため、これらの属性にはすでに JavaScript 呼び出しが含まれているというわけです。

リスト 15. HelpOnFocusComponent クラス
package jsfcssjs;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import java.util.Map;

public class HelpOnFocusComponent extends SetupComponent {

    protected void setup(FacesContext ctx, UIComponent comp) {
        Map<String, Object> attrMap = comp.getAttributes();
        String helpOnFocus = getAttribute(attrMap, "helpOnFocus");
        if (helpOnFocus != null) {
            String helpParam[] = new String[] {
                    EncodeUtils.encodeString(helpOnFocus).toString() };
            insertCall(attrMap, "onfocus", "showHelpOnFocus", helpParam);
            insertCall(attrMap, "onblur", "clearHelpOnBlur", null);
        }
    }
    
    protected String getAttribute(Map<String, Object> attrMap, String attrName) {
        Object attrValue = attrMap.get(attrName);
        if (attrValue != null)
            return attrValue.toString();
        else
            return null;
    }
    
    protected void insertCall(Map<String, Object> attrMap, String attrName,
            String functionName, String functionParams[]) {
        String attrValue = getAttribute(attrMap, attrName);
        if (attrValue != null && attrValue.indexOf(functionName) != -1)
            return;
        StringBuilder buf = EncodeUtils.encodeCall(
                functionName, functionParams);
        if (attrValue != null && attrValue.length() > 0) {
            buf.append(';');
            buf.append(attrValue);
        }
        attrMap.put(attrName, buf.toString());
    }
    
    public String getFamily() {
        return "HelpOnFocus";
    }

}

HelpOnFocus.js ファイル (リスト 16 を参照) に含まれる showHelpOnFocus() 関数と clearHelpOnBlur() 関数の呼び出しは、onfocus 属性と onblur 属性の内部にコーディングされます。setInnerHTML() 関数は、指定された content を指定 id を持つ HTML 要素に挿入します。

リスト 16. HelpOnFocus.js ファイル
function setInnerHTML(id, content) {
    document.getElementById(id).innerHTML = content;
}
    
function showHelpOnFocus(msg) {
    setInnerHTML("helpOnFocus", msg);
}

function clearHelpOnBlur() {
    setInnerHTML("helpOnFocus", "");
}

カスタム JSF コンポーネントの例に漏れず、HelpOnFocusComponentfaces-config.xml に構成する必要があります (リスト 17 を参照)。

リスト 17. faces-config.xml での HelpOnFocusComponent の構成
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" ... version="1.2">
    ...
    <component>
        <component-type>HelpOnFocusComponent</component-type>
        <component-class>jsfcssjs.HelpOnFocusComponent</component-class>
        <component-extension>
            <component-family>HelpOnFocus</component-family>
        </component-extension>
    </component>

</faces-config>

リスト 18 に示す HelpOnFocusTag クラスは、カスタム・コンポーネントのタグ・ハンドラーを実装します。UIComponentELTag 基本クラスは JSF 1.2 API の一部なので、カスタム・コンポーネントを JSF 1.1 ベースのアプリケーションで使用する場合は、タグ・ハンドラーのスーパークラスを UIComponentTag にする必要があります。

リスト 18. HelpOnFocusTag クラス
package jsfcssjs;

import javax.faces.webapp.UIComponentELTag;

public class HelpOnFocusTag extends UIComponentELTag {

    public String getComponentType() {
        return "HelpOnFocusComponent";
    }

    public String getRendererType() {
        return null;
    }

}

カスタム・タグの名前と属性は js.tld ファイルに指定されます (リスト 19 を参照)。HelpOnFocusTag ハンドラーにはその属性のセッター・メソッドがありません。これらのメソッドは UIComponentELTag から継承されるためです。

リスト 19. js.tld ファイル
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee" ... version="2.1">
    <tlib-version>1.0</tlib-version>
    <short-name>js</short-name>
    <uri>/js.tld</uri>
    <tag>
        <name>helpOnFocus</name>
        <tag-class>jsfcssjs.HelpOnFocusTag</tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>id</name>
            <required>false</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>binding</name>
            <required>false</required>
            <deferred-value>
                <type>jsfcssjs.HelpOnFocusComponent</type>
            </deferred-value>
        </attribute>
        <attribute>
            <name>rendered</name>
            <required>false</required>
            <deferred-value>
                <type>boolean</type>
            </deferred-value>
        </attribute>
    </tag>
</taglib>

JavaScript エンコード・ユーティリティー

上記に記載した HelpOnFocusComponent クラスは、EncodeUtils クラスが提供するヘルパー・メソッドを使用します。encodeString() メソッド (リスト 20 を参照) は、JavaScript コード内で戻り値が文字列リテラルとして使用できるようにバックスペース、TAB、CR、LF、そして ASCII 以外の文字をエスケープします。さらに "&<> といった文字をそれぞれ &quot;&amp;&lt;&gt; に置換し、エンコードされた文字列を HTML ページに組み込めるようにします。区切り文字としては、2 つの引用符文字が使用されます。例えば encodeString()abc \ " & < > [TAB] [LF] [CR] 123 を渡した場合の戻り値は、"abc \\ &quot; &amp; &lt; &gt; \t \n \r 123" となります。

リスト 20. EncodeUtils クラスの encodeString() メソッド
package jsfcssjs;

public class EncodeUtils {

    public static StringBuilder encodeString(String str) {
        if (str == null)
            return null;
        StringBuilder buf = new StringBuilder();
        buf.append('"');
        int n = str.length();
        for (int i = 0; i < n; i++) {
            char ch = str.charAt(i);
            switch (ch) {
                case '\\': buf.append("\\\\"); break;
                case '\'': buf.append("\\\'"); break;
                case '"':  buf.append("&quot;"); break;
                case '&':  buf.append("&amp;"); break;
                case '<':  buf.append("&lt;"); break;
                case '>':  buf.append("&gt;"); break;
                case '\t': buf.append("\\t"); break;
                case '\r': buf.append("\\r"); break;
                case '\n': buf.append("\\n"); break;
                default: {
                    if (' ' <= ch && ch <= '~')
                        buf.append(ch);
                    else {
                        buf.append("\\u");
                        for (int j = 3; j >= 0; j--) {
                            int h = (((int) ch) >> (j*4)) & 0x0f;
                            buf.append((char) (h<10 ? '0'+h : 'a'+h-10));
                        }
                    }
                }
            }
        }
        buf.append('"');
        return buf;
    }
    ...
}

encodeCall() メソッド (リスト 21 を参照) は、指定されたパラメーターを使用して関数呼び出しを作成します。

リスト 21. EncodeUtils クラスの encodeCall() メソッド
public class EncodeUtils {
    ...
    public static StringBuilder encodeCall(
            String functionName, String functionParams[]) {
        StringBuilder buf = new StringBuilder();
        buf.append(functionName);
        buf.append('(');
        if (functionParams != null)
            for (int i = 0; i < functionParams.length; i++) {
                if (i > 0)
                    buf.append(',');
                buf.append(functionParams[i]);
            }
        buf.append(')');
        return buf;
    }
    
}

エンコード・メソッドをもっとよく理解するための例として、リスト 22 を見てください。ここに記載されているのは、すでに onfocus 属性を持つ入力コンポーネントです。このコンポーネントには <f:attribute> を使って helpOnFocus 属性も追加されていて、フォーム全体は <js:helpOnFocus> コンポーネントでラップされています。

リスト 22. 文字列エンコードの例
<% request.setAttribute("msg", "abc \\ \" & < > \t \n \r 123"); %>
...
<js:helpOnFocus>
    <h:form>
        <h:inputText ... onfocus="alert('focus')">
            <f:attribute name="helpOnFocus" value="#{msg}"/>
        </h:inputText>
    </h:form>
</js:helpOnFocus>
...
<div id="helpOnFocus">
</div>
...

このページが実行されると、<js:helpOnFocus> コンポーネントはネストされたコンポーネントのツリーをトラバースし、onfocus 属性と onblur 属性を設定することで、ヘルプ・メッセージを画面に表示したり、画面から消したりします。この JSF コンポーネントによってレンダリングされた HTML はリスト 23 のとおりです。onfocus 属性には showHelpOnFocus() 呼び出しと併せて、リスト 22 の例に記載された alert('focus') 式が含まれます。エンコードされた文字列の & 文字と " 区切り文字は、<h:inputText> コンポーネントによってもう 1 度エスケープされます。<h:inputText> コンポーネントは、このコンポーネント自体の属性の値をエンコードします。

リスト 23. エンコードされた文字列
<form ...>
...
<input ... onblur="clearHelpOnBlur()" 
           onfocus="showHelpOnFocus(&quot;abc \\ &amp;quot; &amp;amp; 
&amp;lt; &amp;gt; \t \n \r 123&quot;);alert('focus')" />
...
</form>
...
<div id="helpOnFocus">
</div>
...

Web ブラウザーがリスト 23 の HTML 部分を解析してデコードするときに、onfocus 属性から取得する値は showHelpOnFocus("abc \\ " & < > \t \n \r 123");alert('focus') です。ブラウザーがこのコードを JavaScript エンジンに渡すと、このエンジンは showHelpOnFocus() 関数を呼び出します。この関数によって Web ページの <div id="helpOnFocus"> 要素に abc \ &quot; &amp; &lt; &gt; [TAB] [NL] [CR] 123 が挿入され、ブラウザーが abc \ " & < > 123 を表示するという仕組みです。

developerWorks の Ajax リソース・センター
Ajax resource center にアクセスしてください。ここは、無料のツール、コード、そして Ajax アプリケーションの開発に関する情報を用意されたワンストップ・ショップです。また、Ajax のエキスパートである Jack Herrington がホストする活発な Ajax コミュニティー・フォーラムは、あなたが今まさに探している答えを持っているかもしれない開発者仲間と交流する手段となります。

JSF ページでカスタム・コンポーネントを使用する

HelpOnFocus.jsp ページ (リスト 24 を参照) は、<js:helpOnFocus> コンポーネントを使ったもう 1 つのサンプルです。JSF のフォームには、テキスト・フィールド、チェック・ボックス、そしてドロップダウン・リストという 3 つの入力コンポーネントがあります。この 3 つすべてのコンポーネントは <f:attribute> タグを使用してヘルプ・メッセージを指定します。

リスト 24. サンプル HelpOnFocus.jsp ファイル
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="js" uri="/js.tld" %>

<f:view>
    ...
    <script type="text/javascript"
        src="<%=request.getContextPath()%>/HelpOnFocus.js">
    </script>
    ...
    <js:helpOnFocus>
        ...
        <h:inputText id="text" ...>
            <f:attribute name="helpOnFocus"
                    value="Enter the text you want to search for."/>
        </h:inputText>
        ...
        <h:selectBooleanCheckbox id="matchCase" ...>
            <f:attribute name="helpOnFocus"
                    value="Distinguish between lowercase and uppercase
                                                        characters."/>
        </h:selectBooleanCheckbox>
        ...
        <h:selectOneMenu id="language" ...>
            ...
            <f:attribute name="helpOnFocus"
                    value="Select the language of the searched text."/>
        </h:selectOneMenu>
        ...
        <f:verbatim>
            <div id="helpOnFocus">
            </div>
        </f:verbatim>
        ...
    </js:helpOnFocus>
    ...
</f:view>

上記の HelpOnFocus.jsp ページは、リスト 25 の HTML を生成します。

リスト 25. HelpOnFocus.jsp によって生成された HTML
...
<script type="text/javascript" 
    src="/jsf12js/HelpOnFocus.js">
</script>
...
<input id="searchForm:text" type="text" ... onblur="clearHelpOnBlur()" 
    onfocus="showHelpOnFocus(&quot;Enter the text you want to search for.&quot;)"/>
...
<input id="searchForm:matchCase" type="checkbox" ... onblur="clearHelpOnBlur()" 
    onfocus="showHelpOnFocus(&quot;Distinguish between lowercase and uppercase
        \r\n                                                 characters.&quot;)" />
...
<select id="searchForm:language" ... onblur="clearHelpOnBlur()" 
    onfocus="showHelpOnFocus(&quot;Select the language of the searched text.&quot;)">
    ...
</select>
...
<div id="helpOnFocus">
</div>
...

まとめ

今回の記事では、JSF コンポーネントがレンダリングした HTML を更新する JavaScript イベント・ハンドラーを開発する方法を説明しました。そのなかで紹介した Web テクニックには以下のものが含まれます。

  • className プロパティーを設定してクラス属性の値を変更する
  • innerHTML プロパティーを使用して HTML 要素に内容を挿入する
  • CSS によって JSF コンポーネントの非表示/表示の設定をする
  • JavaScript でクライアント・サイドの検証を実装する

さらにこの記事では、カスタム属性と併せてラッパー・コンポーネントを使用し、新しい機能を既存の JSF コンポーネントに追加する方法も説明しました。


ダウンロード

内容ファイル名サイズ
Sample application for this articlejsfcssjs_p2.zip30KB

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

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=294186
ArticleTitle=JSF と CSS、JavaScript を使用して作成する Ajax アプリケーション: 第 2 回 動的な JSF フォーム
publish-date=02122008