JSF と CSS、JavaScript を使用して作成する Ajax アプリケーション: 第 1 回 JSF ページの外観を整える

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

一般的な Web アプリケーションには、カスケーディング・スタイル・シート (CSS) と JavaScript を JSF (JavaServer Faces) などのサーバー・サイド・フレームワークと一緒に使用する必要があります。CSS によって Ajax やその他のアプリケーション内での Web コンポーネントの視覚的特徴を変更し、見た目のよい独特の外観にすることができるからです。この 2 回連載記事の第 1 回目では、標準 JSF コンポーネントが持つ CSS 関連の属性を利用する方法を説明します。さらに、ネストされたコンポーネントのデフォルト・スタイルを設定するカスタム JSF コンポーネントを作成する方法も学びます。この方法を使えば、Web アプリケーションのすべてのページを統一された外観にするのが非常に簡単になるだけでなく、他のコンポーネントの属性をプログラムによって設定することも可能になります。続く第 2 回目の記事では、JavaScript を使って JSF フォームを一層動的にする方法を紹介します。

Andrei Cioroianu, Senior Java Developer and Consultant, Devsphere

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



2008年 1月 29日

JSF コンポーネントのスタイル属性を使用する

ほとんどすべての JSF HTML コンポーネントには stylestyleClass という 2 つのオプション属性があり、HTML 出力ではそれぞれ style 属性、class 属性としてレンダリングされます。さらに <h:dataTable><h:panelGrid> などの一部のコンポーネントにはファセット用のスタイル属性も追加されています。このセクションでは、JSF HTML ライブラリーに含まれるこれらの CSS 関連の属性について説明します。

CSS ファイルを JSF ページにリンクする

Web ページのスタイルが独特のものである場合には、ページのヘッダーに含まれる <style> 要素のなかにスタイル・ルールを定義することができます。また、style 属性を使って JSF コンポーネントに個別にスタイル情報を指定することも可能ですが、大抵はスタイル・ルールを別個の CSS ファイルに含めて複数のページに適用できるようにするという方法が選ばれます。外部スタイル・シートを Web ページにリンクするには、<link> タグをリスト 1 のように使用します。

リスト 1. <link> タグの使用方法
<link rel="stylesheet" type="text/css"
      href="${pageContext.request.contextPath}/MyStyles.css">
<link rel="stylesheet" type="text/css"
      href="<%=request.getContextPath()%>/styles/MoreStyles.css">

MyFaces Tomahawk の使用

HTML タグではなく JSF コンポーネントを使いたいという場合は、MyFaces Tomahawk の <t:stylesheet> コンポーネントを使用して、このコンポーネントに <link> タグをレンダリングさせるのも一考です。

リスト 1 の href 属性には絶対 URI が含まれています。CSS ファイルの相対リンクを使うこともできますが、JSF ページの URL に /faces/ というパス名が含まれている場合には ${pageContext.request.contextPath} または <%=request.getContextPath()%> を使ってコンテキスト・パスを組み込むほうが賢明です。この場合、CSS ファイルや画像、あるいはその他のリソースへの相対リンクが /faces/ というパス名が含まれる HTTP リクエストとなるため、JSF ではないファイルが Faces サーブレットによって処理されることになります。これで上手くいかないわけではありませんが、あまり効率的ではありません。一方、URL の末尾に .facesを付けることで JSF ページを要求すれば、JSF をベースとしたアプリケーションの相対リンクを確実に使用することができるため、上記の問題は解決することができます。

スタイル・ルールを定義して使用する

CSS ファイルには、HTML 要素に適用されるスタイル・シート・ルールが含まれます。これらの HTML 要素は JSF コンポーネントによってレンダリングすることも、JSF ページのコンポーネント間に組み込むこともできます。例えば、マウスがリンクに重なったときにだけリンクに下線を付けたい場合には、リスト 2 のルールを使用することになります。

リスト 2. リンクのスタイル・ルール
a { text-decoration: none; }
a:hover { text-decoration: underline; }

上記のルールは、<a> 要素が JSF ページに直接含まれているか、あるいは <h:commandLink> などの JSF コンポーネントによって生成されるかにかかわらず、あらゆるリンクに適用されます (リスト 3 を参照)。

リスト 3. HTML および JSF のリンク
<a href="LinkStyles.faces">HTML Link</a>
<h:commandLink value="JSF Link"/>

リスト 4 に、JSF コンポーネントにインライン・スタイルを適用する方法を示します。

リスト 4. スタイル属性を指定した JSF コンポーネント
<h:commandLink value="Red Link" style="color: red"/>

ただし通常は同じスタイル・ルールを複数のコンポーネントに使用できるようにするため、スタイル・クラス (リスト 5 を参照) は CSS ファイルに定義することになります。

リスト 5. スタイル・クラスの例
.GreenClass { color: green; }

スタイル・クラスの名前を指定するには、JSF コンポーネントに styleClass 属性を使用します (リスト 6 を参照)。

リスト 6. styleclass 属性を指定した JSF コンポーネント
<h:commandLink value="Green Link" styleClass="GreenClass"/>

リスト 1 から 6 のコードは、この記事に記載したサンプル・コードのリンク (「ダウンロード」を参照) からダウンロードできる Linkstyles.jsp および Linkstyles.css のファイルに記載されています。

複数のスタイル・クラスを持つ JSF コンポーネント

前述したように、一部の JSF コンポーネントには複数のスタイル属性があります。例えば、<h:message> および <h:messages> には以下に示す 10 個の CSS 関連の属性があります。

  • style
  • styleClass
  • errorClass
  • errorStyle
  • fatalClass
  • fatalStyle
  • infoClass
  • infoStyle
  • warnClass
  • warnStyle

レンダリングされるメッセージごとに、そのメッセージの重大度に応じて上記のうちから 2 つの属性だけが選択されます。UISelectOne および UISelectMany コンポーネントの JSF タグでは、レンダリングされるリストの項目に enabledClass 属性と disabledClass 属性を使用することができます。<h:dataTable><h:panelGrid> の両タグにはメインとなる表、ヘッダー、フッター、行、そして列のクラス属性があります。以降の一連のサンプルで、以下のスクリーン・ショットに示すデータの表で使われている CSS 関連の属性の使用方法を説明します。

図 1. サンプル Tablestyles
サンプル Table

まず始めに必要なのは、表コンポーネントのデータ・モデルです。OrderBean クラス (リスト 7 を参照) は、javax.faces.model.ArrayDataModel を継承します。実際のアプリケーションはモデルのデータをデータベースから取得することになりますが、ここでは簡単にするために OrderBean を静的配列から初期化します。

リスト 7. サンプル OrderBean
package jsfcssjs;

import javax.faces.model.ArrayDataModel;

public class OrderBean extends ArrayDataModel {
    private static ItemBean items[] = new ItemBean[] {
        new ItemBean("Domain Name",        3,   7.99f),
        new ItemBean("SSL Certificate",    1, 119.00f),
        new ItemBean("Web Hosting",        1,  19.95f),
        new ItemBean("Email Box",         20,   0.15f),
        new ItemBean("E-Commerce Setup",   1,  25.00f),
        new ItemBean("Technical Support",  1,  50.00f)
    };

    public OrderBean() {
        super(items);
    }
    
    public float getTotalPrice() {
        float total = 0;
        for (int i = 0; i < items.length; i++)
            total += items[i].getitemPrice();
        return total;
    }
    
}

ItemBean クラス (リスト 8 を参照) には descriptionquantityunitPrice という 3 つの読み取り/書き込みプロパティー、そして itemPrice という読み取り専用プロパティーがあります。

リスト 8. サンプル ItemBean
package jsfcssjs;

public class ItemBean implements java.io.Serializable {
    private String description;
    private int quantity;
    private float unitPrice;
    
    public ItemBean() {
    }
    
    public ItemBean(String description, int quantity, float unitPrice) {
        this.description = description;
        this.quantity = quantity;
        this.unitPrice = unitPrice;
    }
    
    ...

    public float getitemPrice() {
        return unitPrice * quantity;
    }
    
}

データの表に使用するスタイルは、TableStyles.css (リスト 9 を参照) ファイルに含まれます。

リスト 9. サンプル TableStyles.css
body     { font-family: Arial; }
.tablebg { background-color: #D0D0A0; }
.header  { font-weight: bold; }
.footer  { font-weight: bold; }
.text    { text-align: left; }
.number  { text-align: right; }
.graybg  { background-color: #DDDDDD; }
.whitebg { background-color: #FFFFFF; }

上記の TableStyles.css に定義されたスタイル・クラスを使用するのは、サンプル TableStyles.jsp (リスト 10 を参照) に記載されている JSF の表コンポーネントです。styleClass 属性の値は <table> 要素用で、headerclass 属性と footerclass 属性が表のヘッダー・セルとフッター・セルのスタイルを指定します。columnClasses 属性に含まれるカンマで区切られたクラスのリストは、データ・セルの <td> 要素に使用されます。

rowClasses 属性では別のクラスのリストを指定できます。これらのクラスは表の <tr> 要素に使用されるものです。サンプル TableStyles.jsp には行のクラスが 2 つしかありませんが、表は 6 行で構成されています。そのわけは、<h:dataTable> コンポーネントの JSF レンダラーはこの 2 つのスタイル・クラスを交互に使うからです。つまり、1 行目、3 行目、5 行目には graybg クラスを使用し、2 行目、4 行目、6 行目には whitebg クラスを使用します。レンダリング中に現行の行のデータにアクセスするために使用されるのは、var 属性で定義されている row という名前の変数です。データ全体は、value 属性によって表にバインドされている orderBean モデルから取得されます。

リスト 10. サンプル TableStyles.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>Order Table</title>
    <link rel="stylesheet" type="text/css"
        href="<%=request.getContextPath()%>/styles/TableStyles.css">
</head>
<body>
    <h1>Order Table</h1>
    <h:dataTable id="table" var="row" value="#{orderBean}"
            styleClass="tablebg" headerClass="header" footerClass="footer"
            columnClasses="text,number,number,number"
            rowClasses="graybg,whitebg" border="0" cellpadding="5">
        <h:column>
            <f:facet name="header">
                <h:outputText value="Description"/>
            </f:facet>
            <h:outputText value="#{row.description}"/>
            <f:facet name="footer">
                <h:outputText value="Total Price"/>
            </f:facet>
        </h:column>
        <h:column>
            <f:facet name="header">
                <h:outputText value="Quantity"/>
            </f:facet>
            <h:outputText value="#{row.quantity}"/>
        </h:column>
        <h:column>
            <f:facet name="header">
                <h:outputText value="Unit Price"/>
            </f:facet>
            <h:outputText value="#{row.unitPrice}">
                <f:convertNumber type="currency" currencyCode="USD"
                    minFractionDigits="2" maxFractionDigits="2"/>
            </h:outputText>
        </h:column>
        <h:column footerClass="footer number">
            <f:facet name="header">
                <h:outputText value="Item Price"/>
            </f:facet>
            <h:outputText value="#{row.itemPrice}">
                <f:convertNumber type="currency" currencyCode="USD"
                    minFractionDigits="2" maxFractionDigits="2"/>
            </h:outputText>
            <f:facet name="footer">
                <h:outputText value="#{orderBean.totalPrice}">
                    <f:convertNumber type="currency" currencyCode="USD"
                        minFractionDigits="2" maxFractionDigits="2"/>
                </h:outputText>
            </f:facet>
        </h:column>
    </h:dataTable>
</body>
</html>
</f:view>

リスト 11 に、TableStyles.jsp ページによって生成された HTML を記載します。

リスト 11. TableStyles.jsp が生成した HTML
<html>
<head>
    <title>Order Table</title>
    <link rel="stylesheet" type="text/css"
        href="/jsf12css/styles/TableStyles.css">
</head>
<body>
    <h1>Order Table</h1>

<table id="table" class="tablebg" border="0" cellpadding="5">
<thead>
<tr>
<th class="header" scope="col">Description</th>
<th class="header" scope="col">Quantity</th>
<th class="header" scope="col">Unit Price</th>
<th class="header" scope="col">Item Price</th>
</tr>
</thead>
<tfoot>
<tr>
<td class="footer">Total Price</td>
<td class="footer"></td>
<td class="footer"></td>
<td class="footer number">$240.92</td>
</tr>
</tfoot>
<tbody>
<tr class="graybg">
<td class="text">Domain Name</td>
<td class="number">3</td>
<td class="number">$7.99</td>
<td class="number">$23.97</td>
</tr>
<tr class="whitebg">
<td class="text">SSL Certificate</td>
<td class="number">1</td>
<td class="number">$119.00</td>
<td class="number">$119.00</td>
</tr>
<tr class="graybg">
...
</tr>
<tr class="whitebg">
...
</tr>
<tr class="graybg">
...
</tr>
<tr class="whitebg">
...
</tr>
</tbody>
</table>

</body>
</html>

サンプル TableStyles.jsp の JSF の表に含まれる最後の <h:column> コンポーネントは、footerClass 属性を使用して、注文の合計金額を表示するフッター・セルに 2 つのクラスを指定しています。フッター・セルの <td> 要素には、footer および number というこの 2 つのクラスが使用されることになります。<h:column> コンポーネントの footerClass 属性は JSF 1.2 でのみ有効です。これより前の JSF バージョンを使用する場合は、合計金額をレンダリングする <h:outputText> コンポーネントを <div> 要素内にラップして、 number クラスのアライメント効果を追加する必要があります (リスト 12 を参照)。ソース・コードのアーカイブには、JSF 1.1 および JSF 1.2 両方の場合のコード・サンプルが含まれています。

リスト 12. JSF 1.1 の場合のサンプル
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
    ...
    <h:dataTable ...>
        ...
        <h:column>
            ...
            <f:facet name="footer">
                <h:panelGroup>
                    <f:verbatim><div class="number"></f:verbatim>
                    <h:outputText value="#{orderBean.totalPrice}">
                        <f:convertNumber type="currency" currencyCode="USD"
                            minFractionDigits="2" maxFractionDigits="2"/>
                    </h:outputText>
                    <f:verbatim></div></f:verbatim>
                </h:panelGroup>
            </f:facet>
        </h:column>
    </h:dataTable>
    ...
</f:view>

JSF コンポーネントのデフォルト・スタイルを設定する

多数のJSF ページを持つ大規模なアプリケーションを作成するときには、コンポーネントのインスタンス 1 つひとつに手動で styleClass 属性を設定するという方法は遠慮したいものです。標準JSF HTML コンポーネントにはデフォルトのスタイルがありませんが、このセクションで説明するように、JSF コンポーネント・ツリーをトラバースしてプログラムによってデフォルト・スタイル・クラスを設定することは可能です。この操作を実装するには、JSF コンポーネント・ツリーがどのように作成されるか、そして HTML がどのように生成されるかを理解する必要があります。

JSF コンポーネント・ツリーが作成される仕組みについて

すべての faces リクエストをインターセプトする Faces Servlet は、JSF ベースの Web アプリケーションではいずれも web.xml 記述子に構成されます。各リクエストに対し、Faces Servlet は javax.faces.context.FacesContext オブジェクトを初期化して、javax.faces.lifecycle.Lifecycle インスタンスの execute() および render() メソッドを呼び出します。execute() メソッドは 1 つのフェーズを除き、JSF 要求処理ライフサイクルのすべてのフェーズを処理します。その処理されないフェーズとは、最後のフェーズ (Render Response) です。このフェーズは render() メソッドによって実行されます。

状態の保存方法

javax.faces.STATE_SAVING_METHOD コンテキスト・パラメーターの値 (web.xml ファイルに設定可能) に応じて、コンポーネント・ツリーはサーバー・サイドまたはクライアント・サイドのどちらかに保存されます。JSF 実装では session スコープを使用してサーバー・サイドにコンポーネント・ツリーを維持することができますが、この方法は効率的とは言え、ユーザーが送信済みのフォームに戻るためにブラウザーの Back ボタンをクリックすると JSF フレームワークが混乱する可能性があります。そのため、各コンポーネント・ツリーを保存するには、隠しフォーム要素によってページ・インスタンスに関連付けられたツリーをシリアライズしたコピーが保持されるクライアント・サイドのほうが適切です。コンポーネント・ツリーをクライアント・サイドに保存するとトラフィックが増え、オブジェクトをシリアライズおよびデシリアライズするための追加 CPU リソースが必要になりますが、この方法のほうが session スコープを使用するよりも信頼性に優れています。

JSF 実装は最初のフェーズ (Restore View) で javax.faces.application.ViewHandler インスタンスの 1 つであるアプリケーションのビュー・ハンドラーを取得し、要求がポスト・バック (ユーザーが Submit ボタンをクリックした場合など) であれば restoreView() メソッドを呼び出します。この場合、前の faces リクエストが処理されている間にコンポーネント・ツリーが構成され、保存済みである必要があります (囲み記事「状態の保存方法」を参照)。

現行のリクエストがポスト・バックでない場合 (ユーザーが Faces リンクをクリックした場合など)、JSF 実装が呼び出すのは現行 FacesContextrenderResponse() メソッド、そして ViewHandler インスタンスの createView() メソッドです。renderResponse() 呼び出しによって、JSF 実装は現行のフェーズ (Restore View) から Render Response フェーズにジャンプします。デフォルト・ビュー・ハンドラーの createView() メソッドは javax.faces.component.UIViewRoot コンポーネントのみを作成し、ツリーの残りは Render Response フェーズの間に JSF 実装がビュー・ハンドラーの renderView() メソッドを呼び出すと作成されます。

コンポーネント・ツリーがリストアされるか、あるいは作成する必要があるかに関わらず、JSF 実装は renderView() メソッド実行中のある時点で、関連する JSF ページに HTTP リクエストを転送します。例えば /MyApp/MyPage.facesまたは /MyApp/faces/MyPage.jsp を要求すると、実行対象の JSF ページの URI は /MyApp/MyPage.jsp となります。

あらゆる JSF ページに含まれる <f:view><h:form> などのコンポーネント・タグは、JSP タグ・ハンドラーによってサポートされています。これらのタグ・ハンドラーは、javax.servlet.jsp.tagext パッケージに定義されたインターフェースを実装する Java™ クラスです。ページが実行されると、JSP コンテナーがタグ・ハンドラーのインスタンスを作成し、そのメソッドを呼び出します。JSF 1.2 コンポーネントのタグ・ハンドラーはいずれも javax.faces.webapp.UIComponentELTag を継承します。一方、JSF 1.1 の API を使用している場合、タグ・ハンドラー・クラスが継承するのは javax.faces.webapp.UIComponentTag または javax.faces.webapp.UIComponentBodyTag となります。どのバージョンの JSF を使用するかに関わらず、コンポーネント・ツリーが Restore View フェーズでリストアされていなければ、JSF タグ・ハンドラーが Render Response フェーズの間にコンポーネントのインスタンスを作成します。

javax.faces.event.PhaseListener を実装してデフォルト・スタイルを設定しようと思うかもしれませんが、この方法は、ユーザーがリンクをクリックしたり、Web ブラウザーに URL を入力してページを要求する場合に上手く行きません。この場合、JSF 実装がコンポーネント・ツリーをリストアできないためです。コンポーネント・ツリーは Render Response フェーズで作成されることが必須です。この最後のフェーズの前にコンポーネント・ツリーが存在せず、レスポンスをレンダリングした後にコンポーネント・スタイルを更新するのでは HTML 出力がすでに生成されているため遅すぎます。したがって、デフォルト・スタイルは Render Response フェーズの実行中に設定し、その後にコンポーネントをレンダリングする必要があります。

レンダリングの直前にコンポーネント・ツリーをトラバースする

JSF コンポーネントには、rendersChildren という興味深い読み取り専用プロパティーがあります。このプロパティーが false の場合には、encodeBegin() メソッドと encodeEnd() メソッドだけが呼び出されてコンポーネントのレンダリングが行われます。getRendersChildren() が true を返すと、Render Response フェーズの間に encodeChildren() も呼び出されます。これらのメソッドは通常、javax.faces.render.Renderer インターフェースを実装する別のクラスにレンダリングを委任しますが、JSF コンポーネントがレンダラーを使わずに JSF コンポーネント自体で HTML を生成することも可能です。

JSF 1.1 では、タグ・ハンドラー・メソッドは最初にエンコード・メソッドを呼び出します。ただしコンポーネントがコンポーネント自体の子をレンダリングすると、ネストされたコンポーネントのすべてのタグ・ハンドラーがエンコード・メソッドの呼び出しを停止します。これは、true を返した getRendersChildren() メソッドを持つコンポーネントがレンダリングを行うことになるからです。JSF 1.1 に実装されたこのレンダリング・プロセスは、バッファリングを最小にすることから効率的でしたが、アプリケーション開発者が HTML コンテンツを <f:verbatim> コンポーネント内に組み込まなければなりませんでした。JSF 1.2 ではコンポーネント・ツリー全体が作成されてからでないとレンダリングが実行されないため、<f:verbatim> を使用する必要がなくなっています。

JSF 1.1 または JSF 1.2 のどちらを使うにしても、ネストされたコンポーネントは完全に初期化され、encodeChildren() の呼び出し時にレンダリングすることが可能な状態になります。そのため、デフォルト・スタイルを配置する場所としてはここが最適です。次は、JSF コンポーネント・ツリーを再帰的にトラバースして、ネストされたコンポーネントのすべてに styleclass 属性を設定するカスタム・コンポーネントの作成方法を説明します。

ここで紹介する手法は、デフォルト・スタイルだけでなく、JSF コンポーネントのあらゆる属性やプロパティーを設定するために適用できるので、後で再利用できる一般的な基本クラスを作成するのが得策です。SetupComponent クラスは JSF API の UIComponentBase クラスを継承し、setup() という抽象メソッドを定義します。このメソッドは、レンダリングが可能なネストされたコンポーネントすべてで呼び出されます。そして setupTree() メソッド (リスト 13 を参照) が JSF コンポーネント・ツリーの再帰的トラバースを実行します。

リスト 13. SetupComponent クラスの setup() メソッドおよび setupTree() メソッド
package jsfcssjs;

import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
...
public abstract class SetupComponent extends UIComponentBase {

    protected abstract void setup(FacesContext ctx, UIComponent comp);

    protected void setupTree(FacesContext ctx, UIComponent comp) {
        if (!comp.isRendered())
            return;
        setup(ctx, comp);
        for (UIComponent child : comp.getChildren())
            setupTree(ctx, child);
    }
    ...
}

UIComponentBase から継承された getRendersChildren() メソッド (リスト 14 を参照) はオーバーライドされて true を返すため、セットアップ・コンポーネントはそのコンポーネント自体の子のレンダリングを制御することができます。

リスト 14. SetupComponent クラスの getRendersChildren() メソッド
public abstract class SetupComponent extends UIComponentBase {
    ...
    public boolean getRendersChildren() {
        return true;
    }
    ...
}

リスト 15 に記載する encodeChildren() メソッドはオーバーライドされ、それぞれの子に対して encodeTree() を呼び出す前に setupTree() を呼び出します。

リスト 15. SetupComponent クラスの encodeChildren() メソッド
public abstract class SetupComponent extends UIComponentBase {
    ...
    public void encodeChildren(FacesContext ctx)
            throws IOException {
        if (!isRendered())
            return;
        setupTree(ctx, this);
        for (UIComponent child : getChildren())
            encodeTree(ctx, child);
    }
    ...
}

現行のコンポーネントに対してエンコード・メソッドを呼び出す encodeTree()メソッド (リスト 16 を参照) は、getRendersChildren()false を返すと子コンポーネントに対して再帰的に呼び出されます。

リスト 16. SetupComponent クラスの encodeTree() メソッド
public abstract class SetupComponent extends UIComponentBase {
    ...
    protected void encodeTree(FacesContext ctx, UIComponent comp)
            throws IOException {
        if (!comp.isRendered())
            return;
        comp.encodeBegin(ctx);
        if (comp.getRendersChildren())
            comp.encodeChildren(ctx);
        else
            for (UIComponent child : comp.getChildren())
                encodeTree(ctx, child);
        comp.encodeEnd(ctx);
    }

}

カスタム・コンポーネントを作成して構成する

リスト 17 に記載する DefaultStylesComponent クラスは SetupComponent を継承し、styleClass 属性を設定するための setup() メソッドを実装します。この属性の値は、コンポーネントのファミリー・ネームとレンダラーの名前で構成されます。

リスト 17. DefaultStylesComponent クラス
package jsfcssjs;

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

import java.util.Map;

public class DefaultStylesComponent extends SetupComponent {

    protected void setup(FacesContext ctx, UIComponent comp) {
        Map<String, Object> attrMap = comp.getAttributes();
        if (attrMap.get("styleClass") != null)
            return;
        String familyName = getLastName(comp.getFamily());
        String rendererName = getLastName(comp.getRendererType());
        if (familyName == null || rendererName == null)
            return;
        String className = "Default" + familyName;
        if (!familyName.equals(rendererName))
            className += rendererName;
        attrMap.put("styleClass", className);
    }
    
    protected String getLastName(String fullName) {
        if (fullName == null)
            return null;
        int dotIndex = fullName.lastIndexOf('.');
        if (dotIndex != -1)
            return fullName.substring(dotIndex+1);
        else
            return fullName;
    }

    public String getFamily() {
        return "DefaultStyles";
    }

}

上記の DefaultStylesComponent が構成される場所は faces-config.xml です (リスト 18 を参照)。

リスト 18. faces-config.xml での DefaultStylesComponent の構成
<faces-config ...>
    ...
    <component>
        <component-type>DefaultStylesComponent</component-type>
        <component-class>jsfcssjs.DefaultStylesComponent</component-class>
        <component-extension>
            <component-family>DefaultStyles</component-family>
        </component-extension>
    </component>

</faces-config>

カスタム・タグを実装する

DefaultStylesTag クラス (リスト 19 を参照) は JSF 1.2 API で定義された UIComponentELTag クラスを継承し、DefaultStylesComponent を返す getComponentType() メソッド、そして null を返す getRendererType() メソッドを実装します。JSF 1.1 を使用する場合は、このタグ・クラスが UIComponentELTag ではなく UIComponentTag を継承する必要があります。

リスト 19. DefaultStylesTag クラス
package jsfcssjs;

import javax.faces.webapp.UIComponentELTag;

public class DefaultStylesTag extends UIComponentELTag {

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

    public String getRendererType() {
        return null;
    }

}

css.tldファイル (リスト 20 を参照) は、カスタム・タグの defaultStyles 名と属性を定義します。idbinding および rendered 属性のサポートは UIComponentELTag から継承されます。

リスト 20. JSF 1.2 および JSP 2.1 の場合の css.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>css</short-name>
    <uri>/css.tld</uri>
    <tag>
        <name>defaultStyles</name>
        <tag-class>jsfcssjs.DefaultStylesTag</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.DefaultStylesComponent</type>
            </deferred-value>
        </attribute>
        <attribute>
            <name>rendered</name>
            <required>false</required>
            <deferred-value>
                <type>boolean</type>
            </deferred-value>
        </attribute>
    </tag>
</taglib>

一方、JSF 1.1 を使用する場合には TLD ファイルが JSP 1.2 仕様によって定義された構文に準拠していなければなりません。

リスト 21. JSF 1.1 および JSP 1.2 の場合の css.tld ファイル
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE taglib
    PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
    "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
    <tlib-version>1.0</tlib-version>
    <jsp-version>1.2</jsp-version>
    <short-name>css</short-name>
    <uri>/css.tld</uri>
    <tag>
        <name>defaultStyles</name>
        <tag-class>jsfcssjs.DefaultStylesTag</tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>id</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
        <attribute>
            <name>binding</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
        <attribute>
            <name>rendered</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
    </tag>
</taglib>

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

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

このセクションではサンプル JSF を用いて、JSF コンポーネントの外観を整える方法を説明します。このサンプルでは、背景色と枠を利用します。

図 2. サンプル defaultStyles
サンプル defaultStyles

リスト 22 は、上記のサンプル defaultStyles.jsp で使用されている数少ないスタイル・クラスを定義する DefaultStyles.css ファイルです。

リスト 22. サンプル defaultStyles.css
.DefaultForm {}
.DefaultPanelGrid { background-color: #FFFFFF; }
.DefaultInputText { background-color: #FFDDDD;
    border-style: ridge; border-width: thin; }
.DefaultInputTextarea { background-color: #DDFFDD;
    border-style: groove; border-width: thick; }
.DefaultSelectOneMenu { background-color: #DDDDFF;
    border-style: inset; border-width: medium; }
.DefaultCommandButton { background-color: #DDDDDD;
    border-style: outset; border-width: medium; }
.SpecialInputText { background-color: #DDFFFF;
    border-style: double; border-width: thick; }
.EnabledOption { color: #FF0000; }
.DisabledOption { color: #0000FF; }

サンプル DefaultStyles.jsp (リスト 23 を参照) では、<css:defaultStyles> コンポーネントを使って、styleclass 属性を持たない JSF コンポーネントのデフォルト・スタイルを設定します。これらの JSF コンポーネントのうちの 1 つは、SpecialInputText スタイル・クラスを使用します。EnabledOption クラスと DisabledOption クラスはドロップダウン・リストの項目に使用されます。

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

<f:view>
<html>
<head>
    <title>Default Styles</title>
    <link rel="stylesheet" type="text/css"
        href="<%=request.getContextPath()%>/styles/DefaultStyles.css">
</head>
<body>
    <h1>Default Styles</h1>
    <css:defaultStyles>
        <h:form id="form">
            <h:panelGrid columns="1" border="0" cellspacing="5">
                <h:inputText id="text" size="30" value="default text style"/>
                <h:inputText id="special" size="30" value="special text style"
                    styleClass="SpecialInputText"/>
                <h:selectOneMenu id="menu" enabledClass="EnabledOption"
                        disabledClass="DisabledOption">
                    <f:selectItem itemValue="First" itemLabel="First"/>
                    <f:selectItem itemValue="Second" itemLabel="Second"/>
                    <f:selectItem itemValue="Third" itemLabel="Third"
                        itemDisabled="true"/>
                </h:selectOneMenu>
                <h:inputTextarea id="area" rows="5" cols="30"
                    value="default text area style"/>
                <h:commandButton id="button" value="Submit"/>
            </h:panelGrid>
        </h:form>
    </css:defaultStyles>
</body>
</html>
</f:view>

リスト 24 は、defaultStyles.jsp ページが生成した HTML です。これを見ると、すべてのフォーム要素に class 属性があることがわかります。Default で始まるクラス名は、<css:defaultstyles> コンポーネントによって設定されたものです。

リスト 24. defaultStyles.jsp が生成した HTML
<html>
<head>
    <title>Default Styles</title>
    <link rel="stylesheet" type="text/css"
        href="/jsf12css/styles/DefaultStyles.css">
</head>
<body>
    <h1>Default Styles</h1>
    
<form id="form" ... class="DefaultForm" ...>
<input type="hidden" name="form" value="form" />
<table class="DefaultPanelGrid" border="0" cellspacing="5">
<tbody>
<tr>
<td><input id="form:text" type="text" ... 
    class="DefaultInputText" .../></td>
</tr>
<tr>
<td><input id="form:special" type="text" ... 
    class="SpecialInputText" .../></td>
</tr>
<tr>
<td><select id="form:menu" ... class="DefaultSelectOneMenu" ...>
    <option value="First" class="EnabledOption">First</option>
    <option value="Second" class="EnabledOption">Second</option>
    <option value="Third" disabled="disabled" 
        class="DisabledOption">Third</option>
</select></td>
</tr>
<tr>
<td><textarea id="form:area" ... 
    class="DefaultInputTextarea" ...> ... </textarea></td>
</tr>
<tr>
<td><input id="form:button" type="submit" ... 
    class="DefaultCommandButton" /></td>
</tr>
</tbody>
</table>
<input type="hidden" ... id="javax.faces.ViewState" value="..." />
</form>

</body>
</html>

まとめ

この記事では、標準 JSF コンポーネントが提供する CSS サポートについて説明しました。これらのコンポーネントのうち、単純なものには stylestyleClass という 2 つのオプション属性しかありません。グリッドや、データの表などの複雑なコンポーネントには、ファセットと要素に対応する CSS 関連の属性がさらに追加されています。この連載の第 1 回目では、デフォルト・スタイルなどの機能を追加できるように、JSF コンポーネント・ツリーの作成方法、そしてコンポーネントがレンダリングされる直前にこれらのツリーをトラバースする方法を説明しました。連載第 2 回では JavaScript ベースの手法によって JSF フォームをさらに動的にする方法を説明します。次回の記事もお見逃しなく。


ダウンロード

内容ファイル名サイズ
Sample application for this articlejsfcssjs_part1_src.zip25KB

参考文献

学ぶために

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

議論するために

コメント

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=291829
ArticleTitle=JSF と CSS、JavaScript を使用して作成する Ajax アプリケーション: 第 1 回 JSF ページの外観を整える
publish-date=01292008