JS編寫日歷控件(支持單日歷 雙日歷 甚至多日歷等)


    前言: 最近幾天都在研究日歷控件編寫,當然前提我要說明下,當然看過別人寫的源碼 所以腦子一熱 就想用自己的編碼方式 來寫一套可擴展性 可維護性 性能高點的代碼控件出來,就算練習練習下,所以前幾天晚上下班后一直在研究 及 今天早上6點跑起來敲代碼(昨天晚上加班回來到凌晨1點睡覺),就是不相信寫不出來,所以腦子一熱 就睡不覺 一直到現在 總算有點眉目,所以在此給大家分享下編寫方式,代碼不是最重要的 關鍵是要實現一個什么樣的業務邏輯,想寫一個什么樣的出來,還有就是一剛開始 "設計" 非常重要, 個人覺得:一個良好的設計 是提高產品或者說代碼可維護性 可擴展性 性能高的一個重要因素。設計不僅僅是"視覺設計師"的事情,設計對於我們前端開發或者后台開發也是一個非常重要的事情。一直以來會有很多后台開發人員 認為我們前端屬於uI界面的工程師 或者說 我們只會HTML+CSS等 一說到程序員貌似就是指他們后台開發人員,其實我們前端也屬於程序員啊,算了 不扯遠了!

如何來設計?

  一說到 ”設計“ 這個詞 我就想說說我最近做的一個項目,當然這個項目我只做了30%-40% 之前我在另外一家公司 后來就來這家公司做項目,我來后 這個項目已經完成了60%-70% 所以也是抱着一個完成任務的心情來完成任務,首先來說說之前沒有很好的設計一些缺陷:

 1. css不分離所有的css寫在一個文件夾里面 並且每個頁面命名方式都是一樣 比如a頁面 和 b頁面 有一部分是一樣的 所以結構也一樣 但是某一天需求方說 b頁面 那塊我想改下 加一個按鈕或者說 要美化下 a頁面不動  好了 我們就接着在那個css文件里面改 改完后 發現b頁面效果是達到了 但是a頁面樣式變了 不是我們想要的 所以我們接着又改 干脆在b頁面上直接寫內聯樣式(為了完成需求),但是這樣有個缺點 以后不好維護。所以建議 各個頁面的css分離。

2. 公共部分如何來重用? 比如說a頁面 b頁面 c頁面 甚至所有的頁面有一塊很相似的地方 或者說功能也很相似 那么按道理 來說我們可以把它們提取出來作為公用的部分,后台開發人員也可以寫一個VM 在每個頁面需要的地方嵌套進去,對於以后我們維護也很方便 要改只改一個地方 全站生效!按道理來說 這也是一個比較好的設計,但是需求方又提要求了,他說我感覺a頁面不好看 b頁面也不好看 我想在a b頁面公用的那部分增加有一些條件 或者 減少一部分 那么對於后台開發人員VM就不能公用了 我們css文件也不好改或者不能公用了 或者以后又說c頁面我感覺也不怎么好看 我又想改 改來改去 現在開發人員可能納悶了 算了你不是要經常改嗎?我一開始時候 我每個頁面寫一個VM 不管他是公用不公用,這樣的設計當然很不好,如果a頁面 和 b頁面 或者說全站頁面 某部分也有相似的地方 那么以后要維護 那就要維護全站的頁面 並且很容易出錯!對於需求方老是改來改去的需求 我在想一個這么一個問題,我怎么樣能快速開發?怎么樣能設計一個項目?或者說怎么樣去架構一個項目?改來改去 最后不管對於前端也好 后台也好 性能肯定不好 那么我們也只有懷着這樣一個心情 只要能完成項目就行 只要需求方喜歡的就ok 性能嘛 反正慢點就慢點吧 需求方也不懂的!就好比我們要做2層房子一樣 一剛開始設計師都設計好了 什么地方要該怎么做,比如一剛開始 客廳設計一個50平方米 當房子架子搭好了后 突然需求方說客廳要改成100平方米?你說可能嗎 難道都把他們拆了 重新做? 我只想說:所有的頁面設計師都設計好后 需求方應該要花充足的時間去分析 去看 那些不合理盡量早改 一旦設計搞定型了 那就不允許更改!你這么一改 改來改去 沒有毛病的也會改來毛病的!

3.怎么樣有個良好的設計?怎么樣能架構一個良好的項目?我最近一直在思考這樣的問題,如果一個良好的設計 不能適應需求方老是更改需求 屬於一個良好的設計嗎?

算了 上面的問題先討論到這!如果大家有什么想法也可以一起分享出來,下面來看看我寫的日歷控件吧!

有以下優點:

1. 支持IE6+ 火狐 谷歌游覽器等等。

2. 支持單日歷 雙日歷 甚至多日歷面板,暫顯示輸入框的日期只做了單日歷和雙日歷的處理操作。考慮目前基本上用到最多的就是這2種。

3. 支持當前日期 之前的日期不可選擇 不可操作。

4. 給輸入框傳了當前的值保存在value中 方便開發人員獲取。

缺點:

 1. 不支持多國語言 只支持中國的。

下面來看看單日歷的效果圖:

 

雙日歷效果圖如下:

 

一: 看看可配置的參數及提供回調函數:

 

this.config = {
		elemCls             :  '.input',            // 目標元素
		beginYear           :  1990,                //開始日期
		endYear             :  2020,                //結束日期 
		panelCls            :  '.calendarPanel',    // 日歷面板類
		bg_cur_day          :  'bg_cur_day',		// 當前的顏色
		bg_out              :  'bg_out',            // 鼠標hover顏色
		bg_over             :  'bg_over',           // 鼠標out顏色
		date2StringPattern  :  'yyyy-MM-dd',        // 默認顯示格式 yyyy-MM-dd
		patternDelimiter    :  '-',                 // 分隔符 注意:分隔符要和上面顯示格式對應
		panelCount          :  2,                   // 面板的個數 是單日歷 雙日歷 或者 多日歷等
		manyDisabled        :  false,               // 默認情況下為false 如果為true 指當前日期之前的日期不可點擊
		ishasSelect         :  true,                // 是否有下拉框選擇年份和月份 默認為true 暫不做操作 
													// 為以后留接口 因為如果沒有的話 年月份沒有顯示出來 感覺怪怪的

		render              :  null,                // 渲染日歷后觸發
		clickDayCallBack    :  null,                // 點擊某一天后 回調函數
		clickPrevCallBack   :  null,                // 點擊上一月的回調函數
		clickNextCallBack   :  null,                // 點擊下一頁的回調函數
		changeYearCallBack  :  null,                // 下拉框改變年份的回調函數
		changeMonthCallBack :  null                 //  下拉框改變月份的回調函數
	};

 

 1.可以配置開始日期和結束日期:也就是下拉框渲染時候 渲染從開始日期和結束日期渲染:如下代碼判斷:

// 渲染下拉框所有的年份
    _renderYear: function() {
        var self = this,
            _config = self.config,
            _cache = self.cache;
        var html = '';
        for(var i = _config.beginYear; i <= _config.endYear; i+=1) {
            html += '<option value="'+i+'">'+(i + Calendar.model['year'])+'</option>';
        }
        $(".yearSelect").each(function(index,item){
            $(item).html(html);
        });
    },
// 渲染下拉框所有月份
    _renderMonth: function(){
        var self = this,
            _config = self.config,
            _cache = self.cache;
        var html = '';
        for(var i = 0; i < 12; i++) {
            html+= '<option value="'+i+'">'+Calendar.model['months'][i]+'</option>'
        }
        $('.monthSelect').each(function(index,item){
            $(item).html(html);
        });
    },

 2. 輸入框默認顯示格式為XXXX-XX-XX 也可以顯示格式為XXXX/XX/XX 或者其他的都行 但是date2StringPattern配置項也和patternDelimiter配置項對應 也就是說 如果這個date2StringPattern格式為XXXX-XX-XX 那么patternDelimiter分隔符為 - 如果date2StringPattern格式為XXXX/XX/XX  那么patternDelimiter分隔符為 / 對應起來。

 3. panelCount 指面板的個數 如果為2的話 那么我在渲染的時候 會生成2個面板 如果是1個的話 那么就生成一個面板

 4. manyDisabled 參數默認情況下為false 當他為true時候 說明今天是幾號 那么今天之前的日期都為不可點擊 且上一月的按鈕也為不可點擊的。

 5. ishasSelect 默認情況下為true 是指是否需要下拉框 建議一般情況下不用改 默認為true 因為如果不顯示的話 那么我不知道是那年那月 因為設計面板的時候也沒有設計好 如果設計面板時候設計好了的話 那么可以用此參數。

 

提供以下回調函數:

  1. render 渲染日歷后觸發。

  2. clickDayCallBack 點擊面板上某一天后的回調函數

  3. clickPrevCallBack 點擊上一月按鈕的回調函數。

  4. clickNextCallBack 點擊下一月按鈕的回調函數。

  5. changeYearCallBack 下拉框改變年份的回調函數。

  6. changeMonthCallBack 下拉框改變月份的回調函數。

公有的方法:

  show: 顯示日歷

  hide : 隱藏日歷

 Calendar.model = {
    "year"               :      "\u5e74",
    "months"             :      ["\u4e00\u6708","\u4e8c\u6708","\u4e09\u6708","\u56db\u6708","\u4e94\u6708","\u516d\u6708","\u4e03\u6708","\u516b\u6708","\u4e5d\u6708",
                                 "\u5341\u6708","\u5341\u4e00\u6708","\u5341\u4e8c\u6708"],
    "weeks"              :      ["\u65e5","\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d"],
    "string2DatePattern" :      "ymd"
 };

上面年 月 星期 都編碼過了。

思路如下:

1.點擊輸入框時候 判斷日歷是否已經渲染過的 如果已經渲染的 只是隱藏了 那么我直接顯示就可以 否則的話 執行函數 把日歷面板全部渲染出來 如下代碼:

init: function(options){
        this.config = $.extend(this.config,options || {});
        var self = this,
            _config = self.config,
            _cache = self.cache;
        $(_config.elemCls).unbind('click');
        $(_config.elemCls).bind('click',function(){
            // 判斷下 如果日歷面板已經渲染出來后 就不再渲染
            if($(_config.panelCls + ' .calendarDiv').length > 0) {
                self.show();
            }else {
                self.show();
                self._draw();
                self._renderYear();
                self._renderMonth();
                self._changeSelect();
                self._renderData();
            }
            
        });
    },

_draw 函數 就是負責把所有的星期標題  渲染出來 如下圖:

_renderYear函數 是把下拉框的年份計算出來。如下代碼:

// 渲染下拉框所有的年份
	_renderYear: function() {
		var self = this,
			_config = self.config,
			_cache = self.cache;
		var html = '';
		for(var i = _config.beginYear; i <= _config.endYear; i+=1) {
			html += '<option value="'+i+'">'+(i + Calendar.model['year'])+'</option>';
		}
		$(".yearSelect").each(function(index,item){
			$(item).html(html);
		});
	},

 _renderMonth 函數 是渲染下拉框所有的月份 如下代碼:

// 渲染下拉框所有月份
    _renderMonth: function(){
        var self = this,
            _config = self.config,
            _cache = self.cache;
        var html = '';
        for(var i = 0; i < 12; i++) {
            html+= '<option value="'+i+'">'+Calendar.model['months'][i]+'</option>'
        }
        $('.monthSelect').each(function(index,item){
            $(item).html(html);
        });
    },

_changeSelect函數 是負責重新渲染當前的下拉框的年份和月份 重新賦值下拉框的年份和月份  如下代碼:

_changeSelect: function(targetParent,date){
        var self = this;
        var ys,
            ms;
        if(targetParent) {
            ys = $('.yearSelect',targetParent)[0];
            ms = $('.monthSelect',targetParent)[0];
            renderSelectYearVal(ys,targetParent);
            renderSelectMonthVal(ms,targetParent);
        }else {
            $(".js-calendarTable").each(function(index,item){
                ys = $('.yearSelect',item)[0],
                ms = $('.monthSelect',item)[0];
                renderSelectYearVal(ys);
                renderSelectMonthVal(ms);
            });
        }
        function renderSelectYearVal(ys,targetParent) {
            
            for(var i = 0; i < ys.length; i++) {
                if(date) {
                    if(ys.options[i].value == date.getFullYear()){
                        ys[i].selected = true;
                        
                        // 重新獲取當選被選中的年份 給頁面隱藏域輸入框重新賦值
                        var year = $(ys[i]).attr("value");
                        $('.js_year',targetParent).attr("year",year);
                        break;
                    }
                }else {
                    if(ys.options[i].value == self.date.getFullYear()){
                        ys[i].selected = true;
                        break;
                    }
                }
                
            }
        }
        function renderSelectMonthVal(ms,targetParent){
            
            for(var i = 0; i < ms.length; i++) {
                if(date) {
                    if(ms.options[i].value == date.getMonth()){
                        ms[i].selected = true;
                        // 重新獲取當選被選中的年份 給頁面隱藏域輸入框重新賦值
                        var month = $(ms[i]).attr("value");
                        $('.js_year',targetParent).attr("month",month);
                        break;
                    }
                }else {
                    if(ms.options[i].value == self.date.getMonth()){
                        ms[i].selected = true;
                        break;
                    }
                }
            }
        }

_renderData 函數  是負責把幾號渲染出來。
如下圖

提供掩藏域 

/*
 * 一開始克隆當前年份和月份 保存到隱藏域去 目的當點擊上下按鈕時候 互不影響各自的年份 和月份
 */
self._year = self.cloneObject(self.year),
self._month = self.cloneObject(self.month);
$(".calendarDiv .js_year").attr({"year":self._year,"month":self._month});

所有的代碼如下:

css代碼:

<style>
	* {margin:0;padding:0;}
	body {font-family: "微軟雅黑", Tahoma, Verdana;font-size: 12px;font-weight: normal;margin: 50px 10px;}
	span, label, p, input {
		font-family: "微軟雅黑", Tahoma, Verdana;
		font-size: 12px;
		font-weight: normal;
	}
	form {
		margin: 0;
		padding: 0;
	}
	ul {
		margin: 0;
	}
h1 {
	font-family: "微軟雅黑", Tahoma, Verdana;
	font-size: 16px;
	font-weight: bold;
}
h2 {
	font-family: "微軟雅黑", Tahoma, Verdana;
	font-size: 14px;
	font-weight: bold;
}
div.effect {
	border: 1px dotted #3C78B5;
	background-color: #D8E4F1;
	padding: 10px;
	width: 98%;
}
div.source {
	border: 1px solid #CCC;/*#090*/
	background-color: #F5F5F5;/*#DFD*/
	padding: 10px;
	width: 98%;
}
.color_red {
	color:#FF0000;
}
.b {
	font-weight: bold;
}
    select {font-size:12px;background-color:#EFEFEF;}
	table {border:0px solid #CCCCCC;background-color:#FFFFFF}
	th {font-size:12px;font-weight:normal;background-color:#FFFFFF;}
	th.theader {font-weight:normal;background-color:#666666;color:#FFFFFF;width:24px;}
	select.year {width:64px;}
	select.month {width:60px;}
	td {font-size:12px;text-align:center;cursor:pointer;}
	td.sat {color:#0000FF;background-color:#EFEFEF;}
	td.sun {color:#FF0000;background-color:#EFEFEF;}
	td.normal {background-color:#EFEFEF;}
	input.l,input.r,input.b {border: 1px solid #CCCCCC;background-color:#EFEFEF;width:20px;height:20px;cursor:pointer;}
	input.b {width:100%;}
	.calendarPanel .bg_out{background:#EFEFEF;}
	.calendarPanel .bg_over{background:#FFCC00;}
	.calendarPanel .bg_cur_day {background:#00CC33;}
	.calendarPanel{
		background-color: #FFFFFF;
		z-index: 9999;
	}
	.calendarDiv {float:left;height: 216px;width: 200px;border: 1px solid #666666;}
	.hidden{display:none;}
	.calendarPanel .disabled{color:#DCDCDC;cursor:default;}
  </style>

 HTML代碼:

 

<input type="text" class="input" style="width:200px;height:22px;"/>
    <div class="calendarPanel hidden">
        
    </div>

JS所有代碼 頁面相應的地方有注釋

/**
 * 日歷控件編寫 支持單日歷 雙日歷 多日歷等。
 * @author tugenhua
 * @time 2013 11-07
 * @email 879083421@qq.com
 */

 function Calendar() {
	
	this.config = {
		elemCls             :  '.input',            // 目標元素
		beginYear           :  1990,                //開始日期
		endYear             :  2020,                //結束日期 
		panelCls            :  '.calendarPanel',    // 日歷面板類
		bg_cur_day          :  'bg_cur_day',		// 當前的顏色
		bg_out              :  'bg_out',            // 鼠標hover顏色
		bg_over             :  'bg_over',           // 鼠標out顏色
		date2StringPattern  :  'yyyy-MM-dd',        // 默認顯示格式 yyyy-MM-dd
		patternDelimiter    :  '-',                 // 分隔符 注意:分隔符要和上面顯示格式對應
		panelCount          :  2,                   // 面板的個數 是單日歷 雙日歷 或者 多日歷等
		manyDisabled        :  false,               // 默認情況下為false 如果為true 指當前日期之前的日期不可點擊
		ishasSelect         :  true,                // 是否有下拉框選擇年份和月份 默認為true 暫不做操作 
													// 為以后留接口 因為如果沒有的話 年月份沒有顯示出來 感覺怪怪的

		render              :  null,                // 渲染日歷后觸發
		clickDayCallBack    :  null,                // 點擊某一天后 回調函數
		clickPrevCallBack   :  null,                // 點擊上一月的回調函數
		clickNextCallBack   :  null,                // 點擊下一頁的回調函數
		changeYearCallBack  :  null,                // 下拉框改變年份的回調函數
		changeMonthCallBack :  null                 //  下拉框改變月份的回調函數
	};

	this.cache = {
		createPanelHTML : '',
		flag            : true,
		year            : '',             //保存頁面一渲染時候 當前的年份
		month           : '',			  //保存頁面一渲染時候 當前的月份
		storeDateArrs   : []              
	};
	this.date = new Date();
	this.year = this.date.getFullYear();
	this.month = this.date.getMonth();
	
 }
 Calendar.model = {
	"year"               :      "\u5e74",
	"months"             :      ["\u4e00\u6708","\u4e8c\u6708","\u4e09\u6708","\u56db\u6708","\u4e94\u6708","\u516d\u6708","\u4e03\u6708","\u516b\u6708","\u4e5d\u6708",
								 "\u5341\u6708","\u5341\u4e00\u6708","\u5341\u4e8c\u6708"],
	"weeks"              :      ["\u65e5","\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d"],
	"string2DatePattern" :      "ymd"
 };
 Calendar.prototype = {

	init: function(options){
		this.config = $.extend(this.config,options || {});
		var self = this,
            _config = self.config,
			_cache = self.cache;
		$(_config.elemCls).unbind('click');
		$(_config.elemCls).bind('click',function(){
			// 判斷下 如果日歷面板已經渲染出來后 就不再渲染
			if($(_config.panelCls + ' .calendarDiv').length > 0) {
				self.show();
			}else {
				self.show();
				self._draw();
				self._renderYear();
				self._renderMonth();
				self._changeSelect();
				return;
				self._renderData();
			}
			
		});
	},
	_draw: function(){
          var self = this,
              _config = self.config,
			  _cache = self.cache;
			  
			  // 拼接HTML結構
			  _cache.createPanelHTML += '<div class="calendarDiv">'+
											'<table class="js-calendarTable" width="100%" border="0" cellpadding="3" cellspacing="1" align="center">' + 
												'<tr>' + 
													'<th><input class="l goPrevMonthButton" name="goPrevMonthButton" type="button" value="<" \/><\/th>' +
													'<th colspan="5">'+
														'<select class="year yearSelect" name="yearSelect"><\/select>'+
														'<select class="month monthSelect" name="monthSelect"><\/select>'+
													'<\/th>' + 
													'<th><input class="r goNextMonthButton" name="goNextMonthButton" type="button" value=">" \/><\/th>' + 
												'<\/tr>';
			  _cache.createPanelHTML += '<tr>';
			  for(var i = 0; i < 7; i++) {
				  _cache.createPanelHTML += '<th class="theader">'+
										        Calendar.model["weeks"][i] + 
										    '<\/th>';
			  }
			_cache.createPanelHTML += '<\/tr>';
			
			for(var k = 0; k < 6; k+=1) {
				_cache.createPanelHTML += '<tr align="center">';
				for(var j = 0; j < 7; j++) {
					switch (j) {
						case 0:  _cache.createPanelHTML += '<td class="sun"> <\/td>'; break;
						case 6:  _cache.createPanelHTML += '<td class="sat"> <\/td>'; break;
						default: _cache.createPanelHTML += '<td class="normal"> <\/td>'; break;
					}
				}
				_cache.createPanelHTML += '<\/tr>';
			}

			_cache.createPanelHTML += '<tr>' + 
									    '<th colspan="2"><input type="button" class="b clearButton" name="clearButton" value="清空"\/><\/th>'+
										'<th colspan="3"><\/th>' + 
										'<th colspan="2"><input type="button" class="b closeButton" name="closeButton" value="關閉"\/><\/th>' + 
									  '<\/tr>' + 
									  '<input type="hidden" class="js_year" year="" month=""/>' +
									  '<\/table>' + 
									  '<\/div>';
			for(var m = 0; m < _config.panelCount; m+=1) {
				if(_cache.flag) {
					
					$(_config.panelCls).append(_cache.createPanelHTML);
					/*
					 * 一開始克隆當前年份和月份 保存到隱藏域去 目的當點擊上下按鈕時候 互不影響各自的年份 和月份
					 */
					self._year = self.cloneObject(self.year),
					self._month = self.cloneObject(self.month);
					$(".calendarDiv .js_year").attr({"year":self._year,"month":self._month});
				}
			}
			if(!_config.ishasSelect) {
				!$(".yearSelect").hasClass("hidden") && $(".yearSelect").addClass("hidden");
				!$(".monthSelect").hasClass("hidden") && $(".monthSelect").addClass("hidden");
			}
			_cache.year = self.year;
			_cache.month = self.month;
			_cache.flag = false;

			_config.render && $.isFunction(_config.render) && _config.render();

			$(".goPrevMonthButton").unbind('click');
			$(".goPrevMonthButton").bind('click',function(e){
				self._goPrevMonth(e);
				_config.clickPrevCallBack && $.isFunction(_config.clickPrevCallBack) && _config.clickPrevCallBack();
			});
			$(".goNextMonthButton").unbind('click');
			$(".goNextMonthButton").bind("click",function(e){
				self._goNextMonth(e);
				_config.clickNextCallBack && $.isFunction(_config.clickNextCallBack) && _config.clickNextCallBack();
			});
			$(".yearSelect").change(function(e){
				self._update(e);
				_config.changeYearCallBack && $.isFunction(_config.changeYearCallBack) && _config.changeYearCallBack();
			});

			$(".monthSelect").change(function(e){
				self._update(e);
				_config.changeMonthCallBack && $.isFunction(_config.changeMonthCallBack) && _config.changeMonthCallBack();
			});
			$(".clearButton").unbind('click');
			$(".clearButton").bind('click',function(e){
				$(_config.elemCls).val('');
				$(_config.elemCls).attr('value','');
				_cache.storeDateArrs[0] = undefined;
				_cache.storeDateArrs[1] = undefined;
			});

			$(".closeButton").unbind('click');
			$(".closeButton").bind('click',function(){
				self.hide();
			});
	},
	// 渲染下拉框所有的年份
	_renderYear: function() {
		var self = this,
			_config = self.config,
			_cache = self.cache;
		var html = '';
		for(var i = _config.beginYear; i <= _config.endYear; i+=1) {
			html += '<option value="'+i+'">'+(i + Calendar.model['year'])+'</option>';
		}
		$(".yearSelect").each(function(index,item){
			$(item).html(html);
		});
	},
	// 渲染下拉框所有月份
	_renderMonth: function(){
		var self = this,
			_config = self.config,
			_cache = self.cache;
		var html = '';
		for(var i = 0; i < 12; i++) {
			html+= '<option value="'+i+'">'+Calendar.model['months'][i]+'</option>'
		}
		$('.monthSelect').each(function(index,item){
			$(item).html(html);
		});
	},
	_renderData: function(targetParent,date) {
		var self = this,
			_config = self.config,
			_cache = self.cache;

		var dateArray,
			tds;
		if(targetParent) {
			tds = $("td",$(targetParent));
			dateArray = self._getMonthViewDateArray(date.getFullYear(),date.getMonth());
			renderTDs(tds,dateArray,date);
		}else {
			$(".js-calendarTable").each(function(index,item){
				tds = $('td',item);
				dateArray = self._getMonthViewDateArray(self.date.getFullYear(),self.date.getMonth());
				renderTDs(tds,dateArray);
			});
		}
		function renderTDs(tds,dateArray,date){
			$(tds).each(function(index,td){
				$(td).hasClass(_config.bg_cur_day) && $(td).removeClass(_config.bg_cur_day);
			});
			for(var i = 0; i < tds.length; i+=1) {
				!$(tds[i]).hasClass(_config.bg_out) && $(tds[i]).addClass(_config.bg_out);
				$(tds[i]).html("");
				$(tds[i]).html(dateArray[i]) || " ";
				if (i > dateArray.length - 1) continue;
				if(dateArray[i]) {
					$(tds[i]).unbind('click');
					$(tds[i]).bind('click',function(e){
						var target = e.target,
							tagParent = $(target).closest('.js-calendarTable');
						var year = $(".js_year",tagParent).attr("year"),
							month = $(".js_year",tagParent).attr("month");

						var curdate = new Date(year,month,1);

						 if($(_config.elemCls)) {
							if($(this).hasClass("disabled")) {
								return;
							}
							// 暫時只考慮2種情況 單日歷面板 和 雙日歷面板 輸入框顯示問題
							if(_config.panelCount == 2) {
								var parent = $(this).closest('.js-calendarTable'),
									curIndex = $(".js-calendarTable").index(parent);
								
								if(curIndex == 0) {
									_cache.storeDateArrs[0] = new Date(curdate.getFullYear(),curdate.getMonth(),$(this).html())._format(_config.date2StringPattern);
								}else {
									_cache.storeDateArrs[1] = new Date(curdate.getFullYear(),curdate.getMonth(),$(this).html())._format(_config.date2StringPattern);
								}
								if(_cache.storeDateArrs[0] != undefined && _cache.storeDateArrs[1] == undefined){

									//先去掉類 bg_cur_day
									$('td',tagParent).each(function(index,td){
										$(td).hasClass(_config.bg_cur_day) && $(td).removeClass(_config.bg_cur_day);
									});
									$(_config.elemCls).val(_cache.storeDateArrs[0]);
									$(_config.elemCls).attr('value',_cache.storeDateArrs[0]);
									!$(this).hasClass(_config.bg_cur_day) && $(this).addClass(_config.bg_cur_day);

								}else if(_cache.storeDateArrs[0] == undefined && _cache.storeDateArrs[1] != undefined){
									//先去掉類 bg_cur_day
									$('td',tagParent).each(function(index,td){
										$(td).hasClass(_config.bg_cur_day) && $(td).removeClass(_config.bg_cur_day);
									});
									$(_config.elemCls).val(_cache.storeDateArrs[1]);
									$(_config.elemCls).attr('value',_cache.storeDateArrs[1]);
									!$(this).hasClass(_config.bg_cur_day) && $(this).addClass(_config.bg_cur_day);
									
								}else if(_cache.storeDateArrs[0] != undefined && _cache.storeDateArrs[1] != undefined) {
									
									if(Date.parse(_cache.storeDateArrs[0]) >= Date.parse(_cache.storeDateArrs[1])) {
										alert("結束日期必須大於開始日期 或者 開始日期必須小於結束日期");
										return;
									}else {
										$(_config.elemCls).val(_cache.storeDateArrs[0] + '/' + _cache.storeDateArrs[1]);
										$(_config.elemCls).attr("value",_cache.storeDateArrs[0] + '/' + _cache.storeDateArrs[1]);
										self.hide();
									}
								}
							}else{
								$(_config.elemCls).val(new Date(curdate.getFullYear(),curdate.getMonth(),$(this).html())._format(_config.date2StringPattern));
								self.hide();
							}
						 }
						_config.clickDayCallBack && $.isFunction(_config.clickDayCallBack) && _config.clickDayCallBack();
					});

					$(tds[i]).hover(function(){
						if($(this).hasClass("disabled")){
							return;
						}
						!$(this).hasClass(_config.bg_over) && $(this).addClass(_config.bg_over);
						
					},function(){
						$(this).hasClass(_config.bg_over) && $(this).removeClass(_config.bg_over);
					});

					var today = new Date();
					if(today.getFullYear() == self.date.getFullYear() && today.getMonth() == self.date.getMonth()) {
						if(today.getDate() == dateArray[i]){
							// 獲取當前i 第幾項 循環下 當前的i 前面所有的單元格不可點擊
							if(_config.manyDisabled){
								self._cellDisabled(tds,i);
							}
							!$(tds[i]).hasClass(_config.bg_cur_day) && $(tds[i]).addClass(_config.bg_cur_day);
						}
					}else {
						
						$(tds[i]).hasClass(_config.bg_cur_day) && $(tds[i]).removeClass(_config.bg_cur_day);
					}
				}
			}
		}
	},
	_cellDisabled: function(tds,i){
		for(var k = 0; k < i; k++) {
			!$(tds[k]).hasClass("disabled") && $(tds[k]).addClass("disabled");
		}
	},
	// 上一頁按鈕
	_goPrevMonth: function(e){
		var self = this,
			_config = self.config,
			_cache = self.cache;
		var target = e.target,
			targetParent = $(target).closest('.js-calendarTable');
		var year = $(".js_year",targetParent).attr('year'),
			month = $(".js_year",targetParent).attr('month');

		if(_config.manyDisabled) {
			return;
		}
		if(year == _config.beginYear && month == 0) {
			return;
		} 
		month--;
		if(month < 0) {
			year--;
			month = 11;
		}
		var date = new Date(year,month,1);
		self._changeSelect(targetParent,date);
		self._renderData(targetParent,date);
		
		// 重新渲染td背景色
		self._renderTdBg(year,month,targetParent);
	},
	// 下一頁按鈕
	_goNextMonth: function(e){
		var self = this,
			_config = self.config,
			_cache = self.cache;
		var target = e.target,
			targetParent = $(target).closest('.js-calendarTable');

		var year = $(".js_year",targetParent).attr('year'),
			month = $(".js_year",targetParent).attr('month');
		if(year == _config.beginYear && month == 0) {
			return;
		} 
		month++;
		if(month > 12) {
			year++;
			month = 0;
		}
		var date = new Date(year,month,1);
		self._changeSelect(targetParent,date);
		self._renderData(targetParent,date);
		
		// 重新渲染td背景色
		self._renderTdBg(year,month,targetParent);
	},

	// 渲染當前天的背景色 
	_renderTdBg: function(year,month,targetParent){
		var self = this,
			_config = self.config,
			_cache = self.cache;

		if(_cache.year == year && _cache.month == month) {
			return;
		}else {
			var tds = $("td",targetParent);
			
			$.each(tds,function(index,td){
				$(td).hasClass(_config.bg_cur_day) && $(td).removeClass(_config.bg_cur_day);
			});
		}
	},
	/**
	 * 深度克隆一個對象 使原對象和新對象完全獨立
	 */
	cloneObject: function(obj){
		 if(obj === null){
              return null;
          }else if(obj instanceof Array){
             var arr = [];
             for(var i = 0, ilen = obj.length; i < ilen; i+=1){
                 arr[i] = obj[i];
             }
             return arr;
		  }else if(obj instanceof Date || obj instanceof RegExp || obj instanceof Function){
             return obj;
          }else if(obj instanceof Object){
              var o = {};
              for(var i in obj){
                  if(obj.hasOwnProperty(i)){
                      o[i] = cloneObject(obj[i]);
                  }
              }
              return o;
         }else{
             return obj;
         }
	},
	show: function(){
		var self = this,
			_config = self.config;
		$(_config.panelCls).hasClass('hidden') && $(_config.panelCls).removeClass('hidden');
	},
	hide: function(){
		var self = this,
			_config = self.config;
		!$(_config.panelCls).hasClass('hidden') && $(_config.panelCls).addClass('hidden');
	},
	_getMonthViewDateArray: function(y,m) {
		var dateArray = new Array(42);

		// 返回表示星期的第一天的數字
		var dayOfFirstDate = new Date(y, m, 1).getDay(),

			// 返回月份的最后一天
			dateCountOfMonth = new Date(y, m + 1, 0).getDate();
		
		for(var i = 0; i < dateCountOfMonth; i+=1) {
			dateArray[i + dayOfFirstDate] = i + 1;
		}
		return dateArray;
	},
	_update: function(e) {
		var self = this,
			target = e.target,
			targetParent = $(target).closest('.js-calendarTable'),
			monthSelect = $(".monthSelect",targetParent)[0],
			yearSelect = $(".yearSelect",targetParent)[0];

		self.year = yearSelect.options[yearSelect.selectedIndex].value;
		self.month = monthSelect.options[monthSelect.selectedIndex].value;
		self.date = new Date(self.year,self.month,1);

		self._changeSelect(targetParent,self.date);
		self._renderData(targetParent,self.date);
	},
	// 重新渲染當前的下拉框的年份和月份 重新賦值下拉框的年份和月份 
	_changeSelect: function(targetParent,date){
		var self = this;
		var ys,
			ms;
		if(targetParent) {
			ys = $('.yearSelect',targetParent)[0];
			ms = $('.monthSelect',targetParent)[0];
			renderSelectYearVal(ys,targetParent);
			renderSelectMonthVal(ms,targetParent);
		}else {
			$(".js-calendarTable").each(function(index,item){
				ys = $('.yearSelect',item)[0],
				ms = $('.monthSelect',item)[0];
				renderSelectYearVal(ys);
				renderSelectMonthVal(ms);
			});
		}
		function renderSelectYearVal(ys,targetParent) {
			
			for(var i = 0; i < ys.length; i++) {
				if(date) {
					if(ys.options[i].value == date.getFullYear()){
						ys[i].selected = true;
						
						// 重新獲取當選被選中的年份 給頁面隱藏域輸入框重新賦值
						var year = $(ys[i]).attr("value");
						$('.js_year',targetParent).attr("year",year);
						break;
					}
				}else {
					if(ys.options[i].value == self.date.getFullYear()){
						ys[i].selected = true;
						break;
					}
				}
				
			}
		}
		function renderSelectMonthVal(ms,targetParent){
			
			for(var i = 0; i < ms.length; i++) {
				if(date) {
					if(ms.options[i].value == date.getMonth()){
						ms[i].selected = true;
						// 重新獲取當選被選中的年份 給頁面隱藏域輸入框重新賦值
						var month = $(ms[i]).attr("value");
						$('.js_year',targetParent).attr("month",month);
						break;
					}
				}else {
					if(ms.options[i].value == self.date.getMonth()){
						ms[i].selected = true;
						break;
					}
				}
			}
		}
	}
 };

/*
 * 日期格式化方法
 */
 if(!Date.prototype._format) {
	Date.prototype._format = function(str) {
		var o = {
			"M+" : this.getMonth() + 1, //month
			"d+" : this.getDate(),      //day
			"h+" : this.getHours(),     //hour
			"m+" : this.getMinutes(),   //minute
			"s+" : this.getSeconds(),   //second
			"w+" : "\u65e5\u4e00\u4e8c\u4e09\u56db\u4e94\u516d".charAt(this.getDay()),   //week
			"q+" : Math.floor((this.getMonth() + 3) / 3),  //quarter
			"S"  : this.getMilliseconds() //millisecond
		}
		if (/(y+)/.test(str)) {
			str = str.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
		}
		for(var k in o){
			if (new RegExp("("+ k +")").test(str)){
				str = str.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
			}
		}
		return str;
	}
 }
/*
 * 轉換為日期
 */
if(!String.prototype._toDate) {
	 String.prototype._toDate = function(delimiter, pattern) {
		delimiter = delimiter || "-";
		pattern = pattern || "ymd";
		var a = this.split(delimiter);
		var y = parseInt(a[pattern.indexOf("y")], 10);
		if(y.toString().length <= 2) y += 2000;
		if(isNaN(y)) y = new Date().getFullYear();
		var m = parseInt(a[pattern.indexOf("m")], 10) - 1;
		var d = parseInt(a[pattern.indexOf("d")], 10);
		if(isNaN(d)) d = 1;
		return new Date(y, m, d);
	};
}

// 頁面初始化方式
$(function(){
	new Calendar().init({
		//manyDisabled: true
		//ishasSelect: true
	});
});

 代碼也不好解釋,口才不好,具體的可以看代碼 實現相應的邏輯。先到這吧 寫了一天了 明天還得繼續加班 嗨!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM