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>

posted @ 2009-03-27 23:58  Devil_Zhang  阅读(284)  评论(0编辑  收藏  举报