跟我做:Javascript 下的日历控件(Calendar)

右侧是该控件的示范应用,朋友们可点击试用:

下载源码(20080824)

曾经,我发布过《几个javascript类》,得到部分朋友的支持;有道是,授之予鱼,不如授之予渔。同时结合朋友们的评价,我对该日历控件做了兼容性检查,已经“基本”支持火狐浏览器应用了;遗留一个小问题,火狐下面“日”提取有问题,不知道有兴趣的朋友能不能在读完本文后自己动手修正该漏洞(提示:火狐不支持 element.innertext,但支持 innerhtml :))。

每个人都是从“不会”到“会”(或者,从无到有)走过来的。当初为了这样一个在静态 html 文件可应用的轻量级控件,找了不少站点。源于自己出身于 c#,故在应用方式上做了类 c# 包装。

先谈怎么应用,然后说我是怎么实现的。以下是我在源码包中的演示代码:

function calendardemon(){// 如何使用 calendar 类的演示代码. 参见代码 jslibrary/html/calendar.js./// 请尊重作者劳动, 引用需注明出处./// howard.queen@hotmail.com, 2008-07-03./// http://howard-queen.cnblogs.com//全局变量window.mycalendar = null;//日历控件window.currentday = null;//当前选择日期window.mytextbox = null;//当前显示日期的控件window.daychangedcallback = null;//日历选择日期的回调方法window.mycalendarperforms = null;//激活日历的方法, 通过显示控件的相关事件调用//日历选择日期的回调方法daychangedcallback = function(sender, args){try{currentday = args;mytextbox.value = currentday.gettext("年", "月", "日");//输出所选日期. 自定义日期序列化.//mycalendar.hidden();//掩藏控件}catch(e){alert(e.message);}}//激活日历的方法mycalendarperforms = function(textbox){try{if(mycalendar.isvisible())mycalendar.hidden();//掩藏控件else{if(mytextbox.value != '')//注意: mytextbox == textboxcurrentday.fromtext(mytextbox.value, "年", "月", "日");//获取已有日期. 注意根据日期的格式, 让系统进行反序列.mycalendar.show(mytextbox, currentday);//设置日历显示日期. show(sender, currentday), 日历将显示在 sender 附近, 初始化时显示 currentday.}}catch(e){alert(e.message);}}//日历显示容器document.write("

");var pannel = document.getelementbyid('calendarpannel');//日期显示绑定的控件document.write('"mytextbox" type="text" οnclick="javascript:mycalendarperforms(this);" />');mytextbox = document.getelementbyid('mytextbox');//本地化, 缺省为英文var months = new calendarmonths(["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]);var weekdays = new calendarweekdays(["日","一","二","三","四","五","六"]);//设置初始日期. 缺省参数则为今天.currentday = new calendarday();//currentday = new calendarday(2008, 5, 12);//创建日历控件. 注意自定义日历显示位置的位移差, 此处用了(5, -150).mycalendar = new calendar('mycalendar', pannel, daychangedcallback, 5, -150, currentday, months, weekdays);}

应用它涉及到 5 个关键全局变量的定义。该控件实例,当前所选择的日期实例,显示当前日期的绑定控件,日期选择后的回调函数以及激活日历的方法。月份等显示支持本地化设置,甚至当前日期的输出也支持本地化。

“揭密该控件的实现内幕”:

///function calendar(id, pannel, selecteddaychangedevent, toper, lefter, selectedday, calendarmonths, calendarweekdays){// 日期选择控件. 月份从 1 开始.使用方法参见示例代码 jsdemons/html/calendardemon.js./// 参考版本《日期选择 - by ziyue, by jiang hongbin等》./ 请尊重作者劳动, 引用需注明出处./// howard.queen@hotmail.com, 2008-07-03./// http://howard-queen.cnblogs.com 2008-07-10///1, 修改 node.innertext 为 node.innerhtml. 完美支持 firefox.///2, 添加年份无上下限功能.///3, 添加当前所选日期亮显功能.///4, 更改了自定义日期序列化与反序列化, 使得更加友好.///5, 采用显示缓存策略以提高性能.///6, 一些方法的命名更改.//

当前控件名称.///

显示容器.///

选择日期更改事件.///

所选日期.///

月名称.///

周中日名称.this._init(id, pannel, selecteddaychangedevent, toper, lefter, selectedday, calendarmonths, calendarweekdays);}calendar.prototype = {_init: function(id, pannel, selecteddaychangedevent, toper, lefter, selectedday, calendarmonths, calendarweekdays){},_drawcallback: function(sender, args){/// 回调显示.},_render: function(temp){/// [私有]},_renderyear: function(temp){/// [私有]},_rendermonth: function(temp){/// [私有]},_renderweek: function(temp){/// [私有]},_renderday: function(temp){/// [私有]},_oncurrentdaychanging: function(daycell){},_refresh: function(year, month) {/// [私有]},_createcalenday: function(year, month) {/// [私有]},_onyearchanging: function(sender, args){},_onmonthchanging: function(sender, args){},_ondaychanging: function(sender, args){},isvisible: function(){},hidden: function(){},show: function(sender, args){}}function calendarday(year, month, day){/// calendar中的日期.this._init(year, month, day);}calendarday.prototype = {_dayscountofmonths: new array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),_init: function(year, month, day){},_getsubnumber: function(string, head, tail){},checkdate: function(year, month, day){/// 检查日期合法性.///

年份.///

月份.///

月中日数./// 是否合法.},getdayinweek: function(){},isleapyear: function(year){/// 是否闰年///

年份./// 是否闰年.},getmonthdays: function(year, month){/// 获取月的天数///

年份.///

月份./// 天数.},fromtext: function(text, yearcaption, monthcaption, daycaption){/// 设置选定日期.///

日期表达式, 顺序为年、月、日.},gettext: function(yearcaption, monthcaption, daycaption){/// 获取选定日期.},getlocalmonth: function(calendarmonths){},getlocalweekday: function(calendarweekdays){}}function calendarmonths(names){/// 月枚举.///

月自定义名称数组.}function calendarweekdays(names){/// 周中日枚举.///

周中日自定义名称数组.}

如上所示,我将该控件分解为三个类进行实现,calendar 只负责绘制子控件以及事件收集,calendarday 负责对时间进行核准、计算以及输出,剩下两个类似枚举的辅助类。对于 tooltip,这是我早前包装的,不是为日历控件特制。这些都有别于原始素材(注意类 calendar 注释中的 see 以及 seealso 部分),感兴趣的朋友可以就可复用性进行对比。

calendar 只对外透露三个方法: show(),hidden(),isvisible()。何时显示、何时掩藏,都由应用开发者定制,这给开发人员在不同场合的应用制造了便利。

在控件核心类 calendar 的绘制算法上,我将年(_renderyear())、月(_rendermonth())、周(_renderweek())、日(_renderday())独立为方法进行输出,有利于代码的自说明,更方便朋友们的定制与修正。在后期的更新中,我在“年”下拉框上增加了“更多过去”与“更多将来”下拉选项,使得年份也不会拘泥于某个特定时间段,而扩展到了无限。而在显示控件(show())时,为了防止大量重复计算,只在初次加载控件时进行绘制;但这造成一个小问题,用户当前输入框中的内容只在初始化会同步到控件,其他时刻,控件将忽略。如下代码:

show: function(sender, args){/// 显示控件.///

发送者.///

当前日期.if(this._yearsselect == undefined){//第一次加载. tooltip 中有对当前控件的缓存, 因此这里没有必要重复计算.this.selectedday = args;this.offsettop = sender.offsettop;this.offsetleft = sender.offsetleft;this.clientheight = sender.clientheight;this.clientwidth = sender.clientwidth;this._tooltip.show(sender, this);this._yearsselect = document.getelementbyid([this.id, '_yearsselect'].join(''));this._monthsselect = document.getelementbyid([this.id, '_monthsselect'].join(''));this._daystable = document.getelementbyid([this.id, '_daystable'].join(''));for (var weekindex = 0, dayinweek = 0; weekindex this._daystable.rows.length; weekindex++){for (; dayinweek this._daystable.rows[weekindex].cells.length; dayinweek ++){cell = this._daystable.rows[weekindex].cells[dayinweek];switch(dayinweek){case 0://星期日cell.style.color = 'red';break;case 6://星期六cell.style.color = 'green';break;default://其他break;}}dayinweek = 0;}this._createcalenday(this._yearsselect.value, this._monthsselect.value);}else{this._tooltip.show(sender, this);}}

在日期类 calendarday 的设计上,针对日期的输出形式仍有较大开发空间。在后期的更新中,我只是简单的加了对年月日的重命名输出,以及对该输出的反向识别,但没有实现类 c# 的 tostring("yymmdd") 等方法。看我的具体实现:

fromtext: function(text, yearcaption, monthcaption, daycaption){/// 设置选定日期.///

日期表达式, 顺序为年、月、日.if(text == undefined || text == null || text == ''){throw {name:"argumentnullexception", message:"参数为空, 参数名 text."};}if(yearcaption == undefined || yearcaption == null || yearcaption == '')yearcaption = '-';if(monthcaption == undefined || monthcaption == null || monthcaption == '')monthcaption = '-';if(daycaption == undefined || daycaption == null)daycaption = '';var year = parseint(this._getsubnumber(text, '', yearcaption));var month = parseint(this._getsubnumber(text, yearcaption, monthcaption));var day = parseint(this._getsubnumber(text, monthcaption, daycaption));this.checkdate(year, month, day);this.year = year;this.month = month;this.day = day;this._dayinweek = -1;},gettext: function(yearcaption, monthcaption, daycaption){/// 获取选定日期.if(yearcaption == undefined || yearcaption == null || yearcaption == '')yearcaption = '-';if(monthcaption == undefined || monthcaption == null || monthcaption == '')monthcaption = '-';if(daycaption == undefined || daycaption == null)daycaption = '';return [this.year, yearcaption, this.month, monthcaption, this.day, daycaption].join('');},

开发过程的乐趣在于,你所做的给自己或者别人带来了一些便利;对于学习开发的入门兄弟来说更是如此。总是说程序员大概都是懒人,能复用的代码,绝不写第二遍;有些人甚至因此偏执于搞复用:用最少的现有代码,延用过去做好的“控件”。但能否在各种场景下复用,就看你对经验的总结了。

虽然都是小把戏,但还是善意的提醒朋友们:将一个计算过程分滩给多个方法,每个方法短而功能明确,这对以后追加功能或是查找漏洞都会很有帮助。什么时候,我能把设计模式给搞透?


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值