Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / CSS

2 Month Calendar Yahoo WebControl

4.55/5 (7 votes)
4 Jul 2011CPOL5 min read 28.9K   1.2K  
2 Month Calendar Yahoo WebControl with lots of options
Sample Image - maximum width is 600 pixels

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:

C#
//server side code :
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();
XML
<!--client side code-->
  <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.

C#
//Build CalendarLoader.js Constructor
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.

C#
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) :

C#
//Add style for registering sprite.png
//We extract all css lines from calendar.css whose contain url(sprite.png)
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)