本插件是在jQuery 1.6.1基礎上進行開發的。javascript
var _count = 0;css
var _expando = "CCalendar-" + (+new Date()) + "-", html
var CCalendar = function(element,options){java
this.initialize.apply(this,arguments); //在new出來的CCalendar對象的執行上下文中執行initialize方法,並傳入參數element,options。json
_count++; //這裏的arguments是[element,options],經過apply方法後,會把數組中的每一項傳參給initialize,而不是傳數組給它。數組
}app
//默認配置
CCalendar.defcfg = {
id: null,
showCount: 1,
direction: "Right",
startDate: new Date(),
maxDate: false,
minDate: false,
lang: 'zh-cn',
onSelect: null,
onSelectBack: null,
onHide: null,
};dom
CCalendar.prototype = {ide
constructor: CCalendar, 函數
initialize: function (element, options) {
options = options || {}; //若是沒有傳入對象,就使用空對象
this.element = $(element); //把元素轉化成jQuery對象
this.format = GLOBLE.parseFormat(options.format || this.element.data('date-format') || 'yyyy-mm-dd');
//GLOBLE.parseFormat解析日期的格式,優先級:開發人員傳入的格式->元素date-format屬性的格式->默認格式'yyyy-mm-dd'
this.isInput = this.element.is("input");
// 合併默認配置
var dftCfg = CCalendar.defcfg;
for (var i in dftCfg) {
if (options[i] === undefined) { //若是是傳入的參數,就不使用默認值
options[i] = dftCfg[i];
}
}
this.direction = options.direction; //默認提示控件在Right
options.id = options.id || _expando + _count; //id爲CCalendar-當前日期的毫秒數-當前的_count
this.picker = $("<div/>", {"id": options.id, "class": "ui-date"}).appendTo('body').bind({
click: $.proxy(this.click, this) //$.proxy返回一個新函數,而且這個新函數始終保持了特定的上下文(這裏就是this,也就是new出來的CCalendar)
}); //對這個div,也就是this.picker綁定click事件
//$.proxy( me.test, me, you, they ),test(you,they,event)。第一個參數爲function(點擊事件觸發的函數),第二個參數爲context(函數執行的上下文),第三個參數,以及後面的參數,都會傳入到function中去。function中總會有一個event參數。event.type -> click。
this.create(options);
//若是是輸入框
if (this.isInput) {
this.element.bind({
focus: $.proxy(this.show, this), //對傳入的input元素綁定focus和keyup事件
});
}
else { //若是元素不是input,就對元素綁定click事件
this.element.bind('click', $.proxy(this.show, this));
}
},
//建立日曆
create: function (options) {
this.setOptions(options);
this.fill(this.date);
},
setOptions: function (options) {
this.options = options;
this.options.startDate && (this.options.startDate = GLOBLE.parseDate(this.options.startDate, this.format));
//是否傳入了日曆中顯示的選中日期,若是沒有就是當前時間的日期,若是有,傳入的顯示日期必須能夠轉化成相應的日期格式。默認爲:'yyyy-mm-dd'
this.lang =GLOBLE.lang[this.options.lang]; //默認爲'zh-cn'
(this.options.maxDate && (this.options.maxDate = GLOBLE.parseDate(this.options.maxDate, this.format)));
(this.options.minDate && (this.options.minDate = GLOBLE.parseDate(this.options.minDate, this.format)));
//日曆控件裏面能夠選擇的日期最大值和最小值,傳入的日期必須可以轉換成相應的日期格式,默認爲:'yyyy-mm-dd'
this.date = { //日曆上選中的日期,若是沒傳入startDate參數,那麼就是當前時間的日期
year: this.options.startDate.getFullYear(),
month: this.options.startDate.getMonth(),
days: this.options.startDate.getDate()
};
},
//構建主體
fill: function (date) { //日曆上默認被選擇的日期
var dp = this,
options = dp.options;
var disabled,
isShowToday = true,
year = (date.year || this.date.year), //其實這裏的date和this.date是同一個
month = (date.month || this.date.month),
_today = new Date(GLOBLE.today.year, GLOBLE.today.month, GLOBLE.today.date);
//好比在中國大陸、、香港、新加坡、馬來西亞、菲律賓等地區的本地時間比UTC快8小時,記做UTC+8,意思就是比UTC時間快8小時。
this.date.year = year,
this.date.month = month;
var main = this.dom ? this.dom.main : $("<div/>", {"class": "main"}).appendTo(this.picker);
//再新建一個div,添加到id爲options.id的div中。咱們這裏把外層的div叫作div1,main的稱做div2。
main.empty(); //移除元素的全部內容,包括全部文本和子節點。remove,連元素都移除
for (var i = 0; i < options.showCount; i++) { //默認狀況下爲1
var dayCount = GLOBLE.getDaysInMonth(year, month),
//獲得當前被選擇日期的當前月的天數。(假設今天是2014,3月5號,那麼year=2014,month=2)
prev = GLOBLE.getPNDate(year, (month + 1), -1), //獲得上一個月的日期,{year:2014,month:1,day:28}
next = GLOBLE.getPNDate(year, (month + 1), 1), //獲得下一個月的日期,{year:2014,month:3,day:30}
startPad = new Date(year, month, 1).getDay(),
//獲得當前被選擇日期的當前月的第一天是星期幾,返回值是 0(週日) 到 6(週六) 之間的一個整數。{2014.3.1號是星期六}
pday,
list = $("<div/>", { "class": "ui-date-list ui-month" + i + (options.showCount > 1 && year == GLOBLE.today.year &&
month == (GLOBLE.today.month + 1) ? 'ui-month-cur' : "") }); //新建一個div,咱們稱爲div3
var html = ['<div class="ui-date-head ui-date-top">'];
(i == 0) && html.push('<a data-action="YEAR_LEFT" class="ui-year-left" href="javascript:void(0)" title="'
+ dp.lang.previousYear + '">«</a><a data-action="MONTH_LEFT" class="ui-month-left" href="javascript:void(0)" title="'
+ dp.lang.previousMonth + '">‹</a>');
//添加上一年,上一個月的連接,只有i==0時才添加,由於一個日曆上只須要一個。
html.push('<a data-action="SELECT_YEAR" class="ui-year" href="javascript:void(0)" title="' +
dp.lang.selectYear + '">' + year + '</a>年<a data-action="SELECT_MONTH" class="ui-month" data-year="' + year + '"
href="javascript:void(0)" data-dom="month" title="' + dp.lang.selectMonth + '" >' +
(((month + 1) < 10 ? '0' : '') + (month + 1)) + '</a>月'); //若是月份少於10,就添加0,變成09,08這種。{03}
(i + 1 == options.showCount) && html.push('<a data-action="MONTH_RIGHT" class="ui-month-right" href="javascript:void(0)" title="' + dp.lang.nextMonth + '">›</a><a data-action="YEAR_RIGHT" class="ui-year-right" href="javascript:void(0)" title="' + dp.lang.nextYear + '">»</a>');
//添加下一月,下一年的連接,也只添加一次。
html.push('</div><div class="ui-date-content clearfix"><div class="ui-date-week">');
for (var j = 0; j < 7; j++){
html.push('<span class="ui-week ' + j + '">' + dp.lang.week[j] + '</span>'); //添加星期一到星期天
}
html.push('</div><div class="ui-date-days">');
var nday = startPad = startPad == 0 ? 6 : startPad - 1; //假設今天星期六,那麼返回6,startPad 和nday等於5.
for (; startPad >= 0; startPad--) { //循環6次
pday = (prev.day - startPad) //第一次循環時,prev.day=28,startPad =5,pday=23
_innerDay(prev, pday, true); //此方法就是把前一個月的23號顯示在日曆插件上,再循環就是24,25,一直到28號。它的顯示樣式是隱式的
}
for (var j = 1; j <= dayCount; j++) { //31天,2014年3月
disabled = false;
var day = new Date(year, month, j).getDay(); //針對天天,獲得是星期幾
html.push('<div class="d-' + day);
if (options.maxDate && options.maxDate <= new Date(year, month, j) || //默認maxDate 爲null
options.minDate && options.minDate >= new Date(year,month, j)) {
disabled = true;
html.push(' ui-days-disabled');
}
if (GLOBLE.today.year == year && GLOBLE.today.month == month && GLOBLE.today.date == j) {
html.push(' ui-days-current'); //若是是當天的日期,好比3月5號,也就是j=5時,就讓它的class變成current。
if (disabled) isShowToday = false;
}
html.push('"><a data-action="DAY" href="javascript:void(0)" data-year="' + year + '" data-month="' + (month + 1 ) +
'" data-day="' + j + '" data-disabled="' + disabled + '" data-old="' + (new Date(year, month, j) < _today ? 1 : 0 ) +
'" >' + j + '</a></div>');
}
for (var j = 1; j < 42 - dayCount - nday; j++) { //42-31-5 = 6,循環5次,由於日曆上總共要顯示42天
_innerDay(next, j); //把下個月的幾天顯示在日曆上,隱式顯示。
}
html.push('</div></div>');
list.html(html.join("")); //把全部的元素添加到list(div3)
main.append(list); //再把div3添加到div2中,div2在div1中,div1在body中。
if (++month == 12) { //month=2,這時month=3
year++;
month = 0;
}
}
if (GLOBLE.ie) { //若是是ie
this.picker.width(200 * options.showCount); //把div1寬度設置爲(200*1=200)
if (GLOBLE.ie == 6) { //若是是ie6
var background = $('<div />', {"class": "ui-date-mark"}).css( //新建一個div稱爲div4,把寬度和高度設置爲div1的寬度和高度
{
width: this.picker.innerWidth(),
height: this.picker.innerHeight()
}
).html('<iframe frameborder="0" src="about:blank" scrolling="no"></iframe>'); //並在div4中裝入一個iframe
this.picker.append(background); //把div4添加到div1中。這時的html結構就是
//<div1><div2>日曆控件的元素</div2><div4><iframe></iframe></div4></div1>,iframe作墊片,IE6的bug
}
}
this.dom = {
//在this上新添加dom屬性,等於{},對象裏面有一個main屬性就是div2,經過對象的屬性來引用(div2中有日曆控件須要的html標籤)。當從新調用fill時,只須要取到div2,而且empty,就能夠從新往div2添加元素了(生成新的日曆控件內容)
main: main
};
function _innerDay(obj, pday, flag) {
html.push('<div class="d-' + pday);
if (options.maxDate && options.maxDate <= new Date(obj.year, obj.month, pday) ||
options.minDate && options.minDate >= new Date(obj.year, obj.month, pday)) {
disabled = true;
html.push(' ui-days-disabled');
}
html.push(' ui-days-other">');
html.push('<a data-action="DAY" href="javascript:void(0)" data-year="' + obj.year + '" data-disabled="' + disabled +
'" data-month="' + (obj.month + 1 ) + '" data-day="' + pday + '">' + pday + '</a>');
html.push('</div>');
}
},
//顯示
show: function (e) {
this.offset(); //設置日曆控件顯示的位置
this.picker.show();
$(window).bind('resize', $.proxy(this.offset, this)); //窗口改變,就會從新計算日曆控件的位置。
if (e) {
e.stopPropagation(); //已經被 jQuery 作過標準化處理
e.preventDefault();
}
var that = this;
$(document).one('mousedown', function (ev) { //當用戶點擊頁面的其餘地方時(不是日曆控件)就會隱藏日曆控件。one方法代表只執行一次回調方法
if ($(ev.target).closest('.ui-date').length == 0) {
//closest方法,從當前元素開始,沿 DOM 樹向上遍歷,直到找到已應用選擇器的一個匹配爲止。 返回包含零個或一個元素的 jQuery 對象。因爲只有this.packer有.ui-date屬性,所以只有點擊了this.packer纔會有元素,等於0就表明點擊的不是頁面上的日曆控件,所以隱藏
that.hide();
}
});
},
offset: function () {
//jQuery中的.height()、.innerHeight()和.outerHeight(),height就是content的高度,innerHeight是高度+padding,outerHeight是高度+padding+border+(若是傳入true,則加上margin,若是傳入false或者不傳,就不加)
this.height = this.element.outerHeight();
this.width = this.element.outerWidth();
var offset = this.element.offset();//返回元素的偏移座標。該方法返回的對象包含兩個整型屬性:top 和 left,以像素計。此方法只對可見元素有效。
var top = offset.top,
left = offset.left,
ww = $(window).width(),
pw = this.picker.width();
var css = { top: top + this.height };
//元素偏移頁面的高度+元素的高度(不包括margin),這時日曆控件就能夠顯示在元素的下面了(恰好在元素的border下)。
switch (this.direction) { //默認爲Right
case "Left":
css.left = left >= pw ? (left - pw ) + this.width : left
break;
case "Right": //日曆控件的left等於元素的left,左對齊顯示。若是超過了窗口的寬度,就讓日曆控件跟元素右對齊
css.left = ww <= (pw + left) ? left - pw + this.width : left
break;
}
this.picker.css(css);
},
//隱藏
hide: function () {
this.picker.hide(); //日曆控件隱藏
typeof this.options.onHide == "function" && this.options.onHide.call(this);
},
//監聽點擊事件,點擊日曆時,觸發
click: function (e) {
e.stopPropagation();
e.preventDefault();
var dp = this,
options = dp.options,
target = $(e.target).closest('a')[0];
//看用戶點擊的是哪一個連接元素,針對不一樣的連接元素進行不一樣處理,所以在建立日曆控件的時候,只要是a連接的都會響應click事件
if (!target) return false; //若是點擊的是日曆控件上的其餘位置,沒有a連接的,將不作任何處理
if (target.getAttribute('data-disabled') == "true") return false;
//若是是禁止的選項,好比,定義了最大日期和最小日期,那其餘範圍的日期,點擊也不作任何處理
var action = target.getAttribute('data-action'); //除了有a連接,還必須保證a連接上有data-action屬性,此插件就是經過data-action來調用不一樣的回調方法。
if (!action) return false;
switch (action) {
case "SELECT_YEAR": //點擊日曆控件上的年份時,會彈出一個近10年的選擇框,用戶能夠針對這個選擇框點擊上一頁(上一個10年)和下一頁|(下一個十年)
{
dp.createYearModel(dp.date.year, target);
}
break;
case "PREV-10-YEAR": //若是點擊年份選擇框中的上十年或者下十年,就從新生成新的十年選擇框
case "NEXT-10-YEAR":
{
dp.createYearModel(parseInt(target.getAttribute("data-year")), null);
}
break;
case "SELECT_MONTH": //點擊月份,就會彈出一個12個選項(從1到12)的月份選擇框
{
dp.createMonthModel(dp.date.month, target); //實現方案跟年份的差很少,沒年份的複雜,由於月份沒有上十月和下十月,只有12個月
}
break;
case "YEAR": //當點擊年份選擇框中的年份時,就觸發
{
dp.fill({
year: parseInt(target.getAttribute("data-year")) //從新生成新的日期時間
});
}
break;
case "MONTH":
{
dp.fill({
month: parseInt(target.getAttribute("data-month") - 1) //從新生成新的日期時間
});
}
break;
case "DAY":
{
this.select(target); //選擇日時,就會選擇這個日期
}
break;
case "YEAR_LEFT": //日曆控件還有上一年的按鈕
{
dp.date.year -= options.showCount; //年份減小一年,showCount默認爲1
dp.fill(dp.date); //從新生成日期
}
break;
case "YEAR_RIGHT": //以此類推
{
dp.date.year += options.showCount;
dp.fill(dp.date);
}
break;
case "MONTH_LEFT": //以此類推
{
dp.date.month -= options.showCount;
if (dp.date.month < 0) {
dp.date.month = dp.date.month + 12;
dp.date.year--;
}
dp.fill(dp.date);
}
break;
case "MONTH_RIGHT": //以此類推
{
dp.date.month += options.showCount;
if (dp.date.month > 11) {
dp.date.month = dp.date.month - 12;
dp.date.year++;
}
dp.fill(dp.date);
}
break;
default :
{
}
}
},
select: function (target) {
var dp = this,
options = dp.options;
var yyyy = target.getAttribute('data-year'), //獲得當前的年份
yy = yyyy.substr(2),
m = parseInt(target.getAttribute('data-month')), //獲得當前的月份
mm = m < 10 ? '0' + m : m, //格式化爲兩位的月份
d = parseInt(target.getAttribute('data-day')), //獲得當前的天
dd = d < 10 ? '0' + d : d, //格式化天爲兩位數
date = new Date(yyyy, m - 1, d),
time = date.getTime(),
back = GLOBLE.formatDate(date, dp.format), //把當前選擇的日期格式化
backData = { //傳給回調方法的json對象,使之可使用各類格式的日期數據
yyyy: yyyy,
yy: yy,
mm: mm,
m: m,
dd: dd,
d: d,
back: back,
date: date,
time: time
};
//選擇日期前
if (this.options.onSelect && !this.options.onSelect.call(dp, backData)) //若是傳入onSelect調用後,傳入選擇的日期,返回false,則不作任何處理
return null; //這裏可讓開發人員傳入對象時,寫上onSelect方法,而後在方法裏面,判斷選擇的日期是否可選
if (!dp.isInput) {
dp.element.find('input').prop('value', back); //若是元素不是input,就去找input,而後顯示出來
dp.element.data('date', back);
}
else {
dp.element.prop('value', back); //把日期顯示在input框中
}
//選擇日期後
if (!this.options.onSelectBack || ( this.options.onSelectBack && this.options.onSelectBack.call(dp, backData) === true)) {
//若是傳入onSelectBack方法,那麼選擇日期,並顯示在input後,若是 onSelectBack方法根據選擇的日期返回true,就隱藏,若是返回false,就不隱藏日曆,讓用戶繼續選擇。
dp.hide(); //隱藏日曆控件
}
},
//建立月份面板
createMonthModel: function (month, target) {
var self = this,
list = $(".ui-month-list", self.dom.main);
if (list.length == 0) {
list = $("<div/>", {"class": "ui-month-list"});
self.dom.main.append(list);
self.dom["month"] = list;
self.dom.main.bind("mousedown", function (ev) {
if ($(ev.target).closest('.ui-month-list').length == 0) {
list.hide();
}
return false;
});
}
if (target) {
var offset = $(target).position();
list.css({
top: offset.top + 26,
left: offset.left - 23 / 2
}).show();
}
var items = [];
for (var i = 1; i <= 12; i++) {
items.push({
value: i,
label: i,
role: 'MONTH'
});
}
var current = {
value: month,
label: month
};
var html = [];
for (var j = 0, l = items.length; j < l; j++) {
var m = items[j]
html.push('<a href="javascript:void(0)" data-action="' + m.role + '" class="' + (m.value < GLOBLE.today.month + 1 ? " ui-date-old" : "") + (m.value == GLOBLE.today.month + 1 ? " ui-date-current" : "") + '" data-month="' + m.value + '">' + m.label + '</a>');
}
list.html(html.join(""));
},
//建立年份面板
createYearModel: function (year, target) { //傳入的是當前的年份,也就是2014,
var self = this,
years = $(".ui-year-list", self.dom.main);
if (years.length == 0) {
years = $("<div/>", {"class": "ui-year-list"}); //若是沒有顯示年份的選擇框,就新建一個div,表示year
self.dom.main.append(years);
self.dom["year"] = years;
self.dom.main.bind("mousedown", function (ev) {
if ($(ev.target).closest('.ui-year-list').length == 0) {
years.hide();
}
return false;
});
}
if (target) { //把這個年份的選擇框,定位到日曆年份的位置
var offset = $(target).position();
years.css({
top: offset.top + 17,
left: offset.left - 37 / 2
}).show();
}
var items = [ //item數組的第一項是上十年的按鈕
{
value: year - 10,
label: '«',
role: 'PREV-10-YEAR'
}
];
for (var i = year - 6; i < year + 4; i++) { //前6年,當前年份,後3年
items.push({
value: i,
label: i,
role: 'YEAR'
});
}
items[7] = {value: year, label: year, role: 'YEAR', current: true};
items.push({ ////item數組的最後一項是下十年的按鈕
value: year + 10,
label: '»',
role: 'NEXT-10-YEAR'
});
var current = { //當前年份,也就是2014
value: year,
label: year
};
var html = [];
for (var j = 0, l = items.length; j < l; j++) { //針對items數組,建立這個年份的選擇框
var y = items[j]
html.push('<a href="javascript:void(0)" data-action="' + y.role + '" class="' + (y.value < GLOBLE.today.year ? " ui-date-old" : "") + (y.value == GLOBLE.today.year ? " ui-date-current" : "") + '" data-year="' + y.value + '">' + y.label + '</a>');
}
years.html(html.join(""));
},
}
var GLOBLE = {
ie: document.all && navigator.userAgent.match(/\s{1}\d{1}/),
//語言包
lang: {
"zh-cn": {
week: ['日', '一', '二', '三', '四', '五', '六'],
previousMonth: '上一月',
nextMonth: '下一月',
previousYear: '上一年',
nextYear: '下一年',
selectYear: '選擇年',
selectMonth: '選擇月',
more: '更多',
today: '今'
},
"en":{
..... //英文,國際化
}
},
//今天
today: {
year: new Date().getFullYear(),
month: new Date().getMonth(),
date: new Date().getDate()
},
//是不是閏年
isLeapYear: function (year) {
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
},
//獲取月份的天數
getDaysInMonth: function (year, month) {
return [31, (GLOBLE.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
},
//獲取上下月
getPNDate: function (year, month, path) {
if ((month += (path || 0)) < 1) {
month = 12;
year--;
}
else if (month > 12) {
month = 1;
year++;
}
var date = new Date(year, month, 0);
return {
year: date.getFullYear(),
month: date.getMonth(),
day: date.getDate()
};
},
//轉換爲格式對象
parseFormat: function (format) {
var separator = format.match(/[.\/\-\s].*?/), parts = format.split(/\W+/);
if (!separator || !parts || parts.length === 0) {
throw new Error("Invalid date format.");
}
return {
separator: separator,
parts: parts
};
},
//按照格式對象,格式化字符日期
parseDate: function (date, format) {
var parts = date.split(format.separator), date = new Date(), val;
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
if (parts.length === format.parts.length) {
var year = date.getFullYear(), day = date.getDate(), month = date.getMonth();
for (var i = 0, cnt = format.parts.length; i < cnt; i++) {
val = parseInt(parts[i], 10) || 1;
switch (format.parts[i]) {
case 'dd':
case 'd':
day = val;
date.setDate(val);
break;
case 'mm':
case 'm':
month = val - 1;
date.setMonth(val - 1);
break;
case 'yy':
year = 2000 + val;
date.setFullYear(2000 + val);
break;
case 'yyyy':
year = val;
date.setFullYear(val);
break;
}
}
date = new Date(year, month, day, 0, 0, 0);
}
return date;
},
//把日期格式化日期字符串(format的格式)
formatDate: function (date, format) {
var val = {
d: date.getDate(),
m: date.getMonth() + 1,
yy: date.getFullYear().toString().substring(2),
yyyy: date.getFullYear()
};
val.dd = (val.d < 10 ? '0' : '') + val.d;
val.mm = (val.m < 10 ? '0' : '') + val.m;
var date = [];
for (var i = 0, cnt = format.parts.length; i < cnt; i++) {
date.push(val[format.parts[i]]);
}
return date.join(format.separator);
}
};
假設如今你有一個input元素須要綁定日曆控件,那麼只須要var date = new CCalendar(input元素,{})。這時將調用create方法建立日曆,可是不顯示,當input元素得到焦點時,就會觸發show方法,顯示出來。你能夠在第二個參數中,傳入json對象,好比:maxDate(可選擇的最大日期),minDate(可選擇的最小日期),lang(語言版本,中文,英文),onSelect(選擇日期前,觸發的方法),onHide(隱藏日期後,觸發的方法),onSelectBack(選擇日期後,觸發的方法)等屬性值。
若是你須要弄成jQuery插件,或者弄到sea.js模塊加載中去,請看:http://www.cnblogs.com/chaojidan/p/4213942.html
加油!