MVC3, Razor, Html.TextAreaFor(): adjust height to fit contents

12,984

Solution 1

The code looks fine. One possible improvement would be to externalize it into a reusable helper to avoid polluting the view:

public static class TextAreaExtensions
{
    public static IHtmlString TextAreaAutoSizeFor<TModel, TProperty>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression,
        object htmlAttributes
    )
    {
        var model = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model;
        var text = model as string ?? string.Empty;
        int width = 85;
        int lines = 1;
        string[] arr = text.Split(new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.None);
        foreach (var str in arr)
        {
            if (str.Length / width > 0)
            {
                lines += str.Length / width + (str.Length % width <= width / 2 ? 1 : 0);
            }
            else
            {
                lines++;
            }
        }
        var attributes = new RouteValueDictionary(htmlAttributes);
        attributes["style"] = string.Format("width:{0}em; height:{1}em;", width, lines);
        return htmlHelper.TextAreaFor(expression, attributes);
    }
}

and in the view:

@Html.TextAreaAutoSizeFor(m => m.Text, new { id = "text" })

Solution 2

That looks great, you can also use the JQuery autogrow textarea plugin.

It will save you some coding, and may even be more efficient.

Solution 3

You can reduce this to one line with some LINQ magic:

var lines = Model.Text.Split( new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None )
  .Aggregate( 0, (total, next) => 
    total += next.Length <= width ? 1 
      : (int)Math.Ceiling( (double)next.Length / width ) );

Note that there's a wee problem with the way you're splitting. If there really are mixed \n, \r, and \r\n line endings in your input (unlikely), this split will split in order from left to right, so it won't ever split on the string \r\n, which will mean an empty line between the \r and the \n. So you'll see I moved the \r\n as the first string in the split.

Share:
12,984
Handprint
Author by

Handprint

Technologies/Skill Sets: .NET, MVC(VM), NHibernate, EntityFramework, Parallel Computing Education: B.S. Computer Science, B.A. English Lit Current Studies: Design Patterns, openSuse+Apache+Mono+MariaDB+ASP.NET MVC, TFD/TDD

Updated on November 22, 2022

Comments

  • Handprint
    Handprint over 1 year

    I'm currently using the following code in the View to adjust the height of a Html.TextAreaFor() to fit its contents. Is there a significantly better &/or less verbose way to do this?

    ...
    int width = 85;
    int lines = 1;
    string[] arr = Model.Text.Split(new string[] {"\r\n", "\n", "\r"}, StringSplitOptions.None);
    foreach (var str in arr)
    {
        if (str.Length / width > 0)
        {
            lines += str.Length / width + (str.Length % width <= width/2 ? 1 : 0);
        }
        else
        {
            lines++;
        }
    }
    @Html.TextAreaFor(m => m.Text,
                      new
                      {
                          id = "text",
                          style = "width:" + width + "em; height:" + lines + "em;"
                      })
    
    ...
    
  • Handprint
    Handprint almost 12 years
    Great plugin. Torn between this answer and Darin Dimitrov's! Might be a wee bit fancier than I need.
  • Ethan Brown
    Ethan Brown almost 12 years
    Right you are; I corrected this problem in the answer. Good catch!
  • Handprint
    Handprint almost 12 years
    Reusable, makes maintenance easier, and unclutters the view... and works like a charm! Just had to add the using directives: using System.Web.Routing; using System.Web.Mvc; using System.Web.Mvc.Html; using System.Linq.Expressions;
  • Handprint
    Handprint almost 12 years
    Thanks for the fix. Compact and works! Used it in conjunction with Darin's answer.