/*!
* DatePicker
*
* Copyright (C) 2012 shin. All Rights Reserved.
*/
;(function($) {
$.gDatePicker = function(element, options) {
// オプションのデフォルト設定
var defaults = {
// 表示有効範囲
// "with_end" 指定がある場合、with_end で指定した日付以降が有効。指定がなく true 場合、本日以降が有効。
valid_range : false,
// 別の gDatePickerと連携
with_end : false,
// 参照日付(別の gDatePickerと連携用)
reference_date : false,
// 返却用日付フォーマット(最も使いそうな Yy mn dj しか対応してない)
date_format : 'Y年n月j日',
// 表示位置オフセット
offset : [ 3, 6 ],
// 表示開始日付
start_date : false,
// 初期日付を処理するコールバック関数
onOpen : null,
// 日付が無効な場合に処理するコールバック関数
onDisabled : null,
// 日付選択後に実行するコールバック関数
onSelect : null
};
// プロパティー
var current_view, view_mode, datepicker, picker_title, clickable_element, picker_day, picker_month, picker_year;
var system_month, system_year, system_day, initial_month, initial_year, initial_day;
var selected_month, selected_year, default_day, default_month, default_year;
var plugin = this;
plugin.settings = {};
// 1週間
var title_week = [ '日', '月', '火', '水', '木', '金', '土' ];
// 月
var title_months = [ '1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月' ];
// DOM element
var $element = $(element);
/*
* 初期化
*/
var init = function(update) {
// ユーザー設定のマージ
if (!update) {
plugin.settings = $.extend({}, defaults, options);
}
// 表示モード
view_mode = [ 'years', 'months', 'days' ];
// enabling/disabling dates 配列の初期化
disabled_dates = [];
enabled_dates = [];
// 現在の日付
var date = new Date();
// オープン時の基準日付
var reference_date;
if (plugin.settings.reference_date) {
reference_date = plugin.settings.reference_date;
plugin.settings.valid_range = true;
} else {
if ($element.data('reference_date_cache') && $element.data('reference_date_cache') != undefined) {
reference_date = $element.data('reference_date_cache');
} else {
reference_date = date;
}
}
// 日付の分割
initial_year = reference_date.getFullYear();
initial_month = reference_date.getMonth();
initial_day = reference_date.getDate();
system_year = date.getFullYear();
system_month = date.getMonth();
system_day = date.getDate();
// デフォルト日付の取得
var default_date = check_date($element.val() || (plugin.settings.start_date ? plugin.settings.start_date : ''));
// デフォルト設定されている日付が無効な場合
if (default_date && is_disabled(default_date.getFullYear(), default_date.getMonth(), default_date.getDate())) {
// 値のクリア
$element.val('');
// 日付が無効な場合に処理するコールバック関数
if (plugin.settings.onDisabled && typeof plugin.settings.onDisabled == 'function') {
plugin.settings.onDisabled($element);
}
}
// 日付をキャッシュにセット
update_dependent(default_date);
// 初期設定の場合
if (!update) {
clickable_element = $element;
// 要素のクリックイベント
clickable_element.bind('click', function(e) {
e.preventDefault();
// 要素が disabled でない場合
if (!$element.attr('disabled')) {
// DatePickerが visible の場合は閉じる
if (datepicker.css('display') != 'none')
plugin.hide();
// DatePickerが visible でない場合は表示
else {
plugin.show();
}
}
});
}
// 再設定の場合以下は処理しない
if (update) {
return;
}
// フレームの作成
var html = '
'
+ '
'
+ '
'
+ '
'
+ '
'
+ '
';
// HTMLを DOM化
datepicker = $(html);
// DatePickerのグローバル参照を可能にする
plugin.datepicker = datepicker;
// DatePickerの各部品の作成
picker_title = $('table.udp_picker_title', datepicker);
picker_year = $('table.udp_picker_year', datepicker);
picker_month = $('table.udp_picker_month', datepicker);
picker_day = $('table.udp_picker_day', datepicker);
// DatePicker の DOM 追加
$('body').append(datepicker);
// mouseover/mouseout イベント
datepicker.delegate('td:not(.udp_disabled, .udp_sunday_disabled, .udp_saturday_disabled, .udp_outer_month, .udp_blocked)', 'mouseover', function() {
$(this).addClass('udp_hover');
}).delegate('td:not(.udp_disabled, .udp_sunday_disabled, .udp_saturday_disabled, .udp_outer_month, .udp_blocked)', 'mouseout', function() {
$(this).removeClass('udp_hover');
});
// 前ボタンクリック
$('.udp_previous', picker_title).bind('click', function() {
// 無効じゃなかったら
if (!$(this).hasClass('udp_blocked')) {
// 年 pickerだったら
if (current_view == 'years') {
selected_year -= 12;
}
// 月 pickerだったら
else if (current_view == 'months') {
selected_year--;
}
// 日 pickerだったら
else if (--selected_month < 0) {
selected_month = 11;
selected_year--;
}
// 表示パネル操作
manage_view_mode();
}
});
// ヘッダーにクリックイベントをバインド
$('.udp_caption', picker_title).bind('click', function() {
// 年 pickerだったら
if (current_view == 'years') {
current_view = ($.inArray('days', view_mode) > -1 ? 'days' : ($.inArray('months', view_mode) > -1 ? 'months' : 'years'));
}
// 月 pickerだったら
else if (current_view == 'months') {
current_view = ($.inArray('years', view_mode) > -1 ? 'years' : ($.inArray('days', view_mode) > -1 ? 'days' : 'months'));
}
// 日 pickerだったら
else {
current_view = ($.inArray('months', view_mode) > -1 ? 'months' : ($.inArray('years', view_mode) > -1 ? 'years' : 'days'));
}
// 表示パネル操作
manage_view_mode();
});
// 次ボタンクリック
$('.udp_next', picker_title).bind('click', function() {
// 無効じゃなかったら
if (!$(this).hasClass('udp_blocked')) {
// 年 pickerだったら
if (current_view == 'years') {
selected_year += 12;
}
// 月 pickerだったら
else if (current_view == 'months') {
selected_year++;
}
// 日 pickerだったら
else if (++selected_month == 12) {
selected_month = 0;
selected_year++;
}
// 表示パネル操作
manage_view_mode();
}
});
// 日 pickerのクリックイベント
picker_day.delegate('td:not(.udp_disabled, .udp_sunday_disabled, .udp_saturday_disabled, .udp_outer_month)','click',function() {
select_date(selected_year, selected_month, to_number($(this).html()), $(this));
});
// 月 pickerのクリックイベント
picker_month.delegate('td:not(.udp_disabled)', 'click', function() {
// get the month we've clicked on
var matches = $(this).attr('class').match(/udp\_month\_([0-9]+)/);
// 選択月を設定
selected_month = to_number(matches[1]);
// 年と月のみ選択可
if ($.inArray('days', view_mode) == -1) {
// 選択された日付をセットして閉じる
select_date(selected_year, selected_month, 1, $(this));
} else {
// 日パネルを表示
current_view = 'days';
manage_view_mode();
}
});
// 年 pickerのクリックイベント
picker_year.delegate('td:not(.udp_disabled)', 'click', function() {
// 選択年を設定
selected_year = to_number($(this).html());
// 年のみ選択可
if ($.inArray('months', view_mode) == -1) {
// 選択された日付をセットして閉じる
select_date(selected_year, 1, 1, $(this));
} else {
// 月パネルを表示
current_view = 'months';
manage_view_mode();
}
});
// イベントのバインド
$(document).bind({'mousedown' : plugin._mousedown});
// 表示パネル操作
manage_view_mode();
}
/*
* gDatePicker非表示
*/
plugin.hide = function() {
datepicker.css('display', 'none');
}
/*
* gDatePicker表示
*/
plugin.show = function() {
// 初期表示は日
current_view = "days";
// 初期日付の取得
var default_date;
// 初期日付を処理するコールバック関数
if (plugin.settings.onOpen && typeof plugin.settings.onOpen == 'function') {
// コールバック関数の実行
default_date = check_date(plugin.settings.onOpen($element));
} else {
default_date = check_date($element.val() || (plugin.settings.start_date ? plugin.settings.start_date : ''));
}
// 設定されている日付が取得できた場合
if (default_date) {
// 年月日をセット
default_year = default_date.getFullYear();
default_month = default_date.getMonth();
default_day = default_date.getDate();
selected_month = default_date.getMonth();
selected_year = default_date.getFullYear();
// 無効日の場合
if (is_disabled(default_year, default_month, default_day)) {
// 値をクリア
$element.val('');
// 日付が無効な場合に処理するコールバック関数
if (plugin.settings.onDisabled && typeof plugin.settings.onDisabled == 'function') {
plugin.settings.onDisabled($element);
}
// デフォルトの年月をセット
selected_month = initial_month;
selected_year = initial_year;
}
}
// 日付が設定されていないか不正な日付形式だった場合、デフォルトの年月をセット
else {
selected_month = initial_month;
selected_year = initial_year;
}
// 表示パネル操作
manage_view_mode();
// Datepickerのサイズを取得
var datepicker_width = datepicker.outerWidth(), datepicker_height = datepicker.outerHeight();
// アイコン位置を取得
var left = $element.offset().left + $element.outerWidth(true) + plugin.settings.offset[0];
var top = $element.offset().top - datepicker_height + plugin.settings.offset[1];
// ブラウザの表示領域を取得
var window_width = $(window).width();
var window_height = $(window).height();
// ブラウザの表示エリア位置を取得
var window_scroll_top = $(window).scrollTop();
var window_scroll_left = $(window).scrollLeft();
// 表示位置調整
if (left + datepicker_width > window_scroll_left + window_width) {
left = window_scroll_left + window_width - datepicker_width;
}
if (left < window_scroll_left) {
left = window_scroll_left;
}
if (top + datepicker_height > window_scroll_top + window_height) {
top = window_scroll_top + window_height - datepicker_height;
}
if (top < window_scroll_top) {
top = window_scroll_top;
}
// 表示位置
datepicker.css({
'left' : left,
'top' : top
});
// 開く
datepicker.fadeIn('linear');
}
/**
* オプションアップデート
*/
plugin.update = function(values) {
// オプションをアップデート
plugin.settings = $.extend(plugin.settings, values);
// 初期化し直し
init(true);
}
/**
* エスケープ
*/
var format_escape = function(str) {
// 特殊文字をエスケープする
return str.replace(/([-+*.,?^${}()|[\]\/\\])/g, '\\$1');
}
/*
* 日付チェック
*/
var check_date = function(str_date) {
// 文字列化
str_date += '';
// 値が入っていたら
if ($.trim(str_date) != '') {
// フォーマッターのエスケープ
var format = format_escape(plugin.settings.date_format);
// フォーマット文字列から許可された文字の格納
var matches = new Array;
// フォーマットに対応する日付取得正規表現の格納
var regexp = new Array;
// 許可された文字の取得
var format_chars = [ 'd', 'j', 'm', 'n', 'Y', 'y' ];
for ( var i = 0; i < format_chars.length; i++) {
if ((position = format.indexOf(format_chars[i])) > -1) {
matches.push({character : format_chars[i], position : position});
}
}
// 文字位置のソート
matches.sort(function(a, b) {
return a.position - b.position;
});
// フォーマットに対応する日付取得正規表現の取得
$.each(matches, function(index, match) {
switch (match.character) {
case 'd':
regexp.push('0[1-9]|[12][0-9]|3[01]');
break;
case 'j':
regexp.push('[1-9]|[12][0-9]|3[01]');
break;
break;
case 'm':
regexp.push('0[1-9]|1[012]+');
break;
case 'n':
regexp.push('[1-9]|1[012]');
break;
case 'Y':
regexp.push('[0-9]{4}');
break;
case 'y':
regexp.push('[0-9]{2}');
break;
}
});
// 正規表現がセットされたら
if (regexp.length) {
// 日から処理していくため反転
matches.reverse();
// 正規表現を組み立て
$.each(matches, function(index, match) {
// それぞれの文字に対応するように括弧入れる
format = format.replace(match.character, '(' + regexp[regexp.length - index - 1] + ')');
});
// 正規表現の最終処理(完全一致)
regexp = new RegExp('^' + format + '$', 'ig');
// 正規表現にマッチしたら
if ((segments = regexp.exec(str_date))) {
var tmpdate = new Date();
var original_day = tmpdate.getDate(), original_month = tmpdate.getMonth() + 1, original_year = tmpdate.getFullYear();
// 反転したの元に戻す
matches.reverse();
// フォーマットに対応した値を取り出す
$.each(matches, function(index, match) {
// 対応文字
switch (match.character) {
case 'm':
case 'n':
original_month = to_number(segments[index + 1]);
break;
case 'd':
case 'j':
original_day = to_number(segments[index + 1]);
break;
case 'Y':
original_year = to_number(segments[index + 1]);
break;
case 'y':
original_year = '19' + to_number(segments[index + 1]);
break;
}
});
// Date型に変換
var date = new Date(original_year, (original_month || 1) - 1, original_day || 1);
// きちんとセットされていれば
if (date.getFullYear() == original_year && date.getDate() == (original_day || 1) && date.getMonth() == ((original_month || 1) - 1)) {
// Date型を返す
return date;
}
}
}
}
return false;
}
/**
* 日選択
*/
var generate_picker_day = function() {
// 選択された月
var pick_month = new Date(selected_year, selected_month + 1, 0).getDate();
// 先月分
var previous_month = new Date(selected_year, selected_month, 0).getDate();
var previous_first_day = new Date(selected_year, selected_month, 1).getDay();
// 先月分が何日あるか
previous_first_day = previous_first_day < 0 ? 7 + previous_first_day : previous_first_day;
// ヘッダー
manage_title(selected_year + '年 ' + title_months[selected_month]);
// 開き
var html = '';
// 曜日
for ( var i = 0; i < 7; i++) {
html += '' + title_week[i % 7] + ' | ';
}
html += '
';
// 42日分(7週間)
for ( var i = 0; i < 42; i++) {
// 7日分(1週間)
if (i > 0 && i % 7 == 0) {
html += '
';
}
// 何日目?先月の場合マイナス
var day = (i - previous_first_day + 1);
// 先月の場合
if (i < previous_first_day) {
html += '' + (previous_month - previous_first_day + i + 1) + ' | ';
}
// 来月の場合
else if (day > pick_month) {
html += '' + (day - pick_month) + ' | ';
}
// 今月
else {
// 曜日取得
var weekday = i % 7;
// 週末(日曜、土曜)
var weekend = [ 0, 6 ];
var class_name = '';
// 無効日の場合
if (is_disabled(selected_year, selected_month, day)) {
// 土曜、日曜
if ($.inArray(weekday, weekend) > -1) {
if (weekday == 0) {
class_name = 'udp_sunday_disabled';
} else {
class_name = 'udp_saturday_disabled';
}
}
// 平日
else {
class_name += ' udp_disabled';
}
// 現在の日
if (selected_month == system_month && selected_year == system_year && system_day == day) {
class_name += ' udp_disabled_current';
}
}
// 有効日の場合
else {
// 土曜、日曜
if ($.inArray(weekday, weekend) > -1) {
if (weekday == 0) {
class_name = 'udp_sunday';
} else {
class_name = 'udp_saturday';
}
}
// 選択されている日
if (selected_month == default_month && selected_year == default_year && default_day == day) {
class_name += ' udp_selected';
}
// 現在の日
if (selected_month == system_month && selected_year == system_year && system_day == day) {
class_name += ' udp_current';
}
}
// 1日分
html += '' + (day) + ' | ';
}
}
// 閉じ
html += '
';
// 日テーブル に追加
picker_day.html($(html));
// 日 pickerの表示
picker_day.css('display', '');
}
/**
* 月選択
*/
var generate_picker_month = function() {
// ヘッダーの変更
manage_title(selected_year + '年');
// 開き
var html = '';
// 12か月分
for ( var i = 0; i < 12; i++) {
// 3か月分
if (i > 0 && i % 3 == 0) {
html += '
';
}
var class_name = 'udp_month_' + i;
// 無効なら
if (is_disabled(selected_year, i)) {
class_name += ' udp_disabled';
}
// 選択されている月
else if (default_month !== false && default_month == i) {
class_name += ' udp_selected';
}
// 現在の月
else if (system_month == i && system_year == selected_year) {
class_name += ' udp_current';
}
// 3か月分の td 追加
html += '' + title_months[i] + ' | ';
}
// 閉じ
html += '
';
// 月テーブル に追加
picker_month.html($(html));
// 月 pickerの表示
picker_month.css('display', '');
}
/**
* 年選択
*/
var generate_picker_year = function() {
// ヘッダーの変更(現在の年から過去7つ、未来4つ)
manage_title(selected_year - 7 + ' - ' + (selected_year + 4));
// 開き
var html = '';
// 12年分
for ( var i = 0; i < 12; i++) {
// 3年分
if (i > 0 && i % 3 == 0) {
html += '
';
}
var class_name = '';
// 無効なら
if (is_disabled(selected_year - 7 + i)) {
class_name += ' udp_disabled';
}
// 選択されている年
else if (default_year && default_year == selected_year - 7 + i) {
class_name += ' udp_selected'
}
// 現在の年
else if (system_year == (selected_year - 7 + i)) {
class_name += ' udp_current';
}
// 3年分の td 追加
html += '' + (selected_year - 7 + i) + '年 | ';
}
// 閉じ
html += '
';
// 年テーブル に追加
picker_year.html($(html));
// 年 pickerの表示
picker_year.css('display', '');
}
/**
* 非選択エリアチェック
*/
var is_disabled = function(year, month, day) {
// パラメータチェック
if ((year == undefined || isNaN(year)) && (month == undefined || isNaN(month)) && (day == undefined || isNaN(day))) {
return false;
}
// 有効範囲指定がある場合
if (plugin.settings.valid_range) {
// 全部くっつけて数値化
var now = to_number(str_concat(year, (typeof month != 'undefined' ? md_pad(month) : ''), (typeof day != 'undefined' ? md_pad(day) : '')));
// 文字列とした場合のサイズ
var len = (now + '').length;
// 日チェック
if (len == 8 && (now < to_number(str_concat(initial_year, md_pad(initial_month),md_pad(initial_day))))) {
return true;
}
// 月チェック
else if (len == 6 && (now < to_number(str_concat(initial_year, md_pad(initial_month))))) {
return true;
}
// 年チェック
else if (len == 4 && (now < initial_year)) {
return true;
}
}
// 月は0から始まるので1足す
if (typeof month != 'undefined') {
month = month + 1;
}
// それ以外は有効
return false;
}
/**
* ヘッダーの操作
*/
var manage_title = function(caption) {
// ヘッダーの書き換え
$('.udp_caption', picker_title).html(caption);
// 有効範囲指定がある場合
if (plugin.settings.valid_range || (view_mode.length == 1 && view_mode[0] == 'months')) {
// 現在の年月を取得
var year = selected_year, month = selected_month, previous;
// 日表示の時
if (current_view == 'days') {
// 前をクリック
previous = (month - 1 < 0 ? str_concat(year - 1, '11') : str_concat(year, md_pad(month - 1)));
}
// 月表示の時
else if (current_view == 'months') {
// 前をクリック
previous = year - 1;
}
// 年表示の時
else if (current_view == 'years') {
// 前をクリック
previous = year - 7;
}
// 前に戻れない時の処理
if ((view_mode.length == 1 && view_mode[0] == 'months') || is_disabled(previous)) {
// 前ボタン無効
$('.udp_previous', picker_title).addClass('udp_blocked');
$('.udp_previous', picker_title).removeClass('udp_hover');
}
// 前ボタン有効
else {
$('.udp_previous', picker_title).removeClass('udp_blocked');
}
}
}
/**
* ビューの操作
*/
var manage_view_mode = function() {
// 日の picker 表示
if (picker_day.text() == '' || current_view == 'days') {
// 日の picker が影も形も作られていない
if (picker_day.text() == '') {
// いろいろやるので一時的に画面の外へ移動
datepicker.css('left', -1000);
// サイズ取得のため一時的に日の picker を表示
datepicker.css({
'display' : 'block'
});
// 日の picker を作成
generate_picker_day();
// 日の pickerサイズ取得
var width = picker_day.outerWidth();
var height = picker_day.outerHeight();
// ヘッダーのサイズ幅を日の pickerと合わせる
picker_title.css('width', width);
// 年の picker のサイズを日の pickerと合わせる
picker_year.css({
'width' : width,
'height' : height
});
// 月の picker のサイズを日の pickerと合わせる
picker_month.css({
'width' : width,
'height' : height
});
// サイズ取得のため一時的に表示した日の picker を非表示
datepicker.css({
'display' : 'none'
});
}
// 日の picker を作成
else {
generate_picker_day();
}
// 年、月の picker を非表示
picker_year.css('display', 'none');
picker_month.css('display', 'none');
}
// 月 picker 表示
else if (current_view == 'months') {
// 月の picker を作成
generate_picker_month();
// 年、日の picker を非表示
picker_year.css('display', 'none');
picker_day.css('display', 'none');
}
// 年 picker 表示
else if (current_view == 'years') {
// 年の picker を作成
generate_picker_year();
// 月、日の picker を非表示
picker_month.css('display', 'none');
picker_day.css('display', 'none');
}
}
/**
* 日付フォーマッター
*/
var formater = function(date) {
var result = '',
// 日 1 - 31
j = date.getDate(),
// 月 1 - 12
n = date.getMonth() + 1,
// 年
y = date.getFullYear() + '';
// フォーマット文字の入れ替え
for ( var i = 0; i < plugin.settings.date_format.length; i++) {
// 現在の文字取得
var chr = plugin.settings.date_format.charAt(i);
// 各フォーマット文字の入れ替え
switch (chr) {
// 2桁年
case 'y':
y = y.substr(2);
// 4桁年
case 'Y':
result += y;
break;
// 0パディングされた月
case 'm':
n = md_pad(n);
// 0パディング無の月
case 'n':
result += n;
break;
// 0パディングされた日
case 'd':
j = md_pad(j);
// 0パディング無の日
case 'j':
result += j;
break;
// たぶんセパレータ
default:
result += chr;
}
}
// フォーマット済日付
return result;
}
/**
* 日付が選択された際のアクション
*/
var select_date = function(year, month, day, cell) {
// Date型日付の作成
var default_date = new Date(year, month, day, 12, 0, 0);
// フォーマット済の値を作成
var selected_value = formater(default_date);
// フォーマット済の値を設定
$element.html(selected_value);
// gDatePicker非表示
plugin.hide();
// 選択された日付(Date型)をキャッシュにセット
update_dependent(default_date);
// 日付選択後に実行するコールバック関数
if (plugin.settings.onSelect && typeof plugin.settings.onSelect == 'function') {
// コールバック関数の実行
plugin.settings.onSelect(selected_value, year, md_pad(month + 1), md_pad(day), $element);
}
$element.focus();
}
/**
* すべての引数を連結
*/
var str_concat = function() {
var str = '';
// 文字列連結
for ( var i = 0; i < arguments.length; i++) {
str += (arguments[i] + '');
}
return str;
}
/**
* 月日ゼロパディング
*/
var md_pad = function(str) {
// 文字列化
str += '';
// 長さに満たない部分をゼロ埋め
while (str.length < 2) {
str = '0' + str;
}
return str;
}
/**
* 文字列から数値へ変換
*/
var to_number = function(str) {
return parseInt(str, 10);
}
/**
* 対となる gDatePickerの更新
*/
var update_dependent = function(date) {
// 対となる gDatePickerが存在したら
if (plugin.settings.with_end) {
// 対となる要素
$.each(plugin.settings.with_end, function() {
var $with_end = $(this);
// バインドされていない
if (!($with_end.data && $with_end.data('gDatePicker'))) {
// 開始日をセット
$with_end.data('reference_date_cache', date);
}
// バインド済
else {
// データを参照
var udp_data = $with_end.data('gDatePicker');
// 開始日の更新
udp_data.update({
'reference_date' : date,
'valid_range' : true
});
}
});
}
}
/**
* onMouseDown イベント
*/
plugin._mousedown = function(e) {
// DatePicker表示中
if (datepicker.css('display') == 'block') {
// 外クリックで非表示
if ($(e.target).parents().filter('.gDatePicker').length == 0) {
plugin.hide();
}
}
return true;
}
// 初期化
init();
}
$.fn.gDatePicker = function(options) {
return this.each(function() {
// 要素がすでにバインドされていたら
if ($(this).data('gDatePicker') != undefined) {
// 前回のデータを所得
var plugin = $(this).data('gDatePicker');
// 削除
plugin.datepicker.remove();
// イベント削除
$(document).unbind('mousedown', plugin._mousedown);
}
// 新しいインスタンスを作成
var plugin = new $.gDatePicker(this, options);
// 保存
$(this).data('gDatePicker', plugin);
});
}
})(jQuery);