#region License /* ************************************************************** * Author: Rick Strahl * © West Wind Technologies, 2008 - 2010 * http://www.west-wind.com/ * * Created: 09/04/2008 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. ************************************************************** */ #endregion using System; using System.Text; using System.Web.UI.WebControls; using System.Web; using System.Globalization; using System.ComponentModel; using System.Drawing; using System.Web.UI; using Westwind.Utilities; using Westwind.Web.JsonSerializers; namespace Westwind.Web.Controls { /// /// ASP.NET jQuery DatePicker Control Wrapper /// by Rick Strahl /// http://www.west-wind.com/ /// /// License: Free /// /// 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: /// ~/scripts/jquery.js (available from WebResource) /// ~/scripts/jquery-ui.js (custom build of jQuery.ui) /// ~/scripts/themes/base (choose any theme name one theme to display styling) /// /// 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. /// [ToolboxBitmap(typeof(System.Web.UI.WebControls.Calendar)), DefaultProperty("Text"), ToolboxData("<{0}:jQueryDatePicker runat=\"server\" />")] public class jQueryDatePicker : TextBox { public jQueryDatePicker() { // Date specific width Width = Unit.Pixel(80); } /// /// The currently selected date /// [DefaultValue(typeof(DateTime), ""), Category("Date Selection")] public DateTime? SelectedDate { get { DateTime defaultDate = DateTime.Parse("01/01/1900", CultureInfo.InstalledUICulture); if (Text == "") return defaultDate; DateTime.TryParse(Text, out defaultDate); return defaultDate; } set { if (!value.HasValue) Text = ""; else { string dateFormat = DateFormat; if (dateFormat == "Auto") dateFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; Text = value.Value.ToString(dateFormat); } } } /// /// Determines how the datepicking option is activated /// [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; /// /// Url to a Calendar Image or WebResource to use the default resource image. /// Applies only if the DisplayMode = ImageButton /// [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"; /// /// The CSS that is used for the calendar /// [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/jquery-ui.css")] public string CalendarCss { get { return _CalendarCss; } set { _CalendarCss = value; } } private string _CalendarCss = "~/scripts/themes/base/jquery-ui.css"; /// /// Theme applied to the base CSS url. Replaces /base/ with the theme selected /// [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"; /// /// Location for the calendar JavaScript /// [Description("Location for the calendar JavaScript or empty for none. WebResource loads from resources")] [Category("Resources"), DefaultValue("~/scripts/jquery-ui.js")] public string CalendarJs { get { return _CalendarJs; } set { _CalendarJs = value; } } private string _CalendarJs = "~/scripts/jquery-ui.js"; /// /// Location of jQuery library. Use WebResource for loading from resources /// [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"; /// /// Determines the Date Format used. Auto uses CurrentCulture. Format: MDY/ month, date,year separator /// [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"; /// /// Minumum allowable date. Leave blank to allow any date /// [Description("Minumum allowable date")] [Category("Date Selection"), DefaultValue(typeof(DateTime?), null)] public DateTime? MinDate { get { return _MinDate; } set { _MinDate = value; } } private DateTime? _MinDate = null; /// /// Maximum allowable date. Leave blank to allow any date. /// [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; /// /// Client event handler fired when a date is selected /// [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 = ""; /// /// Client event handler that fires before the date picker is activated /// [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 = ""; /// /// Determines where the Close icon is displayed. True = top, false = bottom. /// [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; /// /// Code that embeds related resources (.js and css) /// /// protected void RegisterResources(ClientScriptProxy scriptProxy) { scriptProxy.LoadControlScript(this, jQueryJs, ControlResources.JQUERY_SCRIPT_RESOURCE, ScriptRenderModes.HeaderTop); scriptProxy.RegisterClientScriptInclude(Page, typeof(ControlResources), CalendarJs, ScriptRenderModes.Header); string cssPath = CalendarCss; if (!string.IsNullOrEmpty(Theme)) cssPath = cssPath.Replace("/base/", "/" + Theme + "/"); scriptProxy.RegisterCssLink(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 (Page.IsPostBack && DisplayMode == DatePickerDisplayModes.Inline) Text = Page.Request.Form[ClientID]; // Note this is the right value! } /// /// Most of the work happens here for generating the hook up script code /// /// protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); // MS AJAX aware script management ClientScriptProxy scriptProxy = ClientScriptProxy.Current; // Register resources RegisterResources(scriptProxy); string dateFormat = 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 = OnClientSelect; if (DisplayMode == DatePickerDisplayModes.Button) sbOptions.Append("showOn: 'button',"); else if (DisplayMode == DatePickerDisplayModes.ImageButton) { string img = ButtonImage; if (img == "WebResource") img = scriptProxy.GetWebResourceUrl(this, typeof(ControlResources), ControlResources.CALENDAR_ICON_RESOURCE); else img = ResolveUrl(ButtonImage); sbOptions.Append("showOn: 'button', buttonImageOnly: true, buttonImage: '" + img + "',buttonText: 'Select date',"); } else if (DisplayMode == DatePickerDisplayModes.Inline) { // need to store selection in the page somehow for inline since it's // not tied to a textbox scriptProxy.RegisterHiddenField(this, ClientID, Text); onSelect = ClientID + "OnSelect"; } if (!string.IsNullOrEmpty(onSelect)) sbOptions.Append("onSelect: " + onSelect + ","); if (DisplayMode != DatePickerDisplayModes.Inline) { if (!string.IsNullOrEmpty(OnClientBeforeShow)) sbOptions.Append("beforeShow: function(y,z) { $('#ui-datepicker-div').maxZIndex(); " + OnClientBeforeShow + "(y,z); },"); else sbOptions.Append("beforeShow: function() { $('#ui-datepicker-div').maxZIndex(); },"); } if (MaxDate.HasValue) sbOptions.Append("maxDate: " + WebUtils.EncodeJsDate(MaxDate.Value) + ","); if (MinDate.HasValue) sbOptions.Append("minDate: " + WebUtils.EncodeJsDate(MinDate.Value) + ","); if (ShowButtonPanel) sbOptions.Append("showButtonPanel: true,"); sbOptions.Append("dateFormat: '" + dateFormat + "'}"); // Write out initilization code for calendar StringBuilder sbStartupScript = new StringBuilder(400); sbStartupScript.AppendLine("$( function() {"); if (DisplayMode != DatePickerDisplayModes.Inline) { scriptProxy.RegisterClientScriptBlock(Page, typeof(ControlResources), "__attachDatePickerInputKeys", AttachDatePickerKeysScript, true); sbStartupScript.AppendFormat("var cal = jQuery('#{0}').datepicker({1}).attachDatepickerInputKeys();\r\n", ClientID, sbOptions); } else { sbStartupScript.AppendLine("var cal = jQuery('#" + ClientID + "Div').datepicker(" + sbOptions.ToString() + ")"); if (SelectedDate.HasValue && SelectedDate.Value > new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)) { WestwindJsonSerializer ser = new WestwindJsonSerializer(); ser.DateSerializationMode = JsonDateEncodingModes.NewDateExpression; string jsDate = ser.Serialize(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), UniqueID + "OnSelect", "function " + ClientID + "OnSelect(dateStr)\r\n" + "{\r\n" + ((!string.IsNullOrEmpty(OnClientSelect)) ? OnClientSelect + "(dateStr);\r\n" : "") + "jQuery('#" + ClientID + "')[0].value = dateStr;\r\n}\r\n", true); } sbStartupScript.AppendLine("} );"); scriptProxy.RegisterStartupScript(Page, typeof(ControlResources), "_cal" + UniqueID, sbStartupScript.ToString(), true); } /// /// /// /// public override void RenderControl(HtmlTextWriter writer) { if (DisplayMode != DatePickerDisplayModes.Inline) base.RenderControl(writer); else { if (DesignMode) writer.Write("
Inline Calendar Placeholder
"); else writer.Write("
"); } // this code is only for the designer if (HttpContext.Current == null) { if (DisplayMode == DatePickerDisplayModes.Button) { writer.Write(" "); } else if ((DisplayMode == DatePickerDisplayModes.ImageButton)) { string img; if (ButtonImage == "WebResource") img = Page.ClientScript.GetWebResourceUrl(GetType(), ControlResources.CALENDAR_ICON_RESOURCE); else img = ResolveUrl(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) d = new Date(1900,0,1,1,1); 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; } $.maxZIndex = $.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 } }