代码改变世界

JS编写日历控件(支持单日历 双日历 甚至多日历等)

2013-11-09 18:16  龙恩0707  阅读(3732)  评论(9编辑  收藏  举报

    前言: 最近几天都在研究日历控件编写,当然前提我要说明下,当然看过别人写的源码 所以脑子一热 就想用自己的编码方式 来写一套可扩展性 可维护性 性能高点的代码控件出来,就算练习练习下,所以前几天晚上下班后一直在研究 及 今天早上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
	});
});

 代码也不好解释,口才不好,具体的可以看代码 实现相应的逻辑。先到这吧 写了一天了 明天还得继续加班 嗨!!