一个方便易用的日期拾取工具DatePicker(状态模式)
重构和设计模式是程序员的任督二脉,写这个控件的时候顺便又修炼一下自己的功夫^_^
其实这个日期控件的模式并不是我自创的,如果你注意过Windows 7或者vista的日期工具,你会发现我这个控件跟Windows的那个工具表现行为是完全类似的。点击查看效果 查看更新日志
其实日期拾取控件应该是很简单也很常用的一种控件,主要用到Date对象,对日期进行操作。但是单纯靠Date对象提供的原生的方法是不够的,这里我也扩展了几个方法,当然有些也是参考了网上的一些资料。
-
o
- getMonthStartWeekday(year, month)
这个方法主要是根据给定的年月得到本月的1号是星期几。 - getMonthLastDate(year, month)
这个方法主要是根据给定的年月,得到本月的最后一天是几号。 - formatDate(date, formater)
这个方法用于根据一个Date实例和给出的日期格式返回格式化的日期字符串。
例如给定的是yyyy-MM-dd 则返回值为 2010-08-08。如果给定的是yyyy-M-d返回值为2010-8-8,如果为yyyy/MM/dd返回值为2010/08/08. - getYearRange(year, type)
本方法根据给出的年,以及一个枚举类型的年段类型(包括decade和century两种类型)计算得到当前年份所处的年代和世纪。
o
o
o
具体的代码实现请查看源代码。这些是一些常用的工具方法,相信大家都有自己的理解。这里重点要说明的是为什么这里要用到状态模式以及用这个模式的好处以及缺点。
大部分的日期拾取工具都提供了对于月份的导航,以及对于年份的导航,大部分是使用向前向后按钮或者下拉列表的形式实现。
但是如果年段跨的太大可能给用户选择年份带来麻烦,所以这里我借鉴了Windows的实现方法,通过对年代以及世纪的选择让用户可以更方便的选择自己需要的日期,而且所有选择都在同一个窗口中实现,用户不用到处寻找,更加的清晰明了。
随之带来的一个问题就是这样日期拾取器将会存在四种状态,分别是:日期状态,月份状态,年代状态,世纪状态。但是这四种状态都有非常类似的行为表现,例如:导航,向前向后;顶部向上一层的按钮,主内容区的选择事件等等。
开始的时候我是通过一个枚举类型的变量(其实就是一个json对象),通过switch或if else来判断当前是什么状态,来实现不同的动作。但是带来的一个问题就是代码非常的杂乱,要进行扩充或修改非常麻烦,饶是开发工具本身带了重构的功能也非常不方便。好吧我承认,不用说修改就是读都成问题……
遇到了诸多的不便之后我下定决心将代码重新组织。经过简单分析发现,这是一个典型的状态模式可以解决的问题。不同的状态完全可以抽离出来,做成不同的类,然后让拥有功能的对象做自己该做的事情,而且这样符合OO的思路。
所以我将四个状态抽离出来:DateState,MonthState,DecadeState,CenturyState。这四个对象都实现了IState这个接口,让我们看一下这个接口几个主要方法:
-
o
- pre()
向前的方法。 - next()
向后的方法。 - selected()
选择事件。 - toUpperState()
向上一层的方法。
o
o
o
每个状态对象都实现了接口中的方法,虽然他们各自实现细节不一样,我们的主体对象不需要了解每个状态的实现细节,它只需要知道,所以的状态对象都实现了接口契约中的功能。OK,让它们各自干各自的事情吧,主体对象太累了,它需要休息。
而且用户也不想了解这些让人头疼的状态,他们知道的就是当显示日期的时候点击向前向后月份会发生变化,而显示年代的时候向前或向后会一下改变十年,十年之后谁还有兴趣了解这个是吧,程序员的眼泪也会为别的事情而流,不是嘛,嘿嘿。
下面看一下我们的主体对象要做哪些工作吧。
首先主体对象会有一个当前状态的属性(currState),来获取当前的状态,另外它还会有四个状态的实例,当初始化完成的时候我们可以调用currState的show()方法来执行显示动作。
当然主体对象中还负责DOM初始化,DOM事件的初始化,以及对各个事件处理方法。当然事件处理方法内部调用的就是currState对象的方法。
这样即使回头我们想添加一个以千年计的模式,那么很简单,你只需要扩展一个千年模式,主体对象完全不用改动。是不是很轻松呢。
当然虽然我们这里用了这个模式,增加了代码的可维护性,但是这里我们可能以后都不会需要改动它了,而且使用了状态模式会让代码变得更多。但是我们的思路更条理了,而且我们还修炼了功夫,这个买卖划得来的。
抛砖引玉,欢迎与您交流!
源代码及相关文件下载
转载请注明原文出处《一个方便易用的日期拾取工具DatePicker(状态模式)》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。
左右跳月份的时候 中间日期最好月份也跳一下
否则都不知道跳到几月了
[回复]
@左右跳, 感谢提出的意见,那个地方确实是我少些了一个setTitle()的方法,另外还改正了两个其他的bug,请查看DatePicker的更新日志。
[回复]
怎么返回到今天?
[回复]
@edielei, 通过年月回到当前年月就可以了,没有做直接返回今天的功能。当前日期只是会高亮显示。
[回复]
很好哦,请问哪里有皮肤2下载啊。
[回复]
@vk, 实在不好意思,忘记把源代码发上来了,现在已补上,请与文章底部点击下载,也可使用如下链接下载 http://www.dklogs.net/wp-content/uploads/2010/10/datapicker.zip
[回复]
@DK, 谢谢
[回复]
@vk, 有个小错误,依次点击输入框,月份,输入框,会有双层bug。现已修正,请重新下载源码。
依次点击:
输入框,月份,输入框,
双层bug制造完成-_-
[回复]
@MoontoC, 感谢您的指正,汗,犯了一个非常低级的错误,if语句后面加了一个分号……修改了多个input触发时的逻辑,修正初始化时没有取消前置状态的bug。希望多多支持。(ps.我以前貌似发现过这个错误,后来不知道干啥去了竟然忘记改了)
[回复]
建议作者不要管的太宽了, 毕竟历法不是公式可以解决的事情
js的引擎一般来说只能处理1900-2099之间的历法, 其他都只是用公式套用, 做不准的, 你的日期工具连公元前都有管, 但是单纯的Date类只是用1582年10月15之后的统一算法来计算, 有兴趣的话可以去查查这一天发生了什么, Date同样无法处理大于2099的年份, 公式是不对的, 闰年的计算部分至少不是, 比如3200年不是闰年
四年一闰,百年不闰,四百年再闰, 这是公历的基础算法, 其中还有一条是可以整除3200, 并且能整除172800, 所以3200年不行,
公元前的算法截然不同, 而星期的算法就扯了, 星期制度是罗马人在公元321年开始最早启用的, 在当年的3月7日前是没有星期的, 公式通用但不在计算范围内
综上, 建议作者不要管太宽, 定义在1900年向后200年足矣, 多了你也用不到的, 大家也都用不到的, 各国的历法都有变动, 而变动的时间完全不同, 有的国家的历法可能少十多天, 能作准的也只有1900开始向后, 而计算机程序里大部分都以此为标准, 一定要前后推算的话, 结果都会是有出入的
另外建议作者出个农历的部分加上, 可能更实用一些
[回复]
另外补一句, win7的日历并不是因为懒得管才只做了200年的
[回复]
@MoontoC, 汗,不是我想管的宽,只是这个地方做的时候没考虑这么多,所以也没做这方面的限制,我知道那个不准的问题,只是比较懒,就没做界限的限制,等有空一定做上!十分感谢!
[回复]
@MoontoC, 其实农历需要加,两栏并列的那种模式也应该有,不过最近忙公司的项目,没什么时间来完善,不过还是十分感谢你的支持和意见,还请多指教!
[回复]