Archive for September 12th, 2009

jQuery UI Datepicker and z-Index

The jQuery date picker is a nice little date picking solution that’s easy to use and work with. I’ve used this control in many places and some time ago wrapped it into an ASP.NET server control to make it easier to work with yet.

The control works great, but there’s one thing that a number of people using the control have run into:  When the date picker is used with other controls that use z-Indexes extensively it’s quite easy to end up with a date picker that doesn’t pop up properly:

DatePickerPopup

Ooops… not quite the expected behavior, huh?

What’s happening in this example is that the date picker is hosted inside of a position absolute element (the dialog) which sits on top of a modal overlay. Both the modal overlay and the dialog have higher z-Index values than the date picker popup.

There’s a relatively easy fix for this which should work on a page or even application level in most cases:

<style type="text/css">
    #ui-datepicker-div
    {
        z-index: 9999999;
    }
</style>

The pop up window that the date picker uses is called ui-datepicker-div and so you can style the thing with a sufficiently high z-index value that it always pops up above anything else you might have configured.

Fixed z-Index will cause Problems

That solves the problem if your z-index values on pages are relatively fixed. But this may not always be the case. For example, if you have multiple dialogs on a page that are draggable you probably want to have the ability to have dialogs dropped to automatically pop to the top of the zOrder.

zOrder tracking can be a real bitch if you have multiple components or applications all trying to figure out the best way to pop to the top of the stack by utilizing some fixed z-Index value. Just consider the modal popup, the dialog and the date picker. Add to that a few draggable dialogs and the whole concept and you quickly run out of fixed values that work in multiple situations.

When it really comes down to it z-index needs to be managed slightly differently with an easy way to change the z-Index to the maximum value to guarantee that the item ends up on top – generically regardless of what else is on the page.

I talked about this via a jQuery maxZIndex Plug-in a while ago. The plug-in offers an easy way to find the max z-Index used in a page and also assign a new max z-Index to an element. I use this extensively in my draggable component as well as in the modal dialog and other components. Because it’s so short here it is again inline:

$.maxZIndex = $.fn.maxZIndex = function(opt) {
    /// <summary>
    /// Returns the max zOrder in the document (no parameter)
    /// Sets max zOrder by passing a non-zero number
    /// which gets added to the highest zOrder.
    /// </summary>    
    /// <param name="opt" type="object">
    /// inc: increment value, 
    /// group: selector for zIndex elements to find max for
    /// </param>
    /// <returns type="jQuery" />
    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);
    });
}

So… this can also be applied to the date picker. Ideally something like this should be used internally by the date picker, but well, we don’t live in an ideal world. The next best thing is that we can hook the datepicker in initialization and use the max zindex plug in to assign the high zindex:

jQuery('#txtDate').datepicker({ showButtonPanel: true, 
showOn: 'button',
buttonImageOnly: true,
buttonImage: '/wconnect/images/calendar.gif',
beforeShow: function() {$('#ui-datepicker-div').maxZIndex(); },
dateFormat: 'mm/dd/yy' }).attachDatepickerInputKeys();

Now no matter what z-index is used the date pop up always pops up on top of the content:

DatePopupGood

As I mentioned previously because there are a handful of common things I do to my date picker control I’ve wrapped this thing up into an ASP.NET server control that handles all of these configuration options. You can find this control as part of the West Wind Web Toolkit in the Westwind.Web project if you’re curious.

I suppose the same thing could also be done in JavaScript with some sort of factory function that sets all your default options on the date control. Certainly you don’t want to set those 6 or 7 common settings on each instance.

© Rick Strahl, West Wind Technologies, 2005-2009
Posted in jQuery  HTML  JavaScript  
kick it on DotNetKicks.com


Implementing a jQuery-Calendar ASP.NET Control

[9/12/2009 – Note this control has been updated to the latest version of jQuery UI DatePicker so the code in the post below is slightly out of date. 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 Calendar control from Marc Garbanski. 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. I spend an hour or so cleaning it up and separating it out so it can be distributed independently:

The control is a very basic wrapper around jQuery Calendar 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. The control also wraps all the resources including jQuery so the control is self contained.

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.

I'm still trying to decide how to best integrate with jQuery in general in my own control libraries and with additional libraries in particular. My current leanings are to leaving scripts external with options to explicitly set the script path. One of the big problems is always script versioning, but also the possibility that scripts are already being included into a page - a possibly likely scenario for jQuery for example.

In this control the default is WebResource which uses embedded resources, with specific path overrides for jQuery.js, jquery-calendar.js and jquery-calendar.css which adds a few extra properties, but gives a little more controls. I'd be interested to hear how others are dealing with maintstream script resources.

There are also a few related tools included (from the wwHoverPanel library which will also have this control in it as wwWebDatePicker once I get around to updating it) like ClientScriptProxy required to co-exist with MS AJAX and a few resource embedding related utilities.

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 calendar is all client side the control 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. 

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.

[updated: Oct. 10, '07 with feedback from comments]

/// <summary>
/// ASP.NET jQuery Calendar Control Wrapper
/// by Rick Strahl
/// http://www.west-wind.com/
/// 
/// based on jQuery calendar client control by Marc Grabanski    
/// http://marcgrabanski.com/code/jquery-calendar/
/// 
/// Simple DatePicker control that uses jQuery-calendar to pop up 
/// a date picker. 
/// 
/// Important Requirements:
/// scripts/jquery.js   
/// scripts/jquery-calendar.js
/// scripts/calendar.css
/// 
/// 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}:jQueryCalendar runat='server' width='80px'></{0}:jQueryCalendar>")]
public class jQueryCalendar : TextBox
{
    public jQueryCalendar()
    {
        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
                this.Text = value.Value.ToString("d");
        }
    }
 
 
    /// <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."),
    DefaultValue("WebResource")]
    public string CalendarCss
    {
        get { return _CalendarCss; }
        set { _CalendarCss = value; }
    }
    private string _CalendarCss = "WebResource";
 
 
    /// <summary>
    /// Location for the calendar JavaScript
    /// </summary>
    [Description("Location for the calendar JavaScript or empty for none. WebResource loads from resources")]
    [Category("Resources"), DefaultValue("WebResource")]
    public string CalendarJs
    {
        get { return _CalendarJs; }
        set { _CalendarJs = value; }
    }
    private string _CalendarJs = "WebResource";
 
 
    /// <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>
    /// Determines where the Close icon is displayed. True = top, false = bottom.
    /// </summary>
    [Description("Determines where the Close icon is displayed. True = top, false = bottom.")]
    [Category("Date Selection"), DefaultValue(true)]
    public bool CloseAtTop
    {
        get { return _CloseAtTop; }
        set { _CloseAtTop = value; }
    }
    private bool _CloseAtTop = true;
 
 
    /// <summary>
    /// Code that embeds related resources (.js and css)
    /// </summary>
    /// <param name="p"></param>
    protected void RegisterResources(ClientScriptProxy p)
    {
        // *** Make sure jQuery is loaded
        if (this.jQueryJs == "WebResource")
            ControlResources.LoadjQuery(this.Page);
        else if(!string.IsNullOrEmpty(this.jQueryJs))
            p.RegisterClientScriptInclude(this.Page, typeof(ControlResources),
                "_jqueryjs", this.ResolveUrl(this.jQueryJs));
 
        // *** Load the calandar script
        string script = this.CalendarJs;
 
        // *** Load jQuery Calendar Scripts
        if (script == "WebResource")
            p.RegisterClientScriptResource(this.Page, typeof(ControlResources),
                ControlResources.JQUERY_CALENDAR_SCRIPT_RESOURCE);
        else if (!string.IsNullOrEmpty(script))
            p.RegisterClientScriptInclude(this.Page, typeof(ControlResources),
                                "__jqueryCalendar",
                                this.ResolveUrl(script));
 
        // *** Load the related CSS reference into the page
        script = this.CalendarCss;
        if (script == "WebResource")
            script = p.GetWebResourceUrl(this.Page, typeof(ControlResources),
                                        ControlResources.JQUERY_CALENDAR_CSS_RESOURCE);
        else if (!string.IsNullOrEmpty(script))
            script = this.ResolveUrl(this.CalendarCss);
 
        // *** Register Calendar CSS 'manually'
        string css = @"<link href=""" + script +
                    @""" type=""text/css"" rel=""stylesheet"" />";
        p.RegisterClientScriptBlock(this.Page, typeof(ControlResources), "_calcss", css, false);
    }
 
    /// <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 p = ClientScriptProxy.Current;
 
        // *** Register resources
        this.RegisterResources(p);
 
        string dateFormat = this.DateFormat;
 
        if (string.IsNullOrEmpty(dateFormat))
            dateFormat = "MDY/";
        else if(dateFormat == "Auto")
        {
             string sep = CultureInfo.CurrentCulture.DateTimeFormat.DateSeparator;
             dateFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern.ToLower().Replace(sep, "").Replace("yyyy", "y").Replace("mm", "m").Replace("dd", "d").ToUpper() + sep;
        }
 
        // *** 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( "autoPopUp: 'button',");                       
        else if (this.DisplayMode == DatePickerDisplayModes.ImageButton)
        {
            string img = this.ButtonImage;
            if (img == "WebResource")
                img = p.GetWebResourceUrl(this, typeof(ControlResources), ControlResources.CALENDAR_ICON_RESOURCE);
            else
                img = this.ResolveUrl(this.ButtonImage);
 
            sbOptions.Append("autoPopUp: 'button', buttonImageOnly: true, buttonImage: '" + img + "',buttonText: 'Select date',");
        }
        else if (this.DisplayMode == DatePickerDisplayModes.Inline)
        {
            p.RegisterHiddenField(this, this.ClientID, this.Text);
            onSelect = this.ClientID + "OnSelect";    
        }
 
        if (!string.IsNullOrEmpty(onSelect))
            sbOptions.Append("onSelect: " + onSelect + ",");
 
        if (this.MaxDate.HasValue)
            sbOptions.Append("maxDate: " + wwWebUtils.EncodeJsDate(MaxDate.Value) + ",");
 
        if (this.MinDate.HasValue)
            sbOptions.Append("minDate: " + wwWebUtils.EncodeJsDate(MinDate.Value) + ",");
 
        if (!this.CloseAtTop)
            sbOptions.Append("closeAtTop: false,");
 
        sbOptions.Append("dateFormat: '" + dateFormat + "'}");
 
 
        // *** Write out initilization code for calendar
        StringBuilder sbStartupScript = new StringBuilder(400);
        sbStartupScript.AppendLine("jQuery(document).ready( function() {");
 
 
        if (this.DisplayMode != DatePickerDisplayModes.Inline)
           sbStartupScript.AppendLine("var cal = jQuery('#" + this.ClientID + "').calendar(" + sbOptions.ToString() + ");");
        else
        {
            sbStartupScript.AppendLine("var cal = jQuery('#" + this.ClientID + "Div').calendar(" + sbOptions.ToString() + ");"); 
 
            if (this.SelectedDate.HasValue && this.SelectedDate.Value > new DateTime(1900,1,1,0,0,0,DateTimeKind.Utc))
                sbStartupScript.AppendLine("popUpCal.setDateFor(cal[0],new Date('" + this.Text + "'));");
 
            sbStartupScript.AppendLine("popUpCal.reconfigureFor(cal[0]);");
 
            // *** Assign value to hidden form var on selection
            p.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("} );");
        p.RegisterStartupScript(this.Page, typeof(ControlResources), "_cal" + this.ID ,
            sbStartupScript.ToString() , true);
    }
 
 
    /// <summary>
    /// 
    /// </summary>
    /// <param name="writer"></param>
    public override void RenderControl(HtmlTextWriter writer)
    {
        if (this.DisplayMode != DatePickerDisplayModes.Inline)
            base.RenderControl(writer);
        else
        {
            writer.Write("<div id='" + this.ClientID + "Div'></div>");
        }
 
        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();
            }
        }
 
    }
}
 
 
public enum DatePickerDisplayModes
{
    Button,        
    ImageButton,
    AutoPopup,
    Inline
}

 

Enjoy,

+++ Rick --

© Rick Strahl, West Wind Technologies, 2005-2009
kick it on DotNetKicks.com


FacyBox – jQuery Based Facebook/FancyBox Style Lightbox

FacyBox is a jQuery-based, Facebook/Fancybox-style lightbox which can display images, divs, or entire remote pages. Basically it is a FaceBox with FancyBox look.

Benefits of using FacyBox compared to original include: faster page load, smaller file size, locally testable (does not require you to host a server just to try locally), gallery mode available, useable as modal dialog, clean separation between JavaScript/CSS (no images/styles inside JavaScript), etc.

Developed by Mauricio Wolff; FacyBox jQuery Plugin is available for download under MIT License.  You can find further information, demo & download on FacyBox Website.

Similar Posts:


  • Sponsored Links

  •  

    September 2009
    M T W T F S S
    « Aug   Oct »
     123456
    78910111213
    14151617181920
    21222324252627
    282930  
  • .

    Copyright © 1996-2010 Answer My Query. All rights reserved.
    iDream theme by Templates Next | Powered by WordPress