Extending an existing server control is an interesting and a
simple job most of the time. I have chosen TextBox control for this purpose.
Let us first analyze our need of building an extended TextBox control as we do
not want to extend it just for the sake of an example! This analysis can be
divided into two parts finding deficiencies to make up and suggesting
enhancements that are useful. I emphasize on the analysis so that we can extend
it meaningfully and purposefully making it a really useful member of our
control tool kit. With inclusion of these properties in our control you will
also be able to learn how a custom control can emit HTML and JavaScript as per
required.
There is a deficiency that really becomes problematic for
developers; when the TextMode of the TextBox is set to Multiline, the MaxLength
property stops working. We will discuss why it cannot support this.
While focusing on enhancements there are several which can
be very useful, for example providing a support for entering cased input
uppercase or lowercase, numeric, and alphanumeric. A textbox can also be self
validating, these enhancements can add a good worth to it. So after a little
analysis, we are able to figure out what we want to overcome and what we want
to add in our new control.
Our new Textbox, ExtendedTextBox, will have the following
features.
1.
It supports a cased input by providing TextCaseMode property.
2.
It supports an input mode for numeric and alphanumeric input by
providing InputMode property.
3.
It supports MaxLength property when TextMode is set to MultiLine also.
4.
It provides built in required field validation by providing IsRequired
property.
Now getting specific to the implementation, the following
are the steps to accomplish all this.
5.
First, create a new project of Web Control Library. Please note that VB
and Visual C# in Standard Edition do not support the Web Control Library
template. It is only available in the professional edition.
6.
Give the class some suitable name, in our case ExtendedTextBox, and
extend it from its base i.e. TextBox as shown below.
Listing 3
[Description("Extended TextBox v1.0 (Developed by Mahr G. Mohyuddin)")]
[DefaultProperty("Text")]
[ToolboxData("<{0}:ExtendedTextBox runat=server></{0}:ExtendedTextBox1>")]
public class ExtendedTextBox : System.Web.UI.WebControls.TextBox
{
...
You can see the class has been adorned with some attributes,
Description, DefaultProperty and ToolboxData. Description attribute keeps the
description of the control that is displayed on bringing the ouse over it in the
tool box. DefaultProperty is used to bring the given property to focus when the
property window of a control is displayed. ToolBoxData is a mandatory attribute
that is used to define the look of control declaration in aspx markup when it
is dropped on the page. It ensures that the control is given the class name;
otherwise it will flag an error. You can also add more attributes like DefaultEvent,
which add declaration of the specified event in the code behind when a control
is double clicked. ToolBoxItem specifies whether the control should be visible
or not; when it is set to True the control is visible and vice versa. ToolBoxBiitmap
sepecifies the bitmap image that should be used as the icon for the control and
finally ToolBoxItemFilter attribute specifies the filter Visual Studio 2005
uses to determine whether the control should be enabled or disabled for a
specific designer.
Now, add properties that we need to expose to let the world
use features we are going to provide. This includes TextCaseMode, InputMode, IsRequired,
and ErrorMessage. Definition of all properties is similar. For instance,
TextCaseMode sets the case of the text to be entered. It can be Uppercase,
Lowercase or NotSet. We will need an enumeration for this purpose. The
following code snippet shows the definition of TextCaseMode property.
Listing 4
[Description("Sets the case of the text entered")][Category("Behavior")
][DefaultValue(TextCaseMode.NotSet)]
public TextCaseMode TextCaseMode
{
get
{
object o = ViewState["TextCaseMode"];
return (o != null ? (TextCaseMode)o: TextCaseMode.NotSet);
}
set
{
ViewState["TextCaseMode"] = value;
}
}
The property has been adorned with three attributes:
Description, Category and DefaultValue. Use of Description is quite evident. Category
attribute takes the name of the category in the property pane of the control in
Visual Studio IDE. All properties of a control are organized in Accessibly, Appearance,
Behavior, Data, Layout and Misc categories. We want the new property
TextCaseMode to be displayed in Behavior category.
Another important attribute is DefaultValue which sets a
default value,; the specified one, is shown when the no value is set by user.
In our case we want the text case mode not to be set by default. Therefore, NotSet
has been provided as the default value.
This property is of type TextCaseMode enum that has been
defined. A property that is of type enum is shown in the form of Dropdownlist
in the control property pane.
Figure 1
The new properties added to the control have been encircled.
You can see that the name with description (provided in Description attribute)
of the selected property appears on the bottom of the pane.
A value of property is set and retrieved in/from ViewState
having name of property of the property as key, since ASP.NET controls save
their state through ViewState. Similarly, a custom control should also save its
state. In other words, the value of properties exposed by a custom control
should be retained on postbacks. It can be possible only if we set the value of
a property in ViewState and get from it as shown above. The following code
snippet shows the definition of TextCaseMode and InputMode enums.
Listing 5
public enum TextCaseMode
{
UpperCase, LowerCase, NotSet
} public enum InputMode
{
Alphanumeric, Numeric, NotSet
}
Similarly, define other properties InputMode, IsRequired and
ErrorMessage. In short, InputMode property is used to set the mode the text
being entered that can either be Numeric, Alphanumeric or NotSet. The enum used
has been defined in Listing 3. IsRequired property when set to true makes
input in the textbox mandatory and stops all postback until some value is
entered. It shows the error, in tooltip, given in ErrorMessage property when the
mouse comes over it. The following code snippet shows the definition of the
rest of properties.
Listing 6
[Description("Sets input mode")][Category("Behavior")][DefaultValue
(InputMode.NotSet)]
public InputMode InputMode
{
get
{
object o = ViewState["InputMode"];
return o != null ? (InputMode)o: InputMode.NotSet;
}
set
{
ViewState["InputMode"] = value;
}
}
[Description("Mandates if the text is required")][Category("Behavior")
][DefaultValue(false)]
public bool IsRequired
{
get
{
object o = ViewState["IsRequired"];
return o == null ? false : true;
}
set
{
ViewState["IsRequired"] = value;
}
}
[Description("Displays messages if textbox input is required")][Category(
"Behavior")][DefaultValue("")]
public string ErrorMessage
{
get
{
object o = ViewState["ErrorMessage"];
return o != null ? (string)o: "";
}
set
{
ViewState["ErrorMessage"] = value;
}
}
Since all the features of the control should work on client
side we have client side script events to handle this. We have to write
JavaScript code that will be emitted by the control as per demanded by the user
who specify them through properties.
For this purpose we have to override PreRender event of the
control so that our new control will be able to add the JavaScript logic before
completing the rendering of the control.
We will add this code in the overridden PreRender event of
the control as the following exhibit shows.
Listing 7
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
string alphaNumeric = "function alphaNumeric(textBox){ " +
"if(/[^0-9A-Za-z]/.test(textBox.value)){ " +
"textBox.value=textBox.value.replace(/([^0-9A-Za-z])/g,''); " +
"textBox.title = ''; " + "} " +
" }";
string numeric = "function numeric (textBox){ " +
"if(/[^0-9a-z]/.test(textBox.value)){ " +
"textBox.value= textBox.value.replace(/([^0-9])/g,''); " +
"textBox.title = ''; " + "} " +
" }";
...
The code snippet shows that we have called PreRender event
of the base class i.e. TextBox and then declared and initialized two string
members alphaNumeric and numeric that define javascript methods for respective
functionality. I will explain one of them as an example and all upcoming
JavasSript code will be quite similar. Please note that we have not registered
them yet, that will be done later. We have defined alphaNumeric method that
takes the textbox object as parameter and tests it on a regular expression
(regExp) object by calling test method. If it is true then call the replace
method on the result, that replaces any undesired input char with empty string;
it uses again the same regExp object with a global search qualifier g to search
the input. You may have noticed that before closing the body of the methods, we
have cleared the title property of the textbox which shows an error message as the
tooltip when IsRequired is set to True. So on any input we should clear the
tooltip.
Likewise, define other javascript methods for
UppCase,LoweCase, MaxLength and IsRequired features and set the respective
string variables so that later could be registered on demand.
Listing 8
…
string upperCase = "function upperCase(textBox){ " +
"if(/[^0-9A-Z]/.test(textBox.value)){ "+
"textBox.value=textBox.value.toUpperCase().replace(/([^0-9A-Z])/g,''); "+
"textBox.title = ''; "+
"} " +
"}";
string lowerCase = "function lowerCase(textBox){ " +
"if(/[^0-9A-Z]/.test(textBox.value)){ "+
"textBox.value=textBox.value.toLowerCase().replace(/([^0-9A-Z])/g,''); "+
"textBox.title = ''; "+
"} "+
"}";
…
UpperCase and lowerCase methods have similar logic, the only
change is the input is upper/lower cased by calling string class’s method
toUpperCase() and toLowerCase(). Now it is time to register them on the basis
of the property set by the user. The user may form any combination of the
properties TextCaseMode (UpperCase,Lowercase,NotSet), InputMode
(Numeic,AlphaNUmeric,NotSet). For this purpose we have to define another method
which will call these methods depending upon the properties’ values. The
following code snippet shows this.
Listing 9
…
string cleintScript = "function extendedTextBox_PropertyChange(xTextBox){";
// method body starts
if (this.InputMode == InputMode.Numeric)
{
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "numeric",
numeric, true);
cleintScript += "numeric(xTextBox); ";
}
if (this.InputMode == InputMode.Alphanumeric)
{
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "alphaNumeric",
alphaNumeric, true);
cleintScript += "alphaNumeric(xTextBox); ";
}
if (this.TextCaseMode == TextCaseMode.UpperCase)
{
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "upperCase",
upperCase, true);
cleintScript += "upperCase(xTextBox); ";
}
if (this.TextCaseMode == TextCaseMode.LowerCase)
{
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "lowerCase",
lowerCase, true);
cleintScript += "lowerCase(xTextBox); ";
}
cleintScript += " }"; // end of method body
…
We have defined a string variable “clientScript” initialized
with the method name; the forthcoming code registers script for a particular
method and appends its script, i.e being added in the body of the main work
horse method extendedTextBox_PropertyChange. At the end, append the string that
closes the body of the method. Now we have to register
extendedTextBox_PropertyChange method. The following code snippet not only registers
it, but also adds it in the attributes collection of the control for
onpropertychange event. We can also use onkeypress event, but using this will
notice little elapse in uppercasing and lowercasing the input character.
Listing 10
…
Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
"cleintScript",
cleintScript, true);
this.Attributes.Add("onpropertychange", "extendedTextBox_ PropertyChange (this);");
…
Next, we have defined checkMaxLength method that makes
MaxLength property of the textbox working when its TextMode is set to
MultiLine. In TextBox control this problem arises when TextBox is MultiLine
because in this case it is rendered into “TextArea” html control (<textarea
id="ExtendedTextBox1" />) instead of “input control with type
text” (<input name="ExtendedTextBox1" type="text"
id="ExtendedTextBox1). It is important to know that TextArea control does
not support MaxLengh property. Therefore, when we set TextMode property to
MultiLine, the MaxLength property stops working. To resolve this problem we
have to write some client side logic and make part of the control as shown in
the following listing.
Listing 11
…
string checkMaxLength = "function checkMaxLength(textBox) {" +
" if(textBox) { " +
" return ( textBox.value.length <" + this.MaxLength + "); " +
" } " +
"} ";
if (this.TextMode == TextBoxMode.MultiLine)
{
Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
"checkLength",
checkMaxLength, true);
this.Attributes.Add("onkeypress", "return checkMaxLength(this);");
}
It checks and returns true if the length of input is less
than or equal to the value of MaxLength property and false if vice versa. Then
we have to register it as usual and add in the attributes collection of the
control for onkeypress event.
Now, there is another very important feature added in the
new textbox control. It mandates the input if specified using the IsRequired
property. The implementation of this is simple, we have to handle form’s client
side onSubmit event as shown in following code.
Listing 12
…
if (this.IsRequired == true)
{
this.Page.Form.Attributes.Add("onSubmit", "if(" + this.ClientID +
".value==''){"+
this.ClientID + ".title = '" + this.ErrorMessage + "';"+
"return false;} "+
"else{ " + this.ClientID + ".title = ''; "+
" return true;}");
}
} // end of PreRender
Instead of making a separate method, the handler for
onSubmit has been added directly in the attributes collection of the control.
It checks if IsRequired property is set to True then sets the title property
(used for tooltip) of control with the error message given by the user in
ErrorMessage property and return false, otherwise clears the title property and
return true i.e. allows postbacks.
When IsRequired property is set to True, it performs the required
field validation, stops all postabacks and shows the error message in tooltip
when the mouse is brought over the control. The Following figure shows this.
Figure 2