slack

テクてく Lotus 技術者 Slack に参加しよう!

2010/02/09

XPages でのカレンダーコントロール (Part 1)

Classic Notes アプリケーションやこれまでの Domino Web アプリでカレンダーを表示させ、日付を入力したり、選択したりすることは少なくなかったと思います。
ところが XPages の開発においてこのカレンダーのコントロールがないことがしばしば話題に上ったりしています。当然コアコントロールとして装備されていてもおかしくはないのですが・・・・

今回ご紹介するのは、カレンダーをカスタムコントロールとして作成する方法を Keith Strickland 氏の keithstric.com  SnTT: XPages Blank Calendar Control (Part 1) からご紹介します。

オリジナルのタイトルにもある通り、今回はまずブランクのカレンダー、つまりカレンダーの表示だけについて見ていくことになります。

最初は、表を使ってカレンダーの外観を作成するわけですが、まずはカレンダーの曜日が表示される部分にあたるヘッダーのコードです。
<xp:table styleClass="calTable" id="calTable">
<thead styleClass="calTableHead">
<th>Sun</th>
<th>Mon</th>
<th>Tue</th>
<th>Wed</th>
<th>Thu</th>
<th>Fri</th>
<th>Sat</th>
</thead>
<xp:text escape="false" id="startRows">
<xp:this.value><![CDATA[#{javascript:"<tr>"}]]></xp:this.value>
</xp:text>

カレンダーの始まりは日曜日になっていますが、その月の始まりの日が日曜日ではない場合ブランクのセルを表示させるといった処理をするため、まず何年何月の1日の位置がどこから始まり、その月が何日あるのかをページがロードされる際に把握しておかなければなりません。これらを「beforePageLoad」イベントで viewScope の変数を使って実現します。
<xp:this.beforePageLoad><![CDATA[#{javascript:if (!viewScope.containsValue("dispDate")) {
viewScope.dispDate = @Now();
viewScope.dispCalYear = @Year(viewScope.dispDate);
viewScope.dispCalMonth = @Month(viewScope.dispDate);
viewScope.daysInMonth = new Date(viewScope.dispCalYear,viewScope.dispCalMonth,0).getDate();
viewScope.firstDayInMonth = new Date(viewScope.dispCalYear,viewScope.dispCalMonth -1,1).getDay();
}}]]></xp:this.beforePageLoad>

ブランクの数が分かったら、繰り返しの内部にある計算結果フィールドを使って、そのカレンダーに必要な数だけのブランクを作成する処理を記述します。
<xp:repeat id="calBlanks" rows="7" value="#{javascript:viewScope.firstDayInMonth;}" var="calBlankVar">
<xp:text id="blankCells" contentType="html">
<xp:this.value><![CDATA[#{javascript:'<td class="calBlank"> </td>';}]]></xp:this.value>
</xp:text>
</xp:repeat>

ここでその月の日数分だけ回して、表の TD の記述の中に日の数を埋めていく処理に移ります。ここで注意しなければいけないのは、

  1. 計算結果フィールドにはその行のタグの開始と終了も追加して記述すること。そして週の最初のエントリの後ではなく前に置くこと。
  2. セルの中に埋め込む日数の数の計算結果には、実際の日にプラス 1 すること。これは最初の日がゼロから始まるためです。
  3. セルの内容についてロジックを考える必要はありません。新しい行が必要かどうかは繰り返しの中の最初の計算結果フィールドで決定します。
<xp:repeat id="calDay" rows="31"
value="#{javascript:viewScope.daysInMonth;}" var="calVar"
indexVar="calIndex">
<xp:text escape="false" id="rowEndBegin">

<xp:this.value><![CDATA[#{javascript:"</tr><tr>"}]]></xp:this.value>
<xp:this.rendered><![CDATA[#{javascript:if (calIndex != 0) {
if (((calIndex + viewScope.firstDayInMonth) % 7) == 0) { 
return true; 
}else{ 
return false; 
}
}}]]></xp:this.rendered>
</xp:text>
<xp:text escape="false" id="dayCells">
<xp:this.value><![CDATA[#{javascript:var calDayTD = '<td class="calDay"><span class="dayNum">' + (calVar+1) + '</span></td>';
return calDayTD;}]]></xp:this.value>
</xp:text>
</xp:repeat>

最後に、その月の最終日が土曜日でない場合は必要なセルにブランクを設定し、表はこの行で最後だということで終了される必要があります。これを別の繰り返しコントロールと計算結果フィールドを使って実現しています。
<xp:repeat id="calBlanksEnd" rows="14">
<xp:this.value><![CDATA[#{javascript:var lastRow = 42 - (viewScope.daysInMonth + viewScope.firstDayInMonth);
if (lastRow > 13) {
lastRow = lastRow - 14;
}else if (lastRow > 6) {
lastRow = lastRow - 7;
}
return lastRow;}]]></xp:this.value>
<td class="calBlank"></td>
</xp:repeat>
<xp:text escape="false" id="lastRow">
<xp:this.value><![CDATA[#{javascript:"</tr>"}]]></xp:this.value>
</xp:text>

最後にこのカレンダーの上部にタイトルヘッダーを追加して、何年何月が表示されているかと、前後の月に移動できるようリンクを追加します。
これは 2 つのリンクとひとつの計算結果フィールドで実現できます。
計算結果フィールドを使って表示される月は、すべての月の情報が入っている配列の中のコンテンツから決定され、viewScope.dispCalMonth の変数から 1 をマイナスした値が使われます。それぞれのリンクは viewScope の変数を変更する処理を行います。これらをひとつのパネルの中に記述します。

<xp:panel id="calendar">
  <xp:panel styleClass="calMonthTitle" id="Header">
   <xp:link escape="true" id="monthBack"><xp:this.text><![CDATA[<<]]></xp:this.text>
    <xp:eventHandler event="onclick" submit="true"
     refreshMode="complete">
     <xp:this.action><![CDATA[#{javascript:var curMonth = @Month(viewScope.dispDate);
var curYear = @Year(viewScope.dispDate)
curMonth = (@Month(viewScope.dispDate)-1);
var curDate = new Date(curYear,curMonth -1,1);

viewScope.put("dispDate",curDate);
viewScope.put("dispCalYear",@Year(viewScope.dispDate));
viewScope.put("dispCalMonth",@Month(viewScope.dispDate));

var monthDays = new Date(viewScope.dispCalYear,viewScope.dispCalMonth,0).getDate();
var monthFirstDay = new Date(viewScope.dispCalYear,viewScope.dispCalMonth -1,1).getDay();
viewScope.put("daysInMonth",monthDays);
viewScope.put("firstDayInMonth",monthFirstDay);}]]></xp:this.action>
    </xp:eventHandler></xp:link> 
     
   <xp:text escape="false" id="dispMonth">
    <xp:this.value><![CDATA[#{javascript:var months = new Array("January","Feburary","March","April","May","June","July","August","September","October","November","December");
var curMonth = months[(viewScope.dispCalMonth-1)];
return curMonth + " " + viewScope.dispCalYear;}]]>
</xp:this.value >
   </xp:text>
     
   <xp:link escape="true" id="monthForward">
    <xp:this.text><![CDATA[>>]]></xp:this.text>
    <xp:this.onclick><![CDATA[#{javascript:var curMonth = @Month(viewScope.dispDate);
var curYear = @Year(viewScope.dispDate)
curMonth = (@Month(viewScope.dispDate)+1);
var curDate = new Date(curYear,curMonth -1,1);

viewScope.put("dispDate",curDate);
viewScope.put("dispCalYear",@Year(viewScope.dispDate));
viewScope.put("dispCalMonth",@Month(viewScope.dispDate));

var monthDays = new Date(viewScope.dispCalYear,viewScope.dispCalMonth,0).getDate();
var monthFirstDay = new Date(viewScope.dispCalYear,viewScope.dispCalMonth -1,1).getDay();
viewScope.put("daysInMonth",monthDays);
viewScope.put("firstDayInMonth",monthFirstDay);}]]></xp:this.onclick>
    <xp:eventHandler event="onclick" submit="true"
     refreshMode="complete">
     <xp:this.action><![CDATA[#{javascript:var curMonth = @Month(viewScope.dispDate);
var curYear = @Year(viewScope.dispDate)
curMonth = (@Month(viewScope.dispDate)+1);
var curDate = new Date(curYear,curMonth -1,1);

viewScope.put("dispDate",curDate);
viewScope.put("dispCalYear",@Year(viewScope.dispDate));
viewScope.put("dispCalMonth",@Month(viewScope.dispDate));

var monthDays = new Date(viewScope.dispCalYear,viewScope.dispCalMonth,0).getDate();
var monthFirstDay = new Date(viewScope.dispCalYear,viewScope.dispCalMonth -1,1).getDay();
viewScope.put("daysInMonth",monthDays);
viewScope.put("firstDayInMonth",monthFirstDay);}]]></xp:this.action>
    </xp:eventHandler>
   </xp:link></xp:panel>



以下はこれまでのコードの最終完成形です。
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.beforePageLoad><![CDATA[#{javascript:if (!viewScope.containsValue("dispDate")) {
viewScope.dispDate = @Now();
viewScope.dispCalYear = @Year(viewScope.dispDate);
viewScope.dispCalMonth = @Month(viewScope.dispDate);
viewScope.daysInMonth = new Date(viewScope.dispCalYear,viewScope.dispCalMonth,0).getDate();
viewScope.firstDayInMonth = new Date(viewScope.dispCalYear,viewScope.dispCalMonth -1,1).getDay();
}}]]></xp:this.beforePageLoad>

<xp:panel id="calendar">
<xp:panel styleClass="calMonthTitle" id="Header">
<xp:link escape="true" id="monthBack"><xp:this.text><![CDATA[<<]]></xp:this.text>
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:var curMonth = @Month(viewScope.dispDate);
var curYear = @Year(viewScope.dispDate)
curMonth = (@Month(viewScope.dispDate)-1);
var curDate = new Date(curYear,curMonth -1,1);

viewScope.put("dispDate",curDate);
viewScope.put("dispCalYear",@Year(viewScope.dispDate));
viewScope.put("dispCalMonth",@Month(viewScope.dispDate));

var monthDays = new Date(viewScope.dispCalYear,viewScope.dispCalMonth,0).getDate();
var monthFirstDay = new Date(viewScope.dispCalYear,viewScope.dispCalMonth -1,1).getDay();
viewScope.put("daysInMonth",monthDays);
viewScope.put("firstDayInMonth",monthFirstDay);}]]></xp:this.action>
</xp:eventHandler></xp:link> 
  
<xp:text escape="false" id="dispMonth">
<xp:this.value><![CDATA[#{javascript:var months = new Array("January","Feburary","March","April","May","June","July","August","September","October","November","December");
var curMonth = months[(viewScope.dispCalMonth-1)];
return curMonth + " " + viewScope.dispCalYear;}]]></xp:this.value>
</xp:text>
  <xp:link escape="true" id="monthForward"><xp:this.text><![CDATA[>>]]></xp:this.text>
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:var curMonth = @Month(viewScope.dispDate);
var curYear = @Year(viewScope.dispDate)
curMonth = (@Month(viewScope.dispDate)+1);
var curDate = new Date(curYear,curMonth -1,1);

viewScope.put("dispDate",curDate);
viewScope.put("dispCalYear",@Year(viewScope.dispDate));
viewScope.put("dispCalMonth",@Month(viewScope.dispDate));

var monthDays = new Date(viewScope.dispCalYear,viewScope.dispCalMonth,0).getDate();
var monthFirstDay = new Date(viewScope.dispCalYear,viewScope.dispCalMonth -1,1).getDay();
viewScope.put("daysInMonth",monthDays);
viewScope.put("firstDayInMonth",monthFirstDay);}]]></xp:this.action>
</xp:eventHandler></xp:link></xp:panel>
<xp:table styleClass="calTable" id="calTable">
<thead styleClass="calTableHead">
<th>Sun</th>
<th>Mon</th>
<th>Tue</th>
<th>Wed</th>
<th>Thu</th>
<th>Fri</th>
<th>Sat</th>
</thead>
<xp:text escape="false" id="startRows">
<xp:this.value><![CDATA[#{javascript:"<tr>"}]]></xp:this.value>
</xp:text>
<xp:repeat id="calBlanks" rows="7" value="#{javascript:viewScope.firstDayInMonth;}" var="calBlankVar">
<xp:text id="blankCells" contentType="html">
<xp:this.value><![CDATA[#{javascript:'<td class="calBlank"> </td>';}]]></xp:this.value>
</xp:text>
</xp:repeat>
<xp:repeat id="calDay" rows="31"
value="#{javascript:viewScope.daysInMonth;}" var="calVar"
indexVar="calIndex">
<xp:text escape="false" id="rowEndBegin">

<xp:this.value><![CDATA[#{javascript:"</tr><tr>"}]]></xp:this.value>
<xp:this.rendered><![CDATA[#{javascript:if (calIndex != 0) {
if (((calIndex + viewScope.firstDayInMonth) % 7) == 0) { 
return true; 
}else{ 
return false; 
}
}}]]></xp:this.rendered>
</xp:text>
<xp:text escape="false" id="dayCells">
<xp:this.value><![CDATA[#{javascript:return '<td class="calDay"><span class="dayNum">' + (calVar+1) + '</span></td>';}]]></xp:this.value>
</xp:text>
</xp:repeat>
<xp:repeat id="calBlanksEnd" rows="14">
<xp:this.value><![CDATA[#{javascript:var lastRow = 42 - (viewScope.daysInMonth + viewScope.firstDayInMonth);
if (lastRow > 13) {
lastRow = lastRow - 14;
}else if (lastRow > 6) {
lastRow = lastRow - 7;
}
return lastRow;}]]></xp:this.value>
<td class="calBlank"></td>
</xp:repeat>
<xp:text escape="false" id="lastRow">
<xp:this.value><![CDATA[#{javascript:"</tr>"}]]></xp:this.value>
</xp:text>
</xp:table>
</xp:panel>
</xp:view>


最後に、CSS を使って、カレンダーの中に情報を表示されるためのコントロールのサンプルがダウンロードできます。
ダウンロード

0 件のコメント:

コメントを投稿