Displaying a Character Counter for Multiline Textboxes

Introduction

Many Textbox fields restrict the number of characters entered in them for the sake of business validation. With single line textboxes it is just a matter of setting the MaxLength property. However, MaxLength property doesn't work as expected for multiline textboxes. When you set the TextMode property of ASP.NET TextBox control to MultiLine the Textbox control is rendered as a TEXTAREA element which doesn't support this property. Developers often use custom client side JavaScript code to enforce that only a certain number of characters are entered into a multiline textbox. But this must be done for every multiline textbox control for which you want the restriction enforced. Wouldn't it be easy if you can enforce this restriction without touching the Textbox control markup or its server side code? ASP.NET AJAX client behaviors allow you to do just that and in the remainder of this article you will learn how.

Understanding the problem

Before you proceed further let's understand the problem we are trying to solve. When you use Textbox web control with its TextMode property set to Singleline, ASP.NET emits an INPUT element in the resultant HTML markup. For example, if your Textbox web control markup is like this :

<asp:TextBox ID="TextBox1" runat="server" MaxLength="20" />

The resultant HTML markup will be :

<input name="TextBox1" type="text" maxlength="20" id="TextBox1" />

Notice how the MaxLength property of the wTextbox web control is getting converted as maxlength attribute of the INPUT tag. However, if you set TextMode property of the Textbox to MultiLine then the same textbox gets emitted as TEXTAREA element as shown below :

<textarea name="TextBox1" rows="2" cols="20" id="TextBox2"></textarea>

Notice that there is no maxlength attributed in the above markup (since TEXTAREA doesn't support it). Instead the MaxLength property is rendered as cols attribute. In short you cannot restrict the amount of text entered into a multiline textbox unless you write some extra pieces of code.

Non-AJAX Solutions

At first glance you may think of using the two common solutions :

  1. Handle client side events of TEXTAREA element (such as keypress, keydown and keyup) using client side JavaScript function.
  2. Validate the textbox web control on the server

The first approach is, no doubt, a commonly used solution. However, you are required to add the relevant event handling attributes in the Attributes collection of the web control. The down side is, there is a sort of tight coupling between the client side JavaScript function and the TEXTAREA. Consider the code below that adds a client side event handler for keypress event.

TextBox1.Attributes.Add("onkeyup", "return MyFunction();");

Imagine a case that you have used the above technique at dozens of places across several web forms. For some reason you now want to change the function name or want to do something else in addition to checking the length of the text entered. In such cases it can be tedious to change the server side code in all those places.

The second approach, i.e., validating on the server side is not a good choice since it will involve an extra server round trip. You can use this approach as an additional safeguard against clients bypassing the client side validation (browsers with JavaScript disabled, programmatic form submissions etc.) but relying entirely on server side validation would be a poor solution.

What is an AJAX Client Behavior?

AJAX client behaviors can be very handy in such situations because they extend existing HTML elements by adding some extra functionality. The behavior can be developed independent of the web control and once developed can be simply attached (or removed) to the control.

At code level an AJAX client behavior is a class that inherits from Sys.UI.Behavior class. Normally, behaviors don't create anything new. Rather they add extra features to the existing HTML elements. Once developed an AJAX client behavior can be attached with an element with the help of an extender control. An extender control is a server side class that inherits from ExtenderControl base class.

Example Scenario

Consider a case where you have a TextBox web control whose TextMode property is set to MultiLine. This textbox accepts profile information from the user and you want to enforce following features:

  • The textbox must accept only certain number of characters say 500.
  • As the user is entering the text a counter should be displayed indicating how much text has been entered or how much can still be entered. (Twitter does something similar when you start entering text)
  • If user exceeds the maximum length (500 in this case) the textbox should disallow further entry or it should display a warning message.
  • All this should be achieved without touching the Textbox web control markup or its server side code.

Software Needed

In order to work through the example that follows you need to have ASP.NET 3.5 or above installed on your machine. Though the example is developed using Microsoft Visual Studio 2008 you can also use Microsoft Visual Studio 2010 or Microsoft Visual Web Developer Express Edition.



Displaying a Character Counter for Multiline Textboxes

Creating a New Web Site

To begin with, create a new ASP.NET Web Site using Microsoft Visual Studio. Figure 1 shows the "New Web Site" dialog. Give the Web site folder name as MultiLineTextBoxExtenderDemo.

[Figure01.jpg]
Figure 1

Next, add a new JScript to the website by right clicking the Website in Solution Explorer and then choosing "Add New Item" menu option (Figure 2).

[Figure02.jpg]
Figure 2

Give file name as JScript.js. You will add the client side code of the AJAX client behavior in this file.

Creating CounterType Enumeration

Type.registerNamespace('MyNamespace');

Begin by declaring a namespace - MyNamespace. MyNamespace will contain an enumeration named CounterType and a class named MultilineTextBoxBehavior.

You will have two settings for the character counter. You can either display total number of characters entered into the textbox or the number of characters that can still be entered into the textbox. To indicate your preference for a particular textbox you will use CounterType enumeration. The CounterType enumeration is shown below:

MyNamespace.CounterType = function() { };

MyNamespace.CounterType.prototype =
{
    TotalCharacters: 0,
    RemainingCharacters: 1
}
MyNamespace.CounterType.registerEnum("MyNamespace.CounterType");

The code above defines the prototype of the enumeration. The two enumeration values viz. TotalCharacters and RemainingCharacters indicate the respective behavior for the character counter. The registerEnum() method registers the enumeration with AJAX framework.

Creating MultilineTextBoxBehavior Class

Next, you will create MultilineTextBoxBehavior class. The constructor of the class looks like this :

MyNamespace.MultilineTextBoxBehavior = function(element) {
    MyNamespace.MultilineTextBoxBehavior.initializeBase(this, [element]);
    this._maxLength = 10;
    this._warningMessage = "Text exceeded the maximum limit!";
    this._counterElementId = "";
    this._warningElementId = "";
    this._allowOverflow = false;
    this._counterType = MyNamespace.CounterType.TotalCharacters;
    this._normalCSS = "";
    this._warningCSS = "";
}

The constructor takes a parameter (element) that represents the HTML element whose behavior is to be extended. This element is passed to the base class constructor using initializeBase() method. Recollect from our earlier discussion that client behaviors inherit from Sys.UI.Behavior base class. The initializeBase() method takes two parameters viz. instance of the child class and an array of HTML elements. The constructor further initializes some private variables. Later you will create public properties to wrap these variables. Most of the variable declarations are straightforward except _counterType. You set it to an enumeration value from CounterType enumeration created earlier.

Next, code initialize() method as it is shown below :

 MyNamespace.MultilineTextBoxBehavior.prototype =
{
    initialize: function() {
        MyNamespace.MultilineTextBoxBehavior.callBaseMethod(this, 'initialize');
        var codeCheckCounter = Function.createDelegate(this, this._CheckCounter);
        $addHandler(this.get_element(), 'keyup', codeCheckCounter);
        this._CheckCounter();
        this._SetNormalCSS();
    }
...

The initialize method initializes the MultilineTextBoxBehavior instance under consideration. Inside, you first call the initialize() method of the base class using callBaseMethod(). A delegates is then created for private method _CheckCounter(). Doing so preserves the context of the code. The _CheckCounter() method ensures that the text entered into the textbox does not exceed the maximum length. It also takes care of updating the character counter to reflect the latest value. The delegate thus created is used as event handler for keyup event of the textbox. The task of attaching the event handler to the keyup event is done by $addHandler() method. The first parameter of $addHandler() method is the element whose event is to be handled. The get_element() method returns the underlying HTML element (recollect that we passed it to the base class in the constructor). The second parameter is the event name and the third parameter is the delegate pointing to the event handler. The _CheckCounter() method is then called explicitly to ensure that initially, i.e. when the page loads and no data is entered, the counter is displayed. The _SetNormalCSS() method sets the CSS class for the counter and warning labels (discussed later).

Note: Handling keyup event takes care of the length validation when user manipulates text using keyboard or cut-pastes text using Ctrl + X and Ctrl + V key combinations. However, cutting or pasting text using browser shortcut menu is not taken care of. IE exposes onpaste and oncut events that can be used for this purpose but not all browsers support these events. One way around is to call _CheckCounter function in the onblur event handler also. To make the solution foolproof you may also consider adding a server side check.

    
    get_MaxLength: function() {
        return this._maxLength;
    },
    set_MaxLength: function(value) {
        this._maxLength = value;
    },

    get_WarningMessage: function() {
        return this._warningMessage;
    },
    set_WarningMessage: function(value) {
        this._warningMessage = value;
    },

    get_CounterElementId: function() {
        return this._counterElementId;
    },
    set_CounterElementId: function(value) {
        this._counterElementId = value;
    },

    get_WarningElementId: function() {
        return this._warningElementId;
    },
    set_WarningElementId: function(value) {
        this._warningElementId = value;
    },

    get_CounterType: function() {
        return this._counterType;
    },
    set_CounterType: function(value) {
        this._counterType = value;
    },

    get_AllowOverflow: function() {
        return this._allowOverflow;
    },
    set_AllowOverflow: function(value) {
        this._allowOverflow = value;
    },

    get_NormalCSS: function() {
        return this._normalCSS;
    },
    set_NormalCSS: function(value) {
        this._normalCSS = value;
    },

    get_WarningCSS: function() {
        return this._warningCSS;
    },
    set_WarningCSS: function(value) {
        this._warningCSS = value;
    }



Displaying a Character Counter for Multiline Textboxes

What follows the initialize() method is a series of public properties. The properties and their purpose is listed below:

Property Description
MaxLength Indicates the maximum number of characters that can be entered into the textbox.
WarningMessage Indicates a string message that will be displayed to the end user if number of characters exceed MaxLength and AllowOverflow property is set to true.
CounterElementId Represents the client side ID of the HTML element that will be used for displaying character counter.
WarningElementId Represents the client side ID of the HTML element that will be used for displaying warning message.
CounterType Indicates the type of counter from CounterType enumeration.
AllowOverflow This boolean property indicates whether the user is allowed to enter characters even after MaxLength exceeds.
NormalCSS Indicates the name of a CSS class that is applied to the counter element and warning element when character count is less than MaxLength value.
WarningCSS Indicates the name of a CSS class that is applied to the counter element and warning element when character count exceeds MaxLength value.

The _CheckCounter() method is important one as it performs the actual job of validating user input.

    
   _CheckCounter: function() {
        var element = this.get_element();

        if (element.value.length > this._maxLength) {
            if (!this._allowOverflow) {
                element.value = element.value.substring(0, this._maxLength);
            }
            else {
                $get(this._warningElementId).innerHTML = this._warningMessage;
                this._SetWarningCSS();
            }
        }
        else {
            $get(this._warningElementId).innerHTML = "";
            this._SetNormalCSS();
        }

        if (this._counterType == MyNamespace.CounterType.TotalCharacters) {
            $get(this._counterElementId).innerHTML = element.value.length;
        }

        if (this._counterType == MyNamespace.CounterType.RemainingCharacters) {
            $get(this._counterElementId).innerHTML = this._maxLength - element.value.length;
        }

    }

The _CheckCounter() method is acting as an event handler for keyup event of the textarea. Its job is to set the character counter and warning message. The character counter and warning message elements will be typically SPAN or DIV elements.

You check the length of the text entered into the textarea and if required truncate the text using JavaScript substring() method. If the number of characters exceed the MaxLength value you need to display warning message. This is done using $get() method and innerHTML property. If the character count has exceeded the MaxLength value you will display the counter and warning message in some different way (say in red color) so as to capture user's attention. This is done with the help of _SetWarningCSS() method.

If you have set counter type as TotalCharacters you need to display the total number of characters entered in the textarea. The innerHTML property is used to display the total number of characters. If the counter type is RemainingCharacters then the counter displays number of characters that can still be entered.

    _SetNormalCSS: function() {
        var counter_element = $get(this._counterElementId);
        var warning_element = $get(this._warningElementId);
        Sys.UI.DomElement.removeCssClass(counter_element, this._warningCSS);
        Sys.UI.DomElement.removeCssClass(warning_element, this._warningCSS);
        Sys.UI.DomElement.addCssClass(counter_element, this._normalCSS);
        Sys.UI.DomElement.addCssClass(warning_element, this._normalCSS);
    },
    _SetWarningCSS: function() {
        var counter_element = $get(this._counterElementId);
        var warning_element = $get(this._warningElementId);
        Sys.UI.DomElement.removeCssClass(counter_element, this._normalCSS);
        Sys.UI.DomElement.removeCssClass(warning_element, this._normalCSS);
        Sys.UI.DomElement.addCssClass(counter_element, this._warningCSS);
        Sys.UI.DomElement.addCssClass(warning_element, this._warningCSS);
    },

The _SetNormalCSS() and _SetWarningCSS() methods set the CSS class of counter and warning elements. The _SetNormalCSS() method first gets hold of counter element and warning element with the help of $get() method and _counterElementId and _warningElementId private variables. The removeCssClass() method of the DomElement class removes a CSS class applied to an element. The method takes two parameters viz. element whose CSS class is to be removed and name of the CSS class that is to be removed. Similarly, addCssClass() method applies a CSS class to an element. Using these two methods you set the CSS class of the counter and warning elements to normal CSS class i.e. CSS class that is applied when text length is within permissible limit.

The _SetWarningCSS() method does exactly the opposite of _SetNormalCSS() method (i.e. it removes normal CSS class and applies warning CSS class).

Now that you have completed the MultilineTextBoxBehavior class, register it with AJAX framework using registerClass() method.

      MyNamespace.MultilineTextBoxBehavior.registerClass
('MyNamespace.MultilineTextBoxBehavior', Sys.UI.Behavior);

The registerClass() method takes two parameters. The first parameter is the fully qualified name of the class being registered and the second parameter is the base class. Since you are creating a behavior you need to pass the second parameter as Sys.UI.Behavior.



Displaying a Character Counter for Multiline Textboxes

Creating a Server Side Extender Control

Now that your client side part of the behavior is ready let's develop the server side part. The server side part, an extender control consists of a class that inherits from the ExtenderControl base class. The skeleton code of the extender class is shown below :

 namespace MyNamespace
{
    [TargetControlType(typeof(TextBox))]
    public class MultilineTextBoxExtender : ExtenderControl
    {
       ...
    }
}

As you can see from the above code, in addition to inheriting from ExtenderControl class the MultilineTextBoxExtender class is decorated with TargetControlType attribute. The TargetControlType attribute specifies the target control type that this extender will be extending. Since you are developing this extender control class for ASP.NET Textbox web control you specified its type using typeof operator.

The extender control class thus created needs to do a few things before it can be used. They include :

  1. Create properties in the extender class matching the properties of the client side AJAX class i.e. MultilineTextBoxBehavior
  2. Override GetScriptReferences() method
  3. Override GetScriptDescriptors() method

To begin, create all the required properties namely MaxLength, WarningMessage, CounterElementId, WarningElementId, CounterType, AllowOverflow, NormalCSS and WarningCSS. The following code shows these properties :

        
        private int intMaxLength;
        private string strWarningMessage;
        private string strCounterElementId;
        private string strWarningElementId;
        private CounterType enumCounterType;
        private bool blnAllowOverflow;
        private string strNormalCSS;
        private string strWarningCSS;

        public int MaxLength
        {
            get
            {
                return intMaxLength;
            }
            set
            {
                intMaxLength = value;
            }
        }

        public string WarningMessage
        {
            get
            {
                return strWarningMessage;
            }
            set
            {
                strWarningMessage = value;
            }
        }

        public string CounterElementId
        {
            get
            {
                return strCounterElementId;
            }
            set
            {
                strCounterElementId = value;
            }
        }

        public string WarningElementId
        {
            get
            {
                return strWarningElementId;
            }
            set
            {
                strWarningElementId = value;
            }
        }

        public CounterType CounterType
        {
            get
            {
                return enumCounterType;
            }
            set
            {
                enumCounterType = value;
            }
        }

        public bool AllowOverflow
        {
            get
            {
                return blnAllowOverflow;
            }
            set
            {
                blnAllowOverflow = value;
            }
        }

        public string NormalCSS
        {
            get
            {
                return strNormalCSS;
            }
            set
            {
                strNormalCSS = value;
            }
        }

        public string WarningCSS
        {
            get
            {
                return strWarningCSS;
            }
            set
            {
                strWarningCSS = value;
            }
        }

Since these properties are straightforward we won't discuss them in detail. The CounterType property also needs an enumeration to be defined as shown below :

    
	public enum CounterType
    {
        TotalCharacters,
        RemainingCharacters
    }

Once your properties are ready proceed with GetScriptReferences() method.

        
	protected override IEnumerable<ScriptReference> GetScriptReferences()
        {
            ScriptReference[] references = new ScriptReference[1];
            references[0] = new ScriptReference();
            references[0].Path = ResolveClientUrl("JScript.js");
            return references;
        }

The overridden GetScriptReferences() method returns an array of ScriptReference objects. The ScriptReference class represents a script reference of the ScriptManager control. Since you have only one script reference i.e. JScript.js you create just one object of ScriptReference class and set its Path property to JScript.js file. The ResolveClientUrl() method creates a URL suitable to be used on the client side.

        
	protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
        {
            ScriptDescriptor[] descriptors = new ScriptDescriptor[1];
            ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("MyNamespace.MultilineTextBoxBehavior", targetControl.ClientID);
            descriptor.AddProperty("MaxLength", this.MaxLength);
            descriptor.AddProperty("WarningMessage", this.WarningMessage);
            descriptor.AddProperty("CounterElementId", this.CounterElementId);
            descriptor.AddProperty("WarningElementId", this.WarningElementId);
            descriptor.AddProperty("CounterType", this.CounterType);
            descriptor.AddProperty("AllowOverflow", this.AllowOverflow);
            descriptor.AddProperty("NormalCSS", this.NormalCSS);
            descriptor.AddProperty("WarningCSS", this.WarningCSS);
            descriptors[0] = descriptor;
            return descriptors;
        }

The overridden GetScriptDescriptors() method receives a reference to the target control i.e. the control instance to which this behavior is applied. Inside, you create an instance of ScriptBehaviorDescriptor class. The ScriptBehaviorDescriptor class stores the mapping between client side properties and server side properties. The constructor of the ScriptBehaviorDescriptor class accepts the fully qualified name of the client side class and the client side ID of the target control. You then need to call the AddProperty() method for each property to be mapped. The GetScriptDescriptors() method returns an array of ScriptDescriptor objects. In the inheritance chain, ScriptBehaviorDescriptor is inherited from the ScriptDescriptor class. The ScriptBehaviorDescriptor instance is then added to the ScriptDescriptor array. Finally, the ScriptDescriptor array is returned.

This completes your extender control class. You may compile your website just to ensure that everything is error free before going ahead.

Using the AJAX Client behavior

Now its time to use your MultilineTextBoxBehavior on a web form. Open the default web form and drag and drop a ScriptManager control on it. Design the web form as shown in Figure 3.

[Figure03.jpg]
Figure 3

The web form consists of a Textbox web control and two Label web controls. Set the TextMode property of the Textbox control to Multiline and Rows property to 5. The lblCounter label will display the character counter and the lblWarning will display warning message in case the text in the Textbox exceeds the maximum limit.

Switch to the HTML source view of the web form and at the top add the @Register directive as shown below :

<%@ Register Namespace="MyNamespace" TagPrefix="mte" %>

Using the @Register directive you register the extender control (MultilineTextBoxExtender) with the page framework. Once registered it can be used in the web form markup as follows :

<mte:MultilineTextBoxExtender 
ID="mte1" 
runat="server" 
TargetControlID="TextBox1" 
MaxLength="10" 
WarningMessage="Text exceeded maximum limit!" 
CounterElementId="lblCounter" 
WarningElementId="lblWarning" 
CounterType="RemainingCharacters" 
AllowOverflow="true" 
NormalCSS="normal" 
WarningCSS="warning" /> 

As you can see, the code above sets the TargetControlID property to TextBox1. This is how the behavior knows about the web control that is to be extended. Then all the properties we created in the MultilineTextBoxExtender are set. The NormalCSS and WarningCSS properties specify to CSS classes normal and warning. These CSS classes can either be created in a separate style sheet or can be placed in a style section. These classes are shown below :

.normal
{
    font-size:20px;
    font-weight:bold;
    color:Black;
}

.warning
{
    font-size:20px;
    font-weight:bold;
    color:Red;
}

If you run the web form you should see something as shown in Figure 4.

[Figure04.jpg]
Figure 4

Figure 5 shows how the behavior displays the warning message when text exceeds the MaxLength value.

[Figure05.jpg]
Figure 5

You can also check by changing CounterType to TotalCharacters and by setting AllowOverflow to false.

Summary

An ASP.NET AJAX client behavior allows you to modify behavior of a web control. At the code level they are classes that inherit from Sys.UI.Behavior base class. A server side extender control is a class that inherits from ExtenderControl base class. An extender control allows you to attach a client behavior to a server control and configure the behavior properties. In our example we created MultilineTextBoxBehavior behavior that tracks the number of characters entered in a multiline textbox and also enforces the maximum length on the entered text. The client behavior thus created was attached with a Textbox control using the MultilineTextBoxExtender extender control. The MultilineTextBoxBehavior can be used conveniently in many situations where you need to display a visual indicator of the number of characters entered in a multiline textbox and want to enforce a limit on the amount of text entered.

Related Articles





About the Author

Bipin Joshi

Bipin Joshi is a blogger and writes about apparently unrelated topics - Yoga & technology! A former Software Consultant by profession, Bipin has been programming since 1995 and has been working with the .NET framework ever since its inception. He has authored or co-authored half a dozen books and numerous articles on .NET technologies. He has also penned a few books on Yoga. He was a well known technology author, trainer and an active member of Microsoft developer community before he decided to take a backseat from the mainstream IT circle and dedicate himself completely to spiritual path. Having embraced Yoga way of life he now codes for fun and writes on his blogs. He can also be reached there.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • CentreCorp is a fully integrated and diversified property management and real estate service company, specializing in the "shopping center" segment, and is one of the premier retail service providers in North America. Company executives travel a great deal, carrying a number of traveling laptops with critical current business data, and no easy way to back up to the network outside the office. Read this case study to learn how CentreCorp implemented a suite of business continuity services that included …

Most Popular Programming Stories

More for Developers

RSS Feeds