ASP.NET MVC Textbox with characters remaining HtmlHelper extension method

Here is a couple of HtmlHelper extension methods, CharactersRemainingTextBoxFor and CharactersRemainingTextAreaFor which render a textbox/textarea with a span tag that displays the number of characters remaining.  They also include the JavaScript to make this work, and to limit the number of characters entered in the textarea. The maximum limit comes from the model by using the StringLengthAttribute.

Download source

There are multiple overloads with different htmlAttributes options but here I’ll just show one for each method plus the private method both use.

private enum TextType
{
 TextBox,
 TextArea
}

public static MvcHtmlString CharactersRemainingTextBoxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
 return CharactersRemainingTextFor(html, expression, htmlAttributes, TextType.TextBox);
}

public static MvcHtmlString CharactersRemainingTextAreaFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
 return CharactersRemainingTextFor(html, expression, htmlAttributes, TextType.TextArea);
}

private static MvcHtmlString CharactersRemainingTextFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes, TextType textType)
{
 MemberExpression memberExpression = expression.Body as MemberExpression;

 //Attempt to get the StringLengthAttribute from the Model
 StringLengthAttribute stringLengthAttribute = memberExpression.Member.GetCustomAttributes(typeof(StringLengthAttribute), false).FirstOrDefault() as StringLengthAttribute;

 TagBuilder textboxTag;
 if (textType == TextType.TextBox)
 {
 textboxTag = new TagBuilder("input");
 }
 else
 {
 textboxTag = new TagBuilder("textarea");
 }

 textboxTag.MergeAttributes(htmlAttributes);

 if (textType == TextType.TextBox)
 {
 textboxTag.MergeAttribute("type", "text");
 }

 //If the Id attribute is not set the set it to a new Guid
 if (!textboxTag.Attributes.ContainsKey("id"))
 {
 textboxTag.Attributes.Add("id", Guid.NewGuid().ToString());
 }

 //Set the name attribute to allow the binding on the POST
 textboxTag.MergeAttribute("name", memberExpression.Member.Name);

 //Get the text to display in the textbox
 string text = Convert.ToString(ModelMetadata.FromLambdaExpression(expression, html.ViewData).Model);

 if (textType == TextType.TextBox)
 {
 textboxTag.MergeAttribute("value", text);
 }
 else
 {
 textboxTag.SetInnerText(text);
 }

 if (stringLengthAttribute != null && stringLengthAttribute.MaximumLength > 0)
 {
 //Text area ignores maxlength but required to work out remaining characters
 textboxTag.Attributes.Add("maxlength", Convert.ToString(stringLengthAttribute.MaximumLength));
 textboxTag.Attributes.Add("onkeyup", string.Format("if (this.value.length > this.getAttribute('maxlength')) this.value = this.value.substring(0, this.getAttribute('maxlength')); document.getElementById('{0}').innerHTML = (this.getAttribute('maxlength') - (this.value.length)) + ' characters remaining';", string.Concat(textboxTag.Attributes["id"], "_remaining")));

 if (textType == TextType.TextArea)
 {
 //Use JavaScript to restrict the number of characters
 textboxTag.Attributes.Add("onkeypress", "key = window.event ? event.keyCode : event.which; if((this.value.length >= this.getAttribute('maxlength')) && (key == 32 || key == 13 || key > 47)) { if (window.event) { window.event.returnValue = null; } else { event.cancelDefault; return false; } }");
 }

 //Create the tag to display the number of remaining characters
 TagBuilder spanTag = new TagBuilder("span");
 spanTag.Attributes.Add("id", string.Concat(textboxTag.Attributes["id"], "_remaining"));

 //If a class attribute is provided also give this class to the span tag
 if (htmlAttributes.ContainsKey("class"))
 {
 spanTag.AddCssClass(Convert.ToString(htmlAttributes["class"]));
 }
 spanTag.InnerHtml = string.Format("{0} characters remaining", stringLengthAttribute.MaximumLength - text.Length);

 return MvcHtmlString.Create(string.Concat(textboxTag.ToString(TagRenderMode.Normal), spanTag.ToString(TagRenderMode.Normal)));
 }

 return MvcHtmlString.Create(textboxTag.ToString(TagRenderMode.Normal));
}

The code is fairly self explanitory; first it attempts to get the StringLengthAttribute which is used to limit the size of the textboxes and to work out the remaining characters.

Next I build my textbox/textarea tags merging any additional attributues.  If the id attribute isn’t set I set it to a new Guid. I need a unique id here which is also used as part of the id of the span tag that will display the characters remaining so the helper can be used multiple times on a single page.

I then check if the StringLengthAttribute was set, and if so hook up the client events to display the remaining characters etc, then set up the span tag used to display the remaining characters. The JavaScript I used I got from here.

Here is the model I am using with the StringLengthAttribute being used.

public class HomeModel
{
 [StringLength(25)]
 public string Text1 { get; set; }

 [StringLength(150)]
 public string Text2 { get; set; }
}

And here are the methods being used in the view.

<p>
<%= Html.CharactersRemainingTextBoxFor(m => m.Text1) %>
</p>
<p>
<%= Html.CharactersRemainingTextAreaFor(m => m.Text2) %>
</p>

Download source

Posted on by Joe in C#, JavaScript, MVC

5 Responses to ASP.NET MVC Textbox with characters remaining HtmlHelper extension method

  1. Pingback: Tweets that mention ASP.NET MVC Textbox with characters remaining HtmlHelper extension method | Joe Stevens' Blog -- Topsy.com

  2. andy m

    works a treat, joe. thanks a lot for sharing this!!

  3. Java Whiz

    Thank you so much! This was exactly what I was looking for and only took 5 minutes to implement. Great!

  4. kevin

    hi,
    i’ve defined attributes with MetadataType annotation

    [MetadataType(typeof(MyClassMetaData))]
    public partial class MyClassViewModel
    {

    }

    public partial class MyClassMetaData
    {
    [StringLength(200)]
    public string Property1 { get; set; }
    }

    how can i access Property1 in a HtmlHelper ?
    thank you

  5. kevin

    sorry, i meant “how can i access StringLength attribute” !

Add a Comment