Fork me on GitHub
日期级联组件

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

2014-01-14 14:50 by 龙恩0707, 569 阅读, 3 评论, 收藏编辑

    最近研究下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;
    },
复制代码

     上面的代码 用了这句判断  = $(nodeMonth).attr('month') || self.defaultDate.m, 默认情况下 也就是说页面一加载的时候 可以获取默认的月份,但是当我触发change事件后 我取的月份 是从= $(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代码如下:

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

 

  最近迷上了Git,这货堪称神器,用了它就再也不想用其他VCS了,就像上了高速就不想再走国道一样。

  一般人使用Git+Github来搭建进行本地远程交互,不过Github弄个私人仓库是要刀乐思的,如果你很抠,或者你的伟大idea不想被别人瞄去,可以考虑用Git+金山快盘搭建私人服务器。

  不多说,以下是步骤:

  1. 下载安装金山快盘,指定路径,申请账号
  2. 下载安装Git桌面版,配置各参数
  3. 在快盘文件夹里新建一个文件夹,在这个文件夹里新建仓库一定要选中央资料库,否则会push不上(个人档案库会在这个目录下面生成.git的隐藏文件夹,而中央资料库会直接在这个目录生成管理目录,即没有外包.git文件夹,push上来的文件不会有源文件,只有Git自己的管理文件了。跟SVN差不多,SVN服务器上的repository也是没有源文件的,只有它自己的管理文件)                                                                                                          
  4. 建好之后就可以把本地的项目push上去了,本地只管pull、push就行,仓库的同步交由快盘负责。

     到此为止就可以使用Git+快盘仓库了,如果不考虑电脑的消耗资源,看起来区别跟svn不大。但是无论从实用性和方便性,Git都优于SVN,SVN+金山快盘是要在本地跑SVN服务的,相当耗资源,而Git不跑什么服务,顶多跑个十来M的快盘,小意思。
     并且SVN的分支管理远不及Git,不过Git也有点小缺点,比如更改文件后文件图标不立马变色,调用第三方的对比工具(如BeyondCompare)弹出速度比较慢(最慢的时候我都以为电脑死机了,并且文件如果没有变化diff的话BeyondCompare就不弹出,不人性化)。不过这些都是小问题,可以忍受。

  另:基于局域网共享文件夹的Git服务器模式也类似,注意创建仓库的时候选中央资料库就行了。Git的强大之处在于,你可以在局域网内的任何一个共享路径下创建仓库,而不需要运行任何服务。所有的操作都是基于本地的。这也不难理解可以直接放在快盘里了。
 
分类: javascript
标签: JS日期级联组件代码分析及demo

开源2个小框架(一个Winform框架,一个Web框架)

一 源码位置

1. Winform框架 

2. web框架

二 高效学习编程的办法

1 任务驱动方式学习软件开发

大部分人学习软件开发技术是通过看书,看视频,听老师上课的方式。这些方式有一个共同点即按知识点进行讲解。比如拿c#编程为例,首先是讲解大量的基础概念,如类和对象,继承多态,事件委托,泛型接口等等。这种学习方法的一个弊端是学习了大量的知识点,但是不知如何运用。

换一个角度,采用任务驱动的模式,提供一些从易到难的编程任务,一个个进行攻克,给出任务的同时,列出解决该任务需要掌握的知识,通过简单介绍这些知识和学习者自己百度google的方式,让学习者自己通过努力完成任务,任务过程中提供简单的答疑(只提供思路)。编程任务需要精心设计(如身份证解析,统计文章中出现单词个数,猜单词游戏,飞机大战游戏,通讯录管理系统等等)。

2 对比式,相互启发式学习

比如可以采取同一个编程任务,分别实现一个pc版及一个web版的方式,通过比较2种版本不同的实现方式,对照着进行学习。既可以体会到pc端软件开发和web软件开发的不同之处,又能从比较高的角度体会到编程的共通点。

pc开发经验多的学习者可以快速掌握web开发方法,web开发经验的多学习者可以快速掌握pc开发方法。

三 WinForm小框架

采用Winform+WeifenLuo.WinFormsUI.Docking.dll技术。截图如下:

 

 使用Winform框架新增一个窗体的步骤:

1.新增一个窗体,窗体的Text属性假设为[主页],窗体的名字假设为Frm_Home

2. 修改该窗体继承的基类Form->DockContent,并添加引用using WeifenLuo.WinFormsUI.Docking;

3. Frm_Main中InitFormText2FormTypeNameDic函数中,添加 一句dicWinformBase.Add("主页", "CSharpGoWinForm.Frm_Home");

注意这里字典的Key要和第一步中窗体的Text属性一样,这里字典的Value要和第一步中窗体的名字属性一样,并且前面需要加上命名空间.(CSharpGoWinForm.Frm_Home)

四 Web小框架

采用Jquery EasyUI + zTree + Bootstrap技术。截图如下:

   使用Web框架新增一个窗体的步骤:

1.新增一个aspx文件,假设叫StringJoinAndSplit.aspx

2.在Default.aspx的js代码段var zNodes =...部分,新增

 

{
name: "字符串合并与分解",
file: "StringJoinAndSplit.aspx"
}

 五 你可以用这2个小框架做什么

1.累积自己做的一些小实验,便于以后检索;

2.收集别人的一些开源小类库代码;

六 我打算用这2个小框架做什么

设计一个个由简到难的编程任务(来自于真实项目实践),分别提供web版和pc版的实现,达到.NET开发知识学习的目的,做为接下来系列随笔的基础。

提供的2个框架都比较简单,不会对这2个框架的实现原理进行详细解释,希望有心者自己去摸索。

winform框架主要用到了点反射技术,树形控件,字典应用;

web框架用到了点jquery easyui, zTree, BootStrap;

初学者可以选择自己的学习方向,跟着这2个小框架,自行学习相关的知识。

移动端JS 触摸事件基础

 

一、手机上的触摸事件

基本事件:

touchstart   //手指刚接触屏幕时触发
touchmove    //手指在屏幕上移动时触发
touchend     //手指从屏幕上移开时触发

下面这个比较少用:
touchcancel //触摸过程被系统取消时触发

每个事件都有以下列表,比如touchend的targetTouches当然是 0 咯:

touches         //位于屏幕上的所有手指的列表
targetTouches   //位于该元素上的所有手指的列表
changedTouches  //涉及当前事件的所有手指的列表

每个事件有列表,每个列表还有以下属性:

复制代码
其中坐标常用pageX,pageY:
pageX    //相对于页面的 X 坐标
pageY    //相对于页面的 Y 坐标
clientX  //相对于视区的 X 坐标
clientY  //相对于视区的 Y 坐标
screenX  //相对于屏幕的 X 坐标
screenY  //相对于屏幕的 Y 坐标

identifier // 当前触摸点的惟一编号
target   //手指所触摸的 DOM 元素
复制代码

其他相关事件:

event.preventDefault()   //阻止触摸时浏览器的缩放、滚动条滚动
var supportTouch = "createTouch" in document //判断是否支持触摸事件

更多深入内容?点击:http://www.cesclub.com/bw/jishuzhongxin/Webjishu/2011/1216/18069.html

 

二、示例

以下是获取不同类型滑动的代码具体做法,结合前人的思想,封装好了,可以借鉴学习:

复制代码
var touchFunc = function(obj,type,func) {
    //滑动范围在5x5内则做点击处理,s是开始,e是结束
    var init = {x:5,y:5,sx:0,sy:0,ex:0,ey:0};
    var sTime = 0, eTime = 0;
    type = type.toLowerCase();

    obj.addEventListener("touchstart",function(){
        sTime = new Date().getTime();
        init.sx = event.targetTouches[0].pageX;
        init.sy = event.targetTouches[0].pageY;
        init.ex = init.sx;
        init.ey = init.sy;
        if(type.indexOf("start") != -1) func();
    }, false);

    obj.addEventListener("touchmove",function() {
        event.preventDefault();//阻止触摸时浏览器的缩放、滚动条滚动
        init.ex = event.targetTouches[0].pageX;
        init.ey = event.targetTouches[0].pageY;
        if(type.indexOf("move")!=-1) func();
    }, false);

    obj.addEventListener("touchend",function() {
        var changeX = init.sx - init.ex;
        var changeY = init.sy - init.ey;
        if(Math.abs(changeX)>Math.abs(changeY)&&Math.abs(changeY)>init.y) {
            //左右事件
            if(changeX > 0) {
                if(type.indexOf("left")!=-1) func();
            }else{
                if(type.indexOf("right")!=-1) func();
            }
        }
        else if(Math.abs(changeY)>Math.abs(changeX)&&Math.abs(changeX)>init.x){
            //上下事件
            if(changeY > 0) {
                if(type.indexOf("top")!=-1) func();
            }else{
                if(type.indexOf("down")!=-1) func();
            }
        }
        else if(Math.abs(changeX)<init.x && Math.abs(changeY)<init.y){
            eTime = new Date().getTime();
            //点击事件,此处根据时间差细分下
            if((eTime - sTime) > 300) {
                if(type.indexOf("long")!=-1) func(); //长按
            }
            else {
                if(type.indexOf("click")!=-1) func(); //当点击处理
            }
        }
        if(type.indexOf("end")!=-1) func();
    }, false);
};
复制代码

 

 

  

 
 
分类: 移动前端
posted on 2014-01-14 21:52  HackerVirus  阅读(575)  评论(0编辑  收藏  举报