JS日期級聯元件程式碼分析及demo

龍恩0707發表於2014-01-14

    最近研究下JS日期級聯效果 感覺還不錯,然後看了下kissy也正好有這麼一個元件,也看了下原始碼,寫的還不錯,通過google最早是在2011年 淘寶的虎牙(花名)用原審JS寫了一個(貌似據說是從YUI那邊重構下的) 具體的可以看他的 部落格園 , 感覺kissy元件原始碼 思路也是和YUI類似 所以我今天的基本思路也和他們的一樣 只是通過自己分析下及用自己的方式包裝下。

基本原理

 1.傳參中有 '年份下拉框dom節點', '月份下拉框dom節點', '天數下拉框dom節點', "開始日期","結束日期","預設日期"配置項

     1.如果開始傳參日期為空 那麼預設是從"1900-01-01"開始

     2.如果"結束日期為空" 那麼預設結束日期為當前的時間。

     3. 如果預設日期為空 那麼預設日期預設為當前的時間。

 2. 月份對應的天數可以直接寫死 如:_dayInMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] ; 分別為1月份到12月份的各個月份的預設天數,當然還有2月份閏年29天的情況 待會在程式碼中會有判斷的。

 3. 分別渲染出年份區間,月份區間,及相應的天數。(如果有預設的日期的話 且預設日期大於或者等於開始日期 且小於或者等於結束日期的話) 那麼頁面載入的時候 顯示預設日期。

 4. 繫結change事件 當切換到不同年份的時候 月份和天數也要分別渲染出來。

基本配置項如下:

   nodeYear
'#year',    年份下拉框dom節點
 nodeMonth  '#month',  月份下拉框dom節點
 nodeDay  '#day',      日期下拉框dom節點
 dateStart   '',             開始日期(為空 預設日期從1900-01-01開始)
 dateEnd  '',             結束日期(可選 預設為空就為當前時間)
dateDefault   ''             預設日期(可選 預設為空就為當前時間)

對外提供的方法

1. getDate()  返回當前時間,格式為yyyy-mm-dd

2. getYear() 返回當前的年份

3. getMonth() 返回當前的月份

4. getDay() 返回當前月份中的天數.

JSFiddle demo連結如下:

 檢視demo 請點選我!

下面程式碼分析如下:

1. 初始化呼叫init方法:分別獲取開始時間 結束時間 預設時間的 "年,月,天"。如下程式碼:

// 開始時間可選 如果為空的話 那麼預設開始時間是1900-01-01
        if(_config.dateStart != '') {

            this.startDate = {
                y: new Date(_config.dateStart).getFullYear(),
                m: new Date(_config.dateStart).getMonth() + 1,
                d: new Date(_config.dateStart).getDate()
            };
        }else {
            var dateStart = '1900/01/01';

            this.startDate = {
                y: new Date(dateStart).getFullYear(),
                m: new Date(dateStart).getMonth() + 1,
                d: new Date(dateStart).getDate()
            };
        }
        
        // dateEnd 預設為空 如果沒有傳入的話 就取當前的時間
        if(_config.dateEnd == '') {
            this.endDate = {
                y: new Date().getFullYear(),
                m: new Date().getMonth() + 1,
                d: new Date().getDate()
            };
        }else {
            this.endDate = {
                y: new Date(_config.dateEnd).getFullYear(),
                m: new Date(_config.dateEnd).getMonth() + 1,
                d: new Date(_config.dateEnd).getDate()
            };
        }
        
        // 預設時間可選 如果預設時間為空的話 那麼就取當前的時間
        if(_config.dateDefault != '') {
            this.defaultDate = {
                y: new Date(_config.dateDefault).getFullYear(),
                m: new Date(_config.dateDefault).getMonth() + 1,
                d: new Date(_config.dateDefault).getDate()
            };
        }else {
            this.defaultDate = {
                y: new Date().getFullYear(),
                m: new Date().getMonth() + 1,
                d: new Date().getDate()
            };
        }
        // 判斷時間是否合理
        if((Date.parse(self._changeFormat(_config.dateStart)) > Date.parse(self._changeFormat(_config.dateEnd))) || 
          (Date.parse(self._changeFormat(_config.dateDefault)) > Date.parse(self._changeFormat(_config.dateEnd)))){
            return;
        }

2. 渲染下拉框的年份:呼叫 y = self._renderYear();這個方法。

    1. 獲取年份的區間範圍,獲取方法就是:獲取開始時間的年份 和 結束時的年份 如下程式碼:

/*
 * 獲取年份的範圍 最小-最大
 * @method _getYearRange 
 * @return {min,max}
 */
_getYearRange: function(){
      var self = this,
        _config = self.config;
    return {
        min: self.startDate.y,
        max: self.endDate.y
    }
},

    2. 接著渲染年份,從最近的年份開始渲染,如果有預設的年份 且 滿足條件的話 那麼預設的年份顯示出來。如下程式碼:

/*
 * 渲染年份下拉框
 * @method _renderYear
 * private
 */
    _renderYear: function(){
        var self = this,
            _config = self.config,
            _cache = self.cache;
        var nodeyear = $(_config.nodeYear)[0],
            y = self.defaultDate.y,
            range,
            option;

        if(nodeyear) {
            range = self._getYearRange();
            for(var i = range.max; i >= range.min; i--) {
                option = new Option(i,i);

                // 如果有預設年份的話
                if(i == y) {
                    option.selected = true;
                }
                // 相容所有瀏覽器 插入到最後
                nodeyear.add(option,undefined);
            }
        }
        $(nodeyear).attr('year',y);
        return y;
    },

3. 接著渲染月份 呼叫這個方法  y引數就是剛剛返回的年份  m = self._renderMonth(y);

    1. 同理 渲染月份也要獲取月份的範圍 預設都是從1月份到12月份 但是也有列外。比如如下2個判斷。

/*
     * 獲取月份的範圍
     * @method _getMonthRange
     * @param {y} Number
     */
    _getMonthRange: function(y){
        var self = this,
            _config = self.config;
        var startDate = self.startDate,
            endDate = self.endDate,
            min = 1,
            max = 12;
        /*
         * 如果預設年份等於開始年份的話 那麼月份最小取得是開始的月份
         * 因為如果開始是1900-05-01 如果預設的是 1900-03-02 那麼最小月份肯定取得是5
         * 因為預設時間不可能小於開始時間
         */
        if(y == startDate.y) { // 開始年份
            min = startDate.m;
        }
        
        /*
         * 同理 如果預設年份等於2014-04-01 那麼取得是當前的年份(endDate未傳的情況下)
         * 那麼最大的肯定取得是當前年份的 月份 不可能取的是4 因為只渲染出當前月份出來 
         * 後面的月份沒有渲染出來
         */
        if(y == endDate.y) {
            max = endDate.m;
        }
        return {
            min: min,
            max: max
        }
    },

     2. 知道月份的範圍後 然後根據上面的年份渲染相應的月份:程式碼如下:

/*
     * 根據年份 渲染所有的月份
     * @method _renderMonth
     * @param {y} 年份
     */
    _renderMonth: function(y){
        var self = this,
            _config = self.config;
        var nodeMonth = $(_config.nodeMonth)[0],
            m = $(nodeMonth).attr('month') || self.defaultDate.m,
            range,
            option,
            t = false;
        if(nodeMonth) {
            range = self._getMonthRange(y);
            
            nodeMonth.innerHTML = '';
            for(var i = range.min; i <= range.max; i++) {
                option = new Option(self.bitExpand(i),self.bitExpand(i));
                
                // 如果有預設的月份的話
                if(i == m) {
                    option.selected = true;
                    m = i;
                    t = true;
                }
                // 相容所有瀏覽器 插入到最後
                nodeMonth.add(option,undefined);
            }
            if(!t) {
                m = range.min;
            }
        }
        
        $(nodeMonth).attr('month',m);
        return m;
    },

     上面的程式碼 用了這句判斷  m = $(nodeMonth).attr('month') || self.defaultDate.m, 預設情況下 也就是說頁面一載入的時候 可以獲取預設的月份,但是當我觸發change事件後 我取的月份 是從m = $(nodeMonth).attr('month') 這個裡面取得。上面程式碼 nodeMonth.innerHTML = ''; 也是為了change時候 請清空掉 然後重新生成的。

4.  渲染天數 通過這個方法: self._renderDay(y,m);

     1. 渲染天數 同理也要獲得相應的天數。呼叫_getDayRange方法。此方法中有判斷是閏年的情況的。如下程式碼:

/*
     * 獲得天數的範圍
     * @method _getDayRange
     * @param {y,m} {number,number}
     */
    _getDayRange: function(y,m){
        var self = this,
            _config = self.config,
            _cache = self.cache;
        var startDate = self.startDate,
            endDate = self.endDate,
            min = 1,
            max;

        if(m) {
            if(m == 2) {
                max = self._isLeapYear(y) ? 29 : 28;
            }else {
                max = _cache._dayInMonth[m-1];
            }
            // 如果年月份都等於開始日期的話 那麼min也等於開始日
            if(y == startDate.y && m == startDate.m) {
                min = startDate.d;
            }
            // 如果年月份都等於結束日期的話 那麼max也等於結束日
            if(y == endDate.y && m == endDate.m) {
                max = endDate.d;
            }
        }
        return {
            min: min,
            max: max
        }
    },

     2.接著渲染天數的方法如下:

_renderDay: function(y,m) {
        var self = this,
            _config = self.config;
        var nodeDay = $(_config.nodeDay)[0],
            d = $(nodeDay).attr('day') || self.defaultDate.d,
            range,
            option,
            t = false;
        if(nodeDay) {
            range = self._getDayRange(y,m);

            nodeDay.innerHTML = '';
            for(var i = range.min; i <= range.max; i++) {
                option = new Option(self.bitExpand(i),self.bitExpand(i));

                // 如果有預設的天數的話
                if(i == d) {
                    option.selected = true;
                    d = i;
                    t = true;
                }
                // 相容所有瀏覽器 插入到最後
                nodeDay.add(option,undefined);
            }
            if(!t) {
                d = range.min;
            }
        }
        
        $(nodeDay).attr('day',d);
        return d;
    },

 5 最後用繫結change事件 呼叫_bindEnv方法。如:

/*
     * 繫結所有事件
     * @method _bindEnv
     * private
     */
    _bindEnv:function(){
        var self = this,
            _config = self.config,
            _cache = self.cache;
        //年份改變
        $(_config.nodeYear).change(function(e){
            
            var y = e.target.value,
                m = self._renderMonth(y);
            
            self._renderDay(y,m);
            $(_config.nodeYear).attr('year',y);
        });
        //月份改變
        $(_config.nodeMonth).change(function(e){
            
            var m = e.target.value,
                y = $(_config.nodeYear).attr('year');
            
            self._renderDay(y,m);
            $(_config.nodeMonth).attr('month',m);
        });

        //日期改變
        $(_config.nodeDay).change(function(e){
            var d = e.target.value;
            $(_config.nodeDay).attr('day',d);
        });
    },

HTML程式碼如下:

<label>出生日期: </label>
    <select id="year"> </select><select id="month"> </select><select id="day"> </select><ul>
        <li><em>getDate</em> : <button id="testDate">日期</button><input id="textDate"/></li>
        <li><em>getYear</em> : <button id="testYear"></button><input id="textYear"/></li>
        <li><em>getMonth</em> : <button id="testMonth"></button><input id="textMonth"/></li>
        <li><em>getDay</em> : <button id="testDay"></button><input id="textDay"/></li>
    </ul>

JS程式碼如下:

/**
 * JS日期級聯元件
 * @constructor DateCascade
 * @param {object} 可配置的物件
 * @time 2014-1-13
 * @author 879083421@qq.com
 */
 
 function DateCascade(options) {
    
    this.config = {
        nodeYear          : '#year',        // 年份下拉框dom
        nodeMonth         : '#month',       // 月份下拉框dom
        nodeDay           : '#day',         // 日期下拉框dom
        dateStart         : '',             // 開始日期
        dateEnd           : '',             // 結束日期(可選 預設為空就為當前時間)
        dateDefault       : ''              // 預設日期
    };
 
    this.cache = {
        _dayInMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]  // 月份對應的天數
    };
    this.init(options);
 }

 DateCascade.prototype = {

    constructor: DateCascade,

    init: function(options) {
        
        this.config = $.extend(this.config,options || {});
        var self = this,
            _config = self.config,
            _cache = self.cache;
        
        var y,
            m;

        /* 開始時間 和 截至時間 預設時間*/

        // 開始時間可選 如果為空的話 那麼預設開始時間是1900-01-01
        if(_config.dateStart != '') {

            this.startDate = {
                y: new Date(_config.dateStart).getFullYear(),
                m: new Date(_config.dateStart).getMonth() + 1,
                d: new Date(_config.dateStart).getDate()
            };
        }else {
            var dateStart = '1900/01/01';

            this.startDate = {
                y: new Date(dateStart).getFullYear(),
                m: new Date(dateStart).getMonth() + 1,
                d: new Date(dateStart).getDate()
            };
        }
        
        // dateEnd 預設為空 如果沒有傳入的話 就取當前的時間
        if(_config.dateEnd == '') {
            this.endDate = {
                y: new Date().getFullYear(),
                m: new Date().getMonth() + 1,
                d: new Date().getDate()
            };
        }else {
            this.endDate = {
                y: new Date(_config.dateEnd).getFullYear(),
                m: new Date(_config.dateEnd).getMonth() + 1,
                d: new Date(_config.dateEnd).getDate()
            };
        }
        
        // 預設時間可選 如果預設時間為空的話 那麼就取當前的時間
        if(_config.dateDefault != '') {
            this.defaultDate = {
                y: new Date(_config.dateDefault).getFullYear(),
                m: new Date(_config.dateDefault).getMonth() + 1,
                d: new Date(_config.dateDefault).getDate()
            };
        }else {
            this.defaultDate = {
                y: new Date().getFullYear(),
                m: new Date().getMonth() + 1,
                d: new Date().getDate()
            };
        }
        // 判斷時間是否合理
        if((Date.parse(self._changeFormat(_config.dateStart)) > Date.parse(self._changeFormat(_config.dateEnd))) || 
          (Date.parse(self._changeFormat(_config.dateDefault)) > Date.parse(self._changeFormat(_config.dateEnd)))){
            return;
        }

        // 渲染年份
        y = self._renderYear();

        // 渲染月份
        m = self._renderMonth(y);

        // 渲染天
        self._renderDay(y,m);

        // 所有繫結事件
        self._bindEnv();
    },
    /*
     * 渲染年份下拉框
     * @method _renderYear
     * private
     */
    _renderYear: function(){
        var self = this,
            _config = self.config,
            _cache = self.cache;
        var nodeyear = $(_config.nodeYear)[0],
            y = self.defaultDate.y,
            range,
            option;

        if(nodeyear) {
            range = self._getYearRange();
            for(var i = range.max; i >= range.min; i--) {
                option = new Option(i,i);
                
                // 如果有預設年份的話
                if(i == y) {
                    option.selected = true;
                }
                
                // 相容所有瀏覽器 插入到最後
                nodeyear.add(option,undefined);
            }
        }
        $(nodeyear).attr('year',y);
        return y;
    },
    /*
     * 根據年份 渲染所有的月份
     * @method _renderMonth
     * @param {y} 年份
     */
    _renderMonth: function(y){
        var self = this,
            _config = self.config;
        var nodeMonth = $(_config.nodeMonth)[0],
            m = $(nodeMonth).attr('month') || self.defaultDate.m,
            range,
            option,
            t = false;
        if(nodeMonth) {
            range = self._getMonthRange(y);
            
            nodeMonth.innerHTML = '';
            for(var i = range.min; i <= range.max; i++) {
                option = new Option(self.bitExpand(i),self.bitExpand(i));
                
                // 如果有預設的月份的話
                if(i == m) {
                    option.selected = true;
                    m = i;
                    t = true;
                }
                // 相容所有瀏覽器 插入到最後
                nodeMonth.add(option,undefined);
            }
            if(!t) {
                m = range.min;
            }
        }
        
        $(nodeMonth).attr('month',m);
        return m;
    },
    _renderDay: function(y,m) {
        var self = this,
            _config = self.config;
        var nodeDay = $(_config.nodeDay)[0],
            d = $(nodeDay).attr('day') || self.defaultDate.d,
            range,
            option,
            t = false;
        if(nodeDay) {
            range = self._getDayRange(y,m);

            nodeDay.innerHTML = '';
            for(var i = range.min; i <= range.max; i++) {
                option = new Option(self.bitExpand(i),self.bitExpand(i));

                // 如果有預設的天數的話
                if(i == d) {
                    option.selected = true;
                    d = i;
                    t = true;
                }
                // 相容所有瀏覽器 插入到最後
                nodeDay.add(option,undefined);
            }
            if(!t) {
                d = range.min;
            }
        }
        
        $(nodeDay).attr('day',d);
        return d;
    },
    /*
     * 繫結所有事件
     * @method _bindEnv
     * private
     */
    _bindEnv:function(){
        var self = this,
            _config = self.config,
            _cache = self.cache;
        //年份改變
        $(_config.nodeYear).change(function(e){
            
            var y = e.target.value,
                m = self._renderMonth(y);
            
            self._renderDay(y,m);
            $(_config.nodeYear).attr('year',y);
        });
        //月份改變
        $(_config.nodeMonth).change(function(e){
            
            var m = e.target.value,
                y = $(_config.nodeYear).attr('year');
            
            self._renderDay(y,m);
            $(_config.nodeMonth).attr('month',m);
        });

        //日期改變
        $(_config.nodeDay).change(function(e){
            var d = e.target.value;
            $(_config.nodeDay).attr('day',d);
        });
    },
    /*
     * 獲取年份的範圍 最小-最大
     * @method _getYearRange 
     * @return {min,max}
     */
    _getYearRange: function(){
        var self = this,
            _config = self.config;
        return {
            min: self.startDate.y,
            max: self.endDate.y
        }
    },
    /*
     * 獲取月份的範圍
     * @method _getMonthRange
     * @param {y} Number
     */
    _getMonthRange: function(y){
        var self = this,
            _config = self.config;
        var startDate = self.startDate,
            endDate = self.endDate,
            min = 1,
            max = 12;
        /*
         * 如果預設年份等於開始年份的話 那麼月份最小取得是開始的月份
         * 因為如果開始是1900-05-01 如果預設的是 1900-03-02 那麼最小月份肯定取得是5
         * 因為預設時間不可能小於開始時間
         */
        if(y == startDate.y) { // 開始年份
            min = startDate.m;
        }
        
        /*
         * 同理 如果預設年份等於2014-04-01 那麼取得是當前的年份(endDate未傳的情況下)
         * 那麼最大的肯定取得是當前年份的 月份 不可能取的是4 因為只渲染出當前月份出來 
         * 後面的月份沒有渲染出來
         */
        if(y == endDate.y) {
            max = endDate.m;
        }
        return {
            min: min,
            max: max
        }
    },
    /*
     * 獲得天數的範圍
     * @method _getDayRange
     * @param {y,m} {number,number}
     */
    _getDayRange: function(y,m){
        var self = this,
            _config = self.config,
            _cache = self.cache;
        var startDate = self.startDate,
            endDate = self.endDate,
            min = 1,
            max;

        if(m) {
            if(m == 2) {
                max = self._isLeapYear(y) ? 29 : 28;
            }else {
                max = _cache._dayInMonth[m-1];
            }
            // 如果年月份都等於開始日期的話 那麼min也等於開始日
            if(y == startDate.y && m == startDate.m) {
                min = startDate.d;
            }
            // 如果年月份都等於結束日期的話 那麼max也等於結束日
            if(y == endDate.y && m == endDate.m) {
                max = endDate.d;
            }
        }
        return {
            min: min,
            max: max
        }
    },
    /*
     * 判斷是否是閏年
     */
    _isLeapYear: function(y){
        return (y % 4 === 0 && y % 100 !== 0) || (y % 400 === 0);
    },
    /**
     * 是否是Date格式
     * @method _isDate
     * @param {Date} d
     * @private
     * @return {Boolean}
     */
    _isDate: function(d){
        return Object.prototype.toString.call(d) === '[object Date]' && d.toString() !== 'Invalid Date' && !isNaN(d);
    },
    /*
     * 小於10的數字加零
     * @method bitExpand
     */
    bitExpand: function(num) {
        var num = num * 1;
        if(/\d/.test(num)) {
            if(num < 10) {
                return '0' + num;
            }else {
                return num;
            }
        }
    },
    /*
     * 判斷開始日期 預設日期 結束日期的格式
     */
    _changeFormat: function(date) {
        return date.replace(/'-'/g,'/');    
    },
    /*
     * 獲取日期
     */
    getDate: function(){
        var self = this,
            _config = self.config;
        var year = $(_config.nodeYear).attr('year'),
            month = $(_config.nodeMonth).attr('month'),
            day = $(_config.nodeDay).attr('day');
        
        return (year + '-' + self.bitExpand(month) + '-' + self.bitExpand(day));
    },
    /*
     * 獲取年份
     */
    getYear: function(){
        var self = this,
            _config = self.config;
        var year = $(_config.nodeYear).attr('year');
        return year;
    },
    /*
     * 獲取月份
     */
    getMonth: function(){
        var self = this,
            _config = self.config;
        var month = $(_config.nodeMonth).attr('month');
        return month;
    },
    /*
     * 獲取天數
     */
    getDay: function(){
        var self = this,
            _config = self.config;

        var day = $(_config.nodeDay).attr('day');
        return day;
    }
 }
View Code

初始化方式如下:

// 初始化
$(function(){
    var date = new DateCascade({});
    $('#testDate').click(function(e){
        $('#textDate').val(date.getDate());
    });

    $('#testYear').click(function(e){                  $('#textYear').val(date.bitExpand(date.getYear()));
    });

    $('#testMonth').click(function(e){
                $('#textMonth').val(date.bitExpand(date.getMonth()));
    });

    $('#testDay').click(function(e){
                $('#textDay').val(date.bitExpand(date.getDay()));
    });
});

DEMO下載

相關文章