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
});
});
代码也不好解释,口才不好,具体的可以看代码 实现相应的逻辑。先到这吧 写了一天了 明天还得继续加班 嗨!!
JS编写日历控件(支持单日历 双日历 甚至多日历等)的更多相关文章
- 简洁JS 日历控件 支持日期和月份选择
原文出处 以下这个JS日历控件是我的闲暇之余自己编写的,所有的代码全部在IE7/IE8/Firefox下面测试通过, 而且可以解决被iframe层遮盖的问题.现在只提供两种风格(简洁版和古典版)和两种 ...
- 2.23 js处理日历控件(修改readonly属性)
2.23 js处理日历控件(修改readonly属性) 前言 日历控件是web网站上经常会遇到的一个场景,有些输入框是可以直接输入日期的,有些不能,以我们经常抢票的12306网站为例,详细讲解如 ...
- JS日历控件优化(增加时分秒)
JS日历控件优化 在今年7月份时候 写了一篇关于 "JS日历控件" 的文章 , 当时只支持 年月日 的日历控件,现在优化如下: 1. 在原基础上 支持 yyyy ...
- JS日历控件 灵活设置: 精确的时分秒.
在今年7月份时候 写了一篇关于 "JS日历控件" 的文章 , 当时仅仅支持 年月日 的日历控件,如今优化例如以下: 1. 在原基础上 支持 yyyy-mm-dd 的年月 ...
- 日历控件修改的JS代码
var bMoveable=true; var _VersionInfo=" " ; //============================================= ...
- JS实现日历控件选择后自动填充
最近在做人事档案的项目,在做项目的初期对B/S这块不是很熟悉,感觉信心不是很强,随着和师哥同组人员的交流后发现,调试程序越来越好了,现在信心是倍增,只要自己自己踏实的去研究.理解代码慢慢的效果就出来了 ...
- 自己用js写的两个日历控件
前一阵写了两个日历控件,做了简单的封装,发出来共朋友们参考. 第一个日历控件,条状的日历. (使用方法:调用initBarTime(id,evn),第一个参数是要渲染div的id,第二个参数是点击日期 ...
- js非常强大的日历控件fullcalendar.js, 日期时间库: moment.js
日历控件: https://fullcalendar.io/docs/ https://fullcalendar.io/docs/event_data/events_function/ https:/ ...
- selenium+Python(Js处理日历控件)
日历控件是web网站上经常会遇到的一个场景,有些输入框是可以直接输入日期的,有些不能,以我们经常抢票的12306网站为例,详细讲解如何解决日历控件为readonly属性的问题. 基本思路:先用js去掉 ...
随机推荐
- Mycat入门配置_读写分离配置
1.Mycat的分片 两台数据库服务器: 192.168.80.11 192.168.80.4 操作系统版本环境:centos6.5 数据库版本:5.6 mycat版本:1.4 release 数据库 ...
- HDU4845(SummerTrainingDay02-C 状态压缩bfs)
拯救大兵瑞恩 Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)Total Subm ...
- CentOS 7环境下Pycharm安装流程记录
1.准备安装文件: 方法1: 使用内置火狐浏览器访问下载最新格式为tar.gz的压缩包 网址:https://www.jetbrains.com/pycharm/download/previous.h ...
- HTML页面局部刷新
/.事件响应刷新:有请求才会刷新 1.通过JS HTML DOM或jQuery获取HTML元素,通过DOM方法或jQuery方法监听页面事件,获取用户请求: 2.通过Ajax将用户请求提交至服务器,服 ...
- python实现贪婪算法解决01背包问题
一.背包问题 01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn.01背包是背包问题中最简单的问题.01背包的约束条件是给定几种物 ...
- cf900D. Unusual Sequences(容斥 莫比乌斯反演)
题意 题目链接 Sol 首先若y % x不为0则答案为0 否则,问题可以转化为,有多少个数列满足和为y/x,且整个序列的gcd=1 考虑容斥,设\(g[i]\)表示满足和为\(i\)的序列的方案数,显 ...
- JSP内置对象——out对象/request对象
在这个科技高速发展的时代,迫使我们的脚步一刻都不能停下. 在这个for循环语句当中,我们可以直接使用jsp内置对象中的out对象来给浏览器打印输出,那么这个out对象就是一个内置对象, 在这里,我们使 ...
- 排错-windows下 ORA-12560 TNS 协议适配器错误解决方法
排错-windows下_ORA-12560 TNS 协议适配器错误解决方法 by:授客 QQ:1033553122 问题描述: 修改SQL*Plus窗口属性后,重新打开SQL*Plus时出现ORA-1 ...
- View的measure机制
Android中View框架的工作机制中,主要有三个过程: 1.View树的测量(measure)Android View框架的measure机制 2.View树的布局(layout) And ...
- react native 第一次下载app的欢迎页+每次启动app的启动页设计
欢迎各位同学加入: React-Native群:397885169 大前端群:544587175 大神超多,热情无私帮助解决各种问题. 我想我写的这篇博文可以帮助到很多人,接下来要分享的东西,对app ...