在开发一个网站应用程序时,客户提出了一个具体需求:需要一个能够同时显示5月和6月的日历,并在5月底到6月中旬的时间段内选择日期。对于某些用户类别,他们希望允许这些用户在比赛期间最多选择3天的日期。同时,需要禁止用户选择某些特殊日期。最后,日历视图必须以月初的日期初始化。虽然市场上有许多日历控件,但很难找到一个同时满足这些需求且外观良好的控件。经过一番搜索,找到了Yahoo的日历控件,它几乎满足了所有需求,但缺少设置禁止日期和设置最大日期数量的功能。因此,深入分析了其JavaScript代码,并成功地添加了这两个功能。
在本篇文章中,将不会详细解释对calendar.js
文件所做的更改,而是介绍实现这个Web控件时遇到的一些有趣点。
在本节中,将解释如何使用这个控件以及它的属性。
要实例化一个日历,过程非常简单。以下是服务器端的代码示例:
YuiExtCalendar.MinDateSelectable = new DateTime(2011, 6, 1);
YuiExtCalendar.MaxDateSelectable = new DateTime(2011, 10, 1);
YuiExtCalendar.InitialSelectedDates = new List<DateTime>() { new DateTime(2011, 6, 24), new DateTime(2011, 6, 25) };
YuiExtCalendar.ForbiddenDates = new List<DateTime>() { new DateTime(2011, 7, 10), new DateTime(2011, 7, 11) };
YuiExtCalendar.NumberOfSelectableDates = 5;
YuiExtCalendar.HeaderTitle = "日历标题:";
YuiExtCalendar.DataBind();
以下是客户端的代码示例:
<cc1:YuiExtCalendar ID="YuiExtCalendar" Mode="Flat" runat="server" />
Mode
属性是一个枚举值,有3种可能的值。Flat
值显示一个像传统控件一样的日历。CollapsibleCalendarWithDateSummary
值显示一个日历,当点击带有ImageCalendarUrl
属性的图片时。如果使用CollapsibleCalendarWithDateSummary
或CollapsibleCalendarWithNoDateSummary
,必须提供ImageCalendarUrl
属性。CollapsibleCalendarWithDateSummary
生成一个选择的日期的迷摘要,这在关闭日历时很有用。CollapsibleCalendarWithNoDateSummary
值与CollapsibleCalendarWithDateSummary
完全相同,只是没有摘要。
MinDateSelectable
和MaxDateSelectable
是dateTime
属性,必须一起设置。它允许在日历上限制日期选择的区间。例如,如果希望日历允许在2012年9月19日和2012年12月9日之间选择日期(必须设置MinDateSelectable = 2012-09-19
和MaxDateSelectable = 2012-12-09
)。在服务器端设置这个变量。
InitialSelectedDates
属性是一个List<datetime>
,它允许在页面加载时选择一些日期。在服务器端设置这个变量。
ForbiddenDates
属性是一个List<datetime>
,它允许设置一个禁止日期列表:这些日期将不可选择。可以在calendar.css
中自定义这些禁止日期的UI样式:
/*禁止样式*/ .yui-skin-sam .yui-calendar td.calcell.forbidden{ ##在这里自定义## }
在服务器端设置这个变量。
NumberOfSelectableDates
是一个整型属性,它允许控制选择的日期数量,例如:如果希望一个用户在日历中最多选择4个日期,将这个变量设置为4。在服务器端或客户端设置这个变量。
HeaderTitle
是一个字符串属性,它允许设置日历头部的标题。在服务器端或客户端设置这个变量。
ImageCalendarUrl
是一个字符串属性。它是CollapsibleCalendarWithNoDateSummary
或CollapsibleCalendarWithNoDateSummary
模式中的图片URL。建议在客户端设置这个变量。
SelectedValues
属性是一个列表,它允许在按钮发生回发时获取用户选择的日期。
如果点击日历上写的两个月中的任何一个,可以选择希望查看的月份:
选择实现日历控件,继承CompositeControl
类。为什么?因为当想生成层次化的HTML标签时,子控件的ID会自动命名为'ControlId_childrenControlId_subchildrenControlId_subsubchildrenControlId'
等...这是一个有趣的属性,主要在JavaScript中使用它(替换_ID_)。当在Defaut.aspx页面上测试程序时,查看生成的客户端HTML代码源。
CreateChildControls
是一个覆盖方法,它允许生成HTML。
OnPreRender
是一个覆盖方法,它允许附加到头部JavaScript文件引用和CSS文件引用,以及生成JavaScript以构建和显示日历。
每个JavaScript文件、CSS文件和图片文件都必须在AssemblyInfo.cs中注册,每个文件都是"Embedded Resources"中的一个加载目录,必须动态加载。如何动态读取日历程序集中的文件?使用了Assembly.GetExecutingAssembly().GetManifestResourceStream()
方法来读取文件的内容。
System.IO.Stream streamScriptLoader = Assembly.GetExecutingAssembly().GetManifestResourceStream("YuiExtAspNetCalendar.Loading.calendarLoader.js");
System.IO.StreamReader readerScriptLoader = new System.IO.StreamReader(streamScriptLoader);
string scriptLoader = readerScriptLoader.ReadToEnd();
readerScriptLoader.Close();
日历有几个按钮(右导航、左导航、关闭按钮等)。所有这些按钮都画在一个文件中(奇怪的设计),这个文件是sprite.png
。仅仅把sprite.png
放在目录中是不够的,ASP.NET在执行期间无法找到它。要在程序集中找到它,ASP.NET需要获取AssemblyUrl
,可以通过Page.ClientScript.GetWebResourceUrl()
方法获取它。
string urlImageSprite = Page.ClientScript.GetWebResourceUrl(this.GetType(), "YuiExtAspNetCalendar.resources.sprite.png");
结果:因为calendar.css
需要这个sprite.png
,提取了所有包含background:url('sprite.png')
的CSS行,在服务器端,用正确的URL(AssemblyUrl)替换:
string urlImageSprite = Page.ClientScript.GetWebResourceUrl(this.GetType(), "YuiExtAspNetCalendar.resources.sprite.png");
StringBuilder stylesExtracted = new StringBuilder();
stylesExtracted.Append("\r\n .yui-skin-sam .yui-calcontainer .title{background:url(\"[SPRITE_IMG_URL]\") repeat-x 0 0; border-bottom:1px solid #ccc;font:100% sans-serif;color:#000; font-weight:bold;height:auto;padding:.4em; margin:0 -10px 10px -10px;top:0;left:0;text-align:left;} \r\n");
// ... 更多样式
stylesExtracted = stylesExtracted.Replace("[SPRITE_IMG_URL]", urlImageSprite);
最后一点是,如果在同一页面上放置了几个日历,重复包含JS文件和CSS文件库是没有用的。所以使用了Page.Items
集合,它是HttpRequest
生命周期的持久性。
结果:如果在同一页面上放置了几个日历,做了处理,只加载一次脚本。