Friday, April 9, 2010

Enhanced Textbox Control

Shout it kick it on DotNetKicks.com

Introduction

In application development, there is always the need for validating input controls. To validate input controls, ASP.NET has a set of validation controls. To apply validation on input controls, you require to attach a validation control with the input control.
Here, I will discuss about an extended control which doesn't require attaching a validation control, but just needs specifying the input type.

Implementation

Custom control implementation

As this is a generic control which can be used by any developer in any ASP.NET application, it inherit the ASP.NET TextBox control to support a basic textbox control and to add extra features for validation; it's basically a custom control, i.e., a custom textbox control.

Properties of the control

All properties use the viewstate to persist values. Another thing to note here is the property appends the ID of the control so that if two textbox controls are dropped on the same page, they do not conflict with each other's property values.

ValidationType
This property allows applying a validation type on the textbox control.
The following validation types are supported by the textbox control:

  • None – allows to enter any type of data.
  • Integer – allows to enter only integer. E.g., 123.
  • Numeric – allows to enter only integer and decimal point values. E.g., 123.12 or 123.
  • Alpha – allows to enter alphabets. E.g., A-Z or a- z.
  • AlphaNumeric – allows to enter alpha and numeric data. E.g., A-Z or a-z or 0-9.
  • Custom – allows to enter data based on patterns specified by the user.
  • Required – allows to enter any kind of data but you are required to enter data in the field.
  • RequiredAndInteger – allows to enter integer and must enter data.
  • RequiredAndNumeric – allows to enter numeric and must enter data.
  • RequiredAndAlpha – allows entering alphabets and must enter data.
  • RequiredAndAlphaNumeric – allows to enter alphanumerics and must enter data.
  • RequiredCustom – allow to enter data based on pattern and must enter data.

AssociatedLabelText
This property allows to set the label that should be displayed with an error message when invalidate data is entered.
public string AssociatedLableText
{
     get
     {
          if (ViewState["LableText" + this.ID.ToString()] == null)
               return "Textbox";
          return (string)(ViewState["LableText" + this.ID.ToString()].ToString());
     }
     set
     {
          ViewState["LableText" + this.ID.ToString()] = value;
     }
}
CustomPattern
This property allows to set a custom Regular Expression to validate the entered data. This property is used when the user wants to validate complex data like email.
public string CustomPattern
{
     get
     {
          if (ViewState["CustomPattern" + this.ID.ToString()] == null)
               return string.Empty;
          return (string)(ViewState["CustomPattern" + this.ID.ToString()].ToString());
     }
     set
     {
          ViewState["CustomPattern" + this.ID.ToString()] = value;
     }
}
ReadOnly
This property causes the textbox control to be displayed as a label control so that its does not allow editing data.
public string ReadOnly
{
     get
     {
          if (ViewState["ReadOnly" + this.ID.ToString()] == null)
               return string.Empty;
          return (string)(ViewState["ReadOnly" + this.ID.ToString()].ToString());
     }
     set
     {
          ViewState["ReadOnly" + this.ID.ToString()] = value;
     }
}
LabelCSSClass
This property allows to set the CSS class when the textbox has the ReadOnly property set to true.
public string LabelCSSClass
{
     get
     {
          if (ViewState["LabelCSSClass" + this.ID.ToString()] == null)
               return string.Empty;
          return (string)(ViewState["LabelCSSClass" + this.ID.ToString()].ToString());
     }
     set
     {
          ViewState["LabelCSSClass" + this.ID.ToString()] = value;
     }
}
ShowWaterMark
This property allows to set the watermark in textbox when property set to true.
public bool ShowWaterMark
{
     get
     {
          if (ViewState["ShowWaterMark" + this.ID.ToString()] == null)
               return false;
          return Convert.ToBoolean(ViewState["ShowWaterMark" +this.ID.ToString()].ToString());
     }
     set
     {
          ViewState["ShowWaterMark" + this.ID.ToString()] = value;
     }
}

WaterMarkText
This property allows to set watermark in textbox when ShowWaterMark propety is set to true.
public string WaterMarkText
{
     get
     {
          if (ViewState["WaterMarkText" + this.ID.ToString()] == null)
               return string.Empty;
          return (string)(ViewState["WaterMarkText" +this.ID.ToString()].ToString());
     }
     set
     {
          ViewState["WaterMarkText" + this.ID.ToString()] = value;
     }
}
GroupId
This property is used by the validation function to know with which group it is associated. E.g., if groupId = ‘A’ for the textbox, then the validation event is fired by the button control having groupId = ‘A’ and the textbox control is validated. In this case, it is not going to be validated by the control having groupId = ‘B’.

Note: this property is used by the JavaScript only and it's added to the control directly; it works the same as the group property in .NET, but the difference is this is used by the JavaScript function.

Overridden methods
The Render method of the control cause rendering of the control on the client with the HtmlTextWriter object. By using this method, changing the rendering of the textbox control is possible.

Render
protected override void Render(HtmlTextWriter writer)
{
If the ReadOnly property of the control is true, than only span is going to be rendered in place of the textbox control so it is not displayed as an editable control.
     if (this.ReadOnly)
     {
          writer.Write(@"<span class=""{0}"">{1}</span>",
          this.LabelCSSClass, this.Text);
     }
If the ReadOnly property of the control is false, than it calls the ApplyValidation method which adds JavaScript and validates the data entered by the client in the textbox control.
     else if (!this.ReadOnly)
     {
          string strError= ApplyValidation(writer);
          base.Render(writer);
          if (((int)this.DataType) > 4)
          {
               writer.Write(strError);
          }
     }
ApplyValidation
private string ApplyValidation(HtmlTextWriter writer)
{
The following line of code defines errorMsg and the script which is going to be bound with the textbox control.
     string errorMsg = string.Empty;
     string script = string.Empty;
As you see below, if DataType has a value more than 5, it adds the required attribute with the value true, which is used by JavaScript to check whether the value is present or not. It also binds the keyup JavaScript method with the onBlur and onKeyUp events. A span tag is also rendered to display an error message when there is no data present in the textbox control.
     if (((int)this.DataType) > 5)
     {
          this.Attributes.Add("isRequired", "true");
          this.Attributes.Add("onKeyUp", "keyup('" + this.ClientID + "')");
          this.Attributes.Add("onBlur", "keyup('" + this.ClientID + "')");
          errorMsg = string.Format(@"<span id='spError{2}' style='display:" +@"none;' >{0} {1}</span>",
                         "Enter value in ", this.AssociatedLableText,this.ClientID);
     }
The below line of code adds the ShowWaterMark attribute to the textbox rendered at the client, which is used by the JavaScript function.
     this.Attributes.Add("ShowWaterMark", this.ShowWaterMark.ToString());
If the ShowWaterMark value is set to true, than it adds the LabelValue attribute to the textbox control which is displayed as the watermark in the textbox:
     if(this.ShowWaterMark)
          this.Attributes.Add("LabelValue", this.WaterMarkText);
If DataType is less than 6, it means it is not required to attach the showWaterMarkText method to the onblur event.
     if (((int)this.DataType) < 6)
          this.Attributes.Add("onBlur", "showWaterMarkText('" + this.ClientID + "')");
If the DataType is Integer, requiredAndInteger, numeric, or requireAndNumeric, it binds the IntegerAndDecimal method withonKeyPress so that it is only allowed to enter integer or decimal data in the textbox control.
     if (this.DataType == ValidationType.integer||
          this.DataType == ValidationType.requiredAndInteger)
     {
          script="IntegerAndDecimal(event,this,'false')";
     }
     if (this.DataType == ValidationType.numeric ||
      this.DataType == ValidationType.requiredAndNumeric)
     {
          script = "IntegerAndDecimal(event,this,'true')";
     }
An important thing to note here is the third argument passed to the JavaScript function: when the value passed to the argument is true, it means it allows to enter decimal data, and if it is false, it allows to enter integer data without a decimal point.

If the DataType is alpha, requiredAndalpha, alphaNumeric, or requireAndAlphaNumeric, it binds the AlphaNumericOnly method withonKeyPress so that it allows to enter integer or decimal data only in the textbox control.
     if (this.DataType == ValidationType.alpha ||
          this.DataType == ValidationType.requiredAndAlpha)
     {
          script = "return AlphaNumericOnly(event,'true')";
     }
     if (this.DataType == ValidationType.alphaNuemric ||
          this.DataType == ValidationType.requiredAndAlphaNumeric)
     {
          script = "return AlphaNumericOnly(event,'false')";
     }
An important thing to note here is the second argument passed to the JavaScript function: when the value passed to the argument is true, it allows to enter alpha characters only, and when it is false, it allows to enter alphanumeric characters.

If the DataType is custom or requiredCustom, it binds the checkReg function with the onBlue event. It also adds a span tag with the text box to display an error message when the data entered doesn't match with the Regular Expression assigned to the CustomPattern property. It adds thepattern attribute with the textbox to validate the data.
     if (this.DataType == ValidationType.custom ||
          this.DataType == ValidationType.requiredCustom)
     {
          errorMsg = errorMsg +string.Format(@"<span id='spCuError{2}' style='display:none;' >{0} {1}     </span>", "Invalid data in ", this.AssociatedLableText, this.ClientID);
          this.Attributes.Add("pattern", this.CustomPattern);
          if (this.DataType == ValidationType.custom)
               this.Attributes.Add("onBlur", "checkReg(this,'" + this.CustomPattern + "');");
          else
               this.Attributes.Add("onBlur", "keyup('" + this.ClientID +"'); checkReg(this,'" + this.CustomPattern + "');");
     }
If the TextMode of the textbox control is set to MultiLine, it binds the CountText method with the onKeyPress event, so that it doesn't allow to enter characters more than a maximum length.
     if (this.TextMode == TextBoxMode.MultiLine)
     {
          if (this.MaxLength <= 0)
          this.MaxLength = 200;
          script = "return CountText(this,'" + this.MaxLength + "')";
     }
As CountText is bound to onKeryPress, it overrides other scripts bind on the control with the keypress event.
The following line of code binds the script with the onKeyPress event.
     this.Attributes.Add("onKeypress", script);

This code returns the errorMsg associated with the textbox:

     return errorMsg;
}
JavaScript functions

IntegerAndDecimal
This method allows to enter only integer and decimal data in the textbox control. If the isDecimal value is true, then it allows to enter decimal point values; otherwise, it allows to enter integer values only.
function IntegerAndDecimal(e,obj,isDecimal)
{
     if ([e.keyCode||e.which]==8) //this is to allow backspace
          return true;
     if ([e.keyCode||e.which]==46) //this is to allow decimal point
     {
          if(isDecimal=='true')
          {
               var val = obj.value;
               if(val.indexOf(".") > -1)
               {
                    e.returnValue = false;
                    return false;
               }
               return true;
          }
          else
          {
               e.returnValue = false;
               return false;
          }
     }
     if ([e.keyCode||e.which] < 48 || [e.keyCode||e.which] > 57)
          e.preventDefault? e.preventDefault() : e.returnValue = false;
}
AlphaNumericOnly
This function allows to enter only alphabets or alphanumeric values in the textbox control. It checks if isAlphaonly is true, then allows to enter only alphabets;otherwise, allows to enter alphanumeric values in the textbox control.
function AlphaNumericOnly(e,isAlphaonly)
{
     // copyright 1999 Idocs, Inc. http://www.idocs.com
     var key = [e.keyCode||e.which];
     var keychar = String.fromCharCode([e.keyCode||e.which]);
     keychar = keychar.toLowerCase();
     if(isAlphaonly=='true')
          checkString="abcdefghijklmnopqrstuvwxyz";
     else
          checkString="abcdefghijklmnopqrstuvwxyz0123456789";

     if ((key==null) || (key==0) || (key==8) ||   (key==9) || (key==13) || (key==27) )
          return true;
     else if (((checkString).indexOf(keychar) > -1))
          return true;
     else
          return false;
}

keyup
This function shows and hides the error message associated with the textbox control when the textbox Required property is set to true. It also calls the showWaterMarkText method if the ShowWaterMark text attribute value is set to true.
function keyup(cid)
{
     var showWaterMark = $('#' + cid).attr("ShowWaterMark");
     if (showWaterMark === "True")
          test = watermark + $('#' + cid).attr("LabelValue");
     else
          test = '';
     if ($('#' + cid).val() === '' || $('#' + cid).val() === test) {
          document.getElementById('spError' + cid).style.display = 'block';
          $('#' + cid).removeClass('normal');
          $('#' + cid).addClass('mandatory');
     }
     else {
          document.getElementById('spError' + cid).style.display = 'none';
          $('#' + cid).removeClass('mandatory');
          $('#' + cid).addClass('normal');
     }
     if (showWaterMark === "True")
          showWaterMarkText(cid);
}

CountText
This function does not allow entering more characters than maxlimit. Note: it's used only for multiline textboxes.
function CountText(field, maxlimit)
{
     if (field.value.length < maxlimit) // if too long...trim it!
     {
          return true;
     }
     else
          return false;
}
checkReg
This function is to display the error message when data entered is not valid according to the custom pattern specified in the CustomPatternproperty.
function checkReg(obj,pattern)
{
     if(obj.value!=='' && pattern!=='')
     {
          var pattern1= new RegExp(pattern);
          if(obj.value.match(pattern1))
          {
               document.getElementById('spCuError'+obj.id).style.display='none';
               return true;
          }
          else
          {
               document.getElementById('spCuError'+obj.id).style.display='block';
               return false;
          }
     }
}

callOnload
This function sets the style of the textbox to the mandatory CSS class when the Required property is set to true and no data can be entered by the user. It also calls the showWaterMark function to show the watermark text in the textbox.
function callOnload()
{
     $("input[isRequired=true]").each(
          function(n)
          {
               $('#' +this.id).addClass('mandatory');
               if(this.value==='')
               {
                    $('#' +this.id).removeClass('normal');
                    $('#' +this.id).addClass('mandatory');
               }
               else
               {
                    $('#' +this.id).removeClass('mandatory');
                    $('#' +this.id).addClass('normal');
               }
          });

     $("textarea[isRequired=true]").each(
          function(n)
          {
               $('#' +this.id).addClass('mandatory');
               if(this.value==='')
               {
                    $('#' +this.id).removeClass('normal');
                    $('#' +this.id).addClass('mandatory');
               }
               else
               {
                    $('#' +this.id).removeClass('mandatory');
                    $('#' +this.id).addClass('normal');
               }
          });

          showWaterMark(); // call function to add watermark to textbox
}
showWaterMark
This function get called by callOnLoad method this function show watermarktext in textbox control when the ShowWaterMark attribute value is set to true.
     var watermark = "";
function showWaterMark() {
     $("input[ShowWaterMark=True]").each(
     function(n) {
          showWaterMarkText(this.id);
          $('#' + this.id).focus(function() {
               test = watermark + $('#' + this.id).attr("LabelValue");
               if (this.value == test) {
                    this.value = "";
               }
     });
  });

     $("textarea[ShowWaterMark=True]").each(
          function(n) {
               showWaterMarkText(this.id);
               $('#' + this.id).focus(function() {
                    test = watermark + $('#' + this.id).attr("LabelValue");
                    if (this.value == test) {
                         this.value = "";
          }
     });
   });
}

showWaterMarkText
This function is called on onBlur event or form keyUp event. Function show watermarktext if the there is no value in textbox or value is equal to watermarktext, Otherwise it remove the watermark CSS values.
function showWaterMarkText(id)
{
     test = watermark + $('#' + id).attr("LabelValue");
     if ($('#' + id).val() === ''|| $('#' + id).val() == test)
     {
          $('#' + id).val(watermark + $('#' + id).attr("LabelValue"));
          $('#' + id).css("color", "#999");
          $('#' + id).css("font-style", "italic");

          if(document.getElementById('spError' + id) !== null)
               document.getElementById('spError' + id).style.display = 'none';
     }
     else
     {
          $('#' + id).css("color", "#000");
          $('#' + id).css("font-style", "");
     }
}
validateFormInputs
The function attached with the button click which submits data to the server, but before that, it validates all the textbox controls having thegroupid which is passed in as a function parameter.
function validateFormInputs(gropId)
{
     var isAllValid = true;
     var searchConditon = "";
     if (gropId !== "" && gropId !== undefined)
     {
          searchConditon = searchConditon +"input[isRequired=true][GroupId=" + gropId + "]";
     }
     else
     {
          searchConditon = searchConditon + "input[isRequired=true]";
     }
     $(searchConditon).each(
          function(n)
          {
               test = watermark + $('#' + this.id).attr("LabelValue");
               if (this.value === '' || this.value == test)
                    isAllValid = false;
               document.getElementById('spError' +this.id).style.display = 'block';
          }
          else {
               if (this.pattern !== '' && this.pattern !== undefined) {
                    if (!checkReg(this, this.pattern)) {
                         isAllValid = false;
                    }
               }
               if (document.getElementById('spError' + this.id) != null)
                    document.getElementById('spError' +this.id).style.display = 'none';
        }
   });

     searchConditon = "";
     if (gropId !== "" && gropId !== undefined)
     {
          searchConditon = searchConditon +"textarea[isRequired=true][GroupId=" + gropId + "]";
     }
     else
     {
          searchConditon = searchConditon + "textarea[isRequired=true]";
     }
     $(searchConditon).each(
          function(n) {
               test = watermark + $('#' + this.id).attr("LabelValue");
               if (this.value === ''|| this.value == test)
                    isAllValid = false;
               document.getElementById('spError' +this.id).style.display = 'block';
     }
     else {
          if (document.getElementById('spError' + this.id) != null)
               document.getElementById('spError' +this.id).style.display = 'none';
          }
     });
     return isAllValid;
}
Note: You need to pass the group ID only when you have to validate the controls related to the given group, i.e., if there are two or more groups of the control validated by different buttons.


Advantages

  • SQL injection attack - Because the control only allows to enter data specified in a property, it does not allow entering unwanted characters which creates problems when doing database DML operations. Read more about SQL injection attacks here: http://en.wikipedia.org/wiki/SQL_injection.
  • Rendering cost decrease - It also decreases the cost of rendering. Because you attach the validation control itself on the page and also emit JavaScript if theEnableClient property is set to true.
  • Multiple type of validation in one textbox control - By setting the ValidationType property, we can apply more than one validation method, e.g., required + alpha, required + numeric etc.
  • Watermark - If showWaterMark = true, it allows to show a watermark in the textbox control, which can be used to show help text or a text value to enter in the textbox.
  • ReadOnly - Using this property, we can show the textbox as a label when the form is in read-only mode, i.e., in non-editable form just for viewing purposes.

How to use
Following is an implementation of the textbox control on a page. To get more information about this, download the code and check it out.
<form id="form1" runat="server">
     <table>
          <tr>
               <td>
                    <!--To Allow only ahlpha numeric char.Data must required to enter by user before submit. -->
                    <cc:MyTextBox runat="server" id="MyTextBox2" GroupId="A"
                              AssociatedLableText="FirstName" DataType="requiredAndAlphaNumeric" >
                    </cc:MyTextBox>
               </td>
               <td>
                  <!--To Allow only ahlpha char. Data must required toenter by user before submit. -->
                    <cc:MyTextBox runat="server" id="MyTextBox1" GroupId="A"
                              AssociatedLableText="LastName" DataType="requiredAndAlpha" >
                    </cc:MyTextBox>
               </td>
               <td>
                      <!--Data must required to enter by user before submit. -->
                    <cc:MyTextBox runat="server" id="MyTextBox3" GroupId="B"
                         AssociatedLableText="Remark" TextMode="MultiLine"
                         DataType="required" ></cc:MyTextBox>
               </td>
               <td>
                    <!--Data must match with pattern specified .Data must required to enter by user before submit.-->
                    <cc:MyTextBox runat="server" id="MyTextBox4" GroupId="A"
                         AssociatedLableText="Email" DataType="requiredCustom"
                         CustomPattern="([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+
                         \.([a-zA-Z])+([a-zA-Z])+" >
                    </cc:MyTextBox>
               </td>
               <td>
                    <cc:MyTextBox runat="server" id="MyTextBox5" AssociatedLableText="Test"
                              ShowWaterMark="true" WaterMarkText="TestBox" ></cc:MyTextBox>
               </td>
     </tr>
</table>
<!-- Validate only textboxes which is having gorupid=A when button is clicked-->

<cc:MyButton runat="server" ID="mybutton" OnOnCustomCommand="mybutton_OnCustomCommand" CausesValidation="true" OnClientClick="return validateFormInputs('A');" CommandArgument="" Text="test" />
</form>

Conclusion
This textbox control implementation provides a basic implementation of a custom control with the use of jQuery and JavaScript.

2 comments:

  1. Instead of using display:block and display:none to show and hide things, it would be better if you added classes such as ErrorHidden, ErrorShown. Overriding the style of the subparts makes it so that you are forcing display:block, when perhaps they want display:inline, or display: inlink-block. Additionally, you should probably include a hidden input field for readonly fields as otherwise they will not be submitted as form data when the form is submitted (Because you changed it to a span).

    Again, you shouldn't hard code the styles in your control (Italicize, etc) for watermarks, just add a class called "watermarked" to the control and let the programmer style it themselves. Maybe they want the text as light blue, not italicized. Maybe they want error messages as red, bold, and 1.2em.

    ReplyDelete
  2. Hi! it`s a greate site! I`ll come back again.

    ReplyDelete