Thursday, July 15, 2010

Html.SmartLabelFor

I was really happy with the "EditorFor", "LabelFor" etc helpers in MVC2 and I have been using them all over the place in my project. Most of my models have some kind of required fields in them and although they have been validating correctly and displaying the error messages correctly, I want to notify the user that the field is required on the initial form. Usually this is done by giving some kind of visual indicator for the required fields, such as "*" or making the label as bold. In my initial attempts, I just add "*":


        <div class="editor-label">
            <%: Html.LabelFor(m => m.FullName) %> *
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(m => m.FullName) %>
            <%: Html.ValidationMessageFor(m => m.FullName) %>
        </div>

After looking at it for a while, it looks ugly. It worked, but I want a more elegant solution and I made an Html Helper.

What I want it to do is basically appending an "*" next to the label, ONLY IF the field is marked as REQUIRED, so I can do this:

        <div class="editor-label">
            <%: Html.SmartLabelFor(m => m.FullName) %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(m => m.FullName) %>
            <%: Html.ValidationMessageFor(m => m.FullName) %>
        </div>

If "FullName" is attributed with "Required", it will render "Full Name *" but will just render "Full Name" if it's not marked "Required".

Without further ado, here is the code - pretty simple:
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Linq.Expressions;

    public static class LabelExtensions {
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
        public static MvcHtmlString SmartLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) {
            return LabelHelper(html,
                               ModelMetadata.FromLambdaExpression(expression, html.ViewData),
                               ExpressionHelper.GetExpressionText(expression));
        }

        internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName) {
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (String.IsNullOrEmpty(labelText)) {
                return MvcHtmlString.Empty;
            }

            if (metadata.IsRequired) labelText = labelText + " *";

            TagBuilder tag = new TagBuilder("label");
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(labelText);
            return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
        }
    }
}
Additional readings:

No comments: