文盲的 JavaScript 实战篇之二:制作一个简单的日历控件

    在我们日常的数据维护中,日期的格式总是一个大问题,因为它的书写格式实在太多了,可以是 2008-12-15,也可以是 2008.12.15,还可以是12/15/2008,有的用户在填写时间的时候直接填写成这样2008年12月, 甚至有的用户一不注意,就把数字填写成全角字符如2008-12-15,这就造成了我们数据校验上的麻烦。

    如何让用户在输入日期的时候统一标准则是我们解决这个麻烦的关键。在本文中,将介绍一下,如何制作一个简单的日历控件,让用户选择并生成同意格式的日期数据。

    首先,我们可以设想一下,平时我们让用户输入日期数据的控件是一个 input ,类型为文本类型的元素,我们可以将这个方法沿用,但我们对它进行了一些限定:不允许用户修改该元素的数据。
  1. <input type="text" size="10" name="date1" value="2008-12-15" readonly />
    我们的设想是:当用户点击到这个元素的时候,弹出一个层,这个层内包含日历元素,在选择好日期后,将结果返回到我们点击的这个元素内。那么,我们需要在这个元素上增加一个触发事件 onclick
  1. <input type="text" size="10" name="date1" value="2008-12-15" readonly onclick="calendar(this);" />
    calendar 函数就是我们调用日历控件的函数了,而 this 是将我们点击的元素作为参数传递给日历处理程序中,用以确认初始的日期数据和返回日期数据。

    我们现在就可以在 calendar 函数中,定义我们需要的操作了。但是,我们在定义具体的操作之前,先设置一下日历中需要用到的样式比较好,这样,我们就可以在编写过程的时候直接进行样式的引用,相对在程序内定义样式要方便很多。
  1. .calendar           {width:165px;*width:160px;border:2px outset #CCCCCC;padding:3px;background:white;position:absolute;z-index:50;overflow:hidden;}
  2. .calendar .dayArea  {width:100%;padding-top:3px;margin-top:3px;border-top:1px solid #CCCCCC;text-align:center;font-size:9pt;line-height:20px;}
  3. .calendar .dayArea div  {width:20px;height:20px;float:left;margin-left:1px;margin-top:1px;}
  4. .calendar .day      {border:1px outset #CCCCCC;cursor:pointer;}
  5. .calendar .act      {border:1px outset #CCCCCC;background:#FFDDCC;cursor:pointer;}
  6. .calendar .week     {border:1px solid white;font-weight:bold;}
    现在,开始制作第一个函数,也是初始化函数 calendar()。我们的设想是这样的:在页面内第一次调用日历的时候,生成日历控件,在随后使用的时候,只是改变日历控件的位置和传入的初始日期即可。那么,就给日历定义一个 ID 为 _calendar,用 _ 开头可以尽量避免 ID 的重复,恩,只是个人习惯问题:)。
  1. function calendar(obj){
  2.     if (!document.getElementById('_calendar')){
  3.         var el = document.createElement('div');
  4.         el.id = '_calendar';
  5.         el.className = 'calendar';
  6.         document.body.appendChild(el);
  7.     }else{
  8.         var el = document.getElementById('_calendar');
  9.         el.style.display = 'block';
  10.     }
  11. }
    很简单的,我们就建立了一个活动的层,在再次使用这个函数的时候,会将 _calendar 层显示出来。
    考虑到日历中,年份和月份基本上没有什么变化,我们把年份和月份的函数先行制作出来
  1. function createYear(){
  2.     var el = document.createElement('select');
  3.     el.size = 1;
  4.     var o = document.createElement('option');
  5.     var nt = new Date();
  6.     for (i = nt.getFullYear() + 5 ; i > 1899 ; i -- ){ // 日历中的年份从当前年份之后的五年开始,一直到 1900 年结束
  7.         el.appendChild(o.cloneNode(true));
  8.         el.options[nt.getFullYear() + 5 - i].value = i;
  9.         el.options[nt.getFullYear() + 5 - i].text = i + ' 年';
  10.     }
  11.     return el;
  12. }
  13. function createMonth(){
  14.     var mn = ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'];
  15.     var el = document.createElement('select');
  16.     var o = document.createElement('option');
  17.     for (i = 0 ; i < mn.length ; i ++ ){
  18.         el.appendChild(o.cloneNode(true));
  19.         el.options[i].value = i + 1; // 因为 js 中 0 表示的是一月,但是我们的习惯不是这样的,所以先把这个值进行加 1,来符合我们的习惯
  20.         el.options[i].text = mn[i];
  21.     }
  22.     return el;
  23. }
    createYear 和 createMonth 各自建立了一个 select 控件,分别列出了年的范围和月的范围,我们可以将这两个控件引用到 calendar 函数中。
  1. function calendar(obj){
  2.     var nt = new Date(); // 取得当前机器时间
  3.     var d = obj.value?obj.value:(nt.getFullYear() + '-' + (nt.getMonth() + 1) + '-' + nt.getDate()); // 如果触发日历的控件包含日期信息,则取出这个日期,否则,根据机器时间生成一个 yyyy-m-d 格式的日期
  4.     var s = d.split('-'); // 将 yyyy-m-d 格式的日期切割成数组
  5.     if (!document.getElementById('_calendar')){  // 判断页面是否已经存在 ID 为 _calendar 元素,如果不存在则
  6.         var el = document.createElement('div'); // 使用 Dom 方法建立一个 div 层
  7.         el.appendChild(el.cloneNode(true)); // 在这个层中复制一个没有内容的层
  8.         el.id = '_calendar'// 定义层的 ID
  9.         el.className = 'calendar'// 定义层引用的样式
  10.         el.childNodes[0].appendChild(createYear()); // 在刚才复制的层中添加年份控件
  11.         el.childNodes[0].appendChild(createMonth()); // 在刚才复制的层中添加月份控件
  12.         el.childNodes[0].style.textAlign = 'center'// 定义刚才复制的层为居中显示方式
  13.         var v = document.createElement('input'); // 定义一个中转控件,用来存放原始日期数据
  14.         el.appendChild(v); // 将控件添加到日历层中
  15.         v.style.display = 'none'// 将中转控件显示方式定义为隐藏
  16.         document.body.appendChild(el); // 将日历控件添加到页面内
  17.     }else// 如果存在则
  18.         var el = document.getElementById('_calendar');
  19.         el.style.display = 'block'// 将日历控件显示方式定义为显示
  20.     }
  21.     el.childNodes[0].childNodes[0].onchange = el.childNodes[0].childNodes[1].onchange = function(){getDayList(obj);} // 定义年份、月份控件的触发事件 onchange,当控件选择的内容变动时引用 getDayList 函数
  22.                                                                                                    // 这里使用闭包将我们点击的对象作为参数传递给 getDayList 函数
  23.     el.childNodes[1].value = d; // 将原始日期存放到中转控件内
  24.     el.style.left = obj.offsetLeft + 'px'// 定义日历左边的位置为点击对象的位置
  25.     el.style.top = (obj.offsetTop + obj.offsetHeight - 3) + 'px';  // 定义日历顶端的位置为点击对象底边向上3像素的位置
  26.     el.childNodes[0].childNodes[0].selectedIndex = nt.getFullYear() + 5 - s[0]; // 初始化年份控件的当前选项
  27.     el.childNodes[0].childNodes[1].selectedIndex = s[1] - 1; // 初始化月份控件的当前选项
  28.     getDayList(obj); // 生成日期信息
  29. }
    现在,我们需要在 getDayList 函数中从新处理一下现有的数据,用以生成所显示的月份的日期列表
  1. function getDayList(obj){
  2.     if (!document.getElementById('_calendar')) return// 检测页面内是否存在 ID 为 _calendar 的元素
  3.     var el = document.getElementById('_calendar');
  4.     if (el.childNodes.length > 2) el.removeChild(el.childNodes[el.childNodes.length - 1]); // 如果元素内的子节点超过两个,则删除第三个节点
  5.     var n = el.childNodes[1].value; // 取得原始日期信息
  6.     var y = el.childNodes[0].childNodes[0]; // 取得年份控件
  7.     var m = el.childNodes[0].childNodes[1]; // 取得月份控件
  8.     createDay(el,n.split('-'),[y.options[y.selectedIndex].value,m.options[m.selectedIndex].value],obj); // 将日历控件、原始日期、控件对应日期、点击对象传递给日期生成函数
  9. }
    最后,就是我们的日期生成部分了
  1. function createDay(obj,s,n,result){
  2.     var el = document.createElement('div');
  3.     var d = el.cloneNode(true);
  4.     var w = ['日','一','二','三','四','五','六'];
  5.     d.className = 'week';
  6.     for (var i = 0 ; i < 7 ; i ++ ){ // 生成星期排列
  7.         var e = d.cloneNode(true);
  8.         e.innerHTML = w[i];
  9.         el.appendChild(e);
  10.     }
  11.     var ms = new Date(n[0],n[1] - 1,1); // 取得需要显示的月份的第一天日期
  12.     var me = new Date(n[0],n[1],0); // 取得需要显示的月份的最后一天日期
  13.     for (var i = 0 ; i < ms.getDay() ; i ++ ){ // 根据第一天的星期数补空
  14.         var e = d.cloneNode(true);
  15.         el.appendChild(e);
  16.     }
  17.     d.className = 'day';
  18.     for (var i = 0 ; i < me.getDate() ; i ++ ){ // 根据最后一天的日期,生成相应数量的日期元素
  19.         var e = d.cloneNode(true);
  20.         e.innerHTML = i + 1;
  21.         if ((s[2] == i + 1)&&(s[0]==n[0])&&(s[1]==n[1])) e.style.background = '#CCDDFF';
  22.         el.appendChild(e);
  23.         e.onmouseover = function(){ // 定义日期元素鼠标经过的动作
  24.             this.className = 'act';
  25.         }
  26.         e.onmouseout = function(){ // 定义日期元素鼠标离开的动作
  27.             this.className = 'day';
  28.         }
  29.         e.onclick = function(){ // 定义日期元素点击的动作
  30.             var y = obj.childNodes[0].childNodes[0];
  31.             var m = obj.childNodes[0].childNodes[1];
  32.             result.value = y.options[y.selectedIndex].value + '-' + m.options[m.selectedIndex].value + '-' + this.innerHTML; // 生成选中的日期
  33.             obj.style.display = 'none'// 隐藏日历控件
  34.         }
  35.     }
  36.     el.className = 'dayArea';
  37.     obj.appendChild(el);
  38. }
    这样,一个简单的日历控件就完成了,当然,这个控件的功能并不完善,例如,没有限定最小日期,没有限定最大日期,没有节日,没有。。。。总之,很多功能都没有,仅仅能选择日期而已,并且,代码没有按照面向对象方式编写,很多缺点。。。呵呵

你可能感兴趣的:(文盲的 JavaScript 实战篇之二:制作一个简单的日历控件)