Click here to Skip to main content
Click here to Skip to main content

TextBox controls with limited input

, 7 Sep 2005
Rate this:
Please Sign up or sign in to vote.
An article on an implementation of an abstract TextBox control that can be used for implementing character limited textboxes, like numeric and integer textboxes using client side script.

Example

Three textboxes that each allow different characters

Introduction

There have been many articles on The Code Project that are about HTML TextBoxes which allow limited input. The idea and part of the code I use is based on an article from Tadas Budvytis. While this article was a nice start, I wanted the code to be simpler and reusable. The result is an abstract class that inherits from Sytem.Web.UI.WebControls.TextBox and implements some JavaScript that does most (but really not that much) of the work. The only thing you'll have to do is inherit from the base class and write about 4 to 10 lines of code and you'll have your own TextBox that accepts only positive integers, numeric values, or (if you like) a TextBox that only accepts the string "DeKale" Big Grin | :-D . I've included three implementations of such textboxes.

References

Security issues

One big note on security. The controls use JavaScript on the client side. The user can easily disable client side scripting, so you can never ever trust the input that is checked only at the client. You will have to check the values at the server and the best and the easiest way to do this is through validation controls.

However one can easily create a Composite Control that holds a TextBox and a CompareValidator control, but I chose not to combine these. The reason for this is that you may want to give the designer full control over the position of the controls. But beware that this may give a security risk, because your designer may forget to include the validation control with your TextBox.

The method I chose was to create a custom CompareValidator control for each TextBox implementation. This requires about 3 to 4 lines of code, per validator. Of course you may decide to combine these two controls again into a Composite Control. That's up to you.

Using the code

The classes included in the source code are CharacterFilteringTextBox, SignedIntegerTextBox, NumericTextBox and UnsignedIntegerTextBox. The class diagram looks like this:

The classdiagram

The CharacterFilteringTextBox control

The base class is CharacterFilteringTextBox. The code looks like this (I stripped some comments and the protected properties, they are in the source code):

/// <summary>
/// Base class for all TextBoxes that have use a filter on the client (javascript)
/// to enable a subset of the keys.
/// </summary>
public abstract class CharacterFilteringTextBox : TextBox
{
    private static string _altKey       = "if (evt.altKey) return;";
    private static string _ctrlKey      = "if (evt.ctrlKey) return;";
    private static string _specialKeys  = "if (charCode<32) return;";
    private static string _arrowKeys    = "if (charCode>=33 &&" + 
                                          " charCode<=40) return;";
    private static string _functionKeys = "if (charCode>=112 &&" + 
                                          " charCode<=123) return;";
    private static string _numericKeys  = "if(charCode>=48 &&" + 
                                          " charCode<=57) return;";
    private static string _spaceKey     = "if (charCode==32) return;";

    private static string _negativeSignKey
    {
        get
        {
            return "if (ch=='" + 
                NumberFormatInfo.CurrentInfo.NegativeSign + "') return;";
        }
    }

    private static string _numberDecimalSeparatorKey
    {
        get
        { 
            return "if (ch=='" + 
                NumberFormatInfo.CurrentInfo.NumberDecimalSeparator + 
                "') return;";
        }
    }

    
    /// <summary>
    /// Gets the JS function name for the OnKeyPress event of the TextBox.
    /// </summary>
    protected string OnKeyPressFunctionName
    {
        get { return "Filter" + this.GetType().Name; }
    }

    /// <summary>
    /// Method could be implemented by descendants and must call the
    /// CreateJavascriptFunction(string innerCode) function. Overriding this
    /// method is only needed, when special javascript is needed, that goes
    /// behond the standard protected virtual boolean properties.
    /// </summary>
    protected virtual void CreateJavascriptFunction()
    {
        this.RegisterClientScriptFunction(String.Empty);
    }

    /// <summary>
    /// Creates default code for the descendants and adds the special js code
    /// from the descendants (innerCode) inside.
    /// </summary>
    protected void RegisterClientScriptFunction(string innerCode)
    {
        // Check if the function has already been registered, 
        // before we're doing more than strictly needed.
        if (this.Page.IsClientScriptBlockRegistered(OnKeyPressFunctionName) 
                                                                  == false)
        {
            StringBuilder jsCode = new StringBuilder();

            jsCode.Append("<script language="\""javascript\"><!--\n");
            jsCode.Append("function " + OnKeyPressFunctionName + "(evt)");
            jsCode.Append(
                "{"                                                        +
                "    evt = (evt) ? evt : ((window.event) ? event : null);" +
                "    if (evt)"                                             +
                "    {"                                                    +
                "        var charCode = (evt.charCode) ? evt.charCode :"   +
                "        ((evt.keyCode) ? evt.keyCode : "                  +
                "        ((evt.which) ? evt.which : 0));"                  +
                "        var ch = String.fromCharCode(charCode);"
            );

            // Add the code from the descendant whitch implements valid
            // key's and characters.
            jsCode.Append(innerCode);
            
            // Add the code for the corresponding property that returns true.
            if (this.AllowAltKey)              jsCode.Append(_altKey);
            if (this.AllowCtrlKey)             jsCode.Append(_ctrlKey);
            if (this.AllowArrowKeys)           jsCode.Append(_arrowKeys);
            if (this.AllowDecimalSeparatorKey)
                jsCode.Append(_numberDecimalSeparatorKey);
            if (this.AllowFunctionKeys)        jsCode.Append(_functionKeys);
            if (this.AllowNegativeSignKey)     jsCode.Append(_negativeSignKey);
            if (this.AllowNumericKeys)         jsCode.Append(_numericKeys);
            if (this.AllowSpaceKey)            jsCode.Append(_spaceKey);
            if (this.AllowSpecialKeys)         jsCode.Append(_specialKeys);

            jsCode.Append(
                "        if (window.event) evt.returnValue = false;"       +
                "        else evt.preventDefault();"                       +
                "    }"                                                    +
                "}"
            );
            jsCode.Append("\n--></script>");

            // Add the javascript to the page
            this.Page.RegisterClientScriptBlock(
                OnKeyPressFunctionName, // the name of the function
                jsCode.ToString() // convert to string
                );
        }
    }

    /// <summary>
    /// Overridden. Registrers JavaScript function
    /// to Page and Adds the OnKeyPress event.
    /// </summary>
    protected override void OnPreRender(EventArgs e)
    {
        this.CreateJavascriptFunction();

        // Adding the TextAlign property to the control's stylesheet.
        if (this.TextAlign != null && this.TextAlign != String.Empty)
            this.Style.Add("text-align", this.TextAlign);

        this.Attributes.Add("onkeypress", OnKeyPressFunctionName + "(event)");

        base.OnPreRender(e);
    }
}

As you can see in the class diagram, all protected boolean properties are missing from the code. I've cut them out here, but you can download the source code to see them. Every property simply returns either true or false, so there is nothing spectacular about this. I'll explain later on more about these properties. I'll go shortly through the methods and one of the properties of this base class. I'll start with the OnKeyPressFunctionName property.

protected string OnKeyPressFunctionName
{
    get { return "Filter" + this.GetType().Name; }
}

The OnKeyPressFunctionName is a read-only property that returns the name of the JavaScript function. The trick here is to get for each implementation a unique name. Every class that inherits has its unique class name (when you're keeping all implementations within the same namespace) and this method returns the class name concatenated with the word "Filter". Not the most effective way, because the function name could get a bit long and the Type.Name property has some performance issues (see Performance pitfalls in the References), but the code is rather short and readable.

Next to discuss is the overridden OnPreRender method:

protected override void OnPreRender(EventArgs e)
{
    this.CreateJavascriptFunction();

    if (this.TextAlign != null && this.TextAlign != String.Empty)
        this.Style.Add("text-align", this.TextAlign);

    this.Attributes.Add("onkeypress", OnKeyPressFunctionName + "(event)");

    base.OnPreRender(e);
}

The OnPreRender method doesn't really do much. It calls the CreateJavascriptFunction() method, which could be implemented by a descendant (later on more about this function). After that it inserts the TextAlign property to the control's cascading stylesheet (CSS). Next, the OnKeyPress event will be added to the TextBox and last the base OnPreRender method will be called. The control will generate HTML code that looks something like this:

<input type="text" onkeypress="FilterNumericTextBox(event)"/>

Next method is the CreateJavascriptFunction():

protected virtual void CreateJavascriptFunction()
{
    this.RegisterClientScriptFunction(String.Empty);
}

This method just calls the RegisterClientScriptFunction() method. Descendants can override this function and create some specific JavaScript code that can be added to the control's JavaScript function. Overriding this method will only be necessarily when the basic functionality of the abstract CharacterFilteringTextBox isn't enough. Normally you would only have to override the protected boolean properties that you want to change.

The last and the longest method is RegisterClientScriptFunction():

protected void RegisterClientScriptFunction(string innerCode)
{
    // Check if the function has already been registered, 
    if (this.Page.IsClientScriptBlockRegistered(OnKeyPressFunctionName) == false)
    {
        StringBuilder jsCode = new StringBuilder();

        jsCode.Append("<script language="\""javascript\"><!--\n");
        jsCode.Append("function " + OnKeyPressFunctionName + "(evt)");
        jsCode.Append(
            "{"                                                       +
            "    evt = (evt) ? evt : ((window.event) ? event : null);" +
            "    if (evt)"                                             +
            "    {"                                                    +
            "        var charCode = (evt.charCode) ? evt.charCode :"   +
            "        ((evt.keyCode) ? evt.keyCode : "                  +
            "        ((evt.which) ? evt.which : 0));"                  +
            "        var ch = String.fromCharCode(charCode);"
        );

        // Add the code from the descendant whitch implements valid
        // key's and characters.
        jsCode.Append(innerCode);

        if (this.AllowAltKey)              jsCode.Append(_altKey);
        if (this.AllowCtrlKey)             jsCode.Append(_ctrlKey);
        if (this.AllowArrowKeys)           jsCode.Append(_arrowKeys);
        if (this.AllowDecimalSeparatorKey)
            jsCode.Append(_numberDecimalSeparatorKey);
        if (this.AllowFunctionKeys)        jsCode.Append(_functionKeys);
        if (this.AllowNegativeSignKey)     jsCode.Append(_negativeSignKey);
        if (this.AllowNumericKeys)         jsCode.Append(_numericKeys);
        if (this.AllowSpaceKey)            jsCode.Append(_spaceKey);
        if (this.AllowSpecialKeys)         jsCode.Append(_specialKeys);

        jsCode.Append(
            "        if (window.event) evt.returnValue = false;"       +
            "        else evt.preventDefault();"                       +
            "    }"                                                    +
            "}"
        );
        jsCode.Append("\n--></script>");

        this.Page.RegisterClientScriptBlock(
            OnKeyPressFunctionName, // the name of the function
            jsCode.ToString() // convert to string
            );
    }
}

The method will at first check if the JavaScript function has already been registered to the page and if not, the JavaScript function is generated and the JavaScript is registered to the page. The final code in your browser would look something like this:

function FilterCLASSNAME(evt)
{
    evt = (evt) ? evt : ((window.event) ? event : null);
    if (evt)
    {
        var charCode = (evt.charCode) ? evt.charCode : (
            (evt.keyCode) ? evt.keyCode : 
                ((evt.which) ? evt.which : 0));
        var ch = String.fromCharCode(charCode);
        
        // here the input will be checked and if allowed, returned.
        
        if (window.event) evt.returnValue = false;
        else evt.preventDefault();
    }
}

This code can also be found in Tadas Budvytis' article, but I made some minor improvements to make it work on other browsers than MS IE. You can compare the code to see the differences.

When looking carefully at the JavaScript code above, you will see that this code doesn't really do much. I mean, when only using this code in your browser, you will see that you won't be able to insert any character in the TextBox. Even worse, also the special characters like Tab and Return are caught. That's where the protected virtual boolean properties come in.

The CharacterFilteringTextBox comes with some standard settings. The CharacterFilteringTextBox standard allows Alt+Key combinations, Ctrl+Key combinations, arrow keys and special keys (like Tab, Backspace, Return). All a descendant now has to do is override the properties it wants to change. So if a descendant wants to allow numeric characters, it has to override AllowNumericKeys. The AllowNumericKeys should simply return true.

Descendants

Like I said, descendants don't have to do much. For the UnsignedIntegerTextBox (allows only characters 0 to 9), it's about 2 lines. The code for the UnsignedIntegerTextBox looks like this:

public class UnsignedIntegerTextBox : CharacterFilteringTextBox
{
    /// <summary>Overridden.</summary>
    protected override bool AllowNumericKeys { get { return true; } }

    /// <summary>Overridden.</summary>
    protected override string TextAlign { get { return "right"; } }
}

So this is the only thing you will have to write. Well no.. there is one more thing.... and you mustn't forget. You will have to write a correct Validation control for your TextBox. For the UnsignedIntegerTextBox, it looks like this:

public class UnsignedIntegerTextBoxValidator : CompareValidator
{
    public UnsignedIntegerTextBoxValidator() : base()
    {
        this.Operator = ValidationCompareOperator.GreaterThanEqual;
        this.Type = ValidationDataType.Integer;
        this.ValueToCompare = "0";
    }
}

So when you work in the HTML tab of the designer, you can implement the Validator like this:

<kale:UnsignedIntegerTextBoxValidator 
    Runat="server" ControlToCompare="UnsignedIntegerTextBox1" />

But of course you can always drag and drop the Validator in the designer, but which programmer ever wants to do that?? Wink | ;-)

Drawbacks

Though this implementation fits nicely in to the control model of ASP.NET, there are still some drawbacks to this implementation:

  1. Security. A designer mustn't forget to include a validation control.
  2. JavaScript code is generated on every call. There is some performance loss (see References) on this and more bytes than necessary are sent to the user on every request.

Drawback solutions

Here I give possible solutions to the drawbacks:

  • Drawback 1. You could create a composite control that holds both the TextBox control and the desired Validation control.
  • Drawback 2. You could create a .js file that holds all the different filter JavaScript functions and register that .js file in the OnPreRender method. This way you have a control that contains just three lines of code and you can directly inherit from TextBox.

Bugs

There is one little bug in this implementation. When allowing the function keys (F1 ... F12) through overriding the AllowFunctionKeys property, this also allows the alpha numeric characters 'q' to 'z', because they have the same CharCode as the function keys. I haven't had much time to find out how to correct this. Maybe someone has got a solution for this one.

Conclusion

I showed TextBox controls that write specific client code to the browser that allow a selected range of characters. This implementation has some drawbacks and I described some solutions for these drawbacks. How you should implement it, is totally dependant on the project and other design decisions.

Happy coding.

History

  • 22 August, 2005 - Version 1.1. Improved implementation makes it easier to create descendants. Added overridable TextAlign property.
  • 30 July, 2005 - Version 1.0. Initial version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

DeKale
Web Developer
Netherlands Netherlands
No Biography provided

Comments and Discussions

 
GeneralMy vote of 3 PinmemberAli Al Omairi28-Dec-10 10:36 
GeneralSuggestion PinmemberAli Al Omairi28-Dec-10 10:33 
General[Message Removed] PinmemberMojtaba Vali12-Dec-05 23:25 
GeneralAgree good work! PinmemberTadas Budvytis1-Dec-05 12:01 
GeneralRe: Agree good work! PinmemberDeKale5-Dec-05 11:04 
GeneralOne Question Pinmemberanh tu la6-Nov-05 16:12 
GeneralRe: One Question PinmemberDeKale7-Nov-05 0:58 
NewsCode update PinmemberDeKale8-Sep-05 6:59 
QuestionPaste PinsussAnonymous7-Sep-05 20:31 
AnswerRe: Paste PinmemberDeKale8-Sep-05 6:55 
AnswerRe: Paste PinmemberThiago Rafael14-Sep-05 10:07 
AnswerRe: Paste PinmemberDeKale14-Sep-05 10:38 
GeneralProblems with input PinmemberDeKale11-Aug-05 2:42 
GeneralGood Work !! PinmemberTittle Joseph10-Aug-05 20:19 
GeneralRe: Good Work !! PinmemberDeKale11-Aug-05 3:26 
GeneralRe: Good Work !! PinmemberTittle Joseph11-Aug-05 4:48 
GeneralRe: Good Work !! PinmemberDeKale11-Aug-05 7:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411022.1 | Last Updated 7 Sep 2005
Article Copyright 2005 by DeKale
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid