Implementing a jQuery-Datepicker ASP.NET Control
[9/12/2009 – Note this post is an update of an older post that discussed a wrapper around Marc Garbanski’s jQuery-calender control. This post updates to the current jQuery.ui version (1.72). The control has been integrated into the West Wind Web & Ajax Toolkit for ASP.NET and you can grab the code from there in the Westwind.Web project. The links to the sample and download point at the Toolkit]
I've posted a wrapper ASP.NET around the jQuery.UI Datepickercontrol. This small client side calendar control is compact, looks nice and is very easy to use and I've added it some time back to my control library.
This is primarily an update for the jQuery.ui version, and so I spend a few hours or so cleaning it up which wasn’t as easy as it could have been since the API has changed quite drastically from Marc’s original implementation. The biggest changes have to do with the theming integration and the resulting explosion of related resources.
If you want to use this component you can check it out a sample and the code here:
The control is a very basic wrapper around jQuery.ui Datepicker control and it provides the basic features of the client control wrapped with server side properties so you can just drag and drop the control onto a page.
Building a control wrapper around this control is pretty straight forward. The main complications arise out of determining the best way of dealing with the resources. ASP.NET controls tend to embed all resources into the control assemblies - which has certain advantages such as the ability to automatically compress the content. But it's not always optimal to do this for example, if you have many sites and can rely on shared script resources in a server - or even on a remote server - to serve resources which is more efficient then letting ASP.NET serve resources.
Unlike the original component that I posted 2+ years back this component does not wrap up all of the required resources in ASP.NET resource for fully self contained operation. The reason for this change is that jquery.ui’s theming has drastically changed the amount of dependencies required and so both jquery.ui and the theme items must be deployed separately. By default the expected location are ~/script/jquery-ui-custom.js and a themes folder below it, but this configurable on the control.
This control works by a SelectedDate property that is tied to the underlying text box - or in the case of the Inline calendar a hidden value. Although jQuery datepicker is all client side the control implementation is Postback aware and appropriately persists date values.
The main task of the control is simply to map server properties to the appropriate jQuery-calendar initialization code (in jQuery().ready). Thanks to Marc's simple front end to the control it's pretty straight forward to set up a server control. All of the initialization happens through JavaScript code, so there's a bit of not so clean script generation by the control in the sense the script code generation is always pretty ugly.
The control is super easy to use. In its default configuration it looks something like this:
<div class="samplebox"> <h3>Image Button Popup:</h3> <hr /> This version shows a calendar image button to click on to pop up the calendar:<br /><br /> Enter Date: <ww:jQueryDatePicker runat="server" id="txtImageButton" DisplayMode="ImageButton" DateFormat="MM-dd-yyyy" ShowButtonPanel="true" SelectedDate="08-10-2009" Theme="Redmond" /> </div>
which looks like this (using the Redmond theme from theme roller):
In code behind you can read and write the SelectedDate or Text properties.
There's not a ton of code here so you can check it out for yourself here or by downloading the code from the link above. The code has a dependency on my ClientScript component which is also part of the West Wind Web Toolkit so you may have to download a couple of additional files (ClientScriptProxy.cs, WebUtils.cs at least) or you can replace those calls with the ASP.NET ClientScript or ScriptManager objecs.
Anyways here’s the code:
[updated: Sept. , '09 with for jQuery.ui 1.72]
/// <summary> /// ASP.NET jQuery DatePicker Control Wrapper /// by Rick Strahl /// http://www.west-wind.com/ /// based on jQuery UI DatePicker client control by Marc Grabanski /// http://marcgrabanski.com/code/ui-datepicker/ /// /// Simple DatePicker control that uses jQuery UI DatePicker to pop up /// a date picker. /// /// Important Requirements (configurable): /// ~/scripts/jquery.js (available from WebResource) /// ~/scripts/jquery-ui-custom.js (custom build of jQuery.ui) /// ~/scripts/themes/base (set Theme property for other themes to apply) /// /// Resources are embedded into the assembly so you don't need /// to reference or distribute anything. You can however override /// each of these resources with relative URL based resources. /// </summary> [ToolboxBitmap(typeof(System.Web.UI.WebControls.Calendar)), DefaultProperty("Text"), ToolboxData("<{0}:jQueryDatePicker runat=\"server\" />")] public class jQueryDatePicker : TextBox { public jQueryDatePicker() { // Date specific width this.Width = Unit.Pixel(80); } /// <summary> /// The currently selected date /// </summary> [DefaultValue(typeof(DateTime), ""), Category("Date Selection")] public DateTime? SelectedDate { get { DateTime defaultDate = DateTime.Parse("01/01/1900", CultureInfo.InstalledUICulture); if (this.Text == "") return defaultDate; DateTime.TryParse(this.Text, out defaultDate); return defaultDate; } set { if (!value.HasValue) this.Text = ""; else { string dateFormat = this.DateFormat; if (dateFormat == "Auto") dateFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; this.Text = value.Value.ToString(dateFormat); } } } /// <summary> /// Determines how the datepicking option is activated /// </summary> [Description("Determines how the datepicking option is activated")] [Category("Date Selection"), DefaultValue(typeof(DatePickerDisplayModes), "ImageButton")] public DatePickerDisplayModes DisplayMode { get { return _DisplayMode; } set { _DisplayMode = value; } } private DatePickerDisplayModes _DisplayMode = DatePickerDisplayModes.ImageButton; /// <summary> /// Url to a Calendar Image or WebResource to use the default resource image. /// Applies only if the DisplayMode = ImageButton /// </summary> [Description("Url to a Calendar Image or WebResource to use the default resource image")] [Category("Resources"), DefaultValue("WebResource")] public string ButtonImage { get { return _ButtonImage; } set { _ButtonImage = value; } } private string _ButtonImage = "WebResource"; /// <summary> /// The CSS that is used for the calendar /// </summary> [Category("Resources"), Description("The CSS that is used for the calendar or empty. WebResource loads from resources. This property serves as the base url - use Theme to apply a specific theme"), DefaultValue("~/scripts/themes/base/ui.all.css")] public string CalendarCss { get { return _CalendarCss; } set { _CalendarCss = value; } } private string _CalendarCss = "~/scripts/themes/base/ui.all.css"; /// <summary> /// Theme applied to the base CSS url. Replaces /base/ with the theme selected /// </summary> [Category("Resources"), Description("Theme applied to the base CSS url. Replaces /base/ with the theme selected"), DefaultValue("Redmond")] public string Theme { get { return _Theme; } set { _Theme = value; } } private string _Theme = "Redmond"; /// <summary> /// Location for the calendar JavaScript /// </summary> [Description("Location for the calendar JavaScript or empty for none. WebResource loads from resources")] [Category("Resources"), DefaultValue("~/scripts/jquery-ui-custom.js")] public string CalendarJs { get { return _CalendarJs; } set { _CalendarJs = value; } } private string _CalendarJs = "~/scripts/jquery-ui-custom.js"; /// <summary> /// Location of jQuery library. Use WebResource for loading from resources /// </summary> [Description("Location of jQuery library or empty for none. Use WebResource for loading from resources")] [Category("Resources"), DefaultValue("WebResource")] public string jQueryJs { get { return _jQueryJs; } set { _jQueryJs = value; } } private string _jQueryJs = "WebResource"; /// <summary> /// Determines the Date Format used. Auto uses CurrentCulture. Format: MDY/ month, date,year separator /// </summary> [Description("Determines the Date Format used. Auto uses CurrentCulture. Format: MDY/ month, date,year separator")] [Category("Date Selection"), DefaultValue("Auto")] public string DateFormat { get { return _DateFormat; } set { _DateFormat = value; } } private string _DateFormat = "Auto"; /// <summary> /// Minumum allowable date. Leave blank to allow any date /// </summary> [Description("Minumum allowable date")] [Category("Date Selection"), DefaultValue(typeof(DateTime?), null)] public DateTime? MinDate { get { return _MinDate; } set { _MinDate = value; } } private DateTime? _MinDate = null; /// <summary> /// Maximum allowable date. Leave blank to allow any date. /// </summary> [Description("Maximum allowable date. Leave blank to allow any date.")] [Category("Date Selection"), DefaultValue(typeof(DateTime?), null)] public DateTime? MaxDate { get { return _MaxDate; } set { _MaxDate = value; } } private DateTime? _MaxDate = null; /// <summary> /// Client event handler fired when a date is selected /// </summary> [Description("Client event handler fired when a date is selected")] [Category("Date Selection"), DefaultValue("")] public string OnClientSelect { get { return _OnClientSelect; } set { _OnClientSelect = value; } } private string _OnClientSelect = ""; /// <summary> /// Client event handler that fires before the date picker is activated /// </summary> [Description("Client event handler that fires before the date picker is activated")] [Category("Date Selection"), DefaultValue("")] public string OnClientBeforeShow { get { return _OnClientBeforeShow; } set { _OnClientBeforeShow = value; } } private string _OnClientBeforeShow = ""; /// <summary> /// Determines where the Close icon is displayed. True = top, false = bottom. /// </summary> [Description("Determines where the Today and Close buttons are displayed on the bottom (default styling) of the control.")] [Category("Date Selection"), DefaultValue(true)] public bool ShowButtonPanel { get { return _CloseAtTop; } set { _CloseAtTop = value; } } private bool _CloseAtTop = true; /// <summary> /// Code that embeds related resources (.js and css) /// </summary> /// <param name="scriptProxy"></param> protected void RegisterResources(ClientScriptProxy scriptProxy) { scriptProxy.LoadControlScript(this, this.jQueryJs, ControlResources.JQUERY_SCRIPT_RESOURCE, ScriptRenderModes.HeaderTop); scriptProxy.RegisterClientScriptInclude(this.Page, typeof(ControlResources), this.CalendarJs, ScriptRenderModes.Header); string cssPath = this.CalendarCss; if (!string.IsNullOrEmpty(this.Theme)) cssPath = cssPath.Replace("/base/", "/" + this.Theme + "/"); scriptProxy.RegisterCssLink(this.Page, typeof(ControlResources), cssPath, cssPath); } protected override void OnInit(EventArgs e) { base.OnInit(e); // Retrieve the date explicitly - NOTE: Date written by CLIENTID id & name. if (this.Page.IsPostBack && this.DisplayMode == DatePickerDisplayModes.Inline) this.Text = this.Page.Request.Form[this.ClientID]; // Note this is the right value! } /// <summary> /// Most of the work happens here for generating the hook up script code /// </summary> /// <param name="e"></param> protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); // MS AJAX aware script management ClientScriptProxy scriptProxy = ClientScriptProxy.Current; // Register resources this.RegisterResources(scriptProxy); string dateFormat = this.DateFormat; if (string.IsNullOrEmpty(dateFormat) || dateFormat == "Auto") { // Try to create a data format string from culture settings // this code will fail if culture can't be mapped on server hence the empty try/catch try { dateFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; } catch { } } dateFormat = dateFormat.ToLower().Replace("yyyy", "yy"); // Capture and map the various option parameters StringBuilder sbOptions = new StringBuilder(512); sbOptions.Append("{"); string onSelect = this.OnClientSelect; if (this.DisplayMode == DatePickerDisplayModes.Button) sbOptions.Append("showOn: 'button',"); else if (this.DisplayMode == DatePickerDisplayModes.ImageButton) { string img = this.ButtonImage; if (img == "WebResource") img = scriptProxy.GetWebResourceUrl(this, typeof(ControlResources), ControlResources.CALENDAR_ICON_RESOURCE); else img = this.ResolveUrl(this.ButtonImage); sbOptions.Append("showOn: 'button', buttonImageOnly: true, buttonImage: '" + img + "',buttonText: 'Select date',"); } else if (this.DisplayMode == DatePickerDisplayModes.Inline) { // need to store selection in the page somehow for inline since it's // not tied to a textbox scriptProxy.RegisterHiddenField(this, this.ClientID, this.Text); onSelect = this.ClientID + "OnSelect"; } if (!string.IsNullOrEmpty(onSelect)) sbOptions.Append("onSelect: " + onSelect + ","); if (this.DisplayMode != DatePickerDisplayModes.Inline) { if (!string.IsNullOrEmpty(this.OnClientBeforeShow)) sbOptions.Append("beforeShow: function(y,z) { $('#ui-datepicker-div').maxZIndex(); " + this.OnClientBeforeShow + "(y,z); },"); else sbOptions.Append("beforeShow: function() { $('#ui-datepicker-div').maxZIndex(); },"); } if (this.MaxDate.HasValue) sbOptions.Append("maxDate: " + WebUtils.EncodeJsDate(MaxDate.Value) + ","); if (this.MinDate.HasValue) sbOptions.Append("minDate: " + WebUtils.EncodeJsDate(MinDate.Value) + ","); if (this.ShowButtonPanel) sbOptions.Append("showButtonPanel: true,"); sbOptions.Append("dateFormat: '" + dateFormat + "'}"); // Write out initilization code for calendar StringBuilder sbStartupScript = new StringBuilder(400); sbStartupScript.AppendLine("$(document).ready( function() {"); if (this.DisplayMode != DatePickerDisplayModes.Inline) { scriptProxy.RegisterClientScriptBlock(this.Page, typeof(ControlResources), "__attachDatePickerInputKeys", this.AttachDatePickerKeysScript, true); sbStartupScript.AppendFormat("var cal = jQuery('#{0}').datepicker({1}).attachDatepickerInputKeys();\r\n", this.ClientID, sbOptions); } else { sbStartupScript.AppendLine("var cal = jQuery('#" + this.ClientID + "Div').datepicker(" + sbOptions.ToString() + ")"); if (this.SelectedDate.HasValue && this.SelectedDate.Value > new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)) { WestwindJsonSerializer ser = new WestwindJsonSerializer(); ser.DateSerializationMode = JsonDateEncodingModes.NewDateExpression; string jsDate = ser.Serialize(this.SelectedDate); sbStartupScript.AppendLine("cal.datepicker('setDate'," + jsDate + ");"); } else sbStartupScript.AppendLine("cal.datepicker('setDate',new Date());"); // Assign value to hidden form var on selection scriptProxy.RegisterStartupScript(this, typeof(ControlResources), this.UniqueID + "OnSelect", "function " + this.ClientID + "OnSelect(dateStr)\r\n" + "{\r\n" + ((!string.IsNullOrEmpty(this.OnClientSelect)) ? this.OnClientSelect + "(dateStr);\r\n" : "") + "jQuery('#" + this.ClientID + "')[0].value = dateStr;\r\n}\r\n", true); } sbStartupScript.AppendLine("} );"); scriptProxy.RegisterStartupScript(this.Page, typeof(ControlResources), "_cal" + this.UniqueID, sbStartupScript.ToString(), true); } /// <summary> /// /// </summary> /// <param name="writer"></param> public override void RenderControl(HtmlTextWriter writer) { if (this.DisplayMode != DatePickerDisplayModes.Inline) base.RenderControl(writer); else { if (this.DesignMode) writer.Write("<div id='" + this.ClientID + "Div' style='width: 200px; height: 200px; padding: 20px;background: silver; color; white'>Inline Calendar Placeholder</div>"); else writer.Write("<div id='" + this.ClientID + "Div'></div>"); } // this code is only for the designer if (HttpContext.Current == null) { if (this.DisplayMode == DatePickerDisplayModes.Button) { writer.Write(" <input type='button' value='...' style='width: 20px; height: 20px;' />"); } else if ((this.DisplayMode == DatePickerDisplayModes.ImageButton)) { string img; if (this.ButtonImage == "WebResource") img = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), ControlResources.CALENDAR_ICON_RESOURCE); else img = this.ResolveUrl(this.ButtonImage); writer.AddAttribute(HtmlTextWriterAttribute.Src, img); writer.AddAttribute("hspace", "2"); writer.RenderBeginTag(HtmlTextWriterTag.Img); writer.RenderEndTag(); } } } private string AttachDatePickerKeysScript = @" $.fn.attachDatepickerInputKeys = function(callback) { if (this.length < 1) return this; this.keydown(function(e) { var j = jQuery(this); var di = $.datepicker._getInst(this); if (!di) return; $.datepicker._setDateFromField(di); // force update first var d = j.datepicker('getDate'); if (!d) return true; var month = d.getMonth(); var year = d.getFullYear(); var day = d.getDate(); switch (e.keyCode) { case 84: // [T]oday d = new Date(); break; case 109: case 189: d = new Date(year, month, day - 1); break; case 107: case 187: d = new Date(year, month, day + 1); break; case 77: //M d = new Date(year, month - 1, day); break; case 72: //H d = new Date(year, month + 1, day); break; default: return true; } j.datepicker('setDate', d); if (callback) callback(this); return false; }); return this; } $.fn.maxZIndex = function(opt) { var def = { inc: 10, group: ""*"" }; $.extend(def, opt); var zmax = 0; $(def.group).each(function() { var cur = parseInt($(this).css('z-index')); zmax = cur > zmax ? cur : zmax; }); if (!this.jquery) return zmax; return this.each(function() { zmax += def.inc; $(this).css(""z-index"", zmax); }); } "; } public enum DatePickerDisplayModes { Button, ImageButton, AutoPopup, Inline }
Enjoy,
+++ Rick --
The Voices of Reason
# re: Implementing a jQuery-Datepicker ASP.NET Control
# re: Implementing a jQuery-Datepicker ASP.NET Control
The point is this is a control that's supposed to make the developer's job as easy as possible.
# re: Implementing a jQuery-Datepicker ASP.NET Control
I worked a bit on jQuery and I found an even easier way. I post it on my blog (http://blog.vanmeeuwen-online.nl/2009/10/easy-way-to-use-jquery-datepicker-on.html), but in short, use for all asp.net controls that should get an date a standard piece of text for the name, like txtDate... with some extra text to distinguish and place in the head section of you page the following:
$("input[name*='txtDate']").datepicker();Now all asp.net controls with txtDate in the name, will get the jQuery datepicker functionality. Works great to me.
# re: Implementing a jQuery-Datepicker ASP.NET Control
This code also does a number of things the date picker doesn't do natively. Like the ability to use keystrokes to increment the date in the textbox without bringing up the date picker popup (+,- etc.) as well as handling proper z-Order placement when working with other z-Indexed content etc. converting .NET date formats to datepicker's date format to name a few.
IAC, the idea here is just to make the integration into pages easier and more consistent including designer support. The amount of code to do this is really irrelevant - the code you have to write to get it to render the way you want in your pages is what really counts at least to me.
# re: Implementing a jQuery-Datepicker ASP.NET Control
After using .datepicker() on two of my pages, i realised ASP.Net control route is the best solution.
# re: Implementing a jQuery-Datepicker ASP.NET Control
Plz help me .... Its vry Urgent ... Hope U vl do the needful ...
Email. ID: gopihere@indiatimes.com
# re: Implementing a jQuery-Datepicker ASP.NET Control
# re: Implementing a jQuery-Datepicker ASP.NET Control
# re: Implementing a jQuery-Datepicker ASP.NET Control
http://blog.waleedmohamed.net/2009/12/aspnet-master-page-and-jquery-reference.html
# Exception
# re: Implementing a jQuery-Datepicker ASP.NET Control
thanks
# re: Implementing a jQuery-Datepicker ASP.NET Control
# re: Implementing a jQuery-Datepicker ASP.NET Control
Any ideas?
# re: Implementing a jQuery-Datepicker ASP.NET Control
# re: Implementing a jQuery-Datepicker ASP.NET Control
# re: Implementing a jQuery-Datepicker ASP.NET Control
# re: Implementing a jQuery-Datepicker ASP.NET Control
Thanks for a great Date picker.
I am using this control on a search page with a GridView to show the search results...for some strange reason that I am yet to work out when I move to the next page of the gridview (i.e. when there is enough rows for it to paginate) after stepping through OnPageIndexChanging(object sender, GridViewPageEventArgs e) I get the following exception:
Type: System.InvalidOperationException
Message: The script tag registered for type 'Westwind.Web.Controls.ControlResources' and key '_calcss' has invalid characters outside of the script tags: . Only properly formatted script tags can be registered.
Method: Void CheckScriptTagTweenSpace(System.Web.UI.RegisteredScript, System.String, Int32, Int32)
System.InvalidOperationException: The script tag registered for type 'Westwind.Web.Controls.ControlResources' and key '_calcss' has invalid characters outside of the script tags: . Only properly formatted script tags can be registered.
at System.Web.UI.ScriptRegistrationManager.CheckScriptTagTweenSpace(RegisteredScript entry, String text, Int32 start, Int32 length)
at System.Web.UI.ScriptRegistrationManager.WriteScriptWithTags(HtmlTextWriter writer, String token, RegisteredScript activeRegistration)
at System.Web.UI.ScriptRegistrationManager.RenderActiveScriptBlocks(List`1 updatePanels, HtmlTextWriter writer, String token, List`1 scriptRegistrations)
at System.Web.UI.ScriptRegistrationManager.RenderActiveScripts(List`1 updatePanels, HtmlTextWriter writer)
at System.Web.UI.PageRequestManager.ProcessScriptRegistration(HtmlTextWriter writer)
at System.Web.UI.PageRequestManager.RenderPageCallback(HtmlTextWriter writer, Control pageControl)
at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
at System.Web.UI.Page.Render(HtmlTextWriter writer)
at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
Any ideas...Is this a bug or am have I not implemented it properly?
Thanks,
Andrew
# re: Implementing a jQuery-Datepicker ASP.NET Control
I am also facing same problem as Andrew.
# re: Implementing a jQuery-Datepicker ASP.NET Control
If you can post (or email me) a simple repo scenario that can run on its own I can take a look at it.
# re: Implementing a jQuery-Datepicker ASP.NET Control
Just create a new web site with Ajax Update Panel and put the jQuery Datepicker control on it. Add a New Button control on page with postback =true.
Select the date using the datepicker and click on save button. you will get this error.
# re: Implementing a jQuery-Datepicker ASP.NET Control
Have you made any progress on the error stated by Andrew as we are also getting this error when using the date picker control in ajax update panel.
# re: Implementing a jQuery-Datepicker ASP.NET Control
Just one question: i need to set the language for the datapicker. i tryed to set the attribute CalendarJs as
CalendarJs="development-bundle/ui/i18n/jquery-ui-i18n.js"
but it's not working.
I need to set ut in Italian Language.
Thanks for the help!!
Matteo
# re: Implementing a jQuery-Datepicker ASP.NET Control
I have started customizing the control according to my need.
(When Assign the SelectedDate value to Another JqueryDatePicker it is returning Datetime min value becuase of conversion failed when Tryparse. So that i have used ParseExact with using of this.Dateformat.) I want to post it here because i dont want anyone to disturb you for small doubts.
Thanks Once Again for your Efforts. :)
# re: Implementing a jQuery-Datepicker ASP.NET Control
This article is really great...I will like to appreciate your efforts...You are really very good programemer and even a better thinker .Please Keep up posting good articles as you are really great explainer and and a great tutor.
Thanks.
Stally