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"

No comments:

Post a Comment