最近研究下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;
}
}

初始化方式如下:

// 初始化
$(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下载

JS日期级联组件代码分析及demo的更多相关文章

  1. 纯js时钟特效详细代码分析实例教程

    电子时钟是网上常见的功能,在学习date对象和定时器功能时,来完成一个电子时钟的制作是不错的选择.学习本教程之前,读者需要具备html和css技能,同时需要有简单的javascript基础. 先准备一 ...

  2. tensorflow faster rcnn 代码分析一 demo.py

    os.environ["CUDA_VISIBLE_DEVICES"]=2 # 设置使用的GPU tfconfig=tf.ConfigProto(allow_soft_placeme ...

  3. arcgis api for js之echarts开源js库实现地图统计图分析

    前面写过一篇关于arcgis api for js实现地图统计图的,具体见:http://www.cnblogs.com/giserhome/p/6727593.html 那是基于dojo组件来实现图 ...

  4. arcgis api 3.x for js 之 echarts 开源 js 库实现地图统计图分析(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  5. 微信小游戏 demo 飞机大战 代码分析(四)(enemy.js, bullet.js, index.js)

    微信小游戏 demo 飞机大战 代码分析(四)(enemy.js, bullet.js, index.js) 微信小游戏 demo 飞机大战 代码分析(一)(main.js) 微信小游戏 demo 飞 ...

  6. 微信小游戏 demo 飞机大战 代码分析 (三)(spirit.js, animation.js)

    微信小游戏 demo 飞机大战 代码分析(三)(spirit.js, animation.js) 微信小游戏 demo 飞机大战 代码分析(一)(main.js) 微信小游戏 demo 飞机大战 代码 ...

  7. 微信小游戏 demo 飞机大战 代码分析 (二)(databus.js)

    微信小游戏 demo 飞机大战 代码分析(二)(databus.js) 微信小游戏 demo 飞机大战 代码分析(一)(main.js) 微信小游戏 demo 飞机大战 代码分析(三)(spirit. ...

  8. 微信小游戏 demo 飞机大战 代码分析 (一)(game.js, main.js)

    微信小游戏 demo 飞机大战 代码分析(一)(main.js) 微信小游戏 demo 飞机大战 代码分析(二)(databus.js) 微信小游戏 demo 飞机大战 代码分析(三)(spirit. ...

  9. js日期控件demo

    最近在钻研前端,写了个日期控件,内涵代码注释,希望能帮助到大家~ 1.html代码 <!DOCTYPE html> <html xmlns="http://www.w3.o ...

随机推荐

  1. Java - equals方法

    java提高篇(十三)-----equals()方法总结 equal和==区别 ==比较对象基于内存引用,两个引用完全相同返回true Java 语言里的 equals方法其实是交给开发者去覆写的,让 ...

  2. HDU6197

    array array array Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others ...

  3. 【代码笔记】iOS-在导航栏中显示等待对话框

    一,效果图. 二,代码. ViewController.m #import "ViewController.h" @interface ViewController () @end ...

  4. git使用相关记录

    上传github操作记录:https://blog.csdn.net/pql925/article/details/72772660 git提交仓库相关:https://blog.csdn.net/M ...

  5. TLS/SSL测试工具

    常用的有SSLScan,我用的是OpenSSL的: openssl s_client -connect www.baidu.com:443

  6. Bootstrap源码分析系列之整体架构

    作为一名合格的前端工程师,你肯定听说过Bootstarp框架.确实可以说Bootstrap框架是最流行的前端框架之一.可是也有人说Bootstrap是给后端和前端小白用的,我认为只要学习它能给我们前端 ...

  7. python subprocess pipe 实时输出日志

    * test11.py import time print "1" time.sleep(2) print "1" time.sleep(2) print &q ...

  8. c#中partial 作用

    申明一下:我也是在百度上找的答案,然后合起来的,这样感觉好理解一点!partial是局部类型的意思就是说有这个关键字的类.结构或接口可以写成几个部分比如: public partial class P ...

  9. (笔记)MySQL 之 Metadata Locking 研究(5.5版本)

      MySQL5.5 中引入了 metadata lock. 顾名思义,metadata lock 不是为了保护表中的数据的,而是保护 database objects(元数据)的.包括表结构.sch ...

  10. C# System.IO.Path

    Path的常用方法 函数列表 对一个路径做相应操作,包括文件路径,目录路径,通常会用到Path这个类, 本文列举一些常用的操作. 获取指定路径字符串的目录信息 public static string ...