1. Original Entry + Comments2. Write a Comment3. Preview Comment
New comments for this entry are disabled.


May 16, 2006  |  Yet another pop-up calendar  |  14272 hit(s)

THe topic of creating a pop-up calendar seems to be a perennial favorite.[1] I played around with one some time ago, which in that case mostly consisted of channeling someone else's ideas.

And so again. My friend Michael B was working on his own version, which involved what I thought was a significant improvement: rather than popping up the calendar in a pop-up (that is, using window.open), he was using CSS attributes to show and hide a calendar. The problem with using window.open is that it can be blocked by pop-up blockers. But simply showing and hiding a calendar should be no problem, assuming JavaScript is enabled.

The basic concept is this. A normal ASP.NET calendar lurks on the page, but its style visibility attribute is initially set to hidden. A client-side show-the-calendar button runs JavaScript to set the style visibility attribute to visible. Gentle User selects a date (the postback, she occurs), and then server-side code returns the date to whatever (ASP.NET) text box you want. (Naturally, the calendar has to be absolutely positioned, and should probably have a z-order that guarantees that it will appear in front of everything else.)

Here's what it looks like -- you can play with it on this page.



Naturally, there were complications. Some were self-imposed, of which the biggest was that I wanted to re-use a single Calendar control. This meant (as I eventually figured out) that I had to store information about the textbox that the Calendar was "bound" to and about the top/left location where the Calendar should appear -- I wanted it to have an offset from the button that had invoked it, rather than a fixed position on the page.

The button that invokes the calendar is all client-side, so I was obliged to use a somewhat clunky mechanism of a) writing a client-side function that would take the ID of the "bound" text box, b) capture the top/left coordinates of the invoking button, and apply them to the calendar, and c) store all that in a hidden field. This last enables me to pass the info up to the server on postback. Because I was doing all this in a hurry, I elected to store the three pieces of information in a string delimited by "+".[2]

When the user selects a date in the calendar, server-side code plucks the ID of the "bound" text box out of the hidden field, finds the actual text box instance, and sets its Text property to the date. Server code then hides the calendar, since we're now done with it.

A tricky thing arose in month navigation -- the calendar does a postback, but we don't want it to disappear until the user actually selects a date. In that case, server code plucks the top and left values out of the hidden field and (re)applies those to the calendar so it will (continue to) show up at the right spot.

You can see the source code for the whole page here. For a change of pace, I wrote the server code in C#; I need the practice.

There's a lot of code required for this -- client code, server code, and fussy function calls for the buttons doing the invoking. This really wants encapsulation in a control. I played briefly with making the whole business a user control, but realized I needed to do actual work to determine the ID of the Calendar control inside a container user control. So I put that off till I could "think about it more."

But anyway, I have a sense of what needs to be done, I guess. I'll work it all into a single control Real Soon Now.

[1] To some extent, this kind of discussion will be moot in the near future, when we can use any of dozens of soon-to-be AJAX-y calendars that require no server involvement at all.

[2] Incidentally, I ran into some DHTML and JavaScript issues here. The top and left style properties return the value plus the units -- e.g. 100px. In IE, you can get the posTop and posLeft properties, which are just numbers, no units. But those aren't supported in Firefox. So I had to get top, which is a string, and strip px. Then I want to add 15 pixels to it -- (top + 15). But it's a string! So the + operator concatenates (stupid overloaded operators, haha) and comes up with, say, 10015. You need to coerce the damm thing back into a number, which I did with this hackery: (1 * top) + 15. I assume there's a better way to do this, probably involving a method named Parse, yes?




Jim Vierra   21 May 06 - 11:04 AM

    Some thoughts:

Shouldn't the calendar be all client side? This makes it more flexible. Whether it "posts back" or calls back or just fils the box is an application issue and not a browser/server issue in my view.

In the past - sorry I can't find a quick sample - I have used HML injection to create a client side calendar on a text box. It can be initiated through a behavior or just via the click event. The event catches the element id and uses it to position and show a calendar. What to do with the date after it updates the parent control is left for the parent control to determine. This can be made to work for bot client side and server side controls since all rendering and positioning are left to the client and the dom and their events.

The "Ajaxy" controls are probably doing just this. SInce Ajax/Atlas can send script libraries this becomes a jumping off point to extending existing pages and controls with less change to the page.


 
mike   21 May 06 - 12:00 PM

I'm not quite sure about the "should" part -- there certainly could be an all client-side calendar, which is why I mentioned AJAX. The example uses the ASP.NET Calendar control, which doesn't lend itself to a pure client implementation (I don't think -- ?), given that it wants to perform a postback when any date or navigation link is clicked. I am imagining that using a client-based calendar involves perhaps substantial extra effort handling the client events (?).

Anyway, I don't disagree that a client-based calendar has advantages, but I don't think it's necessarily a fatal flaw to use the existing ASP.NET Calendar control in this way. I would like to see an example of a pure client calendar, tho ...


 
Jim Vierra   21 May 06 - 2:25 PM

Mike -

I am not criticizing anything. All of these solutions are good and I have used most.

Put a simple table in a div that has a style sheet ether inline or external. Inject the div into the page when the text(date) box gets clicked. You need only one even on the table; "onclick". You can then retrieve what was clicked from teh Dom. When it is a TD element that has a number or a nav element asking for a month change just adjust and re-inject or handle the date selection passing it to the datebox. Let the datebox onchange event handle the outcome. Either postback or callback.

This can be added to atlas using the script manager.

Server-side controls can be altered when sending the page. client-side can be sort of sub-classed to using teh DOM.

Of courser on new sites built from the beginning with Ajax a pure server-side solution would be easiest for most to implement.

I have always tried to design pages that avoided post-back by using DHTML/DOM techniques and, when possible, data islands. I think merging some of this with Ajax can enhance the user experience.

Your example work great. I started muy first calendar in a similar way. Seeing many calendars on Windows Forms with little ellipses to open the date-picker made me copy this. Later I decided that the natural flow was to click in the data box to type a date. Why not just oopen the dat-picker on the click. It still allows a date to be type directly into the control but quickly allows the user to choose a date if that is easier.

As you indicated - there will certainly be more popup calendars because this is not a one-size-fits-all world.




 
Dave   11 Jul 06 - 4:10 PM

I think it worked great.
Just need to factor in saving date value for multiple calendars on one form.

But why souldn't I do just a little work after you already did all the rest...

Thanks Mike


 
Nor   02 Mar 07 - 3:39 PM

Dear Mike:

I want to similar example in VB.net

<asp:TextBox ID="txtFechaI" runat="server" CssClass="px_datos" Text='<%# Bind("FCH_INI") %>' AutoPostBack="True"></asp:TextBox>
<a href="javascript:;" onclick="PopupPicker('HiddenCal');" title="Calendario">
<asp:Image ID="imgCal" runat="server" ImageAlign="Middle" ImageUrl="../img/cal.gif" /></a>

In this event assign value al textbox(template):

Dim FechaI As Date
Dim Obj As Object = DataPermisos.FindControl("txtFechaI")

Dim Tb As TextBox = DirectCast(Obj, TextBox)

FechaI = HiddenCal.Value

If Not Nothing Is Obj Then

Tb.Text = ""

Else
Tb.Text = FechaI.Date.ToString("dd MMM yyyy")

End If


And have an error in line : Tb.Text = FechaI.Date.ToString("dd MMM yyyy")


NullReferenceException was unhandled by user code
Object reference not set to an instance of an object.

I dont know what is my mistake.
Thanks for your help.
Regards.


 
mike   02 Mar 07 - 10:56 PM

Nor, the best advice I have offhand is to set a breakpoint on the beginning of this event handler and step through it to see at what point values stop being what you expect them to be.

 
LadyReader   30 May 07 - 6:29 AM

In my application, when the user selects a date, a drop down list specific to that date should be populated. The date selected populates the textbox but I need it to additionally trigger the ontextchanged event for that textbox, which it does not. Can you suggest how to do this?

 
mike   12 Jun 07 - 8:57 AM

@Ladyreader -- sorry for the delay, I didn't see your comment till just now for some reason.

You can call the event-handling method yourself any time you like, perhaps like this:
protected void DatePicker_SelectionChanged(object sender, 
System.EventArgs e)
{
ResetCalendarProperties("hidden");
if (_tb != null)
{
_tb.Text = DatePicker.SelectedDate.ToShortDateString();
txtFromDate_TextChanged(txtFromDate, e);
}
}

// [...]

protected void txtFromDate_TextChanged(object sender,
EventArgs e)
{
this.Label2.Text = txtFromDate.Text;
}


 
Karu Garusinghe   13 Aug 07 - 11:33 AM

Hi Mike

I used your Calendar program
I am getting following error message. I don't know where I am wrong. Pls help me

namespace name 'StringSplitOptions' could not be found

Thank You


 
mike   13 Aug 07 - 8:26 PM

@karu -- I'm not sure what the problem might be. StringSplitOptions is a standard enumeration that should always be available:

http://msdn2.microsoft.com/en-us/library/system.stringsplitoptions.aspx

The only thing I can even imagine is that the capitalization might be off -- ? Since C# is case sensitive. I can't otherwise think of what the problem might be.




 
chris   27 Apr 08 - 12:47 AM

Hi,

I'm new to ASP and .Net but this is great. But I tried using it on a GridView and it seems not to be working. I have a textbox and calendar control inside the GridView. Any idea would be highly appreciated.

More power.

Thanks.


 
steve   12 May 08 - 10:33 AM

Hey Mike,

I really like this. I too get a "The type or namespace name 'StringSplitOptions' could not be found" error (in 1.1). It works great in 2.0. I'm a newbie, but I really like how you have this set-up and would like to use it. Is there a way around the StringSplitOptions. I'm guessing the namespace is not available in 1.1...

Thanks!!


 
mike   13 May 08 - 11:28 PM

@steve - correct, String.Split() exists in 1.1, but it doesn't take the second parameter that's a string split options enumeration value. I think you might be able to just leave it off. However, you have to pass the delimiter as a char array instead of a string array. They have an example here:

http://msdn.microsoft.com/en-us/library/system.string.split(VS.71).aspx


@chris - do you have any more details about how it isn't working?


 
steve   14 May 08 - 1:44 PM

First of all Mike, thank-you for taking the time to respond to my comment. FYI - I played around with it a bit today (keep in mind, I'm just learning this stuff, and to be honest, I'm still not even sure what String.Split even does)...but here's what I came up with:

char [] hiddenTargetStringArray = hiddenCalendarProperties.Value.ToCharArray();
string targetID = hiddenTargetStringArray[TARGETID].ToString();
_tb = (TextBox)Page.FindControl(targetID);
DatePicker.Attributes.CssStyle["Top"] = hiddenTargetStringArray[TOP].ToString();
DatePicker.Attributes.CssStyle["Left"] = hiddenTargetStringArray[LEFT].ToString();
DatePicker.Attributes.CssStyle["visibility"] = hideFlag;

I'm close. It almost works. The only thing it doesn't seem to do is populate the text box... .

Again, thanks again for everything...if you have another quick suggestion I'd appreciate it...either way, Thanks!!


 
mike   14 May 08 - 10:25 PM

Ok, I think I've got it ... I've put a version up that I believe (?) will work with the .NET Framework version 1.1, which is to say, a version that uses an older overload of String.Split. The source is here; the new parts are highlighted (I think I got them all, anyway).

http://mikepope.com/blog/fun/Calendar_Popup_Css_1_1.htm

Let me know ...


 
steve   15 May 08 - 5:14 AM

You totally Rock!! It works!

I can't tell you how much this helps me...if I win lotto on Friday, you're definitely getting a cut.

Thanks!!