Oct 10 2006

ASP.NET Server control code - part 4

Posted by admin under Controls

This is a part of an article serie where we create a ASP.NET 2.0 server control providing date (and time) selection popup . Please start from the beginning.

As I said - I want a readonly textbox and an image triggering the popup. So we start off by inheriting from TextBox.



namespace JSCalendarControl
{
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:JSCalendar runat=server></{0}:JSCalendar>"), Designer("JSCalendarControl.JSCalendarControlDesigner")]
    public class JSCalendar : TextBox
    {
...
...

By adding an Image control to the JSCalender control collection we easily solve the "composite nature" of the control.



        protected HiddenField m_oField;
        protected Image m_TriggerButton;
        //protected TextBox m_oField;

        protected override void CreateChildControls()
        {
            EnableViewState = true;
            m_oField = new HiddenField();
            //m_oField = new TextBox();
            m_oField.EnableViewState = true;
            m_oField.ID = "cal_" + ClientID;
            m_oField.Value = "0";
            //m_oField.Value = "0";
            Controls.Add(m_oField);
            m_TriggerButton = new Image();
            m_TriggerButton.ID = "cal_img_" + ClientID;
            m_TriggerButton.ImageUrl = TriggerImagePath;
            Controls.Add(m_TriggerButton);
            base.CreateChildControls();
        }



  

As you can see we do use another field as well - HiddenField m_oField. The reason for that is simply because we don't know which format the "real" textbox will have - i.e it can show the data as mm/dd/yyyy or yyyy-MM-dd or whatever, also with or without time - and even with or without pm/am indicators.

We must ensure we do get the data as we want it - and also that we get ALL the data needed - remember we are supposed to handle a DateTime value from the control (but it might very well be so that the GUI only needs to show the datepart  but store and handle the time part as well).

Anyway, more on that later. Now lets look into the easier parts - I have created some properties around the javascript calendar configuration properties, such as ShowWeekNumber, SingleClick etc.

All we do for those in the property get/set methods is store it in ViewState:



        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue(true)]
        [Localizable(true)]
        public bool SingleClick
        {
            get
            {
                if (ViewState["SingleClick"] == null)
                    return true;
                return (bool)ViewState["SingleClick"];
            }

            set
            {
                ViewState["SingleClick"] = value;
            }
        }



There is also properties for setting the path to the external css and js scripts.

So our fun begins in OnPreRender:



        protected override void OnPreRender(EventArgs e)
        {
            EnsureChildControls();
            if (Page.ClientScript.IsClientScriptBlockRegistered("calendar123") == false)
            {
                string sPath = ResolveUrl(PathToCalendarScriptDir);
                if (sPath.EndsWith("/") == false)
                    sPath = sPath + "/";
                string sScript = "<script type=\"text/javascript\" src=\"" + sPath + "calendar.js\"></script>" + Environment.NewLine;
                //Language
                string sLanguageFile = ResolveUrl(LanguageFile);
                sScript += "<script type=\"text/javascript\" src=\"" + sLanguageFile + "\"></script>" + Environment.NewLine;
                sScript += "<script type=\"text/javascript\" src=\"" + sPath + "calendar-setup.js\"></script>" + Environment.NewLine;
                
  
  



                Page.ClientScript.RegisterClientScriptBlock(typeof(Page), "calendar123", sScript);
            }
            if (DateTimeValue.Year < 1900)
                Text = "";
            else
                Text = DateTimeValue.ToString(OutputFormat_NET);
            base.OnPreRender(e);
        }


Here we first register our external js files - and by using the ResolveUrl function we allow for use of the tilde '~' character.

Next we set the value (Text) of our textbox control to the DateTimeValue - formatted the way you want to show it on the screen. I have naturally chosen to let you specify the output format (OutputFormat_NET) using standard .NET datetime formatting - more on that later.

Now time to Render:



        protected override void Render(HtmlTextWriter output)
        {
            base.Render(output);
            foreach (Control c in Controls)
                c.RenderControl(output);

            string sScript = "<script type=\"text/javascript\">" + Environment.NewLine;
            sScript += "    Calendar.setup({" + Environment.NewLine;
            if (DisplayArea.Length > 0)
            {
                sScript += "displayArea        :    \"" + DisplayArea + "\"," + Environment.NewLine;
                sScript += "daFormat        :    \"" + GetDaFormat(OutputFormat_NET) + "\"," + Environment.NewLine;
            }
            sScript += "ifFormat       :    \"%Y-%m-%d %H::%M::%S\"," + Environment.NewLine;
            sScript += "showsTime      :    " + ShowTime.ToString().ToLower() + ",   " + Environment.NewLine;
            sScript += "timeFormat      :    " + (Clock24Hour == true ? "24" : "12") + ",   " + Environment.NewLine;
            sScript += "weekNumbers      :    " + ShowWeekNumber.ToString().ToLower() + ",   " + Environment.NewLine;
            sScript += "button         :    \"" + m_TriggerButton.ID + "\"," + Environment.NewLine;
            sScript += "singleClick    :    " + SingleClick.ToString().ToLower() + "," + Environment.NewLine;
            //if ( DateTimeValue.Year > 1970  )
            //    sScript += "date    :    new Date(" + DateTimeValue.Year.ToString() + "," + (DateTimeValue.Month-1).ToString() + "," + DateTimeValue.Day.ToString() + "," + (DateTimeValue.Hour-1).ToString() + "," + DateTimeValue.Minute.ToString() + "," + DateTimeValue.Second.ToString() + ")," + Environment.NewLine;
            //else
            sScript += "inputField  : \"" + m_oField.ID + "\"," + Environment.NewLine;
            sScript += "onSelect         :    " + ClientID + "_onSelect," + Environment.NewLine;
            sScript += "step    :    1" + Environment.NewLine;
            sScript += "   });" + Environment.NewLine;

            sScript += "function " + ClientID + "_onSelect(calendar, date) {" + Environment.NewLine;
            sScript += "var input_field = document.getElementById(\"" + ClientID + "\");" + Environment.NewLine;
            sScript += "var dDat = Date.parseDate(date, '%Y-%m-%d %H::%M::%S');";
            sScript += "input_field.value = dDat.print(\"" + GetDaFormat(OutputFormat_NET) + "\");" + Environment.NewLine;
            sScript += "input_field = document.getElementById(\"" + m_oField.ID + "\");" + Environment.NewLine;
            sScript += "input_field.value = date;" + Environment.NewLine;
            sScript += "};" + Environment.NewLine;
            sScript += "</script>" + Environment.NewLine;

            output.Write(sScript);

        }





Basically we are creating the Calendar.setup call needed - and we also need to do some extra stuff here to handle output to our textbox control. While it is possible to select an "DisplayArea" in the JavaScript - the problem is that only works for divs and spans -  not input boxes. And I didn't feel like changing anything in the js code. So I create a extra Javascript function clientid_onSelect which gets called whenever a new date is selected. And that function just sets the value of our output field - and also the hidden field as well.