Friday, September 23, 2011

Simple ASP.net Auto Complete Composite Server Control

Today i will introduce new ASP.net composite server control "Auto Complete"
using  jQuery Auto Complete with few plugin modification that will support the main functionality of our control.

Let us go fast in control properties, the new control contains several properties most of them belongs to plugin itself and others will handle the control.

Properties belongs to plugin:
  1. MinimumChars: get or set the minimum characters to fire the autocomplete action * default = 1 *
  2. MaximumRows: get or set the maximum rows to be returned by the plugin * default = 5 *
  3. AutoFill: get or set autoFill property for the auto complete plugin "if true plugin will set input text with the first item" * default is false * 
  4. MustMatch: get or set mustMatch property for the auto complete plugin* default is false * 
  5. MatchContains:get or set matchContains property for the auto complete plugin* default is false * 
  6. SelectFirst: get or set selectFirst property for the auto complete plugin * default is true * 
  7. Scroll: get or set scroll property for the auto complete plugin * default is true *
  8. ScrollHeight: get or set scrollHeight property for the auto complete plugin * default is 180px *
  9. Delay: get or set delay property for the auto complete plugin* default is 400 mili seconds *
Properties belongs to control:
  1. TextBoxCssClass: get or set text box control css class
  2. PostURL: the url to post the jquery ajax call ** this url should url to an aspx page that will contain the method **
  3. MethodName: method name to be called ** this method should write response of type "text/xml" and has parameter called "searchKey" as string**
  4. MethodNameRequestKey: get or set requet key for method name "it is the query string key for method name if you need to specify your request such you post several posts to the same page"
  5. SelectedText (updated): get or set the text selected by user
  6. SelectedValue (updated): get or set the value selected by user
  7. XMLElementKey: get or set xmlelement key such as "user, employee and etc"
  8. XMLElementTextAttribute (updated): get or set xml element text attribute such as "name, department and etc"
  9. XMLElementValueAttribute (new): get or set xml element value attribute such as "id"
  10. IsRequired (new): provide option if user need to make the control required or not "default is false"
  11. IsSymbolsDisabled (new): provide option if user need to make the control disable symbols (special characters) or not "default is true"
  12. ValidationGroup (new): get or set the validation related group
  13. ErrorMessage (new): get or set required validator error message
  14. LettersErrorMessage (new): get or set the special characters error message
  15. LettersValidationExpression (new): get or set the special characters validation expression
  16. ErrorMessageDisplay (new):  get or set validation message display layout (value of enumeration {Inline, Breakline} ) "default is inline"

Now i will show you the main two methods in the control:

 protected override void CreateChildControls()  
     {  
       Controls.Clear();  
       // setting ids  
       txtAutoComplete.ID = "txtAutoComplete";  
       hdfValue.ID = "hdfValue";  
       // check if user provide cssclass for text input  
       if (!string.IsNullOrEmpty(TextBoxCssClass))  
       {  
         txtAutoComplete.CssClass = TextBoxCssClass + " autocompleteinput";  
       }  
       else  
         txtAutoComplete.CssClass = "autocompleteinput";  
       #region Required Field Validators  
       // text input RFV  
       rfvTextInput.Display = ValidatorDisplay.Dynamic;  
       rfvTextInput.ControlToValidate = "txtAutoComplete";  
       rfvTextInput.ErrorMessage = ErrorMessage;  
       // text input REXV  
       rexvTextInput.Display = ValidatorDisplay.Dynamic;  
       rexvTextInput.ControlToValidate = "txtAutoComplete";  
       rexvTextInput.ErrorMessage = LettersErrorMessage;  
       rexvTextInput.ValidationExpression = LettersValidationExpression;  
       // check if user need this instance required or not  
       if (!IsRequired)  
       {  
         rfvTextInput.Enabled = false;  
       }  
       // check if user need prevent user from entering special characters  
       if (!IsSymbolsDisabled)  
       {  
         rexvTextInput.Enabled = false;  
       }  
       if (!string.IsNullOrEmpty(ValidationGroup))  
       {  
         txtAutoComplete.ValidationGroup = ValidationGroup;  
         rfvTextInput.ValidationGroup = ValidationGroup;  
         rexvTextInput.ValidationGroup = ValidationGroup;  
       }  
       #endregion  
       #region User Interface  
       // adding the text box        
       Controls.Add(txtAutoComplete);  
       if (ErrorMessageDisplay == ErrorMessageDisplayLayout.BreakLine)  
         Controls.Add(new LiteralControl("<br />"));  
       // add validators  
       Controls.Add(rfvTextInput);  
       Controls.Add(rexvTextInput);  
       // adding the hidden field  
       Controls.Add(hdfValue);  
       #endregion  
       // register client script  
       RegisterPluginScript();  
     }  


Note: txtAutoComplete is an instance of TextBox declared  within the control

The other method is RegisterPluginScript() that will register the formatted script that will initialize the autocomplete instance.

 private void RegisterPluginScript()  
     {  
       try  
       {  
         if (string.IsNullOrEmpty(XMLElementKey) || string.IsNullOrEmpty(XMLElementTextAttribute))  
           return;  
         // if user never specify XMLElementValueAttribute --> control will assign the same text element attribute for value element attribute  
         if (string.IsNullOrEmpty(XMLElementValueAttribute))  
           XMLElementValueAttribute = XMLElementTextAttribute;  
         string key = "AutoComplete" + this.ID;  
         if (Page.ClientScript.IsClientScriptBlockRegistered(GetType(), key))  
           return;  
         StringBuilder scriptValue = new StringBuilder();  
         scriptValue.Append("<script type=\"text/javascript\">");  
         scriptValue.Append("$(document).ready(function () {");  
         if (!string.IsNullOrEmpty(MethodNameRequestKey) && !string.IsNullOrEmpty(MethodName))  
           scriptValue.AppendFormat("$('input#{0}').autocomplete('{1}?{2}={3}&',", this.txtAutoComplete.ClientID, PostURL, MethodNameRequestKey, MethodName);  
         else  
           scriptValue.AppendFormat("$('input#{0}').autocomplete('{1}?',", this.txtAutoComplete.ClientID, PostURL);  
         scriptValue.Append("{parse: function (xml) {");  
         scriptValue.Append("var rows = new Array();");  
         scriptValue.AppendFormat("$(xml).find('{0}').each(function (i) ", XMLElementKey);  
         scriptValue.Append("{rows[i] = {");  
         scriptValue.AppendFormat("data: $(this), value: $(this).attr('{0}'), result: $(this).attr('{1}')", XMLElementValueAttribute, XMLElementTextAttribute);  
         scriptValue.Append("};});");  
         scriptValue.Append("return rows;");  
         scriptValue.Append("},");  
         scriptValue.Append("formatItem: function (row, i, n) {");  
         scriptValue.AppendFormat("return row.attr('{0}');", XMLElementTextAttribute);  
         scriptValue.Append("},");  
         scriptValue.AppendFormat("minChars: {0},", MinimumChars.ToString());  
         scriptValue.AppendFormat("max: {0},", MaximumRows.ToString());  
         scriptValue.AppendFormat("autoFill: {0},", AutoFill.ToString().ToLower());  
         scriptValue.AppendFormat("mustMatch: {0},", MustMatch.ToString().ToLower());  
         scriptValue.AppendFormat("matchContains: {0},", MatchContains.ToString().ToLower());  
         scriptValue.AppendFormat("selectFirst: {0},", SelectFirst.ToString().ToLower());  
         scriptValue.AppendFormat("scroll: {0},", Scroll.ToString().ToLower());  
         scriptValue.AppendFormat("scrollHeight: {0},", ScrollHeight.ToString());  
         scriptValue.AppendFormat("delay: {0}", Delay.ToString());  
         scriptValue.Append("});");  
         scriptValue.AppendFormat("$('input#{0}').result(function (event, data, value) ", this.txtAutoComplete.ClientID);  
         scriptValue.Append("{");  
         scriptValue.AppendFormat("var hidden = $('input#{0}');", this.hdfValue.ClientID);  
         scriptValue.Append("hidden.val(value);");  
         scriptValue.Append("}); });");  
         scriptValue.Append("</script>");  
         Page.ClientScript.RegisterStartupScript(GetType(), key, scriptValue.ToString());  
       }  
       catch { }  
     }  

Note: not allowed to forget passing "XMLElementKey and XMLElementTextAttribute" if you forgotten them or one of them the control will not complete it is functionality since those are the main functionality key for the plugin data also you will note that data will be formatted for the plugin from XML data to array to be handled by the plugin. 

Prerequisites:
  1. jQuery plugin registered
  2. jquery.autocomplete registered "new modified one you can get all files you will need here as zip file called AutoCompleteFiles.zip contains three files (plugin, CSS file for results "don not change classes names" and image for loading)"
  3. ASPX page that will handle the posts by the plugin "will be discussed soon"
Implementation:
  1. Registering control in web.config file
     <add tagPrefix="dev" namespace="CustomControls" assembly="CustomControls" />  
    
  2. Adding the control to your ASPX page
     <dev:AutoComplete runat="server" ID="acEmployeeName" MaximumRows="10" MinimumChars="1" MatchContains="true"  
             MethodName="SearchUsers" PostURL="http://localhost/SiteName/Pages/AjaxHelper.aspx"  
             MethodNameRequestKey="op" TabIndex="3" XMLElementKey="User" XMLElementTextAttribute="Name" XMLElementValueAttribute="ID" />  
    
  3. The ASPX page that will handle the ajax calls posted by the plugin will have the folloing implementation
     protected void Page_Load(object sender, EventArgs e)  
         {  
           if (Request["op"] != null && Request["op"] == "SearchUsers")  
           {  
             if (Request["searchKey"] != null)  
               SearchUsers(Request["searchKey"]);  
           }  
         }  
         private void SearchUsers(string searchKey)  
         {  
           StringBuilder usersString = new StringBuilder();  
           // sample data --> data can be retrieved from any source and format the xml with and values you want
           string[] users = new string[] { "Ahmed", "Mohamed", "Fakhry", "Khalil", "Mansor" };  
           // build the XML   
           usersString.Append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");  
           usersString.Append("<Users>");  
    
          for (int i = 0; i < users.Length; i++)
                    if (users[i].ToLower().Contains(searchKey.ToLower()))
                        usersString.AppendFormat("", users[i], i.ToString());
    
           usersString.Append("</Users>");  
           Response.ClearContent();  
           Response.ContentType = "text/xml";  
           Response.Write(usersString.ToString());  
           Response.End();  
         }  
    
As noted the page will write an XML responce that will be reformatted by the control according to "XMLElementKeyXMLElementTextAttribute XMLElementValueAttribute" properties provided --> here 
  • XMLElementKey: User 
  • XMLElementTextAttribute: Name
  • XMLElementValueAttribute: ID
Note: If XMLElementValueAttribute same as XMLElementTextAttribute you can specify  XMLElementTextAttribute only and the control will consider value attribute as the same as the text attribute.

Note: To request the text you get the property called "SelectedText" and for value you will get the property called "SelectedValue"


Note: new updates added as the following
  1. Each item will have text and value.
  2. If the user never specify new property "XMLElementValueAttribute" the control will specify the text property to the value property.


Finally i hope for all good luck and i wish that article helpful

Note: you can find the complete source code here that is called "CustomControls.zip"

Thursday, September 8, 2011

Simple ASP.net Time Picker Composite Server Control

Today i will introduce commonly used server control for ASP.net, let us describe the control in details.

This control support localization for all possible value you need to localize such as
  1. HoursText
  2. MinutesText
  3. AMText
  4. PMText
  5. LayoutDirection
  6. TextAlign
Available properties in the control
  1. AMPM: provide option if user need time control render as AP-PM mode or not
  2. IsRequired: provide option if user need to make the control required or not
  3. ValidationGroup: option to make user provide "ValidationGroup" sub-controls
  4. DropDownListCssClass: option to provide "CssClass" for Drop Down Lists
  5. Time: "The Default Property" get or set the time value for the control the return value is string and the format depends on the "AMPM" property
  6. ShortTimeString: get the 24 hour-format time as string "hh:mm" as "23:30"
  7. LongTimeString: get the 12 hour-format time as string "hh:mmtt" as "01:30pm" 
  8. HoursText: the first item text for hours drop down list
  9. MinutesText: the first item text for minutes drop down list 
  10. AMText: the AM item text for AMPM drop down list 
  11. PMText: the PM item text for AMPM drop down list  
  12. LayoutDirection: the layout direction for drop down lists "ltr||rtl"
  13. TextAlign: the text align for drop down lists "left||right"
  14. SetCurrentTimeDefault: set the current time as default time selected by time picker
Note: also the control support Arabic numeral automatically

now with the main methods and overrides for rendering the control:


Note: you will find that the class inherits from class "WebControl" and this the main class that will be implemented and also implement the interface called "INamingContainer" and note that you not find any implementation for the interface methods and it used only for creating unique id for newly added control.


Control Collection and Create Child Controls Overrides

 public override ControlCollection Controls  
     {  
       get  
       {  
         EnsureChildControls();  
         return base.Controls;  
       }  
     }  

 protected override void CreateChildControls()  
     {  
       Controls.Clear();  
       #region Drop Down Lists  
       // setting ids  
       ddlHours.ID = "ddlHours";  
       ddlMinutes.ID = "ddlMinutes";  
       ddlAMPM.ID = "ddlAMPM";  
       // text align for drop down lists "left||right"        
       ddlHours.Attributes["style"] = "text-align:" + TextAlign.ToString();  
       ddlMinutes.Attributes["style"] = "text-align:" + TextAlign.ToString();  
       ddlAMPM.Attributes["style"] = "text-align:" + TextAlign.ToString();  
       // check if user provide cssclass for drop down lists  
       if (!string.IsNullOrEmpty(DropDownListCssClass))  
       {  
         ddlHours.CssClass = DropDownListCssClass;  
         ddlMinutes.CssClass = DropDownListCssClass;  
         ddlAMPM.CssClass = DropDownListCssClass;  
       }  
       #endregion  
       #region Required Field Validators  
       // hours RFV  
       rfvHours.Display = ValidatorDisplay.Dynamic;  
       rfvHours.ControlToValidate = "ddlHours";  
       rfvHours.ErrorMessage = "*";  
       rfvHours.InitialValue = "-1";  
       // minutes RFV  
       rfvMinutes.Display = ValidatorDisplay.Dynamic;  
       rfvMinutes.ControlToValidate = "ddlMinutes";  
       rfvMinutes.ErrorMessage = "*";  
       rfvMinutes.InitialValue = "-1";  
       // check if user need this instance required or not  
       if (!IsRequired)  
       {  
         rfvHours.Enabled = false;  
         rfvMinutes.Enabled = false;  
       }  
       else  
         if (!string.IsNullOrEmpty(ValidationGroup))  
         {  
           ddlHours.ValidationGroup = ValidationGroup;  
           rfvHours.ValidationGroup = ValidationGroup;  
           ddlMinutes.ValidationGroup = ValidationGroup;  
           rfvMinutes.ValidationGroup = ValidationGroup;  
         }  
       #endregion  
       #region User Interface  
       Controls.Add(new LiteralControl("<table border=\"0\" cellpadding=\"5\" cellspacing=\"0\" dir=\"" + LayoutDirection.ToString() + "\"><tr><td>"));  
       Controls.Add(ddlHours);  
       Controls.Add(rfvHours);  
       Controls.Add(new LiteralControl("</td><td>"));  
       Controls.Add(ddlMinutes);  
       Controls.Add(rfvMinutes);  
       Controls.Add(new LiteralControl("</td><td>"));  
       Controls.Add(ddlAMPM);  
       Controls.Add(new LiteralControl("</td></tr></table>"));  
       #endregion  
       string _time = string.Empty;  
       // time parts variable for 12 and 24 hour format  
       string[] timeParts = null;  
       if (string.IsNullOrEmpty(Time))  
       {  
         if (SetCurrentTimeDefault)  
         {  
           if (AMPM)  
           {  
             _time = DateTime.Now.ToString("hh:mmtt");  
             // 12hour-format time  
             timeParts = new string[3];  
             timeParts[0] = _time.Split(':')[0].ToString();  
             timeParts[1] = _time.Split(':')[1].Substring(0, 2);  
             timeParts[2] = _time.Split(':')[1].Substring(2, 2);  
           }  
           else  
           {  
             _time = DateTime.Now.ToString("hh:mm");  
             // 24hour-format time  
             timeParts = new string[2];  
             timeParts = _time.Split(':');  
           }  
         }  
         // load time drob down lists  
         LoadTime();  
         if (timeParts != null)  
         {  
           if (AMPM)  
           {  
             ddlHours.SelectedValue = timeParts[0];  
             ddlMinutes.SelectedValue = timeParts[1];  
             ddlAMPM.SelectedValue = timeParts[2];  
           }  
           else  
           {  
             ddlHours.SelectedValue = timeParts[0];  
             ddlMinutes.SelectedValue = timeParts[1];  
           }  
         }  
         // check if user need ampm option or not  
         if (!AMPM)  
           ddlAMPM.Visible = false;  
       }  
     }  




and the main method called LoadTime (to initialize all drop down lists with items)
 private void LoadTime()  
     {  
       try  
       {  
         // fill hours drop down list  
         ddlHours.Items.Clear();  
         if (!AMPM)  
           for (int i = 00; i < 24; i++)  
           {  
             if (HttpContext.Current.Session.LCID == 1025) // arabic  
               ddlHours.Items.Add(new ListItem(i.ToString().Length < 2 ? ConvertToArabicNumeral("0" + i.ToString()) : ConvertToArabicNumeral(i.ToString()), i.ToString().Length < 2 ? "0" + i.ToString() : i.ToString()));  
             else  
               ddlHours.Items.Add(new ListItem(i.ToString().Length < 2 ? "0" + i.ToString() : i.ToString(), i.ToString().Length < 2 ? "0" + i.ToString() : i.ToString()));  
           }  
         else  
           for (int i = 01; i <= 12; i++)  
           {  
             if (HttpContext.Current.Session.LCID == 1025) // arabic  
               ddlHours.Items.Add(new ListItem(i.ToString().Length < 2 ? ConvertToArabicNumeral("0" + i.ToString()) : ConvertToArabicNumeral(i.ToString()), i.ToString().Length < 2 ? "0" + i.ToString() : i.ToString()));  
             else  
               ddlHours.Items.Add(new ListItem(i.ToString().Length < 2 ? "0" + i.ToString() : i.ToString(), i.ToString().Length < 2 ? "0" + i.ToString() : i.ToString()));  
           }  
         // add first item to hours drop down list          
         ddlHours.Items.Insert(0, new ListItem(HoursText, "-1"));  
         // fill minutes drop down list  
         ddlMinutes.Items.Clear();  
         for (int i = 00; i < 60; i++)  
         {  
           if (HttpContext.Current.Session.LCID == 1025) // arabic  
             ddlMinutes.Items.Add(new ListItem(i.ToString().Length < 2 ? ConvertToArabicNumeral("0" + i.ToString()) : ConvertToArabicNumeral(i.ToString()), i.ToString().Length < 2 ? "0" + i.ToString() : i.ToString()));  
           else  
             ddlMinutes.Items.Add(new ListItem(i.ToString().Length < 2 ? "0" + i.ToString() : i.ToString(), i.ToString().Length < 2 ? "0" + i.ToString() : i.ToString()));  
         }  
         // add first item to hours drop down list          
         ddlMinutes.Items.Insert(0, new ListItem(MinutesText, "-1"));  
         #region AMPM DropDownList  
         ddlAMPM.Items.Insert(0, new ListItem(AMText, "AM")); // first item  
         ddlAMPM.Items.Insert(1, new ListItem(PMText, "PM")); // second item  
         #endregion  
       }  
       catch (Exception ex)  
       {  
         throw new Exception(ex.Message);  
       }  
     }  

and the time validation method the support 12 hour-format and 24 hour-format

 private bool ValidateTime(string time, out bool IsAMPM)  
     {  
       IsAMPM = false;  
       bool validTime = false;  
       // validate 12hour-format time string  
       Regex rg12 = new Regex("^([1-9]|1[0-2]|0[1-9]){1}(:[0-5][0-9][aApP][mM]){1}$");  
       if (rg12.IsMatch(time))  
       {  
         validTime = true;  
         IsAMPM = true;  
         return validTime;  
       }  
       // validate 24hour-format time string  
       Regex rg24 = new Regex("^([0-1][0-9]|[2][0-3]):([0-5][0-9])$");  
       if (rg24.IsMatch(time))  
       {  
         validTime = true;  
         return validTime;  
       }  
       return validTime;  
     }  

and the last method for handling Arabic numeral

 // convert from english numeral to arabic numeral  
     protected string ConvertToArabicNumeral(string englishNumber)  
     {  
       string arabicNumebr = englishNumber;  
       try  
       {  
         foreach (char enn in englishNumber)  
         {  
           switch (enn)  
           {  
             case '0':  
               arabicNumebr = arabicNumebr.Replace('0', '٠');  
               break;  
             case '1':  
               arabicNumebr = arabicNumebr.Replace('1', '١');  
               break;  
             case '2':  
               arabicNumebr = arabicNumebr.Replace('2', '٢');  
               break;  
             case '3':  
               arabicNumebr = arabicNumebr.Replace('3', '٣');  
               break;  
             case '4':  
               arabicNumebr = arabicNumebr.Replace('4', '٤');  
               break;  
             case '5':  
               arabicNumebr = arabicNumebr.Replace('5', '٥');  
               break;  
             case '6':  
               arabicNumebr = arabicNumebr.Replace('6', '٦');  
               break;  
             case '7':  
               arabicNumebr = arabicNumebr.Replace('7', '٧');  
               break;  
             case '8':  
               arabicNumebr = arabicNumebr.Replace('8', '٨');  
               break;  
             case '9':  
               arabicNumebr = arabicNumebr.Replace('9', '٩');  
               break;  
           }  
         }  
         return arabicNumebr;  
       }  
       catch  
       {  
         return englishNumber;  
       }  
     }  

now with the registering, using and screen shots of the rendered control

Registering the control through web.config file

 <add tagPrefix="uc1" namespace="CustomControls" assembly="CustomControls" />  

Localized English version (AP-PM 12 hour-format)

 <dev:TimePicker runat="server" ID="tpExpectedReturnTime" AMPM="true" IsRequired="true"  
         ValidationGroup="LeaveForm" TabIndex="10" meta:resourcekey="tpExpectedReturnTimeResource" />  

Localized English Version (24 hour-format)

 <dev:TimePicker runat="server" ID="tpLeaveTime" AMPM="false" IsRequired="true" ValidationGroup="LeaveForm"  
         TabIndex="9" meta:resourcekey="tpLeaveTimeResource" />  

Localized Arabic Sample (AP-PM 12 hour-format)


Note: Newly added method "Reset" that will reset time control (all drop down lists)

Finally i hope for all good luck and i wish that article helpful

Note: you can find the complete source code here that is called "CustomControls.zip"