Click here to Skip to main content
15,884,388 members
Articles / Web Development / ASP.NET

ASP Server-Side JavaScript-like Calendar PopUp

Rate me:
Please Sign up or sign in to vote.
4.03/5 (13 votes)
25 Jan 2006CPOL9 min read 170.1K   764   69   34
One of those infamous ASP.NET server-side calendar controls that look and work like a JavaScript control! This is an easy plug-in for any project! I included some functions like being able to have the calendar display over other controls, and the ability to move through different months and keep the

Image 1

Introduction

Working on a project, I found a neat way to make one of those infamous ASP.NET server-side calendar controls that look and work like a JavaScript control! This is an easy plug-in for any project! I included some functions like being able to have the calendar display over other controls, the ability to move through different months and keep the current calendar displayed; in essence, I made the ASP.NET server-side control work just like one of those slick JavaScript calendar objects out there on the net. It also keeps the control over select drop downs and other like controls (with one caveat, when you change months, it does not do this, much to my chagrin!).

Setup

To get this to work in your ASP.NET 2.0 (or you could even use it in 1.1) web application project, you first need to import Calendar.ascx (and of course, Calendar.ascx.cs) into your project in the usual place where you keep user controls. I put mine in a folder I named UserControls.

Next, to get the images to be seen no matter what level of the application you are in, I usually include the project name as a web.config application settings variable. This is useful when your application is not the root web application. If it is the root, then just strip this out of the calendar user control:

XML
<configuration>
    <appSettings>
        <add key="ApplicationName" value="ASP2.0_Calendar"/>
    </appSettings>
    <connectionStrings/>

...the Calendar.ascx code:
<img border="0" 
  src="/<%= ConfigurationManager.AppSettings["ApplicationName"] %>/images/x.gif" 
  align="top" ></a>

Now, you can use my style sheet and calendar theme, or make up your own for the calendar. To use mine, simply put the Calendar.skin file into your application's App_Themes/CalendarTheme folder.

Below is how the Solution Explorer paths look in the actual project.

Image 2

You need to add the theme references to your page header as well:

ASP.NET
<%@ Page Language="C#" ..... Theme="CalendarTheme" 
    Title="ASP2.0 Pop-Up Calendar Example"  StylesheetTheme="CalendarTheme" %>

Now you are ready to add a calendar control to your page.

Using the Code

The first thing with adding a user control to any page in ASP.NET is to register the control with the page. This basically allows the type of control to be seen and allowed by the page. Visual Studio .NET will usually do this for you when you are in Designer mode and drop a control from your Solution Explorer directly on the page:

Image 3

The control object can also be added manually by using the @Register tag, as seen here:

ASP.NET
<%@ Register Src="UserControls/Calendar.ascx" TagName="Calendar" TagPrefix="uc1" %>

Next, you want to add the actual control:

ASP.NET
<uc1:Calendar ID="EndDateCalendar" runat="server" />

I made the row tag <td> have an onclick event so that if you clicked the text box or the little calendar icon, the calendar would popup. Then you can close it with an X box image on the calendar pop-up!

ASP.NET
<td  valign="top" align=left nowrap 
   onclick="javascript:setDisplay('<%=EndDateCalendar.ClientName %>', 
            false);setDisplay('<%=StartDateCalendar.ClientName %>',true);">
....Text Box and Image
</td>

Notice that each calendar object has a method to get its client name. I did this so we could merge JavaScript from the page with JavaScript from the control and make a call to the appropriate calendar's JavaScript display method.

Now, let's take a look at some important points of how the calendar works.

First, let's look at the additions you need to make to the page itself. I say page, but this could be any control, even another user control. I have added a method to allow the text box object to be registered with the calendar control. This allows some automatic exchange of data between your text box or hidden field and the calendar. Right now, only databinding the text box and changing the calendar object's date value will have any cross binding effect without adding an event on the page. I tried adding some code to capture the TextBox.OnTextChanged events, but this didn't work. I suspect it was because the event is on the calendar control and not on the page. It is strange though, since the TextBox.OnDataBinding event on the calendar does get called. Anyone out there know why this is?

C#
protected void Page_Init(object sender, EventArgs e)
{
    if (Page.IsPostBack) return;
    this.StartDateCalendar.RegisterControl(ref StartDate);
    this.EndDateCalendar.RegisterControl(ref EndDate);
    this.Page.LoadComplete += new EventHandler(Page_LoadComplete);
}

We need to add some code to add an event handler for the Page.LoadComplete event for the page or control the calendar is going to be displayed on. I do this because I want the calendar control to have its data loaded after everything else is done on the page. Whether or not you can make it work in a normal page load, I leave up to you.

I make the text box for each calendar control have as its text the calendar control's selected value. I do this so that when the page is reloaded after the calendar selection is changed, we get those values displayed in the text box. So when you select a value from the calendar control and the page refreshes, the value you chose is the value in the text box. After that, I check for a post back on the page, and if not (this is the initial load of the page), then I set both the text box and calendar control values. This is where you would fill a page with values from the data source.

An alternate way is to databind the text box control, which will call the TextBox_DataBinding event handler on the calendar control. This frees up having to set the values on the calendar object specifically.

C#
protected void Page_LoadComplete(object sender, EventArgs e)
{
    StartDate.Text = StartDateCalendar.SelectedDate;
    EndDate.Text = EndDateCalendar.SelectedDate;
    
    if (Page.IsPostBack) return;
    this.StartDate.Text = DateTime.Now.ToShortDateString();
    this.StartDateCalendar.SelectedDate = this.StartDate.Text;
    this.EndDate.Text = DateTime.Now.AddDays(12).ToShortDateString();
    this.EndDateCalendar.SelectedDate =  this.EndDate.Text;
}

//.....Alternate way

this.StartDate.Text = DateTime.Now.ToShortDateString();
this.StartDate.DataBind();
this.EndDate.Text = DateTime.Now.AddDays(12).ToShortDateString();
this.EndDate.DataBind();

Next, we need to set up two events for when the text box control's text get changed by adding event handlers to the TextChanged event. This will make sure that the calendar controls always have the same values as their text boxes. As I stated above, I could not get the TextBox_TextChanged event for the registered text box control to work. I suspect it has something to do with its location on the calendar, but I don't know right now. Anyway, I do it here on the page where the text box control is, and it works fine...

ASP.NET
<--the StartDate textbox on Default.aspx-->
<asp:TextBox ID="StartDate" runat="server" 
   MaxLength="10" OnTextChanged="StartDate_TextChanged"
   ValidationGroup="EditGroup" Width="100px"  ></asp:TextBox>
.......the EndDate textbox on Default.aspx
<asp:TextBox ID="EndDate" runat="server" 
   MaxLength="10" OnTextChanged="EndDate_TextChanged"
   ValidationGroup="EditGroup" Width="100px" ></asp:TextBox>

The Default.cs file:

C#
#region Calendar
protected void StartDate_TextChanged(object sender, EventArgs e)
{
    StartDateCalendar.SelectedDate = StartDate.Text;
}
protected void EndDate_TextChanged(object sender, EventArgs e)
{
    EndDateCalendar.SelectedDate = EndDate.Text;
}
#endregion

Now we can look at the user control.

The first thing we notice is the Page.Load event handler, which checks for an attribute called IsMonthChanging to be true to set up a hidden field. This sets the calendar object's value and state for the control. It also changes the registered control's value if the IsMonthChanging value is false:

C#
protected void Page_Load(object sender, EventArgs e)
{        
   if (!IsMonthChanging)
    {            
        this.SelectedDate = DateCalendar.SelectedDate.ToShortDateString();
        if (Page.FindControl(RegisteredControlName.Value) is TextBox)
        {
            ((TextBox)FindRegisteredControl()).Text = this.SelectedDate;
        }
        else if (FindRegisteredControl() is HiddenField)
        {
            ((HiddenField)FindRegisteredControl()).Value = this.SelectedDate;
        }
    }
}

We see below this, two methods to register either a text box or a hidden field with the calendar. This is here so we can data bind and have the calendar and the text box control automatically exchange their values:

C#
public void RegisterControl(ref TextBox controlToRegister)
{
    //controlToRegister.TextChanged += new EventHandler(TextBox_TextChanged);
    controlToRegister.DataBinding += new EventHandler(TextBox_DataBinding);
    controlToRegister.AutoPostBack = true;
    RegisteredControlName.Value = controlToRegister.ID;
}
public void RegisterControl(ref HiddenField controlToRegister)
{
    controlToRegister.DataBinding += new EventHandler(HiddenField_DataBinding);        
    RegisteredControlName.Value = controlToRegister.ID;
}

The next thing we see is the IsMonthChanging attribute which tells us that we are not changing the calendar value, but instead are changing the displayed month. This will not affect the actual value of the control:

C#
protected bool IsMonthChanging
{
    get { return Convert.ToBoolean(MonthChanging.Value); }
}

The ClientName attribute gives us a reference for the client-side JavaScript outside the control to the control's client rendered name. This is a special name that we use for the entire control, based on the control's ClientId attribute:

C#
public string ClientName
{
    get { return this.ClientID + "_Selectable"; }
}

The SelectedDate attribute allows us to get or set the value for the actual date of the control. It uses a hidden literal field to maintain the state of the control and check for errors. If an input error occurs, it sets the control back to the last valid date value:

C#
public string SelectedDate
{
   get
    {
        if (DateValue.Value != "1/1/0001")
            return DateValue.Value;
        else
            return null;
    }
    set
    {
        try
        {
            DateCalendar.SelectedDate = Convert.ToDateTime(value);
            DateCalendar.VisibleDate = Convert.ToDateTime(value);
            DateValue.Value = value;
        }
        catch (Exception)
        {
            DateCalendar.SelectedDate = Convert.ToDateTime(DateValue.Value);
            DateCalendar.VisibleDate = Convert.ToDateTime(DateValue.Value);
        }
    }
}

Next are two event handlers: DateCalendar_SelectionChanged, which serves to allow us to change the state and value of the control, and DateCalendar_VisibleMonthChanged, which allows us to determine that we are only changing the server control's current displayed month, not the actual value:

C#
protected void DateCalendar_SelectionChanged(object sender, EventArgs e)
{
    DateValue.Value = DateCalendar.SelectedDate.ToShortDateString();
    if (Page.FindControl(RegisteredControlName.Value) is TextBox)
    {
        ((TextBox)FindRegisteredControl()).Text = this.SelectedDate;
    }
    else if (FindRegisteredControl() is HiddenField)
    {
        ((HiddenField)FindRegisteredControl()).Value = this.SelectedDate;
    }
    if (Page.IsPostBack)
    {
        MonthChanging.Value = "False";
    }
}

protected void DateCalendar_VisibleMonthChanged(object sender, MonthChangedEventArgs e)
{
    MonthChanging.Value = "True";
}

Lastly are two event handlers: TextBox_DataBinding, which serves to allow us to change the value of the control when the registered text box is bound with data, and HiddenField_DataBinding, which is an alternate method for hidden fields and allows us to change the value of the control when the registered hidden field is bound with data:

C#
/*protected void TextBox_TextChanged(object sender, EventArgs e)
{
     this.SelectedDate = ((TextBox)FindRegisteredControl()).Text;
}*/
protected void TextBox_DataBinding(object sender, EventArgs e)
{
     this.SelectedDate = ((TextBox)FindRegisteredControl()).Text;
}   

protected void HiddenField_DataBinding(object sender, EventArgs e)
{
     this.SelectedDate = ((HiddenField)FindRegisteredControl()).Value;
}

In the ASCX file, we have some things that we need to look at too. The way we display or hide the control is determined by the boolean settings of its hidden field IsMonthChanging. We set the client ID as the control's ClientName attribute, and then in the style of the <div> tag, we determine if the control is displayed or not by checking the IsMonthChanging attribute. Also notice that the position attribute of the div style is set to absolute. This is done so that the calendar will float over the rest of the page. We also have an IFrame which allows us to keep drop down controls from being displayed on top of the calendar:

ASP.NET
<iframe id="<%= this.ClientName %>_overShelf" 
       scrolling="no" frameborder="0" 
       style="position:absolute; top:0px; left:0px; display:none;"></iframe>
<div id="<%= this.ClientName %>" style="z-index:99999; position:absolute; display:none" >

Our display function has the individual control's name, so we can call from outside the control the exact client JavaScript method name on the control. We set up in the client method how the IFrame and the div are displayed on the page:

JavaScript
function <%= this.ClientName %>_SetDisplay(doDisplay)
{
    if(doDisplay == true)
    {
        document.getElementById('<%= this.ClientName %>').style.display='inline';      
        document.getElementById('<%= this.ClientName %>_overShelf').style.zIndex = 
           document.getElementById('<%= this.ClientName %>').style.zIndex - 1;
        document.getElementById('<%= this.ClientName %>_overShelf').style.width = 
           document.getElementById('<%= this.ClientName %>').offsetWidth;
        document.getElementById('<%= this.ClientName %>_overShelf').style.height = 
           document.getElementById('<%= this.ClientName %>').offsetHeight;
        document.getElementById('<%= this.ClientName %>_overShelf').style.top = 
           document.getElementById('<%= this.ClientName %>').offsetTop;
        document.getElementById('<%= this.ClientName %>_overShelf').style.left = 
           document.getElementById('<%= this.ClientName %>').offsetLeft;
        document.getElementById('<%= this.ClientName %>_overShelf').style.x = 
           document.getElementById('<%= this.ClientName %>').offsetTop;
        document.getElementById('<%= this.ClientName %>_overShelf').style.left = 
           document.getElementById('<%= this.ClientName %>').offsetLeft;
        document.getElementById('<%= this.ClientName %>_overShelf').style.display = 'block';
        document.getElementById('<%= MonthChanging.ClientID %>').value="True";
    }
    else
    {
        document.getElementById('<%= this.ClientName %>').style.display='none';   
        document.getElementById('<%= this.ClientName %>_overShelf').style.zIndex = 
           document.getElementById('<%= this.ClientName %>').style.zIndex - 1;
        document.getElementById('<%= this.ClientName %>_overShelf').style.width = 
           document.getElementById('<%= this.ClientName %>').offsetWidth;
        document.getElementById('<%= this.ClientName %>_overShelf').style.height = 
           document.getElementById('<%= this.ClientName %>').offsetHeight;
        document.getElementById('<%= this.ClientName %>_overShelf').style.top = 
           document.getElementById('<%= this.ClientName %>').offsetTop;
        document.getElementById('<%= this.ClientName %>_overShelf').style.left = 
           document.getElementById('<%= this.ClientName %>').offsetLeft;
        document.getElementById('<%= this.ClientName %>_overShelf').style.x = 
           document.getElementById('<%= this.ClientName %>').offsetTop;
        document.getElementById('<%= this.ClientName %>_overShelf').style.left = 
           document.getElementById('<%= this.ClientName %>').offsetLeft;
        document.getElementById('<%= this.ClientName %>_overShelf').style.display = 'none';
        document.getElementById('<%= MonthChanging.ClientID %>').value="False";
    }

Running the Code

Now, let's run the code and examine how the control works at runtime. We see upon entry of the screen that our text box values are present inside their respective text boxes:

Image 4

Next, if we click either the calendar icon or the text box, we see the calendar pop-up displayed just below the text box. Since the <div> tag's style property in the control has its position set to absolute, the control appears to float over the other controls. Also notice that the date in the text box is selected in red in the calendar:

Image 5

Now if we wanted to change the calendar to another month, we can click the < or the > links on the calendar. This causes a post back just like on any other ASP.NET server-side calendar control, but when the page comes back up, the calendar won't disappear! It stays up until you click a value or the X icon at the top of the calendar! Pretty cool!

Image 6

Now if you change the calendar's value, the calendar disappears and the text box gets that new value. If you were to again click on the text box or the calendar icon, you would see the calendar pop-up in the correct month, with the correct data selected!

Image 7

Points of Interest

I tested this in IE only. I think cross browser, the client script to display the control will not work well. Probably a good refactor of this code would include some client-side code that works cross browser. If you send me this code improved in an acceptable manner, I will modify the article and give you your due credit for being the awesome CodeProject developer you are!

Also, there is room for improvement in how the text box and calendar objects co-exist (as stated above). Any good changes you wish to share will also be added, and the submitter will receive his or her just due on the article!

Thanks for reading!

Updates

  • 01/25/06 - I have added some support for finding the controls in Master-Child page relationships.

License

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


Written By
Web Developer
United States United States
Christopher G. Lasater

I am also a published author, please check out my book:
ISBN: 1-59822-031-4
Title: Design Patterns
Author:Christopher G. Lasater
More from my web site
Amazon.com


Comments and Discussions

 
GeneralClose control Pin
Amy23love25-Oct-07 3:03
Amy23love25-Oct-07 3:03 
GeneralRe: Close control [modified] Pin
DerekFL13-Apr-08 14:49
DerekFL13-Apr-08 14:49 
GeneralCalendar Control Pin
bkwilliams10@hotmail.com24-Oct-07 10:17
bkwilliams10@hotmail.com24-Oct-07 10:17 
GeneralAwesome Pin
Zodraz2-Apr-07 7:13
Zodraz2-Apr-07 7:13 
QuestionWhat is taking up the space? Pin
silvermonopoly20-Mar-07 10:51
silvermonopoly20-Mar-07 10:51 
When I put the Textbox and Calendar.jpg in a table as is done in the example and let it auto-adjust to width, It seems to make a table row about 500px wide, but nothing I do makes it shorter, and removing nowrap makes it wrap but now the table's row height has increase which is not any better. What I can't figure out, is what is causing the size and how can I reduce it? It seems to also be present in the example, but layout was not an issue in the example.

Anyone?

Stephen
QuestionThe DropDownList appear when the visible month is changed Pin
aabukar18-Mar-07 18:50
aabukar18-Mar-07 18:50 
AnswerRe: The DropDownList appear when the visible month is changed Pin
aabukar20-Mar-07 0:37
aabukar20-Mar-07 0:37 
QuestionHow swap values of two textboxes in javascript Pin
Member 385772223-Feb-07 2:55
Member 385772223-Feb-07 2:55 
AnswerRe: How swap values of two textboxes in javascript Pin
anciwasim8-Mar-07 2:37
anciwasim8-Mar-07 2:37 
GeneralSuggest me Pin
gocap11-Dec-06 0:44
gocap11-Dec-06 0:44 
GeneralUsing the control within a user control Pin
Mark Nahirny19-Sep-06 12:51
Mark Nahirny19-Sep-06 12:51 
GeneralDate Format Issue Pin
Annyh29-Jun-06 11:50
Annyh29-Jun-06 11:50 
GeneralDayPilot - Outlook-like calendar/scheduling control for ASP.NET (open-source) Pin
Dan Letecky28-Jun-06 10:48
Dan Letecky28-Jun-06 10:48 
GeneralForm View Pin
woodmark446720-Mar-06 3:44
woodmark446720-Mar-06 3:44 
GeneralRe: Form View Pin
Christopher G. Lasater20-Mar-06 4:57
Christopher G. Lasater20-Mar-06 4:57 
Generalclear the date Pin
chiboon26-Jan-06 17:56
chiboon26-Jan-06 17:56 
GeneralRe: clear the date Pin
Pedro Maia Costa3-Feb-06 4:13
Pedro Maia Costa3-Feb-06 4:13 
GeneralRe: clear the date Pin
Christopher G. Lasater8-Feb-06 7:52
Christopher G. Lasater8-Feb-06 7:52 
QuestionRe: clear the date [modified] Pin
Mike Donner27-Jun-07 8:58
Mike Donner27-Jun-07 8:58 
GeneralThanks Pin
Jozef Sevcik25-Jan-06 9:57
Jozef Sevcik25-Jan-06 9:57 
GeneralRe: Thanks Pin
Christopher G. Lasater25-Jan-06 9:58
Christopher G. Lasater25-Jan-06 9:58 
QuestionWhy not use the ~(tilde) for application path? Pin
Brian Lowe25-Jan-06 4:55
Brian Lowe25-Jan-06 4:55 
AnswerRe: Why not use the ~(tilde) for application path? Pin
Christopher G. Lasater25-Jan-06 8:53
Christopher G. Lasater25-Jan-06 8:53 
GeneralRe: Why not use the ~(tilde) for application path? Pin
Brian Lowe25-Jan-06 9:03
Brian Lowe25-Jan-06 9:03 
GeneralRe: Why not use the ~(tilde) for application path? Pin
Christopher G. Lasater25-Jan-06 9:07
Christopher G. Lasater25-Jan-06 9:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.