Button Control: Simple Calendar Menu Button

このExampleの初期画面は以下。見ての通りのカレンダーピッカー(Calendar Picker)である(YUIのExampleへのリンクはこちら)。

カレンダーアイコンのついているボタンをクリックすると、以下のようにカレンダー(メニュー)が表示される。このとき、「今日」にフォーカスがあたるようになっている(太めの点線で囲まれたところ)。

ここで、カレンダーから日付をピックアップすると、それがテキストフィールドに反映される。下の図は、その状態で再度、カレンダーボタンをクリックした状態。選択した日付が青く表示される。

こんなシンプルなカレンダーピッカーだが、Javascriptは意外に面倒くさい。
YUIのExampleのコードをmodule patternに書き直して、冗長と思われる部分を削除したhtmlを以下に示す。ボタンにカレンダーオブジェクトをしのばせる方法は、「Button Control: Menu Buttons」の2で示した方法である。
以下のscriptでは、onButtonClick()は初回クリック時の1回だけ呼ばれるようにする(関数末にボタンのclickイベントのハンドラをunsubscribeしている)。そうしないと、ボタンクリック時にカレンダーインスタンスが再作成されてしまい、選択した日付を忘れてしまう(日付の選択後に、再度ボタンを押した際に、3番目の画面が表示されず、1番目の画面がでてしまう)。

<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<TITLE>Ajax_Sampling</TITLE>
<style type="text/css">
body {
	margin:0;
	padding:0;
}
</style>
<link rel="stylesheet" type="text/css" href="scripts/yui/fonts/fonts-min.css" />
<link rel="stylesheet" type="text/css" href="scripts/yui/button/assets/skins/sam/button.css" />
<link rel="stylesheet" type="text/css" href="scripts/yui/calendar/assets/skins/sam/calendar.css" />
<script type="text/javascript" src="scripts/yui/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" src="scripts/yui/container/container_core-min.js"></script>
<script type="text/javascript" src="scripts/yui/calendar/calendar-min.js"></script>
<script type="text/javascript" src="scripts/yui/element/element-beta-min.js"></script>
<script type="text/javascript" src="scripts/yui/button/button-min.js"></script>

<style type="text/css" id="defaultstyle">
#container {
	margin: 2px;
	padding: 3px;
	width: 500px;
	height: auto;
	border:1px dashed #999999;
}

/*
    Set the "zoom" property to "normal" since it is set to "1" by the 
    ".example-container .bd" rule in yui.css and this causes a Menu
    instance's width to expand to 100% of the browser viewport.
*/
    div.yuimenu .bd {
        zoom: normal;
}

/*
    Restore default padding of 10px for the calendar containtainer 
    that is overridden by the ".example-container .bd .bd" rule 
    in yui.css.
*/

#calendarcontainer {
     padding:10px;
}

#calendarmenu {
     position: absolute;
}

#calendarpicker button {
     background: url(http://developer.yahoo.com/yui/examples/button/assets/calendar_icon.gif) center center no-repeat;
     text-align: left;
     text-indent: -10em;
     overflow: hidden;
     *margin-left: 10em; /* For IE */
     *padding: 0 3em;    /* For IE */
     white-space: nowrap;
}

#month-field,
#day-field {
     width: 2em;
}
    
#year-field {
     width: 3em;
}

#datefields {
	
	border: solid 1px #666;
	padding: .5em;
	
}
	
#calendarpicker  {
	vertical-align: baseline;
}

</style>

<script type="text/javascript">
//
//モジュールパターンで実装する。
//
YAHOO.namespace("EGP");

YAHOO.EGP.CalendarButton = function() {

	var Event = YAHOO.util.Event;
	var Dom = YAHOO.util.Dom;
	var oCalendarMenu;
	var oButton;

	// カレンダー表示ボタンクリック時のハンドラー
	var onButtonClick = function () {
		
		// Create a Calendar instance and render it into the body 
		// element of the Overlay.
		// OVerlayインスタンスのボディー部にid=buttoncalendarでカレンダーインスタンス
		// を作る。
		// (※)カレンダーインスタンスは1回しか作成されない。
		var oCalendar = new YAHOO.widget.Calendar("buttoncalendar", 
			oCalendarMenu.body.id);

		// Subscribe to the Calendar instance's "select" event to 
		// update the month, day, year form fields when the user
		// selects a date.

		// カレンダーのselectイベントに、markupへの日付の編集と、カレンダーの非表示を
		// sbscribeする。
		oCalendar.selectEvent.subscribe(function (p_sType, p_aArgs) {
			var aDate;
			if (p_aArgs) {
				aDate = p_aArgs[0][0];
				Dom.get("month-field").value = aDate[1];
				Dom.get("day-field").value = aDate[2];
				Dom.get("year-field").value = aDate[0];
			}
			oCalendarMenu.hide();
		});

		// Set focus to either the current day, or first day of the month in 
		// the Calendar	when it is made visible or the month changes

		// Menuのshowイベントに対して、focusDayを仕掛ける。
		// 本日をフォーカスさせ、ページを送ったら先頭日にフォーカスを当てる(点線の枠)。
		oCalendar.renderEvent.subscribe(focusDay, oCalendar, true);

		// カレンダーの表示
		oCalendar.render();

		// Unsubscribe from the "click" event so that this code is 
		// only executed once
		// Buttonのクリックは1回だけなので、unsubscribeする。
		// この後のボタンクリックは、ボタンインスタンスを再表示してMenu処理をするだけ。
		// (これは、oButton生成時に設定される)
		this.unsubscribe("click", onButtonClick);
	
	};

	// 日にちにfocusを当てるための関数。
	var focusDay = function () {
		
		// Calelndarインスタンスを生成したDOM(id="buttoncalendar")から
		// Table Bodyをとってくる。http://www.howtocreate.co.uk/tutorials/javascript/domtables
		var oCalendarTBody = Dom.get("buttoncalendar").tBodies[0],
			// カレンダーの全ての日を取ってくる(aタグで書かれている)。
			aElements = oCalendarTBody.getElementsByTagName("a"),
			oAnchor;
		
		if (aElements.length > 0) {
			// Dom.batch(Array,fn)は、Array要素に対して、繰り返しfnを適用する。
			Dom.batch(aElements, 
					function (element) {
						// elementの親ノードが"today"というclassをもてば、アンカー
						// をそのエレメントに貼る。
						// この場合は、td要素。
						if (Dom.hasClass(element.parentNode, "today")) {
							oAnchor = element;
						}
					}
			);

			// アンカーが貼られなかったら、先頭日に貼る。
			if (!oAnchor) {
				oAnchor = aElements[0];
			}

			// Focus the anchor element using a timer since Calendar will try 
			// to set focus to its next button by default
			// 0msec後にoAnchorにたいして、functionを実行。
			YAHOO.lang.later(0, 
					oAnchor, 
					function () {
						try {
						oAnchor.focus();
						}
					catch(e) {}
					}
			);
		}
	};

	
	return{
    	init: function() {
	
			// Create an Overlay instance to house the Calendar instance
			// Calendarインスタンスを入れるOverlayインスタンスの作成。
			// id=calendarmenuとする。
			oCalendarMenu = new YAHOO.widget.Overlay("calendarmenu", 
					{ visible: false });
	
			// Create a Button instance of type "menu"
			// ボタンインスタンスをtype=menuで作成する。
			oButton = new YAHOO.widget.Button({ 
					type: "menu", 
					id: "calendarpicker", 
					label: "Choose A Date", 
					menu: oCalendarMenu, 
					container: "datefields" });

			// ボタンのappendToイベントに対して、Calendarインスタンスを入れるための
			// 空のbodyをセットする。
			// このbodyのidをcalendarcontainerとする。
			oButton.on("appendTo", function () {
				// Create an empty body element for the Overlay instance 
				// in order 
				// to reserve space to render the Calendar instance into.
		
				oCalendarMenu.setBody("&#32;");
				oCalendarMenu.body.id = "calendarcontainer";
			});
	
			// Add a "click" event listener that will render the Overlay, and 
			// instantiate the Calendar the first time the Button instance is 
			// clicked.
			// ボタンのクリックイベントのハンドラとして、onButtonClickを設定する。
			oButton.on("click", onButtonClick);
			

			// Pressing the Esc key will hide the Calendar Menu and send focus back to 
			// its parent Button

			// oCalendarMenu上でkeyが押され、それがesc keyの場合、フォーカスを
			// ボタンに返す。
			Event.on(oCalendarMenu.element, 
					"keydown", 
					function (p_oEvent) {
						if (Event.getCharCode(p_oEvent) === 27) {
							oCalendarMenu.hide();
							oButton.focus();
						}
					}, 
					null, 
					oButton);
	
		}
	};
}();

//DOMが完全にloadされたら、サンプルを初期化する。
YAHOO.util.Event.onDOMReady(
		//DomReadyイベントで発火するハンドラ
		YAHOO.EGP.CalendarButton.init,
		//ハンドラに渡すオブジェクト(関数)
		YAHOO.EGP.CalendarButton,
		//ハンドラは、上記のオブジェクトのスコープをもつ。   
		true
);

</script>
</HEAD>

<BODY class="yui-skin-sam">
<div id="container">
<p>
Calendar Buttonの作り方のサンプルです。
</p>
    <form id="button-example-form" name="button-example-form" method="post">
        <fieldset id="datefields">
    
            <legend>Date</legend>
            <label for="year-field">Year: </label> 
            	<input id="year-field" type="text" name="year">
            <label for="month-field">Month: </label> 
            	<input id="month-field" type="text" name="month">
            <label for="day-field">Day:</label> 
            	<input id="day-field" type="text" name="day">
    
        </fieldset>
    </form>

</div>
</BODY>		
</HTML>