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

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

JS编写日历控件(支持单日历 双日历 甚至多日历等)的更多相关文章

  1. 简洁JS 日历控件 支持日期和月份选择

    原文出处 以下这个JS日历控件是我的闲暇之余自己编写的,所有的代码全部在IE7/IE8/Firefox下面测试通过, 而且可以解决被iframe层遮盖的问题.现在只提供两种风格(简洁版和古典版)和两种 ...

  2. 2.23 js处理日历控件(修改readonly属性)

    2.23 js处理日历控件(修改readonly属性) 前言    日历控件是web网站上经常会遇到的一个场景,有些输入框是可以直接输入日期的,有些不能,以我们经常抢票的12306网站为例,详细讲解如 ...

  3. JS日历控件优化(增加时分秒)

    JS日历控件优化      在今年7月份时候 写了一篇关于 "JS日历控件" 的文章 , 当时只支持 年月日 的日历控件,现在优化如下:      1. 在原基础上 支持 yyyy ...

  4. JS日历控件 灵活设置: 精确的时分秒.

     在今年7月份时候 写了一篇关于 "JS日历控件" 的文章 , 当时仅仅支持 年月日 的日历控件,如今优化例如以下:      1. 在原基础上 支持 yyyy-mm-dd 的年月 ...

  5. 日历控件修改的JS代码

    var bMoveable=true; var _VersionInfo=" " ; //============================================= ...

  6. JS实现日历控件选择后自动填充

    最近在做人事档案的项目,在做项目的初期对B/S这块不是很熟悉,感觉信心不是很强,随着和师哥同组人员的交流后发现,调试程序越来越好了,现在信心是倍增,只要自己自己踏实的去研究.理解代码慢慢的效果就出来了 ...

  7. 自己用js写的两个日历控件

    前一阵写了两个日历控件,做了简单的封装,发出来共朋友们参考. 第一个日历控件,条状的日历. (使用方法:调用initBarTime(id,evn),第一个参数是要渲染div的id,第二个参数是点击日期 ...

  8. js非常强大的日历控件fullcalendar.js, 日期时间库: moment.js

    日历控件: https://fullcalendar.io/docs/ https://fullcalendar.io/docs/event_data/events_function/ https:/ ...

  9. selenium+Python(Js处理日历控件)

    日历控件是web网站上经常会遇到的一个场景,有些输入框是可以直接输入日期的,有些不能,以我们经常抢票的12306网站为例,详细讲解如何解决日历控件为readonly属性的问题. 基本思路:先用js去掉 ...

随机推荐

  1. SVN查看所有日志提交记录

    1. svn默认显示最近一周的文件提交和修改记录,怎么查看更长时间的日志记录呢? 2. TortoiseSVN 3. 点击show all 或者NEXT 100,就可显示更长时间的文件提交记录.

  2. 设计模式(14)--Command(命令模式)--行为型

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.模式定义:   命令模式属于对象的行为模式.命令模式又称为行动(Action)模式或交易(Transactio ...

  3. iOS设备上出现的click,live,on点击失去效果

    iOS设备上出现的点击事件失效,但是在Android上可以正常使用, 1.iOS设备对标签点击限制,不认为是可点击的标签,需要给要绑定点击事件的标签加上一个样式,cursor:pointer:这样就可 ...

  4. JS--我发现,原来你是这样的JS(四)(看看变量,作用域,垃圾回收机制是啥)

    一.介绍 这是红宝书(JavaScript高级程序设计 3版)的读书笔记第四篇,是红宝书第四章内容(主要是变量和作用域问题),当然其中还有我个人的理解.红宝书这本书可以说是难啃的,要看完不容易,挺厚的 ...

  5. ViewPager中切换界面Fragment被销毁的问题

    ViewPager中切换界面Fragment被销毁的问题分析 使用ViewPager+Fragment实现界面切换,当界面数量大于3时,出现二次滑动后数据消失的情况,下面由Fragment生命周期进行 ...

  6. sql server对并发的处理-乐观锁和悲观锁(转)

    假如两个线程同时修改数据库同一条记录,就会导致后一条记录覆盖前一条,从而引发一些问题. 例如: 一个售票系统有一个余票数,客户端每调用一次出票方法,余票数就减一. 情景: 总共300张票,假设两个售票 ...

  7. Chained Declustering

          此论文描述了在无共享架构的多处理器机器上的数据库系统的数据冗余分布方法.该方法提高了系统的可用性,同时在单节点故障的情况下,可以很好的实现负载均衡.以下是论文的一些摘要,详细可以参见论文原 ...

  8. SQL Server复制入门(二)----复制的几种模式 (转载)

    简介本系列文章的上一篇对复制是什么做了一个概述.本篇文章根据发布服务器,分发服务器和订阅服务器的组织方式和复制类型来讲述常用复制的几种模式. 模式的选择选择复制的模式取决于多个方面.首先需要考虑具体的 ...

  9. JBoss 7 更改response header中的Server参数

    jboss服务器缺省情况下会在HTTP response header中显示自身的标识,如下 Server: Apache-Coyote/1.1 出于安全考虑,如果不想让人知道服务器类型,可以用以下方 ...

  10. oracle中insert 多条数据方法

    oracle中的insert 和 mysql添加多条数据的 方式不太一样 用到的语法: insert all into 表名(需要添加的表字段)values(添加的字段数据一定要对应字段顺序) int ...