Introduction
I have recently worked for a famous French Tennis Company, and had a specific need for a web site application. The tournament begins in the end of the May and finishes in the middle of June. My client wanted to select his dates seeing May and June months at the same time. For some users category, he wanted to allow them to select a maximum number of dates (for example: 3 days during the entire period of the tournament for the A users category). And prevent them from booking some special dates (so, the possibility to put forbidden dates on the calendar). And a last important point which is to select these dates during a date interval with a begin date and an end date (calendar view must be initialized with the first date month). It was impossible to find a 2 month calendar widget with all these options but looking good. I found one with almost all options: the EXCELLENT YAHOO calendar (http://developer.yahoo.com/yui/calendar/). I downloaded the JavaScript open source framework with the calendar component at http://developer.yahoo.com/yui/2/. And I saw that the essentials functionality was present, but 2 functionalities were missing: the possibility to set forbidden date and to set a maximum date number. So I deeply analysed the big "calendar.js" file, and because the design of the code is excellent, very clean and very well commented, I managed to implement these 2 functionalities in this JavaScript file. ;)
In this article, I won't explain my changes in calendar.js but rather the interesting points to do the implementation of the web control.
Background
In this section, I'll explain how to use the control and what the properties are.
Code
To instantiate one calendar, it's easy:
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 = "Title of Calendar :";
YuiExtCalendar.DataBind();
<cc1:YuiExtCalendar ID="YuiExtCalendar"
Mode="Flat"
runat="server" />
Mode
: The Mode
property is an enumeration of 3 possible values. Flat value shows the calendar like any classical widget. CollapsibleCalendarWithDateSummury
value shows the calendar when you click on the image with the ImageCalendarUrl
property. So, if you use CollapsibleCalendarWithDateSummury
or CollapsibleCalendarWithNoDateSummury
, you must provide the ImageCalendarUrl
property. CollapsibleCalendarWithDateSummury
generates a mini summary of your selected dates, it's useful when you close the calendar. CollapsibleCalendarWithNoDateSummury
value is exactly the same like CollapsibleCalendarWithDateSummury
without the summary.
MinDateSelectable
and MaxDateSelectable
are dateTime
properties and must be set together. It allows you to restrict your date selection interval on the calendar. For example, if you wish that your calendar allows to select dates between 19/09/2012
and 09/12/2012
. (You must set MinDateSelectable = 19/09/2012
and MaxDateSelectable = 09/12/2012
.) Set this variable server side.
InitialSelectedDates
property is a List<datetime />
, it allows you to select some datetime
at the load of the page. Set this variable server side.
ForbiddenDates
property is a List<datetime />
, it allows you to set a list of forbidden date: these dates won't be selectable. You can customize the UI style of these forbidden dates in calendar.css : /*Forbidden Style*/ .yui-skin-sam .yui-calendar td.calcell.forbidden{ ##customize_here## }
. Set this variable server side.
NumberOfSelectableDates
is an integer property that allows you to control the number of selected dates, for example: If you wish to enable one user to select 4 dates maximum in calendar, set this variable to 4
. Set this variable server side or client side.
HeaderTitle
is a string
property, it allows you to set the title of the header of the calendar. Set this variable server side or client side.
ImageCalendarUrl
is a string
property. It's the URL of the Image
in mode CollapsibleCalendarWithNoDateSummury
or CollapsibleCalendarWithNoDateSummury
. I advice you to set this variable client side.
SelectedValues
property is a List
, it allows you to get the dates selected by the user when a postback occurs from a button.
If you click on one of 2 months written on the calendar, you can select the month that you wish to see:
Using the Code
I made the choice to implement the widget calendar inheriting the CompositeControl
class. Why? Because, when you want to generate hierarchical HTML tag, the children controls ids are automatically named like this: 'ControlId_childrenControlId_subchildrenControlId_subsubchildrenControlId
', etc... It's an interesting property, and I used it above all in JavaScript (replacement of _ID_
). When you test the program on Defaut.aspx page, watch the HTML code source generated client side. CreateChildControls
is an override method who allows me to generate the HTML. OnPreRender
is an override method who allows me to append to header JavaScript files references and CSS files references id need, and to generate JavaScript to execute to build and display calendar. Each JavaScript file, CSS files and picture file must be and are registered in AssemblyInfo.cs and each file is an "Embedded Resources" in the Loading directory which must be loaded dynamically. How can I read dynamically a file which is inside the calendar assembly? I used the Assembly.GetExecutingAssembly().GetManifestResourceStream()
method to read the content of the files.
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();
Calendar
has several buttons (right navigation, left navigation, close button, etc.)... All these buttons are drawn in one file (strange design), this file is sprite.png. It doesn't suffice to put the sprint.png in the directory, ASP.NET is not able to find it during execution. To find it in the assembly, ASP.NET needs to get the AssemblyUrl
and you get it with Page.ClientScript.GetWebResourceUrl()
method.
string urlImageSprite = Page.ClientScript.GetWebResourceUrl(this.GetType(),
"YuiExtAspNetCalendar.resources.sprite.png");
Consequence: Because calendar.css needs this sprite.png, we extracted all CSS lines that contain background:url('sprite.png')
server side, we replace with the good 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")
.Append(" .yui-skin-sam .yui-calcontainer .calclose{background:url
(\"[SPRITE_IMG_URL]\") no-repeat 0 -300px;width:25px;
height:15px;top:.4em;right:.4em;cursor:pointer;} \r\n")
.Append(" .yui-skin-sam .yui-calendar .calnavleft{background:url
(\"[SPRITE_IMG_URL]\") no-repeat 0 -450px;width:25px;height:15px;
top:0;bottom:0;left:-10px;margin-left:.4em;cursor:pointer;} \r\n")
.Append(" .yui-skin-sam .yui-calendar .calnavright{background:url
(\"[SPRITE_IMG_URL]\") no-repeat 0 -500px;width:25px;height:15px;
top:0;bottom:0;right:-10px;margin-right:.4em;cursor:pointer;} \r\n")
.Append(" .yui-skin-sam .yui-calendar a.calnav:hover{background:url
(\"[SPRITE_IMG_URL]\") repeat-x 0 0;border-color:#A0A0A0;cursor:pointer;} \r\n")
.Append(" .yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn
{border:1px solid #808080;background:url(\"[SPRITE_IMG_URL]\")
repeat-x 0 0;background-color:#ccc;margin:auto .15em;} \r\n")
.Append(" .yui-skin-sam .yui-calcontainer .yui-cal-nav
.yui-cal-nav-btn.yui-default{border:1px solid #304369;
background-color:#426fd9;background:url
(\"[SPRITE_IMG_URL]\") repeat-x 0 -1400px;} \r\n");
stylesExtracted = stylesExtracted.Replace("[SPRITE_IMG_URL]", urlImageSprite);
The last point is that if we put several calendars in the same page, it's useless to include several times JS files and CSS files libraries. So I used Page.Items
collection which is the persistance is the life cycle of an HttpRequest
.
Consequence: If you put several calendars in the same page, I did the treatment to load the script once.
Points of Interest
I improved my knowledge about JavaScript encapsulation in ASP.NET component. I wish to work in a company whose business is the design of toolbox ASP.NET components.
History
I tested this component several times, in lots of situation. Its behaviour looks correct. Don't hesitate to ask if you have any questions.