最近研究下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. Ubuntu 18.04 安装 Docker-ce(就是Docker社区版本)

    一步都不用改,跟着走就行 1.更换国内软件源,推荐中国科技大学的源,稳定速度快(可选) sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak ...

  2. 桥接模式(Bridge)

    1.概念 桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化,属于结构性模式的一种. 2.模式结构 Abstraction(抽象类):定义抽象接口,拥有一个Implementor类型的对象引 ...

  3. HDU6113

    度度熊的01世界 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Su ...

  4. Hadoop HDFS 的 HttpFS

    参考 Hadoop权威指南 第3章中3.4节 背景 要写一个操作HDFS的web后台(文件CRUD),虽然可以直接使用HDFS提供的FileSystem类然后通过get方法获取到一个Distribut ...

  5. python-观察者模式

    源码地址:https://github.com/weilanhanf/PythonDesignPatterns 说明: 存在这样的一种情况:公司领导再开例会那天临时有事,他让秘书给所有会上员工群发了一 ...

  6. nodejs 知识总结

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.添加模块:保存到package.json文件中; # npm install vue --save    注意 ...

  7. 【代码笔记】iOS-产生随机数

    一,代码. - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, ...

  8. css3 之 display 属性

    1.定义 语法:display:none | inline | block | list-item | inline-block | table | inline-table | table-capt ...

  9. css 单行/多行文字垂直居中问题

    例子可以直接看这里http://www.zhangxinxu.com/study/200911/line-height-text-v-center.html 这篇文章中有一点点解释http://blo ...

  10. OSGI企业应用开发(二)Eclipse中搭建Felix运行环境

    上篇文章介绍了什么是OSGI以及使用OSGI构建应用的优点,接着介绍了两款常用的OSGI实现,分别为Apache Felix和Equinox,接下来开始介绍如何在Eclipse中使用Apache Fe ...