ITに関するさまざまなトピックを紹介するサイトです
このコンテンツはお使いのブラウザには対応しておりません。
新しいバージョンのブラウザでアクセスしてください。

カレンダー形式の日付選択

JavaScriptでカレンダー表示機能を実装しました。
「日付選択」ボタンを押すと、カレンダーが表示されます。
デフォルトではシステム日付の年月が表示されます。
下部のプルダウンで年月を変更することができます。年は当年から5年が選択できます。
この日付選択機能はクラス化しています。スタイルはクラス外で指定するようになっています。
選択結果はインスタンス生成時に指定したコールバック関数で受け取るようにしています。
このページのコールバック関数は、選択した日付をalert表示するものになっています。

HTML5で追加されたinput要素の"date"type属性

HTML5では、input要素のtype属性に指定できるデータタイプが追加されています。
フォームの入力データとして日付を入力させるためには、"date"をtype属性に指定します。
上記のJavaScript実装と同じように、カレンダー形式で日付を入力することができます。
ただし、一部のブラウザしか対応しておらず、主要なブラウザではChromeとOperaが対応しているようです。
日時関連では、他にもdatetime、datetime-local、month、week、timeなどがあります。
対応していないブラウザでは"text"として扱われるようです。
以下に、実際にtype属性に"date"を指定したinput要素を配置しました。
Chromeで表示させると、以下のようになりました。
(バージョン 30)
input要素にフォーカスすると下矢印が現れ、クリックするとプルダウンのように1ヶ月のカレンダー形式の日付選択画面が表示されます。

コード

JavaScriptのコードは以下のようになっています。

ユーティリティ関数

/**
 * イベントリスナを登録する。
 * @param {DOMElement} elem DOM要素
 * @param {String} eventType イベントタイプ
 * @param {Function} func リスナ関数
 * @param {Boolean} cap キャプチャモード
 */
function addListener(elem, eventType, func, cap) {
	cap = (cap) ? cap : false;
	if (elem.addEventListener) {
		elem.addEventListener(eventType, func, cap);
	} else if (elem.attachEvent) {
		elem.attachEvent("on" + eventType, func);
	} else {
		elem["on" + eventType] = func;
	}
};

/**
 * イベントリスナを削除する。
 * @param {DOMElement} elem DOM要素
 * @param {String} eventType イベントタイプ
 * @param {Function} func リスナ関数
 * @param {Boolean} cap キャプチャモード
 */
function removeListener(elem, eventType, func, cap) {
	cap = (cap) ? cap : false;
	if (elem.removeEventListener) {
		elem.removeEventListener(eventType, func, cap);
	} else if (elem.detachEvent) {
		elem.detachEvent("on" + eventType, func);
	} else {
		elem["on" + eventType] = null;
	}
};

/**
 * クロージャ関数を作成して返す。
 * @param {Function} func 実行する元の関数
 * @param {Object} obj 関数実行時のthis参照オブジェクト
 * @param {Object[]} [args] 実行する関数へ渡す引数の配列
 * @return {Object} 実行した関数の戻り値
 */
function closure(func, obj, args) {
	var closureFunc = function() {
		var comboArgs = new Array();
		if (args != null && (typeof args == "object")) {
			comboArgs = comboArgs.concat(args);
		}
		comboArgs = comboArgs.concat(Array.prototype.slice.call(arguments));
		return func.apply(obj, comboArgs);
	};
	return closureFunc;
};

共通イベントクラス

/**
 * ブラウザの差異を吸収した共通イベントクラス
 * @constructor
 * @param {Object} evt イベントオブジェクト
 */
function CommonEvent(evt) {
	this.evt = evt;
	this.type = evt.type;
	this.elem = (evt.target) ? evt.target : evt.srcElement;

	this.pageCoords = null;
};

/**
 * 共通イベントクラスのインスタンスを生成する。
 * @param {Object} evt イベントオブジェクト
 * @return {CommonEvent} 共通イベントオブジェクト
 */
CommonEvent.createCommonEvent = function(evt) {
	var evt = (evt) ? evt : ((event) ? event : null);
	if (evt == null) {
		return null;
	} else {
		return new CommonEvent(evt);
	}
};

/**
 * デフォルトイベント動作を抑止する。
 */
CommonEvent.prototype.preventDefault = function() {
	if (this.evt.preventDefault) {
		this.evt.preventDefault();
	}
	this.evt.returnValue = false;
};

/**
 * イベント伝搬を抑止する。
 */
CommonEvent.prototype.stopPropagation = function() {
	if (this.evt.stopPropagation) {
		this.evt.stopPropagation();
	}
	this.evt.cancelBubble = true;
};

/**
 * イベントが発生した位置のページ上での座標を返す。
 * @param {Object} evt イベントオブジェクト
 * @return {Object} 位置情報
 */
CommonEvent.prototype.getPageEventCoords = function() {
	if (this.pageCoords != null) {
		return this.pageCoords;
	}

	var coords = {left:0, top:0};
	var evt = this.evt;
	if (evt.pageX) {
		coords.left = evt.pageX;
		coords.top = evt.pageY;
	} else if (evt.clientX) {
		coords.left = evt.clientX + document.body.scrollLeft
				- document.body.clientLeft;
		coords.top = evt.clientY + document.body.scrollTop
				- document.body.clientTop;
		//大きさが取得できる場合には、html要素の情報も含める
		if (document.body.parentElement
				&& document.body.parentElement.clientLeft) {
			var bodyParent = document.body.parentElement;
			coords.left += bodyParent.scrollLeft - bodyParent.clientLeft;
			coords.top += bodyParent.scrollTop - bodyParent.clientTop;
		}
	}

	this.pageCoords = coords;
	return coords;
};

カレンダー日付選択クラス

/**
 * カレンダー日付選択クラス
 * @constructor
 * @param {String} calendarId カレンダーブロック要素のID
 * @param {Function} callback 日付選択のコールバック関数
 */
function CalendarSelector(calendarId, callback) {
	this.calendarDiv = document.getElementById(calendarId);
	this.callback = callback;

	this.year = null;
	this.month = null;

	this.calendarTable = null;
	this.tableHeader = null;
	this.chooseMonth = null;
	this.chooseYear = null;
	this.dateCells = new Array();
	this.clickDateFunc = null;

	//初期化処理実行
	this.init(calendarId);
};

/**
 * 初期化処理。
 * @param {String} calendarId カレンダーブロック要素のID
 */
CalendarSelector.prototype.init = function(calendarId) {

	//カレンダーのDOM要素構築
	this.makeCalendarDiv(calendarId);

	//日にちDOM要素生成
	this.clickDateFunc = closure(this.chooseDate, this);
	this.makeDateCells();

	//リスナ設定
	addListener(this.chooseMonth, "change",
			closure(this.makeDateCells, this));
	addListener(this.chooseYear, "change",
			closure(this.makeDateCells, this));
};

/**
 * カレンダーのDOM要素を構築する。
 * @param {String} calendarId カレンダーブロック要素のID
 */
CalendarSelector.prototype.makeCalendarDiv = function(calendarId) {

	//全体のDIV要素生成
	var calendarDiv = document.createElement("div");
	calendarDiv.id = calendarId;
	var calStyle = calendarDiv.style;
	calStyle.position = "absolute";
	calStyle.left = "-1000px";
	calStyle.top = "0px";
	calStyle.visibility = "hidden";

	//TABLE要素生成
	var calendarTable = document.createElement("table");
	calendarTable.cellSpacing = "0";
	this.calendarTable = calendarTable;

	var tableRec;
	var tableHeader;
	var cell;

	//ヘッダ用TR要素生成
	tableRec = calendarTable.insertRow(calendarTable.rows.length);
	tableHeader = document.createElement("th");
	tableHeader.className = "tableHeader";
	tableHeader.colSpan = 7;
	this.tableHeader = tableHeader;
	tableRec.appendChild(tableHeader);

	//曜日用TR要素生成
	tableRec = calendarTable.insertRow(calendarTable.rows.length);
	tableHeader = document.createElement("th");
	tableHeader.innerHTML = "日";
	tableRec.appendChild(tableHeader);
	tableHeader = document.createElement("th");
	tableHeader.innerHTML = "月";
	tableRec.appendChild(tableHeader);
	tableHeader = document.createElement("th");
	tableHeader.innerHTML = "火";
	tableRec.appendChild(tableHeader);
	tableHeader = document.createElement("th");
	tableHeader.innerHTML = "水";
	tableRec.appendChild(tableHeader);
	tableHeader = document.createElement("th");
	tableHeader.innerHTML = "木";
	tableRec.appendChild(tableHeader);
	tableHeader = document.createElement("th");
	tableHeader.innerHTML = "金";
	tableRec.appendChild(tableHeader);
	tableHeader = document.createElement("th");
	tableHeader.innerHTML = "土";
	tableRec.appendChild(tableHeader);

	//年月プルダウン用TR要素生成
	tableRec = calendarTable.insertRow(calendarTable.rows.length);
	cell = tableRec.insertCell(tableRec.cells.length);
	cell.colSpan = 7;

	//年プルダウン要素生成
	var today = new Date();
	var optionItem;
	var selectItem = document.createElement("select");
	var thisYear = today.getFullYear();
	for (var i = thisYear; i < thisYear + 5; i++) {
		optionItem = document.createElement("option");
		optionItem.innerHTML = i + "年";
		optionItem.value = i;
		if (thisYear == i) {
			optionItem.setAttribute("selected", "selected");
		}
		selectItem.appendChild(optionItem);
	}
	this.chooseYear = selectItem;
	cell.appendChild(selectItem);

	cell.appendChild(document.createTextNode(" "));

	//月プルダウン要素生成
	selectItem = document.createElement("select");
	for (var i = 0; i < 12; i++) {
		optionItem = document.createElement("option");
		optionItem.innerHTML = (i + 1) + "月";
		if (today.getMonth() == i) {
			optionItem.setAttribute("selected", "selected");
		}
		selectItem.appendChild(optionItem);
	}
	this.chooseMonth = selectItem;
	cell.appendChild(selectItem);

	calendarDiv.appendChild(calendarTable);

	this.calendarDiv = calendarDiv;
	document.body.appendChild(calendarDiv);
};

/**
 * 日にちDOM要素を生成する。
 */
CalendarSelector.prototype.makeDateCells = function() {

	//年月データの取得
	var chooseMonth = this.chooseMonth;
	var chooseYear = this.chooseYear;
	this.month = chooseMonth.selectedIndex;
	this.year = parseInt(chooseYear.options[chooseYear.selectedIndex].value, 10);
	var firstDay = CalendarSelector.getFirstDay(this.year, this.month);
	var daysInMonth = CalendarSelector.getDaysInMonth(this.year, this.month);
	var today = new Date();

	//ヘッダへの年月表示
	this.tableHeader.innerHTML = this.year + "年 "
			+ chooseMonth.options[this.month].text;

	//既存の日にちDOM要素の行を削除
	this.deleteDateCells();

	//日にちDOM要素生成用変数初期化
	var tableRec;
	var cell;
	var dayCounter = 1;
	var done = false;
	var container = this.calendarTable;

	//日にちDOM要素生成処理
	while (!done) {
		//最下行(年月プルダウン)の直前に行を追加
		tableRec = container.insertRow(container.rows.length - 1);
		if (tableRec) {
			for (var i = 0; i < 7; i++) {
				//行にセルを追加
				cell = tableRec.insertCell(tableRec.cells.length);
				//月の初日より前は空白
				if (container.rows.length == 4 && i < firstDay) {
					cell.innerHTML = " ";
					continue;
				}
				//月の最終日まで到達したら行追加処理終了
				if (dayCounter == daysInMonth) {
					done = true;
				}
				//日にち情報設定
				if (dayCounter <= daysInMonth) {
					//今日の日付の場合は要素に特定のidを設定
					if (today.getFullYear() == this.year &&
							today.getMonth() == this.month &&
							today.getDate() == dayCounter) {
						cell.id = "calendarSelectorToday";
					}
					cell.innerHTML = dayCounter;
					addListener(cell, "click", this.clickDateFunc);
					addListener(cell, "mouseover", this.dateMouseOver);
					addListener(cell, "mouseout", this.dateMouseOut);
					this.dateCells[dayCounter - 1] = cell;
					dayCounter++;
				} else {
					//月の最終日より後は空白
					cell.innerHTML = " ";
				}
			}
		} else {
			done = true;
		}
	}
};

/**
 * 既存の日にちDOM要素の行を削除する。
 */
CalendarSelector.prototype.deleteDateCells = function() {

	//リスナ削除
	for (var i = 0; i < this.dateCells.length; i++) {
		if (this.dateCells[i]) {
			removeListener(this.dateCells[i], "click", this.clickDateFunc);
			removeListener(this.dateCells[i], "mouseover", this.dateMouseOver);
			removeListener(this.dateCells[i], "mouseout", this.dateMouseOut);
		}
	}

	this.dateCells = new Array();

	//日にちDOM要素の行を削除
	while(this.calendarTable.rows.length > 3) {
		this.calendarTable.deleteRow(2);
	}
};

/**
 * カレンダー日付選択を表示する。
 * @param {Object} evt イベントオブジェクト
 */
CalendarSelector.prototype.showCalendar = function(evt) {
	var comEvt = CommonEvent.createCommonEvent(evt);
	if (this.calendarDiv.style.visibility != "visible") {
		var elem = comEvt.elem;
		var position = CalendarSelector.getElementPosition(elem);
		this.calendarDiv.style.left = position.left + elem.offsetWidth + "px";
		this.calendarDiv.style.top = position.top + "px";
		this.calendarDiv.style.visibility = "visible";
	} else {
		this.calendarDiv.style.visibility = "hidden";
	}
};

/**
 * 日にちマウスオーバー処理。
 * @param {Object} evt イベントオブジェクト
 */
CalendarSelector.prototype.dateMouseOver = function(evt) {
	var comEvt = CommonEvent.createCommonEvent(evt);
	comEvt.stopPropagation();
	comEvt.elem.className = "hover";
};

/**
 * 日にちマウスアウト処理。
 * @param {Object} evt イベントオブジェクト
 */
CalendarSelector.prototype.dateMouseOut = function(evt) {
	var comEvt = CommonEvent.createCommonEvent(evt);
	comEvt.stopPropagation();
	comEvt.elem.className = "";
};

/**
 * 日付選択処理を実行する。
 * @param {Object} evt イベントオブジェクト
 */
CalendarSelector.prototype.chooseDate = function(evt) {
	var comEvt = CommonEvent.createCommonEvent(evt);
	comEvt.stopPropagation();
	var elem = comEvt.elem;
	if (elem.firstChild) {
		var date = parseInt(elem.firstChild.nodeValue, 10);
		if (date) {
			this.calendarDiv.style.visibility = "hidden";
			this.callback(this.year, this.month + 1, date);
		}
	}
};


// ------ static method ------

/**
 * 月の最初の曜日を取得する。
 * @param {Number} year 年
 * @param {Number} month 月
 * @return {Number} 月の最初の曜日
 */
CalendarSelector.getFirstDay = function(year, month) {
	var firstDate = new Date(year, month, 1);
	return firstDate.getDay();
};

/**
 * 月の日数を取得する。
 * @param {Number} year 年
 * @param {Number} month 月
 * @return {Number} 月の日数
 */
CalendarSelector.getDaysInMonth = function(year, month) {
	var nextMonth = new Date(year, month + 1, 1);
	nextMonth.setHours(nextMonth.getHours() - 3);
	return nextMonth.getDate();
};

/**
 * DOM要素の位置座標を取得する。
 * @param {DOMElement} elem DOM要素
 * @return {Object} DOM要素の位置座標
 */
CalendarSelector.getElementPosition = function(elem) {
	var offsetTrail = elem;
	var offsetLeft = 0;
	var offsetTop = 0;
	while(offsetTrail) {
		offsetLeft += offsetTrail.offsetLeft;
		offsetTop += offsetTrail.offsetTop;
		offsetTrail = offsetTrail.offsetParent;
	}
	return {left:offsetLeft, top:offsetTop};
};

日付選択結果表示関数

/**
 * 日付選択結果を表示する。<br>
 * コールバック関数。
 * @param {Number} year 年
 * @param {Number} month 月
 * @param {Number} date 日
 */
function getDate(year, month, date) {
	alert(year + "年" + month + "月" + date + "日");
};

ページ読み込み時の処理

var calSelector;

$(function(){
	//カレンダー日付選択オブジェクトの生成
	calSelector = new CalendarSelector("calendar", getDate);
	//カレンダー日付選択表示リスナの設定
	$("#showit").click(closure(calSelector.showCalendar, calSelector));
});

当サイトでは、第三者配信による広告サービスを利用しています。 このような広告配信事業者は、ユーザーの興味に応じた商品やサービスの広告を表示するため、当サイトや他サイトへのアクセスに関する情報(氏名、住所、メール アドレス、電話番号は含まれません)を使用することがあります。 このプロセスの詳細やこのような情報が広告配信事業者に使用されないようにする方法については、こちらをクリックしてください。