<<<<< MyFacesカスタマイズめも >>>>>

JSFの実装の一つである、MyFaces(http://myfaces.apache.org/)で提供されている、
カレンダーコンポーネントを、JRA式の土曜日始まりに出来るか?
という素朴な疑問から始まった、MyFacesカスタマイズめもです。
ちなみに.NETのカレンダーコンポーネントは、
FirstDayOfWeek属性を指定することで、何曜日始まりにもできるそうです。

この記事では、以下のことを行います。

  1. 固定的に土曜日始まりのカレンダーを表示する
  2. タグの属性に開始曜日を指定することで、何曜日始まりのカレンダーでも表示できるようにする

[前提]

[ソースのダウンロード]
MyFacesのソースのダウンロードは、ここになります。
ここにいって、myfaces-1.0.9-src.zipもしくは、myfaces-1.0.9-src.tgzをダウンロードします。
そして、適当な解凍ツールで解凍し、展開ディレクトリを適当な場所に置きます。

[コンパイル環境の構築]
MyFacesをコンパイルするには、いくつかの依存するライブラリを集めなくてはなりません。
ほんとはMavenで出来るのかも知れませんが、知らないので集めます。

myfaces.jarをコンパイルするのに必要なJar構成は以下のようになります。

myfaces-1.0.9-src
  /lib
    commons-beanutils-1.6.1.jar
    commons-codec-1.2.jar
    commons-collections-3.0.jar
    commons-digester-1.5.jar
    commons-el.jar
    commons-logging.jar
    jsp-2.0.jar
    jstl.jar
    portlet-api-1.0.jar
    servlet-2.3-jsp-1.2.jar
  /lib/optional
    commons-fileupload-1.0.jar
    commons-validator.jar
    jakarta-oro.jar
    struts.jar

portlet-api-1.0.jar以外は、以下のURLから入手できます。
http://cvs.sourceforge.net/viewcvs.py/myfaces/myfaces/lib/
portlet-api-1.0.jarは、色々探した結果見つからなかったので、
http://fisheye.cenqua.com/viewrep/ws/ws-wsrp4j/lib/shared/portlet-api-1.0.jar
から入手(rawのリンクをクリック)したのですが、myfaces-1.0.9-app.zip(サンプル集)の
myfaces-blank-example.warの/WEB-INF/libの中にも入っています。

(参考)
servlet-2.3-jsp-1.2.jarは、Tomcat5に含まれている、
servlet-api.jarとjsp-api.jarの二つでも代替可能です。
その場合、build.xmlとbuild.default.propertiesを両方読むように書き換える必要があります。

[コンパイル(Ant)の実行]
コマンドラインで、myfaces-1.0.9-src/buildディレクトリを開きます。
そして、以下のantコマンドを実行します。
ant myfaces-jar
成功すると、myfaces-1.0.9-src/lib以下にmyfaces.jarが作成されます。
うまくいかない場合は、Jar構成が間違っていないかどうか確認してください。

[カレンダーを土曜日始まりにする]
ソースツリーをCalendarというファイル名で検索してみると、
myfaces-1.0.9-src\src\components\org\apache\myfaces\custom\calendar
のフォルダが発見されます。

このフォルダにあるJavaソースは以下の3つになります。

これを順番にみていくと、カレンダーの描画処理を実際に行っているのは、
HtmlCalendarRenderer.javaであることに気づきます。

そしてさらに見ていくと、239行目にある、
int weekStartsAtDayIndex = mapCalendarDayToCommonDay(timeKeeper.getFirstDayOfWeek());
の1行が、週の始めの曜日を取得する処理であることが分かります。

ここで取得しているweekStartsAtDayIndexは、152行目で取得しているweekDaysの配列のインデックスであって、
java.util.Calendarの定数とは一致していませんので、注意が必要です。

weekDays
0: MONDAY
1: TUESDAY
2: WEDNESDAY
3: THURSDAY
4: FRIDAY
5: SATURDAY
6: SUNDAY
java.util.Calendarの定数
1: SUNDAY
2: MONDAY
3: TUESDAY
4: WEDNESDAY
5: THURSDAY
6: FRIDAY
7: SATURDAY

つまり、weekStartsAtDayIndexを土曜日に変えれば、
カレンダーが土曜日始まりになると考えられるため、
以下のように修正します。

//int weekStartsAtDayIndex = mapCalendarDayToCommonDay(timeKeeper.getFirstDayOfWeek());
int weekStartsAtDayIndex = 5; // 5 means SATURDAY

これでコンパイルして、myfaces.jarを生成します。
配布されたmyfaces.jarと入れ替えて、カレンダーを表示してみてください。
土曜日始まりに表示されたでしょうか?

■土曜日始まりなイメージ

[カレンダーを指定曜日始まりにする]
inputCalendarタグに、firstDayOfWeek属性を追加して、
例えば、

<x:inputCalendar firstDayOfWeek="3"/>
という風に記述すれば、木曜日始まりのカレンダーを表示する
というように拡張したいと思います。

・TLDファイルの修正
カスタムタグの定義ファイルを修正します。
myfaces-1.0.9-src\tlds\myfaces_ext.tld
myfaces-1.0.9-src\myfaces_ext_sf.tldってのもあるが、よく分かりません。。
sfはSourceForgeの略であるようですが。

中をみていくと、434行目からinputCalendarタグの設定が書いてあります。

    <!-- calendar -->
    <tag>
        <name>inputCalendar</name>
        <tag-class>org.apache.myfaces.custom.calendar.HtmlInputCalendarTag</tag-class>
        <body-content>JSP</body-content>
        <description>
            Provides a calendar.
        </description>
        &ui_input_attributes;
        ... 以下略

属性の定義がずらずらと下に続いているので、
ここにfirstDayOfWeekを追加します。

        <attribute>
            <name>popupSelectDateMessage</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <description>Set the string for "Select [date] as date" (do not replace [date], it will be replaced by the current date).</description>
        </attribute>
        <!-- 追加 -->
        <attribute>
            <name>firstDayOfWeek</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <description>Set the number for First Day Of Week. 0:Monday ... 6:Sunday</description>
        </attribute>
        <!-- 追加終わり -->

これでTLDの修正はOKです。
次にソースコードを修正します。

・ソースコードの修正
まずは、HtmlInputCalendarTag.javaです。
このクラスは、タグの動作を決めるクラスのようですが、
JSFおよびMyFacesのフレームワークに隠蔽されて、
ほとんど処理らしい処理がありません。
以下のような修正を行えばよいです。

・フィールド定義の追加 (104行目付近)

private String _popupSelectDateMessage = null;
// 追加
private String _firstDayOfWeek = null;
// 追加終わり

・releaseメソッドの修正 (130行目付近)

_visibleOnUserRole = null;
// 追加
_firstDayOfWeek = null;
// 追加終わり

・setPropertiesメソッドの修正 (154行目付近)

setStringProperty(component,"popupSelectDateMessage",_popupSelectDateMessage);
// 追加
setStringProperty(component,"firstDayOfWeek",_firstDayOfWeek);
// 追加終わり

・setメソッドの追加 (249行目付近)

public void setPopupWeekString(String popupWeekString)
{
    _popupWeekString = popupWeekString;
}

// 追加
public void setFirstDayOfWeek(String firstDayOfWeek)
{
    _firstDayOfWeek = firstDayOfWeek;
}
// 追加終わり

要は、フィールドを定義して、初期化処理とセッターを追加したというところでしょう。
次に、HtmlInputCalendar.javaを修正します。
このクラスは、HtmlCalendarRendererに実際に渡されるもので、
恐らく、タグライブラリのDTO的役割を果たしているものと思われます。
修正は、HtmlInputCalendarTag.javaとほぼ同じです。

・フィールド定義の追加 (56行目付近)

private String _popupSelectDateMessage = null;
// 追加
private String _firstDayOfWeek = null;
// 追加終わり

・ゲッター/セッターの追加 (284行目付近)

public String getPopupSelectDateMessage()
{
    if (_popupSelectDateMessage != null) return _popupSelectDateMessage;
    ValueBinding vb = getValueBinding("popupSelectDateMessage");
    return vb != null ? (String)vb.getValue(getFacesContext()) : null;
}

// 追加
public void setFirstDayOfWeek(String firstDayOfWeek)
{
    _firstDayOfWeek = firstDayOfWeek;
}

public String getFirstDayOfWeek()
{
    if(_firstDayOfWeek != null) return _firstDayOfWeek;
    ValueBinding vb = getValueBinding("firstDayOfWeek");
    return vb != null ? (String)vb.getValue(getFacesContext()) : null;
}
// 追加終わり

・saveStateメソッドの修正 (295, 314行目付近)

// 修正
//Object values[] = new Object[19];
Object values[] = new Object[20];
// 修正終わり
values[0] = super.saveState(context);

... 省略

values[18] = _popupSelectDateMessage;
// 追加
values[19] = _firstDayOfWeek;
// 追加終わり
return ((Object) (values));

・restoreStateメソッドの修正 (339行目付近)

_popupSelectDateMessage = (String)values[18];
// 追加
_firstDayOfWeek = (String)values[19];
// 追加終わり

最後に、HtmlCalendarRenderer.javaを修正します。
ここまでの作業で、inputCalendarタグのfirstDayOfWeek属性にセットされた値が、
HtmlInputCalendarのfirstDayOfWeekフィールドにセットされるので、
これを取り出して利用するようコードを修正します。(239行目付近)

// 修正
int weekStartsAtDayIndex = mapCalendarDayToCommonDay(timeKeeper.getFirstDayOfWeek());  // もともとのコード
if(inputCalendar.getFirstDayOfWeek() != null)
{
    try
    {
        int firstDayOfWeek = Integer.parseInt(inputCalendar.getFirstDayOfWeek());
        if(firstDayOfWeek >= 0 && firstDayOfWeek <= 6)
        {
            weekStartsAtDayIndex = firstDayOfWeek;
        }
    }
    catch(NumberFormatException e)
    {
        // デフォルトを採択
    }
}
// 修正終わり

ここまで修正できたら、コンパイルしてmyfaces.jarを生成してください。
配布されたmyfaces.jarと入れ替えて、カレンダータグを以下のようにしてみてください。

<x:inputCalendar firstDayOfWeek="3"/>
木曜日始まりに表示されたでしょうか?

■木曜日始まりなイメージ