JS編寫日曆控制元件(支援單日曆 雙日曆 甚至多日曆等)

龍恩0707發表於2013-11-09

    前言: 最近幾天都在研究日曆控制元件編寫,當然前提我要說明下,當然看過別人寫的原始碼 所以腦子一熱 就想用自己的編碼方式 來寫一套可擴充套件性 可維護性 效能高點的程式碼控制元件出來,就算練習練習下,所以前幾天晚上下班後一直在研究 及 今天早上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
	});
});

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

 

相關文章