JavaScript案例精解(五)
日历
运行效果:
思路:
这个例子的思路总体有两个难点:
1、计算任意月份的第一天是星期几,即日历显示时,当前月份对应星期几
2、日历窗口的层(<div>、<span>)的构造,及其相应的事件(点击日期号填写当前的日期,点击年份选择年份,点击月份选择月份)。
在解决第一个问题时:我们选确定好1900-1-1是星期一,然后累计计算任意年份月份的第一天是第几天,然后模7,得出的结果就是当前月份第一天是星期几。这个问题充份考查我们的逻辑推导能力。
至于第二个问题,我感觉没有什么复杂的逻辑。取而代之的是javascript语法对样式表及层对象的灵活控制。
所以说javascript制作日历是个不错的例子,下面我们一一解析代码:
HTML代码:
在HTML代码中,为了追求日历代码的独立性,我们在HTML代码中并未涉及日历所需要的层(<div>),所有日历所需要的层都是由Javascript代码动态创建的。
所以这里的HTML代码只有一个文本框和一个按钮,在该按钮的onclientclick事件中我们调用日历代码中的calendar('TextBox1')函数。calendar函数的实参传入的是接收选中日期的文本框。
<form id="form1" runat="server">
<div>
出生日期:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" OnClientClick="return calendar('TextBox1')" runat="server" Text="Button" />
</div>
</form>
Javascript代码:
由于时间的关系,代码编写仓促,未经过充份测试。这里的代码量有点大,感兴趣的朋友可以进行研究与改进,当然也可以直接复制进行使用。
<script>
//日历的边框线的颜色(车延禄)
var bordcolor="blue";
//日历的背景色
var backcolor="#ccaaff";
//月份和年份标题栏的背景颜色
var titlecolor="#ffccaa";
//星期行的背景色
var weekcolor="yellow";
//当前日期的背景色
var todaycolor = "red";
//日历的字体的大小
var fontsize = "12px";
//日历中前景色
var fontcolor="black";
//日历的宽雅
var width=210;
//为HTML文档的BODY标记动态添加onclick事件,当在页面任意处单击鼠标时,将会隐藏日历。
document.body.onclick = function()
{
var calendar = document.getElementById("calendar");
if(calendar != null)
{
calendar.style.display="none";
}
}
function pit(e) //对象的绝对位置
{
var t = {x:e.offsetLeft,y:e.offsetTop};
if(e=e.offsetParent)
{
var r = pit(e);
t.x+=r.x;
t.y+=r.y;
}
return t;
}
//日历控件的主函数,它调用其它函数来实现日历的构建,形参target是目标控件的ID,日历控件会在ID是target控件下显示,并把选中的日期值填充到ID是target的控件中去。
function calendar(target)
{
//查找ID是calendar的日历对象,如果未找到就创建该创建该日历,如果找到了就显示该日历对象
var datepane = document.getElementById("calendar");
if(datepane == null)
{
//datepane是日历控件的最外层的div,其它所有的div或span都在该div内部显示。
datepane = document.createElement("div");
//下面是设置该DIV的各个属性
datepane.id="calendar";
datepane.style.backgroundColor=backcolor;
datepane.style.width=width;
datepane.style.display = "block";
datepane.style.position = "absolute"; //此属性必须要设置,束则日历控件无法浮动定位显示
datepane.style.borderColor=bordcolor;
datepane.style.borderWidth=1;
datepane.style.borderStyle="solid";
//日历控件单击事件处理代码
datepane.onclick = function()
{
//取消事件冒泡
window.event.cancelBubble = true;
//在日历中单击,清除年份选择列表
var selyear = document.getElementById("ddSelYear");
if(selyear != null)
{
selyear.outerHTML = "";
}
//在日历中单击,清除月份选择列表
var selmonth = document.getElementById("ddSelMonth");
if(selmonth != null)
{
selmonth.outerHTML = "";
}
}
var txt = document.getElementById(target);
//设置日历控件的左边位置,这里注意目标控件用的是offsetLeft属性。
datepane.style.left = txt.offsetLeft+document.body.offsetLeft;
//设置日历控件的右边位置
var e = pit(event.srcElement.parentNode);
datepane.style.top = e.y+txt.offsetHeight;
//datepane.style.top = txt.offsetTop + txt.offsetHeight+document.body.offsetTop;
document.body.appendChild(datepane);
}
else
{
datepane.style.display = "block";
}
//清空日历控件的内容
datepane.innerHTML = "";
//调用createTitle函数添加年份、月份所在的标题栏
datepane.appendChild(createTitle(target));
//调用createWeek函数添加星期栏
datepane.appendChild(createWeek());
//调用createDate函数添加日期
datepane.appendChild(createDate(target));
//取消事件冒泡
window.event.cancelBubble=true;
//阻止按钮的提交
return false;
}
//当点击年份标签时,调用该函数显示年份列表
function selectYear(target)
{
//创建ddSelYear层,该层中包含好多个div(即yearitem),每个div中包函一个年份。
var div = document.createElement("div");
div.id="ddSelYear";
div.style.width=50;
div.style.height=100;
div.style.zIndex=5; //此值宜大些,以便在日历控件最顶层显示该层,以便用户选择年份
div.style.display="block";
div.style.position="absolute"; //使期绝对定位浮动显示
div.style.backgroundColor="#ccffcc"; //设置期背景色
div.style.fontSize = 12;
div.style.overflow = "auto"; //当超出高度时自动产生滚动条
for(var i=1900;i<=2100;i++)
{
//ddSelYear年份列表层中的每一年份项。
var yearitem = document.createElement("div");
yearitem.style.cursor = "hand";
yearitem.innerHTML = i;
//当该年份被点击时触发的代码
yearitem.onclick = function()
{
//将日历控件中的当前年份设为选中的年份
document.getElementById("ddyear").innerHTML = this.innerHTML;
//调用createDate()方法刷新当前选中年月份中的日期结构
createDate(target);
}
div.appendChild(yearitem);
}
//返回该年份列表的层
return div;
}
//当点击月份时调用该函数,显示月份列表,此函数中属性的值的设置与selectYear函数中的内容类似,在此不一一注释
function selectMonth(target)
{
var div = document.createElement("div");
div.id="ddSelMonth";
div.style.width=50;
div.style.height=100;
div.style.zIndex=5;
div.style.display="block";
div.style.position="absolute";
div.style.backgroundColor="#ccffcc";
div.style.fontSize = 12;
div.style.overflow = "auto";
for(var i=1;i<=12;i++)
{(车延禄)
var monthitem = document.createElement("div");
monthitem.style.cursor = "hand";
monthitem.innerHTML = i;
monthitem.onclick = function()
{
document.getElementById("ddmonth").innerHTML = this.innerHTML;
createDate(target);
}
div.appendChild(monthitem);
}
return div;
}
//创建年月份标题函数
function createTitle(target)
{
//标题栏容器div
var title = document.createElement("div");
title.style.backgroundColor = titlecolor;
title.style.width = width;
title.style.textAlign="center";
//取出当前日期,以便下面并把年份div内默认设为当前年份,月份div内默认设为当前月份
var now = new Date();
//创建年份div,设定默认值
var spanyear = document.createElement("span");
spanyear.id="ddyear";
spanyear.style.cursor = "hand";
spanyear.innerHTML = now.getFullYear();
//为该年份div设置单击事件处理程序
spanyear.onclick = function()
{
//调用上面的selectYear函数,显示年份选择列表,以供用户选择,改变年份
var selyear = selectYear(target);
document.getElementById("calendar").appendChild(selyear);
selyear.style.top = this.offsetTop; //年份列表的顶部位置与年份div的顶部对齐
selyear.style.left = this.offsetLeft; //年份列表的左边与年份div的左边对齐
window.event.cancelBubble=true; //取消事件冒泡,这里一定要加上
}
//创建月份div,设定默认值
var spanmonth = document.createElement("span");
spanmonth.id="ddmonth";
spanmonth.style.cursor = "hand";
spanmonth.innerHTML = now.getMonth()+1;
//为该月份div设置单击事件处理程序
spanmonth.onclick=function()
{
//调用上面的selectMonth函数,显示月份选择列表,以供用户选择,改变月份
var selmonth = selectMonth(target);
document.getElementById("calendar").appendChild(selmonth);
selmonth.style.top = this.offsetTop;
selmonth.style.left = this.offsetLeft;
window.event.cancelBubble=true;
}
//加上“年”“月”两个汉字
var txtyear = document.createElement("span");
txtyear.innerHTML = "年";
var txtmonth = document.createElement("span");
txtmonth.innerHTML = "月";
title.appendChild(spanyear);
title.appendChild(txtyear);
title.appendChild(spanmonth);
title.appendChild(txtmonth);
//返回标题栏对象
return title;
}
//创建星期栏的函数
function createWeek()
{
//星期栏对象容器,其中容纳周一至周日七个span标签
var week = document.createElement("div");
week.style.width=width;
week.style.backgroundColor=weekcolor;
week.style.fontSize = 12;(车延禄)
week.style.textAlign="center";
//创建“星期一”的标签
var td1 = document.createElement("span");
//设置标签的显示方式,一定要设置为inline-block,否则span标记的style.width=30不会起作用的。
td1.style.display = "inline-block";
td1.style.width=30;
td1.innerHTML ="一";
//以下的标签与上面类似
var td2 = document.createElement("span");
td2.style.display = "inline-block";
td2.style.width=30;
td2.innerHTML ="二";
var td3 = document.createElement("span");
td3.style.display = "inline-block";
td3.style.width=30;
td3.innerHTML ="三";
var td4 = document.createElement("span");
td4.style.display = "inline-block";
td4.style.width=30;
td4.innerHTML ="四";
var td5 = document.createElement("span");
td5.style.display = "inline-block";
td5.style.width=30;
td5.innerHTML ="五";
var td6 = document.createElement("span");
td6.style.display = "inline-block";
td6.style.width=30;
td6.innerHTML ="六";
var td7 = document.createElement("span");
td7.style.display = "inline-block";
td7.style.width=30;
td7.innerHTML ="日";
//将周日作为一周中的第一天,这样编码更方便。
week.appendChild(td7);
week.appendChild(td1);
week.appendChild(td2);
week.appendChild(td3);
week.appendChild(td4);
week.appendChild(td5);
week.appendChild(td6);
//返回星期栏对象
return week;
}
//创建日期项的函数
function createDate(target)
{
//如果已存在日期栏容器,则将其内容清空,以便下面重新填充
//如果不存在日期栏容器,则创建之
var datediv;
if(document.getElementById("showdate")!=null)
{
datediv = document.getElementById("showdate");
datediv.innerHTML = "";
}
else
{
datediv = document.createElement("div");
}
//取出当前要显示的年份
var year = document.getElementById("ddyear").innerHTML;
//取出当前要显示的月份
var month = document.getElementById("ddmonth").innerHTML;
//日期容器div的ID
datediv.id="showdate";
//调用getDateOfWeek函数,计算当前月份中的第一天是星期几,以便决定从哪个位置开始填写本月第一天。
var weekday = getDateOfWeek(year,month,1);
//计算当前月份的天数,以决定在日期栏容器中填写多少天,它是填写循环结束的条件
var daycount = 31;//当前月中的日期的天数
if(month==4||month==6||month==9||month==11)
{
daycount = 30;
}
else if(month == 2)
{
if(i%400==0||(i%4==0&&i%100!=0))
{
daycount = 29;
}
else
{
daycount = 28;
}
}
var daynum = 1;//要填写的日期,从当前月份的1号开始
var over=false; //是否达到本月最大天数,即当前月份中的日期是否创建完成
//循环日期行
for(var i=0;i<10;i++) //循环日历中的日期行,这里的i<10无实际意义,可以设得更大一些
{
//创建该日历行的容器,第i周的容器
var row = document.createElement("div");
row.style.width = width; //设置期宽雅与日历控件同宽
row.style.height = 25; //行高
row.style.verticalAlign="middle";
//在当前日历行中逐一创建每一天(span),并设置日期和事件
for(var j=0;j<7;j++)
{
//如果这个月的第一天不是星期天,则在里面添加空的span标记,并进入下次循环,
if(i==0 && j<weekday)
{
var cell = document.createElement("span");
cell.style.display = "inline-block";
cell.style.width = 30;
cell.style.textAlign="center";
row.appendChild(cell);
continue; //进行下次循环(车延禄)
}
//创建日期显示span,显示当前日期行中的每一天
var cell = document.createElement("span");
cell.style.display = "inline-block";//此属性必须设置
cell.style.width = 30;
cell.style.cursor = "hand";
cell.style.textAlign="center";
cell.style.fontSize="12";
//如果该span是今天,则将设置期红色背景
var d = new Date();
if(d.getFullYear() == parseInt(year)&&d.getMonth()+1==parseInt(month)&&d.getDate()==parseInt(daynum))
{
cell.style.backgroundColor=todaycolor;
}
//设置日期span中的文本
cell.innerHTML = daynum++;
//设置自定义属性value(yyyy-MM-dd),以便在单击该span时将其值写入文本框
cell.value = year+"-"+month+"-"+cell.innerHTML;
//当该日期被单击时要执行的处理代码
cell.onclick = function()
{
// 把当前span标签中的 value值取出来送到target所指定的文本框中。
var tar = document.getElementById(target);
tar.value = this.value;
document.getElementById("calendar").style.display="none";
}
//把当前日期span添加到当前的日期行中去。
row.appendChild(cell);
//如果达到月底的话就结束循环
if(daynum > daycount)
{
over=true;
break;
}
}
datediv.appendChild(row);
//如果达到月底的话就结束循环
if(over == true)
{
break;
}
}
return datediv;
}
//计算指定的年、月、日是星期几
function getDateOfWeek(year,month,day)
{
var days = 0;//记录从1900-1-1至指定日期的累计总天数
//累加1900-1-1至year-1-1的天数
for(var i=1900;i<year;i++)
{
//闰年与平年
if(i%400==0||(i%4==0&&i%100!=0))
{
days += 366;
}
else
{
days += 365;
}
}
//累加从year-1-1至year-month-1这几个月中的天数
for(var j=1;j<month;j++)
{
switch(parseInt(j))
{
case 1:
days += 31;break;
case 2:
if(year%400==0||(year%4==0&&year%100!=0))
{
days += 29;
}
else
{
days += 28;
}
break;
case 3:
days += 31;break;
case 4:
days += 30;break;
case 5:
days += 31;break;
case 6:
days += 30;break;
case 7:
days += 31;break;
case 8:
days += 31;break;
case 9:
days += 30;break;
case 10:
days += 31;break;
case 11:
days += 30;break;
case 12:
days += 31;break;
}
}
//再累加从year-month-1至year-month-day的天数
days += day;
//总天数模上7就是星期几(车延禄)
return days%7;
}
</script>