一步步教你實現跨遊覽器的JS日曆

在開始之前我們先複習一下javascript的Date對象,這是一個內置對象,它包含了一系列方法供我們操作。按功能可以分爲以下三大類:

獲得時間方法:

  • getDate() 查看Date對象並返回日期
  • getDay() 返回星期幾
  • getHours() 返回小時數
  • getMinutes() 返回分鐘數
  • getMonth() 返回月份值
  • getSeconds() 返回秒數
  • getMilliseconds()返回毫秒值
  • getTime() 返回完整的時間
  • getYear() 返回年份
  • getFullYear()返回一個四位數表示的年份
  • getTimezoneOffset() 返回本地時間和GMT相差的分鐘數

注:用getYear()返回的數並不一定是4位的!處於1900年和1999年間的getYear()方法返回的只有兩位數。在此之前的或是在此之後的年份返回的都是四位數的。getYear()方法不應該再使用了。推薦使用getFullYear方法。另,javascript也提系列基於世界時的時間設置函數,如 getUTCDate(),getUTCDay(),getUTCFullYear(),getUTCHours(),getUTCMilliSeconds(),getUTCMinutes(),getUTCMonth ()與getUTCSeconds()方法。

/********根據一個日期求得星期,如'2009-6-21' return 0(星期日)***********/
var get_day = function (strDate){
    var f = strDate.replace(/-/g,'/');
    f = new Date(f).getDay();
    return "星期"+"天一二三四五六".split('')[f]
}
alert(get_day('2009-7-25'))

設置時間方法:

  • setDate() 改變Date對象的日期
  • setYear() 改變年份
  • setMonth() 改變月份
  • setHours() 改變小時數
  • setMinutes() 改變分鐘數
  • setSeconds() 改變秒數
  • setTime() 改變完整的時間
//判斷是否是閏年,返回true或者false Date.prototype.isLeapYear=function(){   varyear=this.getFullYear();   return(0==year%4&&((year%100!=0)||(year%400==0))); } //根據日期返回該日期所在年的週數 Date.prototype.getWeekNum=function(){   vardat=newDate(this.getFullYear(),0,1);   varweek=dat.getDay();   week=(week==0?7:week);   vardays=this.calDateDistance(dat,"dd")+1;   return((days+6-this.getDay()-7+week)/7); }

注,由於javascript是從0開始的,因此需要對月份進行操作時要加1 .

參數 描述
month 必需。一個表示月份的數值,該值介於 0(一月) ~ 11(十二月) 之間。
day 可選。一個表示月的某一天的數值,該值介於 1 ~ 31 之間(以本地時間計)。在 EMCAScript 標準化之前,不支持該參數。
var now = new Date()
var currentMonth = now.getMonth() -1 //獲得當前的月份
var nextMonth = now.getMonth() //獲得下一個月的月份
//用javascript取得某一年的第一個星期一的日期 
function get(year) {
    var d = new Date(year, 1, 1);
    var day = d.getDay(); //獲取1月1號是星期幾
    d.setDate((8 - day) % 7 + 1);
    return d;
}
//求前 n 天或者後 n 天的日期(用xxxx-xx-xx表示)
var showdate = function(n){
    var d = new Date();
    d.setDate(d.getDate()+n);
    //或者 d = d.getFullYear() + "-" +  (d.getMonth()+1) + "-" + d.getDate();
    d = d.toLocaleDateString().replace(/[\u4e00-\u9fa5]/g,'-').replace(/-$/,'')    
    return d;
}
alert("今天是:"+showdate(0));
alert("昨天是:"+showdate(-1));
alert("明天是:"+showdate(1));
alert("10天前是:"+showdate(-10));
alert("8天后是:"+showdate(8));
//將2005-8-5轉換成2005-08-05格式 
var strDate = '2005-8-5'
window.alert(strDate.replace(/\b(\w)\b/g, '0$1'));
// 對Date的擴展,將 Date 轉化爲指定格式的String 
// 月(M)、日(d)、小時(h)、分(m)、秒(s)、季度(q) 可以用 1-2 個佔位符, 
// 年(y)可以用 1-4 個佔位符,毫秒(S)只能用 1 個佔位符(是 1-3 位的數字) 
// 例子: 
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423 
// (new Date()).Format("yyyy-M-d h:m:s.S")      ==> 2006-7-2 8:9:4.18 
Date.prototype.Format = function(fmt) { //@author: meizz 
    var o = { 
        "M+" : this.getMonth()+1,                 //月份 
        "d+" : this.getDate(),                    //日 
        "h+" : this.getHours(),                   //小時 
        "m+" : this.getMinutes(),                 //分 
        "s+" : this.getSeconds(),                 //秒 
        "q+" : Math.floor((this.getMonth()+3)/3), //季度 
        "S"  : this.getMilliseconds()             //毫秒 
    }; 
    if(/(y+)/.test(fmt)) 
        fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length)); 
    for(var k in o) 
        if(new RegExp("("+ k +")").test(fmt)) 
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length))); 
    return fmt; 
}

轉換時間方法:

  • toGMTString() 把Date對象的日期(一個數值)轉變成一個GMT時間字符串,返回類似下面的值:Weds,15 June l997 14:02:02 GMT(精確的格式依賴於計算機上所運行的操作系統而變)
  • toLocaleString() 把Date對象的日期(一個數值)轉變成一個字符串,使用所在計算機上配置使用的特定日期格式
  • UTC() 使用Date UTC(年、月、日、時、分、秒),以自從1970年1月1日00:00:00(其中時、分、秒是可選的)以來的毫秒數的形式返回
  • toLocaleDateString() 方法可根據本地時間把 Date 對象的日期部分轉換爲字符串,並返回結果
  • toLocaleTimeString() 方法可根據本地時間把 Date 對象的時間部分轉換爲字符串,並返回結果

僅顯示當前月的日曆

javascript日曆可以寫得很簡單,也可以弄得很複雜。作爲起步,我們先實現當前月的顯示,然後再一點點改進。(其實我就不明白,分明是一個個月顯示的,爲什麼不叫月曆,而叫日曆?!)

 

我們先觀察左邊這個樣板,日曆通常是分爲三部分,最上方是年份與星期數,中間是按一定規則排列的天數,最下方是按鈕,當然這幅圖缺了這個。一般而言,難點就是中間部分。我們要確定當月的第一天是星期幾,要與上方的星期數對應,然後一個接一個向下排。而放置天數的方法,一般都是塞在一個表格中,需要嵌套循環來分別生成tr與td。爲此,我想到另一個方法,把天數放置a元素中,利用液體佈局的方式讓它們自然地從左到右從上到下排列在一個div中。這樣做個好處,就是hover樣式是css原生的,不需要我們通過js的mouseover事件來綁定。爲了塞滿div,並分行顯示,我們需要指定a元素的display爲block,這樣它的寬與高才能生效,但這樣一行只能有一個a元素,因此我們還得指定其向左浮動,並限制div的寬與高,這樣元素就按照我們的想法排序了。爲了防止元素溢出,我們同時得把其容器也浮動。

現在的問題怎樣設置這些元素的內容,日曆上面的空間並不都有數字的。首先我們得知道當前月第一天是星期幾,因爲js會把星期幾轉化爲我們日曆的第一行的數字;然後的問題是這個月有幾天。我們先做個測試,把數組填空了再說!

window.onload = function(){
    //*********************第一階段,填空日期數組*******************
    var now = new Date(),
    date = now.getDate(),//當前天數
    month = now.getMonth() + 1,//當前的月份
    year = now.getFullYear(),//當前的年份
    firstday = new Date(now.getFullYear(), month -1  ,1).getDay(), //求出當月的第一天是星期幾
    lastday = new Date(now.getFullYear(), month , 0).getDate(),//上月的第0天就是今月的最後一天
    dates = lastday,//最後一天的號數就是這個月的天數
    arr = new Array(42);//用來裝載日期的數組,日期以‘xxxx-xx-xx’的形式表示
    for(var i = 0,j = firstday; i < dates ; i ++ ,j ++){
        arr[j] = year +'-'+ month +'-'+ (i+1) ;
    }
    /***********************以下是測試部分**********************/
    for(var i = 0;i <42;i++){
        document.write(arr[i]+' ');//打印測試結果
    }
}

接着下來我們就可以繪製UI了,我們用一個DIV來代表日曆本身,頭部由填滿一行的span表示(也就是把span的display設爲block),緊接着是星期數,它們都是a元素,下面是日期數,也是a元素。我們需要區分a元素的樣式,讓表示星期數的帶上一個class來特別設置。樣式我們暫時放棄動態生成。

<!doctype html>
<html dir="ltr" lang="zh-CN">
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=Edge">
        <title>跨遊覽器的JS日曆</title>
        <style type="text/css">
            #jcalendar {
                width:210px;
                background:#E0ECF9;
                border:1px solid #479AC7;
                float:left;
            }
            #jcalendar span {
                float:left;
                width:210px;
                height:20px;
                background:#479AC7;
                color:#f90;
                font-weight:bolder;
                text-align:center;
            }
            #jcalendar .week {
                background:#D5F3F4;
                color:#808080;
            }
            #jcalendar .current{
                background:#336699;
                color:#fff;
            }
            #jcalendar a {
                display:block;
                float:left;
                width:30px;
                height:20px;
                color:#000;
                line-height:20px;
                text-align:center;
                text-decoration:none;
            }
        </style>
        <script type="text/javascript">
        </script>
    </head>
    <body>
    </body>
</html>
window.onload = function(){
    //*********************第一階段,填空日期數組*******************
    //***************************略**************************
    //**********************第二階段,繪製UI******************
    var body = document.getElementsByTagName('body')[0],//body的快捷引用
    a = document.createElement('a'),//日曆的a元素,用於克隆
    calendar = document.createElement('div'),//日曆的容器元素
    thead = document.createElement('span');//日曆的頭部或頁眉
    body.insertBefore(calendar,null);//把日曆加入DOM樹中
    calendar.setAttribute('id','jcalendar');
    thead.innerHTML = year +"年  " + month +"月"
    calendar.appendChild(thead);
    var weeks = "日一二三四五六".split('');//日曆第二行的內容,顯示星期幾
    for(i = 0;i< 7;i++){
        var th = a.cloneNode();
        th.innerHTML = weeks[i];
        th.className = 'week';
        calendar.appendChild(th);
    }
    for(i = 0;i <42;i++){
        var td = a.cloneNode();
        if(arr[i] == undefined ){
            calendar.appendChild(td);
        }else{
            var html = arr[i].split('-')[2];
            td.innerHTML = html;
            if(html == date){
                td.className = 'current';
            }
            calendar.appendChild(td);
        }
    }
}

好了,靜態的日曆就做出來了,現在我們加入動態元素,在頭部添加四個按鈕,讓它選擇上一月,下一月,上一年與下一年。由於我們的日曆是基於那個填空數組,而填空數組是是基於年份與月份,因此年份與月份一改變,我們就不可避免地重繪整個日曆。我們把重繪日曆的邏輯獨立出來做成一個函數,好讓按鈕上的函數去調用它!並在日期上添加懸浮效果與單擊事件。

添加新樣式

#jcalendar .ybn {
    color:#000;
}
#jcalendar .mbn {
    color:#000040;
}
#jcalendar .weekend {
    color:#f00!important;
}
#jcalendar a.day:hover{
    background:#99C3F6;
}

javascript修改爲:

var fillArray = function(year,month){
    var firstday = new Date(year, month -1  ,1).getDay(), //求出當月的第一天是星期幾
    lastday = new Date(year, month , 0).getDate(),//上個月的第零天就是今個月的最後一天
    dates = lastday,//最後一天的號數就是這個月的天數
    arr = new Array(42);//用來裝載日期的數組,日期以‘xxxx-xx-xx’的形式表示
    for(var i = 0,j = firstday; i < dates ; i ++ ,j ++){
        arr[j] = year +'-'+ month +'-'+ (i+1) ;
    }
    return arr;
}
var nextmonth = function(year,month,date){//按鈕事件1
    month = month + 1;
    if(month > 12)   year = year +1,month = 1;
    var arr = fillArray(year,month);
    drawCalendar(arr,year,month,date);
}
var nextyear = function(year,month,date){//按鈕事件2
    year = year + 1;
    var arr = fillArray(year,month);
    drawCalendar(arr,year,month,date);
}
var premonth = function(year,month,date){//按鈕事件3
    month = month - 1;
    if(month < 1)  year = year -1,month =12;
    var arr = fillArray(year,month);
    drawCalendar(arr,year,month,date);
}
var preyear = function(year,month,date){//按鈕事件4
    year = year - 1;
    var arr = fillArray(year,month);
    drawCalendar(arr,year,month,date);
}
var drawCalendar = function(arr,year,month,date){
    var _calendar = document.getElementById("jcalendar");
    if(_calendar) _calendar.parentNode.removeChild(_calendar);
 
    var body = document.getElementsByTagName('body')[0],//body的快捷引用
    a = document.createElement('a'),//日曆的a元素,用於克隆
    calendar = document.createElement('div'),//日曆的容器元素
    thead = document.createElement('span');//日曆的頭部或頁眉
    body.insertBefore(calendar,null);//把日曆加入DOM樹中
    calendar.setAttribute('id','jcalendar');
    var args = year+','+month+','+date,  
    preyear = '<TT class=ybn onclick="preyear('+args+')"><<</TT>',
    premonth = '<TT class=mbn onclick="premonth('+args+')"><</TT>',
    nextmonth = '<TT class=mbn onclick="nextmonth('+args+')">></TT>',
    nextyear = '<TT class=ybn onclick="nextyear('+args+')">>></TT>',
    str = new Date(args.replace(/,/g,'/')).toLocaleDateString();
    thead.innerHTML = preyear + ' '+premonth + ' '+str+' '+nextmonth + ' '+nextyear;
    calendar.appendChild(thead);
    var weeks = "日一二三四五六".split('');//日曆第二行的內容,顯示星期幾
    for(i = 0;i< 7;i++){
        var th = a.cloneNode();
        th.innerHTML = weeks[i];
        th.className = 'week';
        calendar.appendChild(th);
    }
    for(i = 0;i <42;i++){
        var td = a.cloneNode();
        if(arr[i] == undefined ){
            calendar.appendChild(td);
        }else{
            var html = arr[i].split('-')[2];
            td.innerHTML = html;
            td.className = 'day';
            td.href = "javascript:void(0)";//爲ie6準備的
            if(date && html == date){
                td.className = td.className +' current';
            }
            if(i%7 == 0 || i%7 == 6){
                td.className = td.className +' weekend';
            }
            td.onclick = (function(i){
                return function(){
                    alert(i);//這裏後面我們要修改,讓arr[i]填空文本域
                }
            })(arr[i]);
            calendar.appendChild(td);
        }
    }
}
window.onload = function(){
    //*********************第一階段,填空日期數組*******************
    var now = new Date(),
    month = now.getMonth() + 1,//當前的月份
    year = now.getFullYear(),//當前的年份
    date = now.getDate(),
    arr = fillArray(year,month);
    //**********************第二階段,繪製UI******************
    drawCalendar(arr,year,month,date);
}

好了,該有的都有了,現在封裝一下它吧。把上面四個按鈕事件合併成一個,加個更見名達義的名稱,然後統統塞到一個類中。

var Class = {
    create: function() {
        return function() {
            this.initialize.apply(this, arguments);
        }
    }
}
//*********************Jcalendar類開始**********************
var Jcalendar =  Class.create();
Jcalendar.prototype = {
    initialize:function(){
        var $ = new Date();
        this.drawCalendar($.getFullYear(),$.getMonth() + 1,$.getDate());
    },
    fillArray : function(year,month){
        var f = new Date(year, month -1  ,1).getDay(), //求出當月的第一天是星期幾
        dates = new Date(year, month , 0).getDate(),//上個月的第零天就是今個月的最後一天
        arr = new Array(42);//用來裝載日期的數組,日期以‘xxxx-xx-xx’的形式表示
        for(var i = 0; i < dates ; i ++ ,f ++){
            arr[f] = year +'-'+ month +'-'+ (i+1) ;
        }
        return arr;
    },
    drawCalendar : function(year,month,date){    
        var $ = document,$$ = 'createElement',
        _calendar = $.getElementById("jcalendar");
        if(_calendar) _calendar.parentNode.removeChild(_calendar);//推倒重繪!
        var body =  $.getElementsByTagName('body')[0],//body的快捷引用
        weeks = "日一二三四五六".split(''),//日曆第二行的內容,顯示星期幾
        calendar = $[$$]('div'),//日曆的容器元素
        a =  $[$$]('a'),//日曆的a元素,用於克隆
        tt =  $[$$]("tt"),//日曆頁眉的tt元素,用於克隆
        thead = $[$$]('span'),//日曆頁眉
        fragment = $.createDocumentFragment(),//減少DOM刷新頁面的次數
        arr = this.fillArray(year,month),//保存當月的日期
        tts = [],//用於保存tt元素的引用
        ths = this;//用於保存Jcalendar對象的實例的引用
        body.insertBefore(calendar,null);//把日曆加入DOM樹中
        calendar.setAttribute('id','jcalendar');
        for(var i = 0;i<4;i++){//循環生成出個時間按鈕。
            var clone = tt.cloneNode(true);//比重新createElement快
            clone.onclick =  (function(index){
                return function(){//在閉包裏綁定事件
                    ths.redrawCalendar(year,month,date,index)
                }
            })(i);
            tts[i] = clone;//保存引用
            if(i==2) thead.appendChild($.createTextNode(year+"年"+month+"月"+date+"日"));
            thead.appendChild(clone);
        }
        tts[0].innerHTML = '<<';
        tts[1].innerHTML = '<';
        tts[2].innerHTML = '>';
        tts[3].innerHTML = '>>';
        tts[0].className = tts[3].className = 'mbn';
        tts[1].className = tts[2].className = 'ybn';
        fragment.appendChild(thead);     
        for(i = 0;i <7;i++){//星期顯示區
            var th = a.cloneNode(true);
            th.innerHTML = weeks[i];
            th.className = 'week';
            fragment.appendChild(th);
        }
        for(i = 0;i <42;i++){//日期顯示區
            var td = a.cloneNode(true);
            if(arr[i] == undefined ){
                fragment.appendChild(td);
            }else{
                var html = arr[i].split('-')[2];
                td.innerHTML = html;
                td.className = 'day';
                td.href = "javascript:void(0)";//爲ie6準備的
                (date && html == date)&&(td.className += ' current') ;//高亮每個月今天這一天
                (i%7 == 0 || i%7 == 6)&&(td.className += ' weekend') ;//爲週末添加多一個類
                td.onclick = (function(i){
                    return function(){
                        alert(i);
                    }
                })(arr[i]);
                fragment.appendChild(td);
            }
        }
        calendar.appendChild(fragment);
    },
    redrawCalendar : function(year,month,date,index){
        switch(index){
            case 0 ://preyear
                year--;
                break;
            case 1://premonth
                month--;
                (month < 1) &&(year--,month = 12)  ;
                break;
            case 2://nextmonth
                month++;                
                (month > 12)&&(year++,month = 1) ;
                break;
            case 3://nextyear
                year++;
                break;
        }
        this.drawCalendar(year,month,date);
    }
}
//*********************Jcalendar類結束**********************
window.onload = function(){
        new Jcalendar();
}
var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } } var extend = function(destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; } var Jcalendar = Class.create(); Jcalendar.prototype = { initialize:function(options){ this.setOptions(options); var $ = new Date(); this.drawCalendar($.getFullYear(),$.getMonth() + 1,$.getDate()); }, setOptions: function(options) { this.options = {//默認屬性集中寫在這裏。 id:'jcalendar_'+ new Date().getTime(), text_id:null,//用於輸入日期的文本域的ID parent_id:null//指定父節點 }; extend(this.options, options || {}); }, ID:function(id){ return document.getElementById(id) }, TN:function(tn){ return document.getElementsByTagName(tn) }, CE:function(s){ return document.createElement(s) }, getHandle:function(){ return this.ID(this.options.id); }, fillArray : function(year,month){//fill Array var f = new Date(year, month -1 ,1).getDay(), //求出當月的第一天是星期幾 dates = new Date(year, month , 0).getDate(),//上個月的第零天就是今個月的最後一天 arr = new Array(42); //用來裝載日期的數組,日期以‘xxxx-xx-xx’的形式表示 for(var i = 0; i < dates ; i ++ ,f ++){ arr[f] = year +'-'+ month +'-'+ (i+1) ; } return arr; }, addToDom:function(calendar){//add to dom tree var parent = this.ID(this.options.parent_id) || this.TN('body')[0]; parent.insertBefore(calendar,null); }, drawCalendar : function(year,month,date){ (month < 1) &&(year--,month = 12) ; (month > 12)&&(year++,month = 1) ; var $ = this,T='getElementsByTagName', calendar = $.getHandle(),//日曆的容器 weeks = "日一二三四五六".split(''),//日曆第二行的內容,顯示星期幾 arr = $.fillArray(year,month),//保存當月的日期 text_id = $.options.text_id;//用於輸入日期的文本域的ID if(calendar) { calendar.innerHTML = ''; }else{ calendar = $.CE('div'); $.addToDom(calendar);//把日曆加入DOM樹中 calendar.setAttribute('id', $.options.id);//設置ID } calendar.innerHTML+= '<< <'+ year+'年'+month+'月'+date +'日> >>'; for(var i = 0;i <7;i++){ calendar.innerHTML += ''+weeks[i]+''; } for(i = 0;i <42;i++){ calendar.innerHTML += (!arr[i]) ? ' ' :(''+arr[i].split('-')[2]+'') } var tts = calendar[T]("tt"); tts[0].onclick = function(){ $.drawCalendar(year-1,month,date); } tts[1].onclick = function(){ $.drawCalendar(year,month-1,date); } tts[2].onclick = function(){ $.drawCalendar(year,month+1,date); } tts[3].onclick = function(){ $.drawCalendar(year+1,month,date); } var dates = calendar[T]("kbd"),j = dates.length; while (--j >= 0) { dates[j].onclick = function(){ this.parentNode.style.backgroundColor = "#D2F9DF"; } } } } ; var indigene = document.getElementById("Calendar1_entryCal"); indigene.style.display = "none"; var div = document.createElement("div"); indigene.parentNode.insertBefore(div,indigene); div.setAttribute("id","jcalendar_parent"); new Jcalendar({ id:'jcalendar', parent_id:'jcalendar_parent' });

上面的按鈕的顯示符號應該是&lt;與&gt;,而不是單純的小於號或大於號,這是高亮插件的緣故……另,這個日曆改一改,就可以成爲彈出式日期選擇器了!至於它,下次有次再說!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章