一个简单日历控件的分析
时间:2010-09-17 来源:冰封e族
总想写点什么,可又没什么太多的原创东西,即使是原创但技术含量太低实在拿不出手,只好拿js大牛客服果果的号称最精简的日历代码分析一下,顺便完善了代码,该日历适合用于要求不是很苛刻的情况。
先看看第一段js原代码吧!
Date.prototype.fDay=function (){
var $=new Date(this);
$.setDate(1);
return $.getDay()
};
这段是对Date对象添加了原型方法fDay,该方法返回的是当前月份的第一天星期几,注意星期天就是返回0,setDate(1)设置日期格式为当前时间月份的第一天,getDay返回星期几。
Date.prototype.dCount=function (){
var $1=new Date(this),$2=new Date(this);
$1.setDate(1);
$2.setDate(1);
$2.setMonth($2.getMonth()+1);
return ($2-$1)/86400000;
};
第二段也是对Date对象添加了原型方法dCount,该方法返回的是当前月份共有多少天。程序第一行定义两个当前时间变量,然后分别设置为当前月份的第一天,然后把$2变量设置为当前月份的下一个月份,最后减去当前月份得到是一个月的毫秒数,除以一天的毫秒数得到当前月份的天数。
接下来是程序主体:
代码
(CLD={
init:function (){
this.$=new Date;
this.update();
document.onclick=function(e){
e= e||window.event;
var target = e.srcElement || e.target ;
if(target.parentNode.id != 'iCalendar_body' && target.parentNode.id != 'iCalendar' && target != CLD.$$)
CLD.getId('iCalendar').style.display="none";
};
return this
},
getId:function(id){
return typeof id === 'string'?document.getElementById(id):id;
},
update:function(y,m){
var uiList=[],week='日一二三四五六'.split(''),$=this.$,getId = this.getId,
fn=function(a,b){
return '<b class="btn" onclick="CLD.update('+a+')">'+b+'</b>'
};
y&&$.setYear($.getFullYear()+y);
m&&$.setMonth($.getMonth()+m);
var Y=$.getFullYear(),M=$.getMonth()+1,D=$.getDate();
for (var i=0;i<week.length;i++ )uiList.push('<b>'+week[i]+'</b>');
for (i=0;i<$.fDay();i++ )uiList.push('<b> </b>');
for (i=0;i<$.dCount();i++ )uiList.push('<a href="javascript:CLD.set('+Y+','+M+','+(i+1)+')">'+(i+1)+'</a>');
getId('iCalendar_body').innerHTML=fn('-1,null','<<')+fn('null,-1','<')+'<b id="dateCap">'+Y+'年'+M+'月</b>'+fn('null,1','>')+fn('1,null','>>')+uiList.join('');
if(this.$$)this.$$.focus();
getId('iCalendar_mask').width=getId('iCalendar_body').offsetWidth-2;
getId('iCalendar_mask').height=getId('iCalendar_body').offsetHeight;
},
showTo:function(v){
for(var pos={x:0,y:0},$=v;v;v=v.offsetParent){
pos.x+=v.offsetLeft;
pos.y+=v.offsetTop
};
document.title=[pos.x,pos.y]
with(this.getId('iCalendar').style){
left=pos.x+"px";
top=(pos.y+$.offsetHeight)+"px";
display=""
}
},
bind:function(x){
x.onfocus=function(){
CLD.$$=this;
CLD.showTo(this)
};
return this;
},
set:function(y,m,d){
if(CLD.$$){
CLD.$$.value=y+'-'+m+'-'+d;
CLD.getId('iCalendar').style.display="none";
}
}
}).init().bind(CLD.getId('i_1')).bind(CLD.getId('i_2'))
作者使用闭包把程序主体封装在CLD这个命名空间下可以理解成一个对象,第一个方法是init用来初始化程序,主要是初始日期变量,并且调用了update方法,后面这个document.onlick事件是我改写的,原本作者的意图是让用户点击弹出的日历空白地方就隐藏,但我觉得不够合理就改成点击日历区域外的空间就才隐藏日历。如果事件触发的源dom对象包含在日历内(选中日期除外)则不不会隐藏。最后返回了自己也就是CLD这个对象,可以实现类似jQuery那样的链式操作。bind方法也是如此。
下面的getId方法也是我自己添加的,原本程序里是没有的,客服果果直接用id来操作dom,当时在网上直接访问他的代码在ff,ie下都没有问题,后来拷贝到本地运行发现ff下面提示id没定义,但是ie下可以正常运行,后来知道问题在于拷贝到本地的代码加了头部xhtml 1.0的dtd声明,于是ff就以严格模式解析js,直接使用id就报错,如果不声明的话ff会以html5的模式去解析,在ff的控制台下会有警告说明直接使用id不符合w3c标准,但不会报错导致程序无法运行。所以在此为了避免出现不可预料的麻烦还是多写一行代码使用getElementById吧!
紧跟着是程序的核心方法update,主要功能就是组装日历元素包括年月前后翻转按钮,空白b元素,可选的日期元素。week='日一二三四五六'.split('')这是种很巧妙的定义数组的办法,值得借鉴。然后又定义了fn这个方法后面复用的比较多,该方法返回一个b元素并且将update方法绑定到点击事件上,这个b元素就年月的前后加减的按钮。
y&&$.setYear($.getFullYear()+y);
m&&$.setMonth($.getMonth()+m);
这两行是设置年,月并且判断输入的参数y和m是为null,后面会调用到。
for (var i=0;i<week.length;i++ )uiList.push('<b>'+week[i]+'</b>');
将字符串“星期日”至”星期六“循环压入到uiList数组中。
for (i=0;i<$.fDay();i++ )uiList.push('<b> </b>');
将空白填充部分压入到uiList数组中。
for (i=0;i<$.dCount();i++ )uiList.push('<a href="javascript:CLD.set('+Y+','+M+','+(i+1)+')">'+(i+1)+'</a>');
将当前月份的天数循环压入到uiList数组中。
getId('iCalendar_body').innerHTML=fn('-1,null','<<')+fn('null,-1','<')+'<b id="dateCap">'+Y+'年'+M+'月</b>'+fn('null,1','>')+fn('1,null','>>')+uiList.join('');
然后把组合的元素全部以字符串的形式连接起来并置于日历的body当中。
后面是设置iframe的高和宽用于解决ie6下面div无法遮住select元素的bug。
然后是showTo方法,顾名思义是用来显示日历的。主要是计算绑定input的实际位置把它的left直接赋值给日历控件,把top加上input的实际高度再赋值给日历并显示。
最后是bind方法,绑定onfocus事件,set方法将选中的日期赋值给input,用闭包直接调用了init方法然后将日历控件绑定到两个input上。
至此整个程序分析完毕,精简的日历也不简单,里面一些方法都是比较巧妙的设计高度的复用。当然实际项目中应用的日历功能肯定不会这么简陋,一般的功能如设定初始时间,限制输入的日期段,解出绑定控件等等,另外这个日历外层的div是直接写在页面上的,个人觉得也应该封装到js里面去便于移植,以后有时间再基于这个程序扩展和丰富功能。
这里是可以直接运行的代码(拷贝到本地另存为html文件即可)
代码
<!DOCTYPE >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<meta name="copyright" content="" />
<meta name="keywords" content="精简的日历控件" />
<meta name="description" content="精简的日历控件" />
<title>日历</title>
<style>
#iCalendar{border:1px solid #1F62BE;border-top:7px solid #208DDE;width:210px;position:absolute;font-family:Arial;background:#F0F9FF;left:-500px;overflow:hidden; }
#iCalendar_body{_height:170px;padding:0 5px; background:#F0F9FF}
#iCalendar iframe{position:absolute; ;z-index:-1;top:0;left:0;background:#F0F9FF}
#iCalendar a,#iCalendar b{display:block; float:left;zoom: 1;width:20px;height:16px;line-height:16px;padding:2px; background:#F0F9FF;font-size:12px;text-align:center;color:#333;font-family:Arial;text-decoration:none;margin:0px 2px;}
#iCalendar b{background:none;}
#iCalendar a:hover{background:#FE8B1A;color:#fff;font-weight:bold}
#iCalendar .btn{cursor:pointer;width:18px;}
#iCalendar #dateCap{ width:80px;color:#900; }
p{padding:20px;}
</style>
</head>
<body>
<p>
<div id="iCalendar" tabIndex='-1'>
<iframe id="iCalendar_mask" src="" frameBorder=0 ></iframe>
<div id="iCalendar_body"></div>
</div>
<input type="text" id="i_1" /><input type="text" id="i_2" /><br/>
<select id=""><option value="" selected="selected">dhooo.com</option></select>
</p>
</body>
</html>
<script>
alert(i_1);
Date.prototype.fDay=function (){
var $=new Date(this);
$.setDate(1);
return $.getDay()
};
Date.prototype.dCount=function (){
var $1=new Date(this),$2=new Date(this);
$1.setDate(1);
$2.setDate(1);
$2.setMonth($2.getMonth()+1);
return ($2-$1)/86400000;
};
(CLD={
init:function (){
this.$=new Date;
this.update();
document.onclick=function(e){
e= e||window.event;
var target = e.srcElement || e.target ;
if(target.parentNode.id != 'iCalendar_body' && target.parentNode.id != 'iCalendar' && target != CLD.$$)
CLD.getId('iCalendar').style.display="none";
};
return this
},
getId:function(id){
return typeof id === 'string'?document.getElementById(id):id;
},
update:function(y,m){
var uiList=[],week='日一二三四五六'.split(''),$=this.$,getId = this.getId,
fn=function(a,b){
return '<b class="btn" onclick="CLD.update('+a+')">'+b+'</b>'
};
y&&$.setYear($.getFullYear()+y);
m&&$.setMonth($.getMonth()+m);
var Y=$.getFullYear(),M=$.getMonth()+1,D=$.getDate();
for (var i=0;i<week.length;i++ )uiList.push('<b>'+week[i]+'</b>');
for (i=0;i<$.fDay();i++ )uiList.push('<b> </b>');
for (i=0;i<$.dCount();i++ )uiList.push('<a href="javascript:CLD.set('+Y+','+M+','+(i+1)+')">'+(i+1)+'</a>');
getId('iCalendar_body').innerHTML=fn('-1,null','<<')+fn('null,-1','<')+'<b id="dateCap">'+Y+'年'+M+'月</b>'+fn('null,1','>')+fn('1,null','>>')+uiList.join('');
if(this.$$)this.$$.focus();
getId('iCalendar_mask').width=getId('iCalendar_body').offsetWidth-2;
getId('iCalendar_mask').height=getId('iCalendar_body').offsetHeight;
},
showTo:function(v){
for(var pos={x:0,y:0},$=v;v;v=v.offsetParent){
pos.x+=v.offsetLeft;
pos.y+=v.offsetTop
};
document.title=[pos.x,pos.y]
with(this.getId('iCalendar').style){
left=pos.x+"px";
top=(pos.y+$.offsetHeight)+"px";
display=""
}
},
bind:function(x){
x.onfocus=function(){
CLD.$$=this;
CLD.showTo(this)
};
return this;
},
set:function(y,m,d){
if(CLD.$$){
CLD.$$.value=y+'-'+m+'-'+d;
CLD.getId('iCalendar').style.display="none";
}
}
}).init().bind(CLD.getId('i_1')).bind(CLD.getId('i_2'))
</script>