JS日期级联组件代码分析及demo

    最近研究下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 [email protected]

 */

 

 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下载

你可能感兴趣的:(demo)